using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.Common;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Net.Security;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using System.Transactions;
using Microsoft.Extensions.Logging;
using Npgsql.Internal;
using Npgsql.TypeMapping;
using Npgsql.Util;
using IsolationLevel = System.Data.IsolationLevel;
namespace Npgsql;
///
/// This class represents a connection to a PostgreSQL server.
///
// ReSharper disable once RedundantNameQualifier
[System.ComponentModel.DesignerCategory("")]
public sealed class NpgsqlConnection : DbConnection, ICloneable, IComponent
{
#region Fields
// Set this when disposed is called.
bool _disposed;
///
/// The connection string, without the password after open (unless Persist Security Info=true)
///
string _userFacingConnectionString = string.Empty;
///
/// The original connection string provided by the user, including the password.
///
string _connectionString = string.Empty;
ConnectionState _fullState;
///
/// The physical connection to the database. This is when the connection is closed.
///
internal NpgsqlConnector? Connector { get; set; }
///
/// The parsed connection string. Set only after the connection is opened.
///
internal NpgsqlConnectionStringBuilder Settings { get; private set; } = DefaultSettings;
static readonly NpgsqlConnectionStringBuilder DefaultSettings = new();
NpgsqlDataSource? _dataSource;
internal NpgsqlDataSource NpgsqlDataSource
{
get
{
Debug.Assert(_dataSource is not null);
return _dataSource;
}
}
///
/// Flag used to make sure we never double-close a connection, returning it twice to the pool.
///
int _closing;
internal Transaction? EnlistedTransaction { get; set; }
///
/// The global type mapper, which contains defaults used by all new connections.
/// Modify mappings on this mapper to affect your entire application.
///
[Obsolete("Global-level type mapping has been replaced with data source mapping, see the 7.0 release notes at https://www.npgsql.org/doc/release-notes/7.0.html#managing-type-mappings-at-the-connection-level-is-no-longer-supported for more information.")]
public static INpgsqlTypeMapper GlobalTypeMapper => TypeMapping.GlobalTypeMapper.Instance;
///
/// Connection-level type mapping is no longer supported. See the 7.0 release notes at https://www.npgsql.org/doc/release-notes/7.0.html#managing-type-mappings-at-the-connection-level-is-no-longer-supported for configuring type mapping on NpgsqlDataSource.
///
[Obsolete("Connection-level type mapping is no longer supported. See the 7.0 release notes at https://www.npgsql.org/doc/release-notes/7.0.html#managing-type-mappings-at-the-connection-level-is-no-longer-supported for configuring type mapping on NpgsqlDataSource.", true)]
public INpgsqlTypeMapper TypeMapper
=> throw new NotSupportedException();
static Func? _cloningInstantiator;
///
/// The default TCP/IP port for PostgreSQL.
///
public const int DefaultPort = 5432;
///
/// Maximum value for connection timeout.
///
internal const int TimeoutLimit = 1024;
ILogger _connectionLogger = default!; // Initialized in Open, shouldn't be used otherwise
static readonly StateChangeEventArgs ClosedToOpenEventArgs = new(ConnectionState.Closed, ConnectionState.Open);
static readonly StateChangeEventArgs OpenToClosedEventArgs = new(ConnectionState.Open, ConnectionState.Closed);
#endregion Fields
#region Constructors / Init / Open
///
/// Initializes a new instance of the class.
///
public NpgsqlConnection()
=> GC.SuppressFinalize(this);
///
/// Initializes a new instance of with the given connection string.
///
/// The connection used to open the PostgreSQL database.
public NpgsqlConnection(string? connectionString) : this()
=> ConnectionString = connectionString;
internal NpgsqlConnection(NpgsqlDataSource dataSource, NpgsqlConnector connector) : this()
{
_dataSource = dataSource;
Settings = dataSource.Settings;
_userFacingConnectionString = dataSource.ConnectionString;
Connector = connector;
connector.Connection = this;
FullState = ConnectionState.Open;
}
internal static NpgsqlConnection FromDataSource(NpgsqlDataSource dataSource)
=> new()
{
_dataSource = dataSource,
Settings = dataSource.Settings,
_userFacingConnectionString = dataSource.ConnectionString,
};
///
/// Opens a database connection with the property settings specified by the .
///
public override void Open() => Open(false, CancellationToken.None).GetAwaiter().GetResult();
///
/// This is the asynchronous version of .
///
///
/// Do not invoke other methods and properties of the object until the returned Task is complete.
///
///
/// An optional token to cancel the asynchronous operation. The default value is .
///
/// A task representing the asynchronous operation.
public override Task OpenAsync(CancellationToken cancellationToken) => Open(async: true, cancellationToken);
void SetupDataSource()
{
// Fast path: a pool already corresponds to this exact version of the connection string.
if (PoolManager.Pools.TryGetValue(_connectionString, out _dataSource))
{
Settings = _dataSource.Settings; // Great, we already have a pool
return;
}
// Connection string hasn't been seen before. Check for empty and parse (slow one-time path).
if (_connectionString == string.Empty)
{
Settings = DefaultSettings;
_dataSource = null;
return;
}
var settings = new NpgsqlConnectionStringBuilder(_connectionString);
settings.PostProcessAndValidate();
Settings = settings;
// The connection string may be equivalent to one that has already been seen though (e.g. different
// ordering). Have NpgsqlConnectionStringBuilder produce a canonical string representation
// and recheck.
// Note that we remove TargetSessionAttributes to make all connection strings that are otherwise identical point to the same pool.
var canonical = settings.ConnectionStringForMultipleHosts;
if (PoolManager.Pools.TryGetValue(canonical, out _dataSource))
{
// If this is a multi-host data source and the user specified a TargetSessionAttributes, create a wrapper in front of the
// MultiHostDataSource with that TargetSessionAttributes.
if (_dataSource is NpgsqlMultiHostDataSource multiHostDataSource && settings.TargetSessionAttributesParsed.HasValue)
_dataSource = multiHostDataSource.WithTargetSession(settings.TargetSessionAttributesParsed.Value);
// The pool was found, but only under the canonical key - we're using a different version
// for the first time. Map it via our own key for next time.
_dataSource = PoolManager.Pools.GetOrAdd(_connectionString, _dataSource);
return;
}
// Really unseen, need to create a new pool
// The canonical pool is the 'base' pool so we need to set that up first. If someone beats us to it use what they put.
// The connection string pool can either be added here or above, if it's added above we should just use that.
var dataSourceBuilder = new NpgsqlDataSourceBuilder(canonical);
dataSourceBuilder.UseLoggerFactory(NpgsqlLoggingConfiguration.GlobalLoggerFactory);
dataSourceBuilder.EnableParameterLogging(NpgsqlLoggingConfiguration.GlobalIsParameterLoggingEnabled);
var newDataSource = dataSourceBuilder.Build();
// See Clone() on the following line:
_cloningInstantiator = s => new NpgsqlConnection(s);
_dataSource = PoolManager.Pools.GetOrAdd(canonical, newDataSource);
if (_dataSource != newDataSource)
newDataSource.Dispose();
// If this is a multi-host data source and the user specified a TargetSessionAttributes, create a wrapper in front of the
// MultiHostDataSource with that TargetSessionAttributes.
if (_dataSource is NpgsqlMultiHostDataSource multiHostDataSource2 && settings.TargetSessionAttributesParsed.HasValue)
_dataSource = multiHostDataSource2.WithTargetSession(settings.TargetSessionAttributesParsed.Value);
_dataSource = PoolManager.Pools.GetOrAdd(_connectionString, _dataSource);
}
internal Task Open(bool async, CancellationToken cancellationToken)
{
CheckClosed();
Debug.Assert(Connector == null);
if (_dataSource is null)
{
Debug.Assert(string.IsNullOrEmpty(_connectionString));
ThrowHelper.ThrowInvalidOperationException("The ConnectionString property has not been initialized.");
}
_userFacingConnectionString = _dataSource.ConnectionString;
_connectionLogger = _dataSource.LoggingConfiguration.ConnectionLogger;
if (_connectionLogger.IsEnabled(LogLevel.Trace))
LogMessages.OpeningConnection(_connectionLogger, Settings.Host!, Settings.Port, Settings.Database!, _userFacingConnectionString);
return OpenAsync(async, cancellationToken);
async Task OpenAsync(bool async, CancellationToken cancellationToken)
{
FullState = ConnectionState.Connecting;
NpgsqlConnector? connector = null;
try
{
var connectionTimeout = TimeSpan.FromSeconds(ConnectionTimeout);
var timeout = new NpgsqlTimeout(connectionTimeout);
var enlistToTransaction = Settings.Enlist ? Transaction.Current : null;
// First, check to see if we there's an ambient transaction, and we have a connection enlisted
// to this transaction which has been closed. If so, return that as an optimization rather than
// opening a new one and triggering escalation to a distributed transaction.
// Otherwise just get a new connector and enlist below.
if (enlistToTransaction is not null && _dataSource.TryRentEnlistedPending(enlistToTransaction, this, out connector))
{
EnlistedTransaction = enlistToTransaction;
enlistToTransaction = null;
}
else
connector = await _dataSource.Get(this, timeout, async, cancellationToken).ConfigureAwait(false);
Debug.Assert(connector.Connection is null,
$"Connection for opened connector '{Connector?.Id.ToString() ?? "???"}' is bound to another connection");
connector.Connection = this;
Connector = connector;
if (enlistToTransaction is not null)
EnlistTransaction(enlistToTransaction);
LogMessages.OpenedConnection(_connectionLogger, Host!, Port, Database, _userFacingConnectionString, connector.Id);
FullState = ConnectionState.Open;
}
catch
{
FullState = ConnectionState.Closed;
Connector = null;
EnlistedTransaction = null;
if (connector is not null)
{
connector.Connection = null;
connector.Return();
}
throw;
}
}
}
#endregion Open / Init
#region Connection string management
///
/// Gets or sets the string used to connect to a PostgreSQL database. See the manual for details.
///
/// The connection string that includes the server name,
/// the database name, and other parameters needed to establish
/// the initial connection. The default value is an empty string.
///
[AllowNull]
public override string ConnectionString
{
get => _userFacingConnectionString;
set
{
CheckClosed();
_userFacingConnectionString = _connectionString = value ?? string.Empty;
SetupDataSource();
}
}
///
/// Gets or sets the delegate used to generate a password for new database connections.
///
///
///
/// This delegate is executed when a new database connection is opened that requires a password.
///
///
/// The and connection
/// string properties have precedence over this delegate: it will not be executed if a password is specified, or if the specified or
/// default Passfile contains a valid entry.
///
///
/// Due to connection pooling this delegate is only executed when a new physical connection is opened, not when reusing a connection
/// that was previously opened from the pool.
///
///
[Obsolete("Use NpgsqlDataSourceBuilder.UsePeriodicPasswordProvider or inject passwords directly into NpgsqlDataSource.Password")]
public ProvidePasswordCallback? ProvidePasswordCallback { get; set; }
#endregion Connection string management
#region Configuration settings
///
/// Backend server host name.
///
[Browsable(true)]
public string? Host => Connector?.Host;
///
/// Backend server port.
///
[Browsable(true)]
public int Port => Connector?.Port ?? 0;
///
/// Gets the time (in seconds) to wait while trying to establish a connection
/// before terminating the attempt and generating an error.
///
/// The time (in seconds) to wait for a connection to open. The default value is 15 seconds.
public override int ConnectionTimeout => Settings.Timeout;
///
/// Gets the time (in seconds) to wait while trying to execute a command
/// before terminating the attempt and generating an error.
///
/// The time (in seconds) to wait for a command to complete. The default value is 30 seconds.
public int CommandTimeout => Settings.CommandTimeout;
///
/// Gets the name of the current database or the database to be used after a connection is opened.
///
/// The name of the current database or the name of the database to be
/// used after a connection is opened. The default value is the empty string.
public override string Database => Settings.Database ?? Settings.Username ?? "";
///
/// Gets the string identifying the database server (host and port)
///
///
/// The name of the database server (host and port). If the connection uses a Unix-domain socket,
/// the path to that socket is returned. The default value is the empty string.
///
public override string DataSource => Connector?.Settings.DataSourceCached ?? _dataSource?.Settings.DataSourceCached ?? string.Empty;
///
/// User name.
///
public string? UserName => Settings.Username;
#endregion Configuration settings
#region State management
///
/// Gets the current state of the connection.
///
/// A bitwise combination of the values. The default is Closed.
[Browsable(false)]
public ConnectionState FullState
{
// Note: we allow accessing the state after dispose, #164
get
{
if (_fullState != ConnectionState.Open)
return _fullState;
if (Connector is null)
return ConnectionState.Open; // When unbound, we only know we're open
switch (Connector.State)
{
case ConnectorState.Ready:
return ConnectionState.Open;
case ConnectorState.Executing:
return ConnectionState.Open | ConnectionState.Executing;
case ConnectorState.Fetching:
case ConnectorState.Copy:
case ConnectorState.Replication:
case ConnectorState.Waiting:
return ConnectionState.Open | ConnectionState.Fetching;
case ConnectorState.Connecting:
return ConnectionState.Connecting;
case ConnectorState.Broken:
return ConnectionState.Broken;
case ConnectorState.Closed:
ThrowHelper.ThrowInvalidOperationException("Internal Npgsql bug: connection is in state Open but connector is in state Closed");
return ConnectionState.Broken;
default:
ThrowHelper.ThrowInvalidOperationException($"Internal Npgsql bug: unexpected value {{0}} of enum {nameof(ConnectorState)}. Please file a bug.", Connector.State);
return ConnectionState.Broken;
}
}
internal set
{
if (value is < 0 or > ConnectionState.Broken)
ThrowHelper.ThrowArgumentOutOfRangeException(nameof(value), "Unknown connection state", value);
var originalOpen = _fullState.HasFlag(ConnectionState.Open);
_fullState = value;
var currentOpen = _fullState.HasFlag(ConnectionState.Open);
if (currentOpen != originalOpen)
{
OnStateChange(currentOpen
? ClosedToOpenEventArgs
: OpenToClosedEventArgs);
}
}
}
///
/// Gets whether the current state of the connection is Open or Closed
///
/// ConnectionState.Open, ConnectionState.Closed or ConnectionState.Connecting
[Browsable(false)]
public override ConnectionState State
{
get
{
var fullState = FullState;
if (fullState.HasFlag(ConnectionState.Connecting))
return ConnectionState.Connecting;
if (fullState.HasFlag(ConnectionState.Open))
return ConnectionState.Open;
return ConnectionState.Closed;
}
}
#endregion State management
#region Command / Batch creation
///
/// A cached command handed out by , which is returned when disposed. Useful for reducing allocations.
///
internal NpgsqlCommand? CachedCommand { get; set; }
///
/// Creates and returns a
/// object associated with the .
///
/// A object.
protected override DbCommand CreateDbCommand() => CreateCommand();
///
/// Creates and returns a object associated with the .
///
/// A object.
public new NpgsqlCommand CreateCommand()
{
CheckDisposed();
var cachedCommand = CachedCommand;
if (cachedCommand is not null)
{
CachedCommand = null;
cachedCommand.State = CommandState.Idle;
return cachedCommand;
}
return NpgsqlCommand.CreateCachedCommand(this);
}
///
/// A cached batch handed out by , which is returned when disposed. Useful for reducing allocations.
///
internal NpgsqlBatch? CachedBatch { get; set; }
///
public override bool CanCreateBatch => true;
///
protected override DbBatch CreateDbBatch() => CreateBatch();
///
public new NpgsqlBatch CreateBatch()
{
CheckDisposed();
var cachedBatch = CachedBatch;
if (cachedBatch is not null)
{
CachedBatch = null;
return cachedBatch;
}
return NpgsqlBatch.CreateCachedBatch(this);
}
#endregion Command / Batch creation
#region Transactions
///
/// Begins a database transaction with the specified isolation level.
///
/// The isolation level under which the transaction should run.
/// A object representing the new transaction.
/// Nested transactions are not supported.
protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel) => BeginTransaction(isolationLevel);
///
/// Begins a database transaction.
///
/// A object representing the new transaction.
///
/// Nested transactions are not supported.
/// Transactions created by this method will have the isolation level.
///
public new NpgsqlTransaction BeginTransaction()
=> BeginTransaction(IsolationLevel.Unspecified);
///
/// Begins a database transaction with the specified isolation level.
///
/// The isolation level under which the transaction should run.
/// A object representing the new transaction.
/// Nested transactions are not supported.
public new NpgsqlTransaction BeginTransaction(IsolationLevel level)
=> BeginTransaction(level, NpgsqlTransactionOptions.None);
///
/// Begins a database transaction with the specified isolation level and options.
///
/// The isolation level under which the transaction should run.
/// Npgsql-specific options under which the transaction should run, e.g. read-only or deferrable.
/// A object representing the new transaction.
///
/// Nested transactions are not supported.
/// Note that there's no single-argument options-only overload: combined with the existing
/// overloads, that would make calls like BeginTransaction(default) ambiguous.
///
public NpgsqlTransaction BeginTransaction(IsolationLevel level, NpgsqlTransactionOptions options)
=> BeginTransaction(async: false, level, options, CancellationToken.None).GetAwaiter().GetResult();
async ValueTask BeginTransaction(bool async, IsolationLevel level, NpgsqlTransactionOptions options, CancellationToken cancellationToken)
{
if (level == IsolationLevel.Chaos)
ThrowHelper.ThrowNotSupportedException($"Unsupported IsolationLevel: {nameof(IsolationLevel.Chaos)}");
CheckReady();
var connector = Connector;
if (connector is { InTransaction: true })
ThrowHelper.ThrowInvalidOperationException("A transaction is already in progress; nested/concurrent transactions aren't supported.");
// There was a committed/rolled back transaction, but it was not disposed
Debug.Assert(connector != null);
// Note that beginning a transaction doesn't actually send anything to the backend (only prepends).
// But we start a user action to check the cancellation token and generate exceptions
using var _ = connector.StartUserAction(cancellationToken);
connector.Transaction ??= new NpgsqlTransaction(connector);
connector.Transaction.Init(level, options);
return connector.Transaction;
}
///
/// Asynchronously begins a database transaction.
///
/// The isolation level under which the transaction should run.
///
/// An optional token to cancel the asynchronous operation. The default value is .
///
/// A task whose property is an object representing the new transaction.
///
/// Nested transactions are not supported.
///
protected override async ValueTask BeginDbTransactionAsync(IsolationLevel isolationLevel, CancellationToken cancellationToken)
=> await BeginTransactionAsync(isolationLevel, cancellationToken).ConfigureAwait(false);
///
/// Asynchronously begins a database transaction.
///
///
/// An optional token to cancel the asynchronous operation. The default value is .
///
/// A task whose Result property is an object representing the new transaction.
///
/// Nested transactions are not supported.
/// Transactions created by this method will have the isolation level.
///
public new ValueTask BeginTransactionAsync(CancellationToken cancellationToken = default)
=> BeginTransactionAsync(IsolationLevel.Unspecified, cancellationToken);
///
/// Asynchronously begins a database transaction.
///
/// The isolation level under which the transaction should run.
///
/// An optional token to cancel the asynchronous operation. The default value is .
///
/// A task whose property is an object representing the new transaction.
///
/// Nested transactions are not supported.
///
public new ValueTask BeginTransactionAsync(IsolationLevel level, CancellationToken cancellationToken = default)
=> BeginTransactionAsync(level, NpgsqlTransactionOptions.None, cancellationToken);
///
/// Asynchronously begins a database transaction with the specified isolation level and options.
///
/// The isolation level under which the transaction should run.
/// Npgsql-specific options under which the transaction should run, e.g. read-only or deferrable.
///
/// An optional token to cancel the asynchronous operation. The default value is .
///
/// A task whose property is an object representing the new transaction.
/// Nested transactions are not supported.
public ValueTask BeginTransactionAsync(IsolationLevel level, NpgsqlTransactionOptions options, CancellationToken cancellationToken = default)
=> BeginTransaction(async: true, level, options, cancellationToken);
///
/// Enlist transaction.
///
public override void EnlistTransaction(Transaction? transaction)
{
if (EnlistedTransaction != null)
{
if (EnlistedTransaction.Equals(transaction))
return;
try
{
if (EnlistedTransaction.TransactionInformation.Status == System.Transactions.TransactionStatus.Active)
throw new InvalidOperationException($"Already enlisted to transaction (localid={EnlistedTransaction.TransactionInformation.LocalIdentifier})");
}
catch (ObjectDisposedException)
{
// The MSDTC 2nd phase is asynchronous, so we may end up checking the TransactionInformation on
// a disposed transaction. To be extra safe we catch that, and understand that the transaction
// has ended - no problem for reenlisting.
}
}
CheckReady();
var connector = Connector!;
EnlistedTransaction = transaction;
if (transaction == null)
return;
// Until #1378 is implemented, we have no recovery, and so no need to enlist as a durable resource manager
// (or as promotable single phase).
// Note that even when #1378 is implemented in some way, we should check for mono and go volatile in any case -
// distributed transactions aren't supported.
var volatileResourceManager = new VolatileResourceManager(this, transaction);
transaction.EnlistVolatile(volatileResourceManager, EnlistmentOptions.None);
volatileResourceManager.Init();
EnlistedTransaction = transaction;
LogMessages.EnlistedVolatileResourceManager(
connector.LoggingConfiguration.TransactionLogger,
transaction.TransactionInformation.LocalIdentifier,
connector.Id);
}
#endregion
#region Close
///
/// Releases the connection. If the connection is pooled, it will be returned to the pool and made available for re-use.
/// If it is non-pooled, the physical connection will be closed.
///
public override void Close() => Close(async: false).GetAwaiter().GetResult();
///
/// Releases the connection. If the connection is pooled, it will be returned to the pool and made available for re-use.
/// If it is non-pooled, the physical connection will be closed.
///
public override Task CloseAsync()
=> Close(async: true);
internal bool TakeCloseLock() => Interlocked.Exchange(ref _closing, 1) == 0;
internal void ReleaseCloseLock() => Volatile.Write(ref _closing, 0);
internal Task Close(bool async)
{
// Even though NpgsqlConnection isn't thread safe we'll make sure this part is.
// Because we really don't want double returns to the pool.
if (!TakeCloseLock())
return Task.CompletedTask;
switch (FullState)
{
case ConnectionState.Open:
case ConnectionState.Open | ConnectionState.Executing:
case ConnectionState.Open | ConnectionState.Fetching:
break;
case ConnectionState.Broken:
FullState = ConnectionState.Closed;
goto case ConnectionState.Closed;
case ConnectionState.Closed:
ReleaseCloseLock();
return Task.CompletedTask;
case ConnectionState.Connecting:
ReleaseCloseLock();
throw new InvalidOperationException("Can't close, connection is in state " + FullState);
default:
ReleaseCloseLock();
throw new ArgumentOutOfRangeException("Unknown connection state: " + FullState);
}
return CloseAsync(async);
}
async Task CloseAsync(bool async)
{
Debug.Assert(Connector != null);
try
{
var connector = Connector;
LogMessages.ClosingConnection(_connectionLogger, Settings.Host!, Settings.Port, Settings.Database!, _userFacingConnectionString, connector.Id);
if (connector.CurrentReader != null || connector.CurrentCopyOperation != null)
{
// This method could re-enter connection.Close() due to an underlying connection failure.
await connector.CloseOngoingOperations(async).ConfigureAwait(false);
}
Debug.Assert(connector.IsReady || connector.IsBroken, $"Connector is not ready or broken during close, it's {connector.State}");
Debug.Assert(connector.CurrentReader == null);
Debug.Assert(connector.CurrentCopyOperation == null);
if (EnlistedTransaction != null)
{
// A System.Transactions transaction is still in progress.
// Close the connection and disconnect it from the resource manager and reset the connector, but leave the
// connector in an enlisted pending list in the data source. If another connection is opened within
// the same transaction scope, we will reuse this connector to avoid escalating to a distributed
// transaction.
connector.ResetWithinEnlistedTransaction();
connector.Connection = null;
_dataSource?.AddPendingEnlistedConnector(connector, EnlistedTransaction);
EnlistedTransaction = null;
}
else
{
if (Settings.Pooling)
{
// Clear the buffer, roll back any pending transaction and prepend a reset message if needed
// Note that we're doing this only for pooled connections
await connector.Reset(async).ConfigureAwait(false);
}
else
{
// We're already doing the same in the NpgsqlConnector.Reset for pooled connections
// TODO: move reset logic to ConnectorSource.Return
connector.Transaction?.UnbindIfNecessary();
}
connector.Connection = null;
connector.Return();
}
LogMessages.ClosedConnection(_connectionLogger, Settings.Host!, Settings.Port, Settings.Database!, _userFacingConnectionString, connector.Id);
Connector = null;
FullState = ConnectionState.Closed;
}
finally
{
ReleaseCloseLock();
}
}
///
/// Releases all resources used by the .
///
/// when called from ;
/// when being called from the finalizer.
protected override void Dispose(bool disposing)
{
if (_disposed)
return;
if (disposing)
Close();
_disposed = true;
}
///
/// Releases all resources used by the .
///
public override async ValueTask DisposeAsync()
{
if (_disposed)
return;
await CloseAsync().ConfigureAwait(false);
_disposed = true;
}
internal void MakeDisposed()
=> _disposed = true;
#endregion
#region Notifications and Notices
///
/// Fires when PostgreSQL notices are received from PostgreSQL.
///
///
/// PostgreSQL notices are non-critical messages generated by PostgreSQL, either as a result of a user query
/// (e.g. as a warning or informational notice), or due to outside activity (e.g. if the database administrator
/// initiates a "fast" database shutdown).
///
/// Note that notices are very different from notifications (see the event).
///
public event NoticeEventHandler? Notice;
///
/// Fires when PostgreSQL notifications are received from PostgreSQL.
///
///
/// PostgreSQL notifications are sent when your connection has registered for notifications on a specific channel via the
/// LISTEN command. NOTIFY can be used to generate such notifications, allowing for an inter-connection communication channel.
///
/// Note that notifications are very different from notices (see the event).
///
public event NotificationEventHandler? Notification;
internal void OnNotice(PostgresNotice e)
{
try
{
Notice?.Invoke(this, new NpgsqlNoticeEventArgs(e));
}
catch (Exception ex)
{
// Block all exceptions bubbling up from the user's event handler
LogMessages.CaughtUserExceptionInNoticeEventHandler(_connectionLogger, ex);
}
}
internal void OnNotification(NpgsqlNotificationEventArgs e)
{
try
{
Notification?.Invoke(this, e);
}
catch (Exception ex)
{
// Block all exceptions bubbling up from the user's event handler
LogMessages.CaughtUserExceptionInNotificationEventHandler(_connectionLogger, ex);
}
}
#endregion Notifications and Notices
#region SSL
///
/// Returns whether SSL is being used for the connection.
///
internal bool IsSslEncrypted
{
get
{
CheckOpen();
return Connector!.IsSslEncrypted;
}
}
///
/// Returns whether GSS encryption is being used for the connection.
///
internal bool IsGssEncrypted
{
get
{
CheckOpen();
return Connector!.IsGssEncrypted;
}
}
///
/// Returns whether SCRAM-SHA256 is being user for the connection
///
internal bool IsScram
{
get
{
CheckOpen();
return Connector!.IsScram;
}
}
///
/// Returns whether SCRAM-SHA256-PLUS is being user for the connection
///
internal bool IsScramPlus
{
get
{
CheckOpen();
return Connector!.IsScramPlus;
}
}
///
/// Selects the local Secure Sockets Layer (SSL) certificate used for authentication.
///
///
/// See
///
[Obsolete("Use UseSslClientAuthenticationOptionsCallback")]
public ProvideClientCertificatesCallback? ProvideClientCertificatesCallback { get; set; }
///
/// 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.
///
///
///
/// Cannot be used in conjunction with , and
/// .
///
///
/// See .
///
///
[Obsolete("Use UseSslClientAuthenticationOptionsCallback")]
public RemoteCertificateValidationCallback? UserCertificateValidationCallback { get; set; }
///
/// When using SSL/TLS, this is a callback that allows customizing SslStream's authentication options.
///
///
///
/// See .
///
///
public Action? SslClientAuthenticationOptionsCallback { get; set; }
#endregion SSL
#region Backend version, capabilities, settings
// TODO: We should probably move DatabaseInfo from each connector to the pool (but remember unpooled)
///
/// The version of the PostgreSQL server we're connected to.
///
///
/// This can only be called when the connection is open.
///
///
/// In case of a development or pre-release version this field will contain
/// the version of the next version to be released from this branch.
///
///
///
[Browsable(false)]
public Version PostgreSqlVersion
{
get
{
CheckOpen();
return Connector!.DatabaseInfo.Version;
}
}
///
/// The PostgreSQL server version as returned by the server_version option.
///
/// This can only be called when the connection is open.
///
///
public override string ServerVersion
{
get
{
CheckOpen();
return Connector!.DatabaseInfo.ServerVersion;
}
}
///
/// Process id of backend server.
/// This can only be called when there is an active connection.
///
[Browsable(false)]
// ReSharper disable once InconsistentNaming
public int ProcessID
{
get
{
CheckOpen();
return Connector!.BackendProcessId;
}
}
///
/// Reports whether the backend uses the newer integer timestamp representation.
/// Note that the old floating point representation is not supported.
/// Meant for use by type plugins (e.g. NodaTime)
///
[Browsable(false)]
public bool HasIntegerDateTimes
{
get
{
CheckOpen();
return Connector!.DatabaseInfo.HasIntegerDateTimes;
}
}
///
/// The connection's timezone as reported by PostgreSQL, in the IANA/Olson database format.
///
[Browsable(false)]
public string Timezone
{
get
{
CheckOpen();
return Connector!.Timezone;
}
}
///
/// Holds all PostgreSQL parameters received for this connection. Is updated if the values change
/// (e.g. as a result of a SET command).
///
[Browsable(false)]
public IReadOnlyDictionary PostgresParameters
{
get
{
CheckOpen();
return Connector!.PostgresParameters;
}
}
#endregion Backend version, capabilities, settings
#region Copy
///
/// Begins a binary COPY FROM STDIN operation, a high-performance data import mechanism to a PostgreSQL table.
///
/// A COPY FROM STDIN SQL command
/// A which can be used to write rows and columns
///
/// See https://www.postgresql.org/docs/current/static/sql-copy.html.
///
public NpgsqlBinaryImporter BeginBinaryImport(string copyFromCommand)
=> BeginBinaryImport(async: false, copyFromCommand, CancellationToken.None).GetAwaiter().GetResult();
///
/// Begins a binary COPY FROM STDIN operation, a high-performance data import mechanism to a PostgreSQL table.
///
/// A COPY FROM STDIN SQL command
/// An optional token to cancel the asynchronous operation. The default value is None.
/// A which can be used to write rows and columns
///
/// See https://www.postgresql.org/docs/current/static/sql-copy.html.
///
public Task BeginBinaryImportAsync(string copyFromCommand, CancellationToken cancellationToken = default)
=> BeginBinaryImport(async: true, copyFromCommand, cancellationToken);
async Task BeginBinaryImport(bool async, string copyFromCommand, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(copyFromCommand);
if (!IsValidCopyCommand(copyFromCommand))
throw new ArgumentException("Must contain a COPY FROM STDIN command!", nameof(copyFromCommand));
CheckReady();
var connector = Connector!;
LogMessages.StartingBinaryImport(connector.LoggingConfiguration.CopyLogger, connector.Id);
// no point in passing a cancellationToken here, as we register the cancellation in the Init method
connector.StartUserAction(ConnectorState.Copy, attemptPgCancellation: false);
var importer = new NpgsqlBinaryImporter(connector);
try
{
await importer.Init(copyFromCommand, async, cancellationToken).ConfigureAwait(false);
connector.CurrentCopyOperation = importer;
return importer;
}
catch
{
try
{
if (async)
await importer.DisposeAsync().ConfigureAwait(false);
else
importer.Dispose();
}
catch
{
// ignored
}
throw;
}
}
///
/// Begins a binary COPY TO STDOUT operation, a high-performance data export mechanism from a PostgreSQL table.
///
/// A COPY TO STDOUT SQL command
/// A which can be used to read rows and columns
///
/// See https://www.postgresql.org/docs/current/static/sql-copy.html.
///
public NpgsqlBinaryExporter BeginBinaryExport(string copyToCommand)
=> BeginBinaryExport(async: false, copyToCommand, CancellationToken.None).GetAwaiter().GetResult();
///
/// Begins a binary COPY TO STDOUT operation, a high-performance data export mechanism from a PostgreSQL table.
///
/// A COPY TO STDOUT SQL command
/// An optional token to cancel the asynchronous operation. The default value is None.
/// A which can be used to read rows and columns
///
/// See https://www.postgresql.org/docs/current/static/sql-copy.html.
///
public Task BeginBinaryExportAsync(string copyToCommand, CancellationToken cancellationToken = default)
=> BeginBinaryExport(async: true, copyToCommand, cancellationToken);
async Task BeginBinaryExport(bool async, string copyToCommand, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(copyToCommand);
if (!IsValidCopyCommand(copyToCommand))
throw new ArgumentException("Must contain a COPY TO STDOUT command!", nameof(copyToCommand));
CheckReady();
var connector = Connector!;
LogMessages.StartingBinaryExport(connector.LoggingConfiguration.CopyLogger, connector.Id);
// no point in passing a cancellationToken here, as we register the cancellation in the Init method
connector.StartUserAction(ConnectorState.Copy, attemptPgCancellation: false);
var exporter = new NpgsqlBinaryExporter(connector);
try
{
await exporter.Init(copyToCommand, async, cancellationToken).ConfigureAwait(false);
connector.CurrentCopyOperation = exporter;
return exporter;
}
catch
{
try
{
if (async)
await exporter.DisposeAsync().ConfigureAwait(false);
else
exporter.Dispose();
}
catch
{
// ignored
}
throw;
}
}
///
/// Begins a textual COPY FROM STDIN operation, a data import mechanism to a PostgreSQL table.
/// It is the user's responsibility to send the textual input according to the format specified
/// in .
///
/// A COPY FROM STDIN SQL command
///
/// A TextWriter that can be used to send textual data.
///
/// See https://www.postgresql.org/docs/current/static/sql-copy.html.
///
public NpgsqlCopyTextWriter BeginTextImport(string copyFromCommand)
=> BeginTextImport(async: false, copyFromCommand, CancellationToken.None).GetAwaiter().GetResult();
///
/// Begins a textual COPY FROM STDIN operation, a data import mechanism to a PostgreSQL table.
/// It is the user's responsibility to send the textual input according to the format specified
/// in .
///
/// A COPY FROM STDIN SQL command
/// An optional token to cancel the asynchronous operation. The default value is None.
///
/// A TextWriter that can be used to send textual data.
///
/// See https://www.postgresql.org/docs/current/static/sql-copy.html.
///
public Task BeginTextImportAsync(string copyFromCommand, CancellationToken cancellationToken = default)
=> BeginTextImport(async: true, copyFromCommand, cancellationToken);
async Task BeginTextImport(bool async, string copyFromCommand, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(copyFromCommand);
if (!IsValidCopyCommand(copyFromCommand))
throw new ArgumentException("Must contain a COPY FROM STDIN command!", nameof(copyFromCommand));
CheckReady();
var connector = Connector!;
LogMessages.StartingTextImport(connector.LoggingConfiguration.CopyLogger, connector.Id);
// no point in passing a cancellationToken here, as we register the cancellation in the Init method
connector.StartUserAction(ConnectorState.Copy, attemptPgCancellation: false);
var copyStream = new NpgsqlRawCopyStream(connector);
try
{
await copyStream.Init(copyFromCommand, async, forExport: false, cancellationToken).ConfigureAwait(false);
var writer = new NpgsqlCopyTextWriter(connector, copyStream);
connector.CurrentCopyOperation = writer;
return writer;
}
catch
{
try
{
if (async)
await copyStream.DisposeAsync().ConfigureAwait(false);
else
copyStream.Dispose();
}
catch
{
// ignored
}
throw;
}
}
///
/// Begins a textual COPY TO STDOUT operation, a data export mechanism from a PostgreSQL table.
/// It is the user's responsibility to parse the textual input according to the format specified
/// in .
///
/// A COPY TO STDOUT SQL command
///
/// A TextReader that can be used to read textual data.
///
/// See https://www.postgresql.org/docs/current/static/sql-copy.html.
///
public NpgsqlCopyTextReader BeginTextExport(string copyToCommand)
=> BeginTextExport(async: false, copyToCommand, CancellationToken.None).GetAwaiter().GetResult();
///
/// Begins a textual COPY TO STDOUT operation, a data export mechanism from a PostgreSQL table.
/// It is the user's responsibility to parse the textual input according to the format specified
/// in .
///
/// A COPY TO STDOUT SQL command
/// An optional token to cancel the asynchronous operation. The default value is None.
///
/// A TextReader that can be used to read textual data.
///
/// See https://www.postgresql.org/docs/current/static/sql-copy.html.
///
public Task BeginTextExportAsync(string copyToCommand, CancellationToken cancellationToken = default)
=> BeginTextExport(async: true, copyToCommand, cancellationToken);
async Task BeginTextExport(bool async, string copyToCommand, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(copyToCommand);
if (!IsValidCopyCommand(copyToCommand))
throw new ArgumentException("Must contain a COPY TO STDOUT command!", nameof(copyToCommand));
CheckReady();
var connector = Connector!;
LogMessages.StartingTextExport(connector.LoggingConfiguration.CopyLogger, connector.Id);
// no point in passing a cancellationToken here, as we register the cancellation in the Init method
connector.StartUserAction(ConnectorState.Copy, attemptPgCancellation: false);
var copyStream = new NpgsqlRawCopyStream(connector);
try
{
await copyStream.Init(copyToCommand, async, forExport: true, cancellationToken).ConfigureAwait(false);
var reader = new NpgsqlCopyTextReader(connector, copyStream);
connector.CurrentCopyOperation = reader;
return reader;
}
catch
{
try
{
if (async)
await copyStream.DisposeAsync().ConfigureAwait(false);
else
copyStream.Dispose();
}
catch
{
// ignored
}
throw;
}
}
///
/// Begins a raw binary COPY operation (TO STDOUT or FROM STDIN), a high-performance data export/import mechanism to a PostgreSQL table.
/// Note that unlike the other COPY API methods, doesn't implement any encoding/decoding
/// and is unsuitable for structured import/export operation. It is useful mainly for exporting a table as an opaque
/// blob, for the purpose of importing it back later.
///
/// A COPY TO STDOUT or COPY FROM STDIN SQL command
/// A that can be used to read or write raw binary data.
///
/// See https://www.postgresql.org/docs/current/static/sql-copy.html.
///
public NpgsqlRawCopyStream BeginRawBinaryCopy(string copyCommand)
=> BeginRawBinaryCopy(async: false, copyCommand, CancellationToken.None).GetAwaiter().GetResult();
///
/// Begins a raw binary COPY operation (TO STDOUT or FROM STDIN), a high-performance data export/import mechanism to a PostgreSQL table.
/// Note that unlike the other COPY API methods, doesn't implement any encoding/decoding
/// and is unsuitable for structured import/export operation. It is useful mainly for exporting a table as an opaque
/// blob, for the purpose of importing it back later.
///
/// A COPY TO STDOUT or COPY FROM STDIN SQL command
/// An optional token to cancel the asynchronous operation. The default value is None.
/// A that can be used to read or write raw binary data.
///
/// See https://www.postgresql.org/docs/current/static/sql-copy.html.
///
public Task BeginRawBinaryCopyAsync(string copyCommand, CancellationToken cancellationToken = default)
=> BeginRawBinaryCopy(async: true, copyCommand, cancellationToken);
async Task BeginRawBinaryCopy(bool async, string copyCommand, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(copyCommand);
if (!IsValidCopyCommand(copyCommand))
throw new ArgumentException("Must contain a COPY TO STDOUT OR COPY FROM STDIN command!", nameof(copyCommand));
CheckReady();
var connector = Connector!;
LogMessages.StartingRawCopy(connector.LoggingConfiguration.CopyLogger, connector.Id);
// no point in passing a cancellationToken here, as we register the cancellation in the Init method
connector.StartUserAction(ConnectorState.Copy, attemptPgCancellation: false);
var stream = new NpgsqlRawCopyStream(connector);
try
{
await stream.Init(copyCommand, async, forExport: null, cancellationToken).ConfigureAwait(false);
if (!stream.IsBinary)
{
// TODO: Stop the COPY operation gracefully, no breaking
throw connector.Break(new ArgumentException(
"copyToCommand triggered a text transfer, only binary is allowed", nameof(copyCommand)));
}
connector.CurrentCopyOperation = stream;
return stream;
}
catch
{
try
{
if (async)
await stream.DisposeAsync().ConfigureAwait(false);
else
stream.Dispose();
}
catch
{
// ignored
}
throw;
}
}
static bool IsValidCopyCommand(string copyCommand) => copyCommand.AsSpan().TrimStart().StartsWith("COPY", StringComparison.OrdinalIgnoreCase);
#endregion
#region Wait
///
/// Waits until an asynchronous PostgreSQL messages (e.g. a notification) arrives, and
/// exits immediately. The asynchronous message is delivered via the normal events
/// (, ).
///
///
/// The time-out value, in milliseconds, passed to .
/// The default value is 0, which indicates an infinite time-out period.
/// Specifying -1 also indicates an infinite time-out period.
///
/// true if an asynchronous message was received, false if timed out.
public bool Wait(int timeout)
{
if (timeout != -1 && timeout < 0)
throw new ArgumentException("Argument must be -1, 0 or positive", nameof(timeout));
CheckReady();
LogMessages.StartingWait(_connectionLogger, timeout, Connector!.Id);
return Connector!.Wait(async: false, timeout, CancellationToken.None).GetAwaiter().GetResult();
}
///
/// Waits until an asynchronous PostgreSQL messages (e.g. a notification) arrives, and
/// exits immediately. The asynchronous message is delivered via the normal events
/// (, ).
///
///
/// The time-out value is passed to .
///
/// true if an asynchronous message was received, false if timed out.
public bool Wait(TimeSpan timeout) => Wait((int)timeout.TotalMilliseconds);
///
/// Waits until an asynchronous PostgreSQL messages (e.g. a notification) arrives, and
/// exits immediately. The asynchronous message is delivered via the normal events
/// (, ).
///
public void Wait() => Wait(0);
///
/// Waits asynchronously until an asynchronous PostgreSQL messages (e.g. a notification)
/// arrives, and exits immediately. The asynchronous message is delivered via the normal events
/// (, ).
///
///
/// The time-out value, in milliseconds.
/// The default value is 0, which indicates an infinite time-out period.
/// Specifying -1 also indicates an infinite time-out period.
///
///
/// An optional token to cancel the asynchronous operation. The default value is .
///
/// true if an asynchronous message was received, false if timed out.
public Task WaitAsync(int timeout, CancellationToken cancellationToken = default)
{
CheckReady();
LogMessages.StartingWait(_connectionLogger, timeout, Connector!.Id);
return Connector!.Wait(async: true, timeout, cancellationToken);
}
///
/// Waits asynchronously until an asynchronous PostgreSQL messages (e.g. a notification)
/// arrives, and exits immediately. The asynchronous message is delivered via the normal events
/// (, ).
///
///
/// The time-out value as
///
///
/// An optional token to cancel the asynchronous operation. The default value is .
///
/// true if an asynchronous message was received, false if timed out.
public Task WaitAsync(TimeSpan timeout, CancellationToken cancellationToken = default) => WaitAsync((int)timeout.TotalMilliseconds, cancellationToken);
///
/// Waits asynchronously until an asynchronous PostgreSQL messages (e.g. a notification)
/// arrives, and exits immediately. The asynchronous message is delivered via the normal events
/// (, ).
///
///
/// An optional token to cancel the asynchronous operation. The default value is .
///
public Task WaitAsync(CancellationToken cancellationToken = default) => WaitAsync(0, cancellationToken);
#endregion
#region State checks
[MethodImpl(MethodImplOptions.AggressiveInlining)]
void CheckOpen()
{
CheckDisposed();
switch (FullState)
{
case ConnectionState.Open:
case ConnectionState.Open | ConnectionState.Executing:
case ConnectionState.Open | ConnectionState.Fetching:
case ConnectionState.Connecting:
return;
case ConnectionState.Closed:
case ConnectionState.Broken:
ThrowHelper.ThrowInvalidOperationException("Connection is not open");
return;
default:
ThrowHelper.ThrowArgumentOutOfRangeException();
return;
}
}
void CheckClosed()
{
CheckDisposed();
var fullState = FullState;
if (fullState is ConnectionState.Connecting || fullState.HasFlag(ConnectionState.Open))
ThrowHelper.ThrowInvalidOperationException("Connection already open");
}
void CheckDisposed()
=> ObjectDisposedException.ThrowIf(_disposed, this);
internal void CheckReady()
{
CheckDisposed();
switch (FullState)
{
case ConnectionState.Open:
case ConnectionState.Connecting: // We need to do type loading as part of connecting
return;
case ConnectionState.Closed:
case ConnectionState.Broken:
ThrowHelper.ThrowInvalidOperationException("Connection is not open");
return;
case ConnectionState.Open | ConnectionState.Executing:
case ConnectionState.Open | ConnectionState.Fetching:
ThrowHelper.ThrowInvalidOperationException("Connection is busy");
return;
default:
ThrowHelper.ThrowArgumentOutOfRangeException();
return;
}
}
#endregion State checks
#region Schema operations
///
/// Returns the supported collections
///
public override DataTable GetSchema()
=> GetSchema("MetaDataCollections", null);
///
/// Returns the schema collection specified by the collection name.
///
/// The collection name.
/// The collection specified.
public override DataTable GetSchema(string? collectionName) => GetSchema(collectionName, null);
///
/// Returns the schema collection specified by the collection name filtered by the restrictions.
///
/// The collection name.
///
/// The restriction values to filter the results. A description of the restrictions is contained
/// in the Restrictions collection.
///
/// The collection specified.
public override DataTable GetSchema(string? collectionName, string?[]? restrictions)
=> NpgsqlSchema.GetSchema(async: false, this, collectionName, restrictions).GetAwaiter().GetResult();
///
/// Asynchronously returns the supported collections.
///
///
/// An optional token to cancel the asynchronous operation. The default value is .
///
/// The collection specified.
public override Task GetSchemaAsync(CancellationToken cancellationToken = default)
=> GetSchemaAsync("MetaDataCollections", null, cancellationToken);
///
/// Asynchronously returns the schema collection specified by the collection name.
///
/// The collection name.
///
/// An optional token to cancel the asynchronous operation. The default value is .
///
/// The collection specified.
public override Task GetSchemaAsync(string collectionName, CancellationToken cancellationToken = default)
=> GetSchemaAsync(collectionName, null, cancellationToken);
///
/// Asynchronously returns the schema collection specified by the collection name filtered by the restrictions.
///
/// The collection name.
///
/// The restriction values to filter the results. A description of the restrictions is contained
/// in the Restrictions collection.
///
///
/// An optional token to cancel the asynchronous operation. The default value is .
///
/// The collection specified.
public override Task GetSchemaAsync(string collectionName, string?[]? restrictions, CancellationToken cancellationToken = default)
=> NpgsqlSchema.GetSchema(async: true, this, collectionName, restrictions, cancellationToken);
#endregion Schema operations
#region Misc
///
/// Creates a closed connection with the connection string and authentication details of this message.
///
object ICloneable.Clone()
{
CheckDisposed();
// For NativeAOT code size reduction, we avoid instantiating a connection here directly with
// `new NpgsqlConnection(_connectionString)`, since that would bring in the default data source builder, and with it various
// features which significantly increase binary size (ranges, System.Text.Json...). Instead, we pass through a "cloning
// instantiator" abstraction, where the implementation only ever gets set if SetupDataSource above is called (in which case the
// default data source is brought in anyway).
Debug.Assert(_dataSource is not null || _cloningInstantiator is not null);
var conn = _dataSource is null
? _cloningInstantiator!(_connectionString)
: _dataSource.CreateConnection();
conn.SslClientAuthenticationOptionsCallback = SslClientAuthenticationOptionsCallback;
#pragma warning disable CS0618 // Obsolete
conn.ProvideClientCertificatesCallback = ProvideClientCertificatesCallback;
conn.UserCertificateValidationCallback = UserCertificateValidationCallback;
conn.ProvidePasswordCallback = ProvidePasswordCallback;
#pragma warning restore CS0618
conn._userFacingConnectionString = _userFacingConnectionString;
return conn;
}
///
/// Clones this connection, replacing its connection string with the given one.
/// This allows creating a new connection with the same security information
/// (password, SSL callbacks) while changing other connection parameters (e.g.
/// database or pooling)
///
public NpgsqlConnection CloneWith(string connectionString)
{
CheckDisposed();
var csb = new NpgsqlConnectionStringBuilder(connectionString);
csb.Password ??= _dataSource?.GetPassword(async: false).GetAwaiter().GetResult();
if (csb.PersistSecurityInfo && !Settings.PersistSecurityInfo)
csb.PersistSecurityInfo = false;
return new NpgsqlConnection(csb.ToString())
{
SslClientAuthenticationOptionsCallback = SslClientAuthenticationOptionsCallback ?? _dataSource?.SslClientAuthenticationOptionsCallback,
#pragma warning disable CS0618 // Obsolete
ProvideClientCertificatesCallback = ProvideClientCertificatesCallback,
UserCertificateValidationCallback = UserCertificateValidationCallback,
ProvidePasswordCallback = ProvidePasswordCallback,
#pragma warning restore CS0618
};
}
///
/// Clones this connection, replacing its connection string with the given one.
/// This allows creating a new connection with the same security information
/// (password, SSL callbacks) while changing other connection parameters (e.g.
/// database or pooling)
///
public async ValueTask CloneWithAsync(string connectionString, CancellationToken cancellationToken = default)
{
CheckDisposed();
var csb = new NpgsqlConnectionStringBuilder(connectionString);
csb.Password ??= _dataSource is null ? null : await _dataSource.GetPassword(async: true, cancellationToken).ConfigureAwait(false);
if (csb.PersistSecurityInfo && !Settings.PersistSecurityInfo)
csb.PersistSecurityInfo = false;
return new NpgsqlConnection(csb.ToString())
{
SslClientAuthenticationOptionsCallback = SslClientAuthenticationOptionsCallback ?? _dataSource?.SslClientAuthenticationOptionsCallback,
#pragma warning disable CS0618 // Obsolete
ProvideClientCertificatesCallback = ProvideClientCertificatesCallback,
UserCertificateValidationCallback = UserCertificateValidationCallback,
ProvidePasswordCallback = ProvidePasswordCallback,
#pragma warning restore CS0618
};
}
///
/// This method changes the current database by disconnecting from the actual
/// database and connecting to the specified.
///
/// The name of the database to use in place of the current database.
public override void ChangeDatabase(string dbName)
{
ArgumentNullException.ThrowIfNull(dbName);
if (string.IsNullOrEmpty(dbName))
throw new ArgumentOutOfRangeException(nameof(dbName), dbName, $"Invalid database name: {dbName}");
CheckOpen();
Close();
_dataSource = null;
Settings = Settings.Clone();
Settings.Database = dbName;
ConnectionString = Settings.ToString();
Open();
}
///
/// DB provider factory.
///
protected override DbProviderFactory DbProviderFactory => NpgsqlFactory.Instance;
///
/// Clears the connection pool. All idle physical connections in the pool of the given connection are
/// immediately closed, and any busy connections which were opened before was called
/// will be closed when returned to the pool.
///
public static void ClearPool(NpgsqlConnection connection) => PoolManager.Clear(connection._connectionString);
///
/// Clear all connection pools. All idle physical connections in all pools are immediately closed, and any busy
/// connections which were opened before was called will be closed when returned
/// to their pool.
///
public static void ClearAllPools() => PoolManager.ClearAll();
///
/// Unprepares all prepared statements on this connection.
///
public void UnprepareAll()
{
CheckReady();
using (Connector!.StartUserAction())
Connector.UnprepareAll();
}
///
/// Flushes the type cache for this connection's connection string and reloads the types for this connection only.
/// Type changes will appear for other connections only after they are re-opened from the pool.
///
public void ReloadTypes()
{
CheckReady();
_dataSource!.Bootstrap(
Connector!,
NpgsqlTimeout.Infinite,
forceReload: true,
async: false,
CancellationToken.None)
.GetAwaiter().GetResult();
}
///
/// Flushes the type cache for this connection's connection string and reloads the types for this connection only.
/// Type changes will appear for other connections only after they are re-opened from the pool.
///
public Task ReloadTypesAsync()
=> ReloadTypesAsync(CancellationToken.None);
///
/// Flushes the type cache for this connection's connection string and reloads the types for this connection only.
/// Type changes will appear for other connections only after they are re-opened from the pool.
///
public async Task ReloadTypesAsync(CancellationToken cancellationToken)
{
CheckReady();
await _dataSource!.Bootstrap(
Connector!,
NpgsqlTimeout.Infinite,
forceReload: true,
async: true,
cancellationToken).ConfigureAwait(false);
}
///
/// This event is unsupported by Npgsql. Use instead.
///
[EditorBrowsable(EditorBrowsableState.Never)]
public new event EventHandler? Disposed
{
add => throw new NotSupportedException("The Disposed event isn't supported by Npgsql. Use DbConnection.StateChange instead.");
remove => throw new NotSupportedException("The Disposed event isn't supported by Npgsql. Use DbConnection.StateChange instead.");
}
event EventHandler? IComponent.Disposed
{
add => Disposed += value;
remove => Disposed -= value;
}
#endregion Misc
}
#region Delegates
///
/// Represents a method that handles the event.
///
/// The source of the event.
/// A that contains the notice information (e.g. message, severity...).
public delegate void NoticeEventHandler(object sender, NpgsqlNoticeEventArgs e);
///
/// Represents a method that handles the event.
///
/// The source of the event.
/// A that contains the notification payload.
public delegate void NotificationEventHandler(object sender, NpgsqlNotificationEventArgs e);
///
/// Represents a method that allows the application to provide a certificate collection to be used for SSL client authentication
///
///
/// A to be filled with one or more client
/// certificates.
///
public delegate void ProvideClientCertificatesCallback(X509CertificateCollection certificates);
///
/// Represents a method that allows the application to provide a password at connection time in code rather than configuration
///
/// Hostname
/// Port
/// Database Name
/// User
/// A valid password for connecting to the database
[Obsolete("Use NpgsqlDataSourceBuilder.UsePeriodicPasswordProvider or inject passwords directly into NpgsqlDataSource.Password")]
public delegate string ProvidePasswordCallback(string host, int port, string database, string username);
#endregion