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.")] public static INpgsqlTypeMapper GlobalTypeMapper => TypeMapping.GlobalTypeMapper.Instance; /// /// Connection-level type mapping is no longer supported. See the 7.0 release notes for configuring type mapping on NpgsqlDataSource. /// [Obsolete("Connection-level type mapping is no longer supported. See the 7.0 release notes 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(async: false, level, CancellationToken.None).GetAwaiter().GetResult(); async ValueTask BeginTransaction(bool async, IsolationLevel level, 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); 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) => BeginTransaction(async: true, level, 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 async Task ReloadTypesAsync(CancellationToken cancellationToken = default) { 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