using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Npgsql.Internal;
using Npgsql.Internal.Resolvers;
using Npgsql.TypeMapping;
namespace Npgsql;
///
/// Provides a simple API for configuring and creating an , from which database connections can be obtained.
///
public sealed class NpgsqlDataSourceBuilder : INpgsqlTypeMapper
{
static UnsupportedTypeInfoResolver UnsupportedTypeInfoResolver { get; } = new();
readonly NpgsqlSlimDataSourceBuilder _internalBuilder;
///
/// A diagnostics name used by Npgsql when generating tracing, logging and metrics.
///
public string? Name
{
get => _internalBuilder.Name;
set => _internalBuilder.Name = value;
}
///
public INpgsqlNameTranslator DefaultNameTranslator
{
get => _internalBuilder.DefaultNameTranslator;
set => _internalBuilder.DefaultNameTranslator = value;
}
///
/// A connection string builder that can be used to configured the connection string on the builder.
///
public NpgsqlConnectionStringBuilder ConnectionStringBuilder => _internalBuilder.ConnectionStringBuilder;
///
/// Returns the connection string, as currently configured on the builder.
///
public string ConnectionString => _internalBuilder.ConnectionString;
internal static void ResetGlobalMappings(bool overwrite)
=> GlobalTypeMapper.Instance.AddGlobalTypeMappingResolvers(new IPgTypeInfoResolver[]
{
overwrite ? new AdoTypeInfoResolver() : AdoTypeInfoResolver.Instance,
new ExtraConversionsResolver(),
new JsonTypeInfoResolver(),
new RangeTypeInfoResolver(),
new RecordTypeInfoResolver(),
new FullTextSearchTypeInfoResolver(),
new NetworkTypeInfoResolver(),
new GeometricTypeInfoResolver(),
new LTreeTypeInfoResolver(),
// Arrays
new AdoArrayTypeInfoResolver(),
new ExtraConversionsArrayTypeInfoResolver(),
new JsonArrayTypeInfoResolver(),
new RangeArrayTypeInfoResolver(),
new RecordArrayTypeInfoResolver(),
new FullTextSearchArrayTypeInfoResolver(),
new NetworkArrayTypeInfoResolver(),
new GeometricArrayTypeInfoResolver(),
new LTreeArrayTypeInfoResolver()
}, overwrite);
static NpgsqlDataSourceBuilder()
=> ResetGlobalMappings(overwrite: false);
///
/// Constructs a new , optionally starting out from the given .
///
public NpgsqlDataSourceBuilder(string? connectionString = null)
{
_internalBuilder = new(new NpgsqlConnectionStringBuilder(connectionString));
AddDefaultFeatures();
void AddDefaultFeatures()
{
_internalBuilder.EnableTransportSecurity();
_internalBuilder.EnableIntegratedSecurity();
AddTypeInfoResolver(UnsupportedTypeInfoResolver);
// Reverse order arrays.
AddTypeInfoResolver(new LTreeArrayTypeInfoResolver());
AddTypeInfoResolver(new GeometricArrayTypeInfoResolver());
AddTypeInfoResolver(new NetworkArrayTypeInfoResolver());
AddTypeInfoResolver(new FullTextSearchArrayTypeInfoResolver());
AddTypeInfoResolver(new RecordArrayTypeInfoResolver());
AddTypeInfoResolver(new RangeArrayTypeInfoResolver());
AddTypeInfoResolver(new JsonArrayTypeInfoResolver());
AddTypeInfoResolver(new ExtraConversionsArrayTypeInfoResolver());
AddTypeInfoResolver(new AdoArrayTypeInfoResolver());
// Reverse order.
AddTypeInfoResolver(new LTreeTypeInfoResolver());
AddTypeInfoResolver(new GeometricTypeInfoResolver());
AddTypeInfoResolver(new NetworkTypeInfoResolver());
AddTypeInfoResolver(new FullTextSearchTypeInfoResolver());
AddTypeInfoResolver(new RecordTypeInfoResolver());
AddTypeInfoResolver(new RangeTypeInfoResolver());
AddTypeInfoResolver(new JsonTypeInfoResolver());
AddTypeInfoResolver(new ExtraConversionsResolver());
AddTypeInfoResolver(AdoTypeInfoResolver.Instance);
var plugins = new List(GlobalTypeMapper.Instance.GetPluginResolvers());
plugins.Reverse();
foreach (var plugin in plugins)
AddTypeInfoResolver(plugin);
}
}
///
/// Sets the that will be used for logging.
///
/// The logger factory to be used.
/// The same builder instance so that multiple calls can be chained.
public NpgsqlDataSourceBuilder UseLoggerFactory(ILoggerFactory? loggerFactory)
{
_internalBuilder.UseLoggerFactory(loggerFactory);
return this;
}
///
/// Enables parameters to be included in logging. This includes potentially sensitive information from data sent to PostgreSQL.
/// You should only enable this flag in development, or if you have the appropriate security measures in place based on the
/// sensitivity of this data.
///
/// If , then sensitive data is logged.
/// The same builder instance so that multiple calls can be chained.
public NpgsqlDataSourceBuilder EnableParameterLogging(bool parameterLoggingEnabled = true)
{
_internalBuilder.EnableParameterLogging(parameterLoggingEnabled);
return this;
}
#region Authentication
///
/// When using SSL/TLS, this is a callback that allows customizing how the PostgreSQL-provided certificate is verified. This is an
/// advanced API, consider using or instead.
///
/// The callback containing custom callback verification logic.
///
///
/// Cannot be used in conjunction with , or
/// .
///
///
/// See .
///
///
/// The same builder instance so that multiple calls can be chained.
public NpgsqlDataSourceBuilder UseUserCertificateValidationCallback(RemoteCertificateValidationCallback userCertificateValidationCallback)
{
_internalBuilder.UseUserCertificateValidationCallback(userCertificateValidationCallback);
return this;
}
///
/// Specifies an SSL/TLS certificate which Npgsql will send to PostgreSQL for certificate-based authentication.
///
/// The client certificate to be sent to PostgreSQL when opening a connection.
/// The same builder instance so that multiple calls can be chained.
public NpgsqlDataSourceBuilder UseClientCertificate(X509Certificate? clientCertificate)
{
_internalBuilder.UseClientCertificate(clientCertificate);
return this;
}
///
/// Specifies a collection of SSL/TLS certificates which Npgsql will send to PostgreSQL for certificate-based authentication.
///
/// The client certificate collection to be sent to PostgreSQL when opening a connection.
/// The same builder instance so that multiple calls can be chained.
public NpgsqlDataSourceBuilder UseClientCertificates(X509CertificateCollection? clientCertificates)
{
_internalBuilder.UseClientCertificates(clientCertificates);
return this;
}
///
/// Specifies a callback to modify the collection of SSL/TLS client certificates which Npgsql will send to PostgreSQL for
/// certificate-based authentication. This is an advanced API, consider using or
/// instead.
///
/// The callback to modify the client certificate collection.
///
///
/// The callback is invoked every time a physical connection is opened, and is therefore suitable for rotating short-lived client
/// certificates. Simply make sure the certificate collection argument has the up-to-date certificate(s).
///
///
/// The callback's collection argument already includes any client certificates specified via the connection string or environment
/// variables.
///
///
/// The same builder instance so that multiple calls can be chained.
public NpgsqlDataSourceBuilder UseClientCertificatesCallback(Action? clientCertificatesCallback)
{
_internalBuilder.UseClientCertificatesCallback(clientCertificatesCallback);
return this;
}
///
/// Sets the that will be used validate SSL certificate, received from the server.
///
/// The CA certificate.
/// The same builder instance so that multiple calls can be chained.
public NpgsqlDataSourceBuilder UseRootCertificate(X509Certificate2? rootCertificate)
{
_internalBuilder.UseRootCertificate(rootCertificate);
return this;
}
///
/// Specifies a callback that will be used to validate SSL certificate, received from the server.
///
/// The callback to get CA certificate.
/// The same builder instance so that multiple calls can be chained.
///
/// This overload, which accepts a callback, is suitable for scenarios where the certificate rotates
/// and might change during the lifetime of the application.
/// When that's not the case, use the overload which directly accepts the certificate.
///
public NpgsqlDataSourceBuilder UseRootCertificateCallback(Func? rootCertificateCallback)
{
_internalBuilder.UseRootCertificateCallback(rootCertificateCallback);
return this;
}
///
/// Configures a periodic password provider, which is automatically called by the data source at some regular interval. This is the
/// recommended way to fetch a rotating access token.
///
/// A callback which returns the password to be sent to PostgreSQL.
/// How long to cache the password before re-invoking the callback.
///
/// If a password refresh attempt fails, it will be re-attempted with this interval.
/// This should typically be much lower than .
///
/// The same builder instance so that multiple calls can be chained.
///
///
/// The provided callback is invoked in a timer, and not when opening connections. It therefore doesn't affect opening time.
///
///
/// The provided cancellation token is only triggered when the entire data source is disposed. If you'd like to apply a timeout to the
/// token fetching, do so within the provided callback.
///
///
public NpgsqlDataSourceBuilder UsePeriodicPasswordProvider(
Func>? passwordProvider,
TimeSpan successRefreshInterval,
TimeSpan failureRefreshInterval)
{
_internalBuilder.UsePeriodicPasswordProvider(passwordProvider, successRefreshInterval, failureRefreshInterval);
return this;
}
#endregion Authentication
#region Type mapping
///
public void AddTypeInfoResolver(IPgTypeInfoResolver resolver)
=> _internalBuilder.AddTypeInfoResolver(resolver);
///
void INpgsqlTypeMapper.Reset()
=> _internalBuilder.ResetTypeMappings();
///
public INpgsqlTypeMapper MapEnum<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] TEnum>(string? pgName = null, INpgsqlNameTranslator? nameTranslator = null)
where TEnum : struct, Enum
{
_internalBuilder.MapEnum(pgName, nameTranslator);
return this;
}
///
public bool UnmapEnum<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] TEnum>(string? pgName = null, INpgsqlNameTranslator? nameTranslator = null)
where TEnum : struct, Enum
=> _internalBuilder.UnmapEnum(pgName, nameTranslator);
///
[RequiresDynamicCode("Calling MapEnum with a Type can require creating new generic types or methods. This may not work when AOT compiling.")]
public INpgsqlTypeMapper MapEnum([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
Type clrType, string? pgName = null, INpgsqlNameTranslator? nameTranslator = null)
=> _internalBuilder.MapEnum(clrType, pgName, nameTranslator);
///
public bool UnmapEnum([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
Type clrType, string? pgName = null, INpgsqlNameTranslator? nameTranslator = null)
=> _internalBuilder.UnmapEnum(clrType, pgName, nameTranslator);
///
[RequiresDynamicCode("Mapping composite types involves serializing arbitrary types, requiring require creating new generic types or methods. This is currently unsupported with NativeAOT, vote on issue #5303 if this is important to you.")]
public INpgsqlTypeMapper MapComposite<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields)] T>(
string? pgName = null, INpgsqlNameTranslator? nameTranslator = null)
{
_internalBuilder.MapComposite(pgName, nameTranslator);
return this;
}
///
[RequiresDynamicCode("Mapping composite types involves serializing arbitrary types, requiring require creating new generic types or methods. This is currently unsupported with NativeAOT, vote on issue #5303 if this is important to you.")]
public INpgsqlTypeMapper MapComposite([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields)]
Type clrType, string? pgName = null, INpgsqlNameTranslator? nameTranslator = null)
{
_internalBuilder.MapComposite(clrType, pgName, nameTranslator);
return this;
}
///
[RequiresDynamicCode("Mapping composite types involves serializing arbitrary types, requiring require creating new generic types or methods. This is currently unsupported with NativeAOT, vote on issue #5303 if this is important to you.")]
public bool UnmapComposite<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields)] T>(
string? pgName = null, INpgsqlNameTranslator? nameTranslator = null)
=> _internalBuilder.UnmapComposite(pgName, nameTranslator);
///
[RequiresDynamicCode("Mapping composite types involves serializing arbitrary types, requiring require creating new generic types or methods. This is currently unsupported with NativeAOT, vote on issue #5303 if this is important to you.")]
public bool UnmapComposite([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields)]
Type clrType, string? pgName = null, INpgsqlNameTranslator? nameTranslator = null)
=> _internalBuilder.UnmapComposite(clrType, pgName, nameTranslator);
#endregion Type mapping
///
/// Register a connection initializer, which allows executing arbitrary commands when a physical database connection is first opened.
///
///
/// A synchronous connection initialization lambda, which will be called from when a new physical
/// connection is opened.
///
///
/// An asynchronous connection initialization lambda, which will be called from
/// when a new physical connection is opened.
///
///
/// If an initializer is registered, both sync and async versions must be provided. If you do not use sync APIs in your code, simply
/// throw , which would also catch accidental cases of sync opening.
///
///
/// Take care that the setting you apply in the initializer does not get reverted when the connection is returned to the pool, since
/// Npgsql sends DISCARD ALL by default. The option can be used to
/// turn this off.
///
/// The same builder instance so that multiple calls can be chained.
public NpgsqlDataSourceBuilder UsePhysicalConnectionInitializer(
Action? connectionInitializer,
Func? connectionInitializerAsync)
{
_internalBuilder.UsePhysicalConnectionInitializer(connectionInitializer, connectionInitializerAsync);
return this;
}
///
/// Builds and returns an which is ready for use.
///
public NpgsqlDataSource Build()
=> _internalBuilder.Build();
///
/// Builds and returns a which is ready for use for load-balancing and failover scenarios.
///
public NpgsqlMultiHostDataSource BuildMultiHost()
=> _internalBuilder.BuildMultiHost();
}