2186 lines
93 KiB
C#
2186 lines
93 KiB
C#
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.IO;
|
|
using System.Net;
|
|
using System.Net.Security;
|
|
using System.Net.Sockets;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Runtime.Versioning;
|
|
using System.Security.Cryptography.X509Certificates;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.Transactions;
|
|
using Npgsql.Internal;
|
|
using Npgsql.Logging;
|
|
using Npgsql.NameTranslation;
|
|
using Npgsql.Netstandard20;
|
|
using Npgsql.TypeMapping;
|
|
using Npgsql.Util;
|
|
using NpgsqlTypes;
|
|
using IsolationLevel = System.Data.IsolationLevel;
|
|
|
|
namespace Npgsql;
|
|
|
|
/// <summary>
|
|
/// This class represents a connection to a PostgreSQL server.
|
|
/// </summary>
|
|
// ReSharper disable once RedundantNameQualifier
|
|
[System.ComponentModel.DesignerCategory("")]
|
|
public sealed class NpgsqlConnection : DbConnection, ICloneable, IComponent
|
|
{
|
|
#region Fields
|
|
|
|
// Set this when disposed is called.
|
|
bool _disposed;
|
|
|
|
/// <summary>
|
|
/// The connection string, without the password after open (unless Persist Security Info=true)
|
|
/// </summary>
|
|
string _userFacingConnectionString = string.Empty;
|
|
|
|
/// <summary>
|
|
/// The original connection string provided by the user, including the password.
|
|
/// </summary>
|
|
string _connectionString = string.Empty;
|
|
|
|
internal string OriginalConnectionString => _connectionString;
|
|
|
|
ConnectionState _fullState;
|
|
|
|
/// <summary>
|
|
/// The physical connection to the database. This is <see langword="null"/> when the connection is closed,
|
|
/// and also when it is open in multiplexing mode and unbound (e.g. not in a transaction).
|
|
/// </summary>
|
|
internal NpgsqlConnector? Connector { get; set; }
|
|
|
|
/// <summary>
|
|
/// The parsed connection string. Set only after the connection is opened.
|
|
/// </summary>
|
|
public NpgsqlConnectionStringBuilder Settings { get; private set; } = DefaultSettings;
|
|
|
|
static readonly NpgsqlConnectionStringBuilder DefaultSettings = new();
|
|
|
|
ConnectorSource? _pool;
|
|
|
|
internal ConnectorSource Pool
|
|
{
|
|
get
|
|
{
|
|
Debug.Assert(_pool is not null);
|
|
return _pool;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// A cached command handed out by <see cref="CreateCommand" />, which is returned when disposed. Useful for reducing allocations.
|
|
/// </summary>
|
|
internal NpgsqlCommand? CachedCommand { get; set; }
|
|
|
|
/// <summary>
|
|
/// Flag used to make sure we never double-close a connection, returning it twice to the pool.
|
|
/// </summary>
|
|
int _closing;
|
|
|
|
internal Transaction? EnlistedTransaction { get; set; }
|
|
|
|
/// <summary>
|
|
/// The global type mapper, which contains defaults used by all new connections.
|
|
/// Modify mappings on this mapper to affect your entire application.
|
|
/// </summary>
|
|
public static INpgsqlTypeMapper GlobalTypeMapper => TypeMapping.GlobalTypeMapper.Instance;
|
|
|
|
/// <summary>
|
|
/// The connection-specific type mapper - all modifications affect this connection only,
|
|
/// and are lost when it is closed.
|
|
/// </summary>
|
|
public INpgsqlTypeMapper TypeMapper
|
|
{
|
|
get
|
|
{
|
|
if (Settings.Multiplexing)
|
|
throw new NotSupportedException("Connection-specific type mapping is unsupported when multiplexing is enabled.");
|
|
|
|
CheckReady();
|
|
return Connector!.TypeMapper!;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The default TCP/IP port for PostgreSQL.
|
|
/// </summary>
|
|
public const int DefaultPort = 5432;
|
|
|
|
/// <summary>
|
|
/// Maximum value for connection timeout.
|
|
/// </summary>
|
|
internal const int TimeoutLimit = 1024;
|
|
|
|
/// <summary>
|
|
/// Tracks when this connection was bound to a physical connector (e.g. at open-time, when a transaction
|
|
/// was started...).
|
|
/// </summary>
|
|
internal ConnectorBindingScope ConnectorBindingScope { get; set; }
|
|
|
|
static readonly NpgsqlLogger Log = NpgsqlLogManager.CreateLogger(nameof(NpgsqlConnection));
|
|
|
|
static readonly StateChangeEventArgs ClosedToOpenEventArgs = new(ConnectionState.Closed, ConnectionState.Open);
|
|
static readonly StateChangeEventArgs OpenToClosedEventArgs = new(ConnectionState.Open, ConnectionState.Closed);
|
|
|
|
#endregion Fields
|
|
|
|
#region Constructors / Init / Open
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="NpgsqlConnection"/> class.
|
|
/// </summary>
|
|
public NpgsqlConnection()
|
|
=> GC.SuppressFinalize(this);
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of <see cref="NpgsqlConnection"/> with the given connection string.
|
|
/// </summary>
|
|
/// <param name="connectionString">The connection used to open the PostgreSQL database.</param>
|
|
public NpgsqlConnection(string? connectionString) : this()
|
|
=> ConnectionString = connectionString;
|
|
|
|
/// <summary>
|
|
/// Opens a database connection with the property settings specified by the <see cref="ConnectionString"/>.
|
|
/// </summary>
|
|
public override void Open() => Open(false, CancellationToken.None).GetAwaiter().GetResult();
|
|
|
|
/// <summary>
|
|
/// This is the asynchronous version of <see cref="Open()"/>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Do not invoke other methods and properties of the <see cref="NpgsqlConnection"/> object until the returned Task is complete.
|
|
/// </remarks>
|
|
/// <param name="cancellationToken">
|
|
/// An optional token to cancel the asynchronous operation. The default value is <see cref="CancellationToken.None"/>.
|
|
/// </param>
|
|
/// <returns>A task representing the asynchronous operation.</returns>
|
|
public override Task OpenAsync(CancellationToken cancellationToken)
|
|
{
|
|
using (NoSynchronizationContextScope.Enter())
|
|
return Open(true, cancellationToken);
|
|
}
|
|
|
|
void GetPoolAndSettings()
|
|
{
|
|
if (PoolManager.TryGetValue(_connectionString, out _pool))
|
|
{
|
|
Settings = _pool.Settings; // Great, we already have a pool
|
|
return;
|
|
}
|
|
|
|
// Connection string hasn't been seen before. Parse it.
|
|
var settings = new NpgsqlConnectionStringBuilder(_connectionString);
|
|
settings.Validate();
|
|
Settings = settings;
|
|
|
|
var hostsSeparator = settings.Host?.IndexOf(',');
|
|
if (hostsSeparator is -1)
|
|
{
|
|
if (settings.TargetSessionAttributesParsed is not null &&
|
|
settings.TargetSessionAttributesParsed != TargetSessionAttributes.Any)
|
|
{
|
|
throw new NotSupportedException("Target Session Attributes other then Any is only supported with multiple hosts");
|
|
}
|
|
|
|
if (!NpgsqlConnectionStringBuilder.IsUnixSocket(settings.Host!, settings.Port, out _) &&
|
|
NpgsqlConnectionStringBuilder.TrySplitHostPort(settings.Host!.AsSpan(), out var newHost, out var newPort))
|
|
{
|
|
settings.Host = newHost;
|
|
settings.Port = newPort;
|
|
}
|
|
}
|
|
|
|
// 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 and LoadBalanceHosts to make all connection strings
|
|
// that are otherwise identical point to the same pool.
|
|
var canonical = settings.ConnectionStringForMultipleHosts;
|
|
|
|
if (PoolManager.TryGetValue(canonical, out _pool))
|
|
{
|
|
// We're wrapping the original pool in the other, as the original doesn't have the TargetSessionAttributes
|
|
if (_pool is MultiHostConnectorPool mhcpCanonical)
|
|
_pool = new MultiHostConnectorPoolWrapper(Settings, _connectionString, mhcpCanonical);
|
|
// 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.
|
|
_pool = PoolManager.GetOrAdd(_connectionString, _pool);
|
|
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.
|
|
ConnectorSource newPool;
|
|
if (hostsSeparator.HasValue && hostsSeparator != -1)
|
|
{
|
|
if (settings.Multiplexing)
|
|
throw new NotSupportedException("Multiplexing is not supported with multiple hosts");
|
|
if (settings.ReplicationMode != ReplicationMode.Off)
|
|
throw new NotSupportedException("Replication is not supported with multiple hosts");
|
|
newPool = new MultiHostConnectorPool(settings, canonical);
|
|
}
|
|
else if (settings.Multiplexing)
|
|
newPool = new MultiplexingConnectorPool(settings, canonical);
|
|
else if (settings.Pooling)
|
|
newPool = new ConnectorPool(settings, canonical);
|
|
else
|
|
newPool = new UnpooledConnectorSource(settings, canonical);
|
|
|
|
_pool = PoolManager.GetOrAdd(canonical, newPool);
|
|
if (_pool == newPool)
|
|
{
|
|
Debug.Assert(_pool is not MultiHostConnectorPoolWrapper);
|
|
// If the pool we created was the one that ended up being stored we need to increment the appropriate counter.
|
|
// Avoids a race condition where multiple threads will create a pool but only one will be stored.
|
|
NpgsqlEventSource.Log.PoolCreated(newPool);
|
|
}
|
|
else
|
|
newPool.Dispose();
|
|
|
|
// We're wrapping the original pool in the other, as the original doesn't have the TargetSessionAttributes
|
|
if (_pool is MultiHostConnectorPool mhcp)
|
|
_pool = new MultiHostConnectorPoolWrapper(Settings, _connectionString, mhcp);
|
|
|
|
_pool = PoolManager.GetOrAdd(_connectionString, _pool);
|
|
}
|
|
|
|
internal Task Open(bool async, CancellationToken cancellationToken)
|
|
{
|
|
CheckClosed();
|
|
Debug.Assert(Connector == null);
|
|
|
|
if (_pool == null)
|
|
{
|
|
Debug.Assert(string.IsNullOrEmpty(_connectionString));
|
|
|
|
throw new InvalidOperationException("The ConnectionString property has not been initialized.");
|
|
}
|
|
|
|
FullState = ConnectionState.Connecting;
|
|
Log.Trace("Opening connection...");
|
|
|
|
if (Settings.Multiplexing)
|
|
{
|
|
if (Settings.Enlist && Transaction.Current != null)
|
|
{
|
|
// TODO: Keep in mind that the TransactionScope can be disposed
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
// We're opening in multiplexing mode, without a transaction. We don't actually do anything.
|
|
_userFacingConnectionString = Pool.UserFacingConnectionString;
|
|
|
|
// If we've never connected with this connection string, open a physical connector in order to generate
|
|
// any exception (bad user/password, IP address...). This reproduces the standard error behavior.
|
|
if (!((MultiplexingConnectorPool)Pool).IsBootstrapped)
|
|
return BootstrapMultiplexing(async, cancellationToken);
|
|
|
|
Log.Debug("Connection opened (multipelxing)");
|
|
FullState = ConnectionState.Open;
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
return OpenAsync(async, cancellationToken);
|
|
|
|
async Task OpenAsync(bool async, CancellationToken cancellationToken)
|
|
{
|
|
Debug.Assert(!Settings.Multiplexing);
|
|
|
|
NpgsqlConnector? connector = null;
|
|
try
|
|
{
|
|
var connectionTimeout = TimeSpan.FromSeconds(ConnectionTimeout);
|
|
var timeout = new NpgsqlTimeout(connectionTimeout);
|
|
|
|
var enlistToTransaction = Settings.Enlist ? Transaction.Current : null;
|
|
|
|
_userFacingConnectionString = _pool.UserFacingConnectionString;
|
|
|
|
// 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 && _pool.TryRentEnlistedPending(enlistToTransaction, this, out connector))
|
|
{
|
|
EnlistedTransaction = enlistToTransaction;
|
|
enlistToTransaction = null;
|
|
}
|
|
else
|
|
connector = await _pool.Get(this, timeout, async, cancellationToken);
|
|
|
|
Debug.Assert(connector.Connection is null,
|
|
$"Connection for opened connector '{Connector?.Id.ToString() ?? "???"}' is bound to another connection");
|
|
|
|
// Since this connector was last used, PostgreSQL types (e.g. enums) may have been added
|
|
// (and ReloadTypes() called), or global mappings may have changed by the user.
|
|
// Bring this up to date if needed.
|
|
// Note that in multiplexing execution, the pool-wide type mapper is used so no
|
|
// need to update the connector type mapper (this is why this is here).
|
|
if (connector.TypeMapper.ChangeCounter != TypeMapping.GlobalTypeMapper.Instance.ChangeCounter)
|
|
{
|
|
// LoadDatabaseInfo might attempt to execute a query over a connector, which might run in parallel to KeepAlive.
|
|
// Start a user action to prevent this.
|
|
using var _ = connector.StartUserAction(ConnectorState.Executing, cancellationToken);
|
|
await connector.LoadDatabaseInfo(false, timeout, async, cancellationToken);
|
|
}
|
|
|
|
ConnectorBindingScope = ConnectorBindingScope.Connection;
|
|
connector.Connection = this;
|
|
Connector = connector;
|
|
|
|
if (enlistToTransaction is not null)
|
|
EnlistTransaction(enlistToTransaction);
|
|
|
|
Log.Debug("Connection opened");
|
|
FullState = ConnectionState.Open;
|
|
}
|
|
catch
|
|
{
|
|
FullState = ConnectionState.Closed;
|
|
ConnectorBindingScope = ConnectorBindingScope.None;
|
|
Connector = null;
|
|
EnlistedTransaction = null;
|
|
|
|
if (connector is not null)
|
|
{
|
|
connector.Connection = null;
|
|
connector.Return();
|
|
}
|
|
|
|
throw;
|
|
}
|
|
}
|
|
|
|
async Task BootstrapMultiplexing(bool async, CancellationToken cancellationToken)
|
|
{
|
|
try
|
|
{
|
|
var timeout = new NpgsqlTimeout(TimeSpan.FromSeconds(ConnectionTimeout));
|
|
await ((MultiplexingConnectorPool)Pool).BootstrapMultiplexing(this, timeout, async, cancellationToken);
|
|
Log.Debug("Connection opened (multiplexing)");
|
|
FullState = ConnectionState.Open;
|
|
}
|
|
catch
|
|
{
|
|
FullState = ConnectionState.Closed;
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion Open / Init
|
|
|
|
#region Connection string management
|
|
|
|
/// <summary>
|
|
/// Gets or sets the string used to connect to a PostgreSQL database. See the manual for details.
|
|
/// </summary>
|
|
/// <value>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.
|
|
/// </value>
|
|
[AllowNull]
|
|
public override string ConnectionString
|
|
{
|
|
get => _userFacingConnectionString;
|
|
set
|
|
{
|
|
CheckClosed();
|
|
|
|
_userFacingConnectionString = _connectionString = value ?? string.Empty;
|
|
GetPoolAndSettings();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the delegate used to generate a password for new database connections.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <p>
|
|
/// This delegate is executed when a new database connection is opened that requires a password.
|
|
/// </p>
|
|
/// <p>
|
|
/// The <see cref="NpgsqlConnectionStringBuilder.Password"/> and <see cref="NpgsqlConnectionStringBuilder.Passfile"/> 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.
|
|
/// </p>
|
|
/// <p>
|
|
/// 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.
|
|
/// </p>
|
|
/// </remarks>
|
|
public ProvidePasswordCallback? ProvidePasswordCallback { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the delegate used to setup a connection whenever a physical connection is opened synchronously.
|
|
/// </summary>
|
|
[RequiresPreviewFeatures("Physical open callback is an experimental API, and its exact shape may change in the future")]
|
|
public PhysicalOpenCallback? PhysicalOpenCallback { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the delegate used to setup a connection whenever a physical connection is opened asynchronously.
|
|
/// </summary>
|
|
[RequiresPreviewFeatures("Physical open callback is an experimental API, and its exact shape may change in the future")]
|
|
public PhysicalOpenAsyncCallback? PhysicalOpenAsyncCallback { get; set; }
|
|
|
|
#endregion Connection string management
|
|
|
|
#region Configuration settings
|
|
|
|
/// <summary>
|
|
/// Backend server host name.
|
|
/// </summary>
|
|
[Browsable(true)]
|
|
public string? Host => Connector?.Host;
|
|
|
|
/// <summary>
|
|
/// Backend server port.
|
|
/// </summary>
|
|
[Browsable(true)]
|
|
public int Port => Connector?.Port ?? 0;
|
|
|
|
/// <summary>
|
|
/// Gets the time (in seconds) to wait while trying to establish a connection
|
|
/// before terminating the attempt and generating an error.
|
|
/// </summary>
|
|
/// <value>The time (in seconds) to wait for a connection to open. The default value is 15 seconds.</value>
|
|
public override int ConnectionTimeout => Settings.Timeout;
|
|
|
|
/// <summary>
|
|
/// Gets the time (in seconds) to wait while trying to execute a command
|
|
/// before terminating the attempt and generating an error.
|
|
/// </summary>
|
|
/// <value>The time (in seconds) to wait for a command to complete. The default value is 20 seconds.</value>
|
|
public int CommandTimeout => Settings.CommandTimeout;
|
|
|
|
///<summary>
|
|
/// Gets the name of the current database or the database to be used after a connection is opened.
|
|
/// </summary>
|
|
/// <value>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.</value>
|
|
public override string Database => Settings.Database ?? Settings.Username ?? "";
|
|
|
|
/// <summary>
|
|
/// Gets the string identifying the database server (host and port)
|
|
/// </summary>
|
|
/// <value>
|
|
/// 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.
|
|
/// </value>
|
|
public override string DataSource => Connector?.Settings.DataSourceCached ?? string.Empty;
|
|
|
|
/// <summary>
|
|
/// Whether to use Windows integrated security to log in.
|
|
/// </summary>
|
|
public bool IntegratedSecurity => Settings.IntegratedSecurity;
|
|
|
|
/// <summary>
|
|
/// User name.
|
|
/// </summary>
|
|
public string? UserName => Settings.Username;
|
|
|
|
internal string? Password => Settings.Password;
|
|
|
|
// The following two lines are here for backwards compatibility with the EF6 provider
|
|
// ReSharper disable UnusedMember.Global
|
|
internal string? EntityTemplateDatabase => Settings.EntityTemplateDatabase;
|
|
internal string? EntityAdminDatabase => Settings.EntityAdminDatabase;
|
|
// ReSharper restore UnusedMember.Global
|
|
|
|
#endregion Configuration settings
|
|
|
|
#region State management
|
|
|
|
/// <summary>
|
|
/// Gets the current state of the connection.
|
|
/// </summary>
|
|
/// <value>A bitwise combination of the <see cref="System.Data.ConnectionState"/> values. The default is <b>Closed</b>.</value>
|
|
[Browsable(false)]
|
|
public ConnectionState FullState
|
|
{
|
|
// Note: we allow accessing the state after dispose, #164
|
|
get => _fullState switch
|
|
{
|
|
ConnectionState.Open => Connector == null
|
|
? ConnectionState.Open // When unbound, we only know we're open
|
|
: Connector.State switch
|
|
{
|
|
ConnectorState.Ready => ConnectionState.Open,
|
|
ConnectorState.Executing => ConnectionState.Open | ConnectionState.Executing,
|
|
ConnectorState.Fetching => ConnectionState.Open | ConnectionState.Fetching,
|
|
ConnectorState.Copy => ConnectionState.Open | ConnectionState.Fetching,
|
|
ConnectorState.Replication => ConnectionState.Open | ConnectionState.Fetching,
|
|
ConnectorState.Waiting => ConnectionState.Open | ConnectionState.Fetching,
|
|
ConnectorState.Connecting => ConnectionState.Connecting,
|
|
ConnectorState.Broken => ConnectionState.Broken,
|
|
ConnectorState.Closed => throw new InvalidOperationException("Internal Npgsql bug: connection is in state Open but connector is in state Closed"),
|
|
_ => throw new InvalidOperationException($"Internal Npgsql bug: unexpected value {Connector.State} of enum {nameof(ConnectorState)}. Please file a bug.")
|
|
},
|
|
_ => _fullState
|
|
};
|
|
internal set
|
|
{
|
|
var originalOpen = _fullState.HasFlag(ConnectionState.Open);
|
|
|
|
_fullState = value;
|
|
|
|
var currentOpen = _fullState.HasFlag(ConnectionState.Open);
|
|
if (currentOpen != originalOpen)
|
|
{
|
|
OnStateChange(currentOpen
|
|
? ClosedToOpenEventArgs
|
|
: OpenToClosedEventArgs);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets whether the current state of the connection is Open or Closed
|
|
/// </summary>
|
|
/// <value>ConnectionState.Open, ConnectionState.Closed or ConnectionState.Connecting</value>
|
|
[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
|
|
|
|
/// <summary>
|
|
/// Creates and returns a <see cref="System.Data.Common.DbCommand"/>
|
|
/// object associated with the <see cref="System.Data.Common.DbConnection"/>.
|
|
/// </summary>
|
|
/// <returns>A <see cref="System.Data.Common.DbCommand"/> object.</returns>
|
|
protected override DbCommand CreateDbCommand() => CreateCommand();
|
|
|
|
/// <summary>
|
|
/// Creates and returns a <see cref="NpgsqlCommand"/> object associated with the <see cref="NpgsqlConnection"/>.
|
|
/// </summary>
|
|
/// <returns>A <see cref="NpgsqlCommand"/> object.</returns>
|
|
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);
|
|
}
|
|
|
|
#if NET6_0_OR_GREATER
|
|
/// <inheritdoc/>
|
|
public override bool CanCreateBatch => true;
|
|
|
|
/// <inheritdoc/>
|
|
protected override DbBatch CreateDbBatch() => CreateBatch();
|
|
|
|
/// <inheritdoc cref="DbConnection.CreateBatch"/>
|
|
public new NpgsqlBatch CreateBatch() => new(this);
|
|
#else
|
|
/// <summary>
|
|
/// Creates and returns a <see cref="NpgsqlBatch"/> object associated with the <see cref="NpgsqlConnection"/>.
|
|
/// </summary>
|
|
/// <returns>A <see cref="NpgsqlBatch"/> object.</returns>
|
|
public NpgsqlBatch CreateBatch() => new(this);
|
|
#endif
|
|
|
|
#endregion Command / Batch creation
|
|
|
|
#region Transactions
|
|
|
|
/// <summary>
|
|
/// Begins a database transaction with the specified isolation level.
|
|
/// </summary>
|
|
/// <param name="isolationLevel">The isolation level under which the transaction should run.</param>
|
|
/// <returns>A <see cref="System.Data.Common.DbTransaction"/> object representing the new transaction.</returns>
|
|
/// <remarks>Nested transactions are not supported.</remarks>
|
|
protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel) => BeginTransaction(isolationLevel);
|
|
|
|
/// <summary>
|
|
/// Begins a database transaction.
|
|
/// </summary>
|
|
/// <returns>A <see cref="NpgsqlTransaction"/> object representing the new transaction.</returns>
|
|
/// <remarks>
|
|
/// Nested transactions are not supported.
|
|
/// Transactions created by this method will have the <see cref="IsolationLevel.ReadCommitted"/> isolation level.
|
|
/// </remarks>
|
|
public new NpgsqlTransaction BeginTransaction()
|
|
=> BeginTransaction(IsolationLevel.Unspecified);
|
|
|
|
/// <summary>
|
|
/// Begins a database transaction with the specified isolation level.
|
|
/// </summary>
|
|
/// <param name="level">The isolation level under which the transaction should run.</param>
|
|
/// <returns>A <see cref="NpgsqlTransaction"/> object representing the new transaction.</returns>
|
|
/// <remarks>Nested transactions are not supported.</remarks>
|
|
public new NpgsqlTransaction BeginTransaction(IsolationLevel level)
|
|
=> BeginTransaction(level, async: false, CancellationToken.None).GetAwaiter().GetResult();
|
|
|
|
async ValueTask<NpgsqlTransaction> BeginTransaction(IsolationLevel level, bool async, CancellationToken cancellationToken)
|
|
{
|
|
if (level == IsolationLevel.Chaos)
|
|
throw new NotSupportedException("Unsupported IsolationLevel: " + level);
|
|
|
|
CheckReady();
|
|
if (Connector != null && Connector.InTransaction)
|
|
throw new InvalidOperationException("A transaction is already in progress; nested/concurrent transactions aren't supported.");
|
|
|
|
// There was a commited/rollbacked transaction, but it was not disposed
|
|
var connector = ConnectorBindingScope == ConnectorBindingScope.Transaction ?
|
|
Connector
|
|
: await StartBindingScope(ConnectorBindingScope.Transaction, NpgsqlTimeout.Infinite, async,
|
|
cancellationToken);
|
|
|
|
Debug.Assert(connector != null);
|
|
|
|
try
|
|
{
|
|
// 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;
|
|
}
|
|
catch
|
|
{
|
|
EndBindingScope(ConnectorBindingScope.Transaction);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
#if !NETSTANDARD2_0
|
|
/// <summary>
|
|
/// Asynchronously begins a database transaction.
|
|
/// </summary>
|
|
/// <param name="isolationLevel">The isolation level under which the transaction should run.</param>
|
|
/// <param name="cancellationToken">
|
|
/// An optional token to cancel the asynchronous operation. The default value is <see cref="CancellationToken.None"/>.
|
|
/// </param>
|
|
/// <returns>A task whose <see cref="ValueTask{T}.Result"/> property is an object representing the new transaction.</returns>
|
|
/// <remarks>
|
|
/// Nested transactions are not supported.
|
|
/// </remarks>
|
|
protected override async ValueTask<DbTransaction> BeginDbTransactionAsync(IsolationLevel isolationLevel, CancellationToken cancellationToken)
|
|
=> await BeginTransactionAsync(isolationLevel, cancellationToken);
|
|
|
|
/// <summary>
|
|
/// Asynchronously begins a database transaction.
|
|
/// </summary>
|
|
/// <param name="cancellationToken">
|
|
/// An optional token to cancel the asynchronous operation. The default value is <see cref="CancellationToken.None"/>.
|
|
/// </param>
|
|
/// <returns>A task whose Result property is an object representing the new transaction.</returns>
|
|
/// <remarks>
|
|
/// Nested transactions are not supported.
|
|
/// Transactions created by this method will have the <see cref="IsolationLevel.ReadCommitted"/> isolation level.
|
|
/// </remarks>
|
|
public new ValueTask<NpgsqlTransaction> BeginTransactionAsync(CancellationToken cancellationToken = default)
|
|
=> BeginTransactionAsync(IsolationLevel.Unspecified, cancellationToken);
|
|
|
|
/// <summary>
|
|
/// Asynchronously begins a database transaction.
|
|
/// </summary>
|
|
/// <param name="level">The isolation level under which the transaction should run.</param>
|
|
/// <param name="cancellationToken">
|
|
/// An optional token to cancel the asynchronous operation. The default value is <see cref="CancellationToken.None"/>.
|
|
/// </param>
|
|
/// <returns>A task whose <see cref="ValueTask{T}.Result"/> property is an object representing the new transaction.</returns>
|
|
/// <remarks>
|
|
/// Nested transactions are not supported.
|
|
/// </remarks>
|
|
public new ValueTask<NpgsqlTransaction> BeginTransactionAsync(IsolationLevel level, CancellationToken cancellationToken = default)
|
|
{
|
|
using (NoSynchronizationContextScope.Enter())
|
|
return BeginTransaction(level, async: true, cancellationToken);
|
|
}
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Enlist transaction.
|
|
/// </summary>
|
|
public override void EnlistTransaction(Transaction? transaction)
|
|
{
|
|
if (Settings.Multiplexing)
|
|
throw new NotSupportedException("Ambient transactions aren't yet implemented for multiplexing");
|
|
|
|
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 = StartBindingScope(ConnectorBindingScope.Transaction);
|
|
|
|
EnlistedTransaction = transaction;
|
|
if (transaction == null)
|
|
{
|
|
EnlistedTransaction = 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;
|
|
|
|
Log.Debug($"Enlisted volatile resource manager (localid={transaction.TransactionInformation.LocalIdentifier})", connector.Id);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Close
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public override void Close() => Close(async: false).GetAwaiter().GetResult();
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
#if NETSTANDARD2_0
|
|
public Task CloseAsync()
|
|
#else
|
|
public override Task CloseAsync()
|
|
#endif
|
|
{
|
|
using (NoSynchronizationContextScope.Enter())
|
|
return 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);
|
|
}
|
|
|
|
// TODO: The following shouldn't exist - we need to flow down the regular path to close any
|
|
// open reader / COPY. See test CloseDuringRead with multiplexing.
|
|
if (Settings.Multiplexing && ConnectorBindingScope == ConnectorBindingScope.None)
|
|
{
|
|
// TODO: Consider falling through to the regular reset logic. This adds some unneeded conditions
|
|
// and assignment but actual perf impact should be negligible (measure).
|
|
Debug.Assert(Connector == null);
|
|
ReleaseCloseLock();
|
|
|
|
FullState = ConnectionState.Closed;
|
|
Log.Debug("Connection closed (multiplexing)");
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
return CloseAsync(async);
|
|
}
|
|
|
|
async Task CloseAsync(bool async)
|
|
{
|
|
Debug.Assert(Connector != null);
|
|
Debug.Assert(ConnectorBindingScope != ConnectorBindingScope.None);
|
|
|
|
try
|
|
{
|
|
var connector = Connector;
|
|
Log.Trace("Closing connection...", 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);
|
|
|
|
if (ConnectorBindingScope == ConnectorBindingScope.None)
|
|
{
|
|
Debug.Assert(Settings.Multiplexing);
|
|
Debug.Assert(Connector is null);
|
|
|
|
FullState = ConnectionState.Closed;
|
|
Log.Debug("Connection closed (multiplexing, after closing reader)", connector.Id);
|
|
return;
|
|
}
|
|
}
|
|
|
|
Debug.Assert(connector.IsReady || connector.IsBroken);
|
|
Debug.Assert(connector.CurrentReader == null);
|
|
Debug.Assert(connector.CurrentCopyOperation == null);
|
|
|
|
if (EnlistedTransaction != null)
|
|
{
|
|
// A System.Transactions transaction is still in progress
|
|
|
|
connector.Connection = null;
|
|
|
|
// Close the connection and disconnect it from the resource manager but leave the
|
|
// connector in an enlisted pending list in the pool. If another connection is opened within
|
|
// the same transaction scope, we will reuse this connector to avoid escalating to a distributed
|
|
// transaction
|
|
_pool?.AddPendingEnlistedConnector(connector, EnlistedTransaction);
|
|
|
|
EnlistedTransaction = null;
|
|
}
|
|
else
|
|
{
|
|
if (Settings.Pooling)
|
|
{
|
|
// Clear the buffer, roll back any pending transaction and prepend a reset message if needed
|
|
// Also returns the connector to the pool, if there is an open transaction and multiplexing is on
|
|
// Note that we're doing this only for pooled connections
|
|
await connector.Reset(async);
|
|
}
|
|
else
|
|
{
|
|
// We're already doing the same in the NpgsqlConnector.Reset for pooled connections
|
|
// TODO: move reset logic to ConnectorSource.Return
|
|
connector.Transaction?.UnbindIfNecessary();
|
|
}
|
|
|
|
if (Settings.Multiplexing)
|
|
{
|
|
// We've already closed ongoing operations rolled back any transaction and the connector is already in the pool,
|
|
// so we must be unbound. Nothing to do.
|
|
Debug.Assert(ConnectorBindingScope == ConnectorBindingScope.None,
|
|
$"When closing a multiplexed connection, the connection was supposed to be unbound, but {nameof(ConnectorBindingScope)} was {ConnectorBindingScope}");
|
|
}
|
|
else
|
|
{
|
|
connector.Connection = null;
|
|
connector.Return();
|
|
}
|
|
}
|
|
|
|
Connector = null;
|
|
ConnectorBindingScope = ConnectorBindingScope.None;
|
|
FullState = ConnectionState.Closed;
|
|
Log.Debug("Connection closed", connector.Id);
|
|
}
|
|
finally
|
|
{
|
|
ReleaseCloseLock();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Releases all resources used by the <see cref="NpgsqlConnection"/>.
|
|
/// </summary>
|
|
/// <param name="disposing"><see langword="true"/> when called from <see cref="Dispose"/>;
|
|
/// <see langword="false"/> when being called from the finalizer.</param>
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
if (_disposed)
|
|
return;
|
|
if (disposing)
|
|
Close();
|
|
_disposed = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Releases all resources used by the <see cref="NpgsqlConnection"/>.
|
|
/// </summary>
|
|
#if NETSTANDARD2_0
|
|
public ValueTask DisposeAsync()
|
|
#else
|
|
public override ValueTask DisposeAsync()
|
|
#endif
|
|
{
|
|
using (NoSynchronizationContextScope.Enter())
|
|
return DisposeAsyncCore();
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
async ValueTask DisposeAsyncCore()
|
|
{
|
|
if (_disposed)
|
|
return;
|
|
|
|
await CloseAsync();
|
|
_disposed = true;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Notifications and Notices
|
|
|
|
/// <summary>
|
|
/// Fires when PostgreSQL notices are received from PostgreSQL.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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 <see cref="Notification"/> event).
|
|
/// </remarks>
|
|
public event NoticeEventHandler? Notice;
|
|
|
|
/// <summary>
|
|
/// Fires when PostgreSQL notifications are received from PostgreSQL.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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 <see cref="Notice"/> event).
|
|
/// </remarks>
|
|
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
|
|
Log.Error("User exception caught when emitting notice event", 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
|
|
Log.Error("User exception caught when emitting notification event", ex);
|
|
}
|
|
}
|
|
|
|
#endregion Notifications and Notices
|
|
|
|
#region SSL
|
|
|
|
/// <summary>
|
|
/// Returns whether SSL is being used for the connection.
|
|
/// </summary>
|
|
internal bool IsSecure => CheckOpenAndRunInTemporaryScope(c => c.IsSecure);
|
|
|
|
/// <summary>
|
|
/// Returns whether SCRAM-SHA256 is being user for the connection
|
|
/// </summary>
|
|
internal bool IsScram => CheckOpenAndRunInTemporaryScope(c => c.IsScram);
|
|
|
|
/// <summary>
|
|
/// Returns whether SCRAM-SHA256-PLUS is being user for the connection
|
|
/// </summary>
|
|
internal bool IsScramPlus => CheckOpenAndRunInTemporaryScope(c => c.IsScramPlus);
|
|
|
|
/// <summary>
|
|
/// Selects the local Secure Sockets Layer (SSL) certificate used for authentication.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// See <see href="https://msdn.microsoft.com/en-us/library/system.net.security.localcertificateselectioncallback(v=vs.110).aspx"/>
|
|
/// </remarks>
|
|
public ProvideClientCertificatesCallback? ProvideClientCertificatesCallback { get; set; }
|
|
|
|
/// <summary>
|
|
/// <para>
|
|
/// Verifies the remote Secure Sockets Layer (SSL) certificate used for authentication.
|
|
/// </para>
|
|
/// <para>
|
|
/// Cannot be used in conjunction with <see cref="SslMode.Disable" />, <see cref="SslMode.VerifyCA" /> and
|
|
/// <see cref="SslMode.VerifyFull" />.
|
|
/// </para>
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// See <see href="https://msdn.microsoft.com/en-us/library/system.net.security.remotecertificatevalidationcallback(v=vs.110).aspx"/>
|
|
/// </remarks>
|
|
public RemoteCertificateValidationCallback? UserCertificateValidationCallback { get; set; }
|
|
|
|
#endregion SSL
|
|
|
|
#region Backend version, capabilities, settings
|
|
|
|
// TODO: We should probably move DatabaseInfo from each connector to the pool (but remember unpooled)
|
|
|
|
/// <summary>
|
|
/// The version of the PostgreSQL server we're connected to.
|
|
/// <remarks>
|
|
/// <p>
|
|
/// This can only be called when the connection is open.
|
|
/// </p>
|
|
/// <p>
|
|
/// 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.
|
|
/// </p>
|
|
/// </remarks>
|
|
/// </summary>
|
|
[Browsable(false)]
|
|
public Version PostgreSqlVersion => CheckOpenAndRunInTemporaryScope(c => c.DatabaseInfo.Version);
|
|
|
|
/// <summary>
|
|
/// The PostgreSQL server version as returned by the server_version option.
|
|
/// <remarks>
|
|
/// This can only be called when the connection is open.
|
|
/// </remarks>
|
|
/// </summary>
|
|
public override string ServerVersion => CheckOpenAndRunInTemporaryScope(
|
|
c => c.DatabaseInfo.ServerVersion);
|
|
|
|
/// <summary>
|
|
/// Process id of backend server.
|
|
/// This can only be called when there is an active connection.
|
|
/// </summary>
|
|
[Browsable(false)]
|
|
// ReSharper disable once InconsistentNaming
|
|
public int ProcessID
|
|
{
|
|
get
|
|
{
|
|
CheckOpen();
|
|
|
|
return TryGetBoundConnector(out var connector)
|
|
? connector.BackendProcessId
|
|
: throw new InvalidOperationException("No bound physical connection (using multiplexing)");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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)
|
|
/// </summary>
|
|
[Browsable(false)]
|
|
public bool HasIntegerDateTimes => CheckOpenAndRunInTemporaryScope(c => c.DatabaseInfo.HasIntegerDateTimes);
|
|
|
|
/// <summary>
|
|
/// The connection's timezone as reported by PostgreSQL, in the IANA/Olson database format.
|
|
/// </summary>
|
|
[Browsable(false)]
|
|
public string Timezone => CheckOpenAndRunInTemporaryScope(c => c.Timezone);
|
|
|
|
/// <summary>
|
|
/// Holds all PostgreSQL parameters received for this connection. Is updated if the values change
|
|
/// (e.g. as a result of a SET command).
|
|
/// </summary>
|
|
[Browsable(false)]
|
|
public IReadOnlyDictionary<string, string> PostgresParameters
|
|
=> CheckOpenAndRunInTemporaryScope(c => c.PostgresParameters);
|
|
|
|
#endregion Backend version, capabilities, settings
|
|
|
|
#region Copy
|
|
|
|
/// <summary>
|
|
/// Begins a binary COPY FROM STDIN operation, a high-performance data import mechanism to a PostgreSQL table.
|
|
/// </summary>
|
|
/// <param name="copyFromCommand">A COPY FROM STDIN SQL command</param>
|
|
/// <returns>A <see cref="NpgsqlBinaryImporter"/> which can be used to write rows and columns</returns>
|
|
/// <remarks>
|
|
/// See https://www.postgresql.org/docs/current/static/sql-copy.html.
|
|
/// </remarks>
|
|
public NpgsqlBinaryImporter BeginBinaryImport(string copyFromCommand)
|
|
=> BeginBinaryImport(copyFromCommand, async: false, CancellationToken.None).GetAwaiter().GetResult();
|
|
|
|
/// <summary>
|
|
/// Begins a binary COPY FROM STDIN operation, a high-performance data import mechanism to a PostgreSQL table.
|
|
/// </summary>
|
|
/// <param name="copyFromCommand">A COPY FROM STDIN SQL command</param>
|
|
/// <param name="cancellationToken">An optional token to cancel the asynchronous operation. The default value is None.</param>
|
|
/// <returns>A <see cref="NpgsqlBinaryImporter"/> which can be used to write rows and columns</returns>
|
|
/// <remarks>
|
|
/// See https://www.postgresql.org/docs/current/static/sql-copy.html.
|
|
/// </remarks>
|
|
public Task<NpgsqlBinaryImporter> BeginBinaryImportAsync(string copyFromCommand, CancellationToken cancellationToken = default)
|
|
{
|
|
using (NoSynchronizationContextScope.Enter())
|
|
return BeginBinaryImport(copyFromCommand, async: true, cancellationToken);
|
|
}
|
|
|
|
async Task<NpgsqlBinaryImporter> BeginBinaryImport(string copyFromCommand, bool async, CancellationToken cancellationToken = default)
|
|
{
|
|
if (copyFromCommand == null)
|
|
throw new ArgumentNullException(nameof(copyFromCommand));
|
|
if (!copyFromCommand.TrimStart().ToUpper().StartsWith("COPY", StringComparison.Ordinal))
|
|
throw new ArgumentException("Must contain a COPY FROM STDIN command!", nameof(copyFromCommand));
|
|
|
|
CheckReady();
|
|
var connector = StartBindingScope(ConnectorBindingScope.Copy);
|
|
|
|
Log.Debug("Starting binary import", connector.Id);
|
|
// no point in passing a cancellationToken here, as we register the cancellation in the Init method
|
|
connector.StartUserAction(ConnectorState.Copy, attemptPgCancellation: false);
|
|
try
|
|
{
|
|
var importer = new NpgsqlBinaryImporter(connector);
|
|
await importer.Init(copyFromCommand, async, cancellationToken);
|
|
connector.CurrentCopyOperation = importer;
|
|
return importer;
|
|
}
|
|
catch
|
|
{
|
|
connector.EndUserAction();
|
|
EndBindingScope(ConnectorBindingScope.Copy);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Begins a binary COPY TO STDOUT operation, a high-performance data export mechanism from a PostgreSQL table.
|
|
/// </summary>
|
|
/// <param name="copyToCommand">A COPY TO STDOUT SQL command</param>
|
|
/// <returns>A <see cref="NpgsqlBinaryExporter"/> which can be used to read rows and columns</returns>
|
|
/// <remarks>
|
|
/// See https://www.postgresql.org/docs/current/static/sql-copy.html.
|
|
/// </remarks>
|
|
public NpgsqlBinaryExporter BeginBinaryExport(string copyToCommand)
|
|
=> BeginBinaryExport(copyToCommand, async: false, CancellationToken.None).GetAwaiter().GetResult();
|
|
|
|
/// <summary>
|
|
/// Begins a binary COPY TO STDOUT operation, a high-performance data export mechanism from a PostgreSQL table.
|
|
/// </summary>
|
|
/// <param name="copyToCommand">A COPY TO STDOUT SQL command</param>
|
|
/// <param name="cancellationToken">An optional token to cancel the asynchronous operation. The default value is None.</param>
|
|
/// <returns>A <see cref="NpgsqlBinaryExporter"/> which can be used to read rows and columns</returns>
|
|
/// <remarks>
|
|
/// See https://www.postgresql.org/docs/current/static/sql-copy.html.
|
|
/// </remarks>
|
|
public Task<NpgsqlBinaryExporter> BeginBinaryExportAsync(string copyToCommand, CancellationToken cancellationToken = default)
|
|
{
|
|
using (NoSynchronizationContextScope.Enter())
|
|
return BeginBinaryExport(copyToCommand, async: true, cancellationToken);
|
|
}
|
|
|
|
async Task<NpgsqlBinaryExporter> BeginBinaryExport(string copyToCommand, bool async, CancellationToken cancellationToken = default)
|
|
{
|
|
if (copyToCommand == null)
|
|
throw new ArgumentNullException(nameof(copyToCommand));
|
|
if (!copyToCommand.TrimStart().ToUpper().StartsWith("COPY", StringComparison.Ordinal))
|
|
throw new ArgumentException("Must contain a COPY TO STDOUT command!", nameof(copyToCommand));
|
|
|
|
CheckReady();
|
|
var connector = StartBindingScope(ConnectorBindingScope.Copy);
|
|
|
|
Log.Debug("Starting binary export", connector.Id);
|
|
// no point in passing a cancellationToken here, as we register the cancellation in the Init method
|
|
connector.StartUserAction(ConnectorState.Copy, attemptPgCancellation: false);
|
|
try
|
|
{
|
|
var exporter = new NpgsqlBinaryExporter(connector);
|
|
await exporter.Init(copyToCommand, async, cancellationToken);
|
|
connector.CurrentCopyOperation = exporter;
|
|
return exporter;
|
|
}
|
|
catch
|
|
{
|
|
connector.EndUserAction();
|
|
EndBindingScope(ConnectorBindingScope.Copy);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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 <paramref name="copyFromCommand"/>.
|
|
/// </summary>
|
|
/// <param name="copyFromCommand">A COPY FROM STDIN SQL command</param>
|
|
/// <returns>
|
|
/// A TextWriter that can be used to send textual data.</returns>
|
|
/// <remarks>
|
|
/// See https://www.postgresql.org/docs/current/static/sql-copy.html.
|
|
/// </remarks>
|
|
public TextWriter BeginTextImport(string copyFromCommand)
|
|
=> BeginTextImport(copyFromCommand, async: false, CancellationToken.None).GetAwaiter().GetResult();
|
|
|
|
/// <summary>
|
|
/// 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 <paramref name="copyFromCommand"/>.
|
|
/// </summary>
|
|
/// <param name="copyFromCommand">A COPY FROM STDIN SQL command</param>
|
|
/// <param name="cancellationToken">An optional token to cancel the asynchronous operation. The default value is None.</param>
|
|
/// <returns>
|
|
/// A TextWriter that can be used to send textual data.</returns>
|
|
/// <remarks>
|
|
/// See https://www.postgresql.org/docs/current/static/sql-copy.html.
|
|
/// </remarks>
|
|
public Task<TextWriter> BeginTextImportAsync(string copyFromCommand, CancellationToken cancellationToken = default)
|
|
{
|
|
using (NoSynchronizationContextScope.Enter())
|
|
return BeginTextImport(copyFromCommand, async: true, cancellationToken);
|
|
}
|
|
|
|
async Task<TextWriter> BeginTextImport(string copyFromCommand, bool async, CancellationToken cancellationToken = default)
|
|
{
|
|
if (copyFromCommand == null)
|
|
throw new ArgumentNullException(nameof(copyFromCommand));
|
|
if (!copyFromCommand.TrimStart().ToUpper().StartsWith("COPY", StringComparison.Ordinal))
|
|
throw new ArgumentException("Must contain a COPY FROM STDIN command!", nameof(copyFromCommand));
|
|
|
|
CheckReady();
|
|
var connector = StartBindingScope(ConnectorBindingScope.Copy);
|
|
|
|
Log.Debug("Starting text import", connector.Id);
|
|
// no point in passing a cancellationToken here, as we register the cancellation in the Init method
|
|
connector.StartUserAction(ConnectorState.Copy, attemptPgCancellation: false);
|
|
try
|
|
{
|
|
var copyStream = new NpgsqlRawCopyStream(connector);
|
|
await copyStream.Init(copyFromCommand, async, cancellationToken);
|
|
var writer = new NpgsqlCopyTextWriter(connector, copyStream);
|
|
connector.CurrentCopyOperation = writer;
|
|
return writer;
|
|
}
|
|
catch
|
|
{
|
|
connector.EndUserAction();
|
|
EndBindingScope(ConnectorBindingScope.Copy);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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 <paramref name="copyToCommand"/>.
|
|
/// </summary>
|
|
/// <param name="copyToCommand">A COPY TO STDOUT SQL command</param>
|
|
/// <returns>
|
|
/// A TextReader that can be used to read textual data.</returns>
|
|
/// <remarks>
|
|
/// See https://www.postgresql.org/docs/current/static/sql-copy.html.
|
|
/// </remarks>
|
|
public TextReader BeginTextExport(string copyToCommand)
|
|
=> BeginTextExport(copyToCommand, async: false, CancellationToken.None).GetAwaiter().GetResult();
|
|
|
|
/// <summary>
|
|
/// 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 <paramref name="copyToCommand"/>.
|
|
/// </summary>
|
|
/// <param name="copyToCommand">A COPY TO STDOUT SQL command</param>
|
|
/// <param name="cancellationToken">An optional token to cancel the asynchronous operation. The default value is None.</param>
|
|
/// <returns>
|
|
/// A TextReader that can be used to read textual data.</returns>
|
|
/// <remarks>
|
|
/// See https://www.postgresql.org/docs/current/static/sql-copy.html.
|
|
/// </remarks>
|
|
public Task<TextReader> BeginTextExportAsync(string copyToCommand, CancellationToken cancellationToken = default)
|
|
{
|
|
using (NoSynchronizationContextScope.Enter())
|
|
return BeginTextExport(copyToCommand, async: true, cancellationToken);
|
|
}
|
|
|
|
async Task<TextReader> BeginTextExport(string copyToCommand, bool async, CancellationToken cancellationToken = default)
|
|
{
|
|
if (copyToCommand == null)
|
|
throw new ArgumentNullException(nameof(copyToCommand));
|
|
if (!copyToCommand.TrimStart().ToUpper().StartsWith("COPY", StringComparison.Ordinal))
|
|
throw new ArgumentException("Must contain a COPY TO STDOUT command!", nameof(copyToCommand));
|
|
|
|
CheckReady();
|
|
var connector = StartBindingScope(ConnectorBindingScope.Copy);
|
|
|
|
Log.Debug("Starting text export", connector.Id);
|
|
// no point in passing a cancellationToken here, as we register the cancellation in the Init method
|
|
connector.StartUserAction(ConnectorState.Copy, attemptPgCancellation: false);
|
|
try
|
|
{
|
|
var copyStream = new NpgsqlRawCopyStream(connector);
|
|
await copyStream.Init(copyToCommand, async, cancellationToken);
|
|
var reader = new NpgsqlCopyTextReader(connector, copyStream);
|
|
connector.CurrentCopyOperation = reader;
|
|
return reader;
|
|
}
|
|
catch
|
|
{
|
|
connector.EndUserAction();
|
|
EndBindingScope(ConnectorBindingScope.Copy);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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, <see cref="BeginRawBinaryCopy(string)"/> 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.
|
|
/// </summary>
|
|
/// <param name="copyCommand">A COPY TO STDOUT or COPY FROM STDIN SQL command</param>
|
|
/// <returns>A <see cref="NpgsqlRawCopyStream"/> that can be used to read or write raw binary data.</returns>
|
|
/// <remarks>
|
|
/// See https://www.postgresql.org/docs/current/static/sql-copy.html.
|
|
/// </remarks>
|
|
public NpgsqlRawCopyStream BeginRawBinaryCopy(string copyCommand)
|
|
=> BeginRawBinaryCopy(copyCommand, async: false, CancellationToken.None).GetAwaiter().GetResult();
|
|
|
|
/// <summary>
|
|
/// 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, <see cref="BeginRawBinaryCopyAsync(string, CancellationToken)"/> 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.
|
|
/// </summary>
|
|
/// <param name="copyCommand">A COPY TO STDOUT or COPY FROM STDIN SQL command</param>
|
|
/// <param name="cancellationToken">An optional token to cancel the asynchronous operation. The default value is None.</param>
|
|
/// <returns>A <see cref="NpgsqlRawCopyStream"/> that can be used to read or write raw binary data.</returns>
|
|
/// <remarks>
|
|
/// See https://www.postgresql.org/docs/current/static/sql-copy.html.
|
|
/// </remarks>
|
|
public Task<NpgsqlRawCopyStream> BeginRawBinaryCopyAsync(string copyCommand, CancellationToken cancellationToken = default)
|
|
{
|
|
using (NoSynchronizationContextScope.Enter())
|
|
return BeginRawBinaryCopy(copyCommand, async: true, cancellationToken);
|
|
}
|
|
|
|
async Task<NpgsqlRawCopyStream> BeginRawBinaryCopy(string copyCommand, bool async, CancellationToken cancellationToken = default)
|
|
{
|
|
if (copyCommand == null)
|
|
throw new ArgumentNullException(nameof(copyCommand));
|
|
if (!copyCommand.TrimStart().ToUpper().StartsWith("COPY", StringComparison.Ordinal))
|
|
throw new ArgumentException("Must contain a COPY TO STDOUT OR COPY FROM STDIN command!", nameof(copyCommand));
|
|
|
|
CheckReady();
|
|
var connector = StartBindingScope(ConnectorBindingScope.Copy);
|
|
|
|
Log.Debug("Starting raw COPY operation", connector.Id);
|
|
// no point in passing a cancellationToken here, as we register the cancellation in the Init method
|
|
connector.StartUserAction(ConnectorState.Copy, attemptPgCancellation: false);
|
|
try
|
|
{
|
|
var stream = new NpgsqlRawCopyStream(connector);
|
|
await stream.Init(copyCommand, async, cancellationToken);
|
|
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
|
|
{
|
|
connector.EndUserAction();
|
|
EndBindingScope(ConnectorBindingScope.Copy);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Enum mapping
|
|
|
|
/// <summary>
|
|
/// Maps a CLR enum to a PostgreSQL enum type for use with this connection.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// CLR enum labels are mapped by name to PostgreSQL enum labels.
|
|
/// The translation strategy can be controlled by the <paramref name="nameTranslator"/> parameter,
|
|
/// which defaults to <see cref="NpgsqlSnakeCaseNameTranslator"/>.
|
|
/// You can also use the <see cref="PgNameAttribute"/> on your enum fields to manually specify a PostgreSQL enum label.
|
|
/// If there is a discrepancy between the .NET and database labels while an enum is read or written,
|
|
/// an exception will be raised.
|
|
///
|
|
/// Can only be invoked on an open connection; if the connection is closed the mapping is lost.
|
|
///
|
|
/// To avoid mapping the type for each connection, use the <see cref="MapEnumGlobally{T}"/> method.
|
|
/// </remarks>
|
|
/// <param name="pgName">
|
|
/// A PostgreSQL type name for the corresponding enum type in the database.
|
|
/// If null, the name translator given in <paramref name="nameTranslator"/>will be used.
|
|
/// </param>
|
|
/// <param name="nameTranslator">
|
|
/// A component which will be used to translate CLR names (e.g. SomeClass) into database names (e.g. some_class).
|
|
/// Defaults to <see cref="NpgsqlSnakeCaseNameTranslator"/>
|
|
/// </param>
|
|
/// <typeparam name="TEnum">The .NET enum type to be mapped</typeparam>
|
|
[Obsolete("Use NpgsqlConnection.TypeMapper.MapEnum() instead")]
|
|
public void MapEnum<TEnum>(string? pgName = null, INpgsqlNameTranslator? nameTranslator = null)
|
|
where TEnum : struct, Enum
|
|
=> TypeMapper.MapEnum<TEnum>(pgName, nameTranslator);
|
|
|
|
/// <summary>
|
|
/// Maps a CLR enum to a PostgreSQL enum type for use with all connections created from now on. Existing connections aren't affected.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// CLR enum labels are mapped by name to PostgreSQL enum labels.
|
|
/// The translation strategy can be controlled by the <paramref name="nameTranslator"/> parameter,
|
|
/// which defaults to <see cref="NpgsqlSnakeCaseNameTranslator"/>.
|
|
/// You can also use the <see cref="PgNameAttribute"/> on your enum fields to manually specify a PostgreSQL enum label.
|
|
/// If there is a discrepancy between the .NET and database labels while an enum is read or written,
|
|
/// an exception will be raised.
|
|
///
|
|
/// To map the type for a specific connection, use the <see cref="MapEnum{T}"/> method.
|
|
/// </remarks>
|
|
/// <param name="pgName">
|
|
/// A PostgreSQL type name for the corresponding enum type in the database.
|
|
/// If null, the name translator given in <paramref name="nameTranslator"/>will be used.
|
|
/// </param>
|
|
/// <param name="nameTranslator">
|
|
/// A component which will be used to translate CLR names (e.g. SomeClass) into database names (e.g. some_class).
|
|
/// Defaults to <see cref="NpgsqlSnakeCaseNameTranslator"/>
|
|
/// </param>
|
|
/// <typeparam name="TEnum">The .NET enum type to be mapped</typeparam>
|
|
[Obsolete("Use NpgsqlConnection.GlobalTypeMapper.MapEnum() instead")]
|
|
public static void MapEnumGlobally<TEnum>(string? pgName = null, INpgsqlNameTranslator? nameTranslator = null)
|
|
where TEnum : struct, Enum
|
|
=> GlobalTypeMapper.MapEnum<TEnum>(pgName, nameTranslator);
|
|
|
|
/// <summary>
|
|
/// Removes a previous global enum mapping.
|
|
/// </summary>
|
|
/// <param name="pgName">
|
|
/// A PostgreSQL type name for the corresponding enum type in the database.
|
|
/// If null, the name translator given in <paramref name="nameTranslator"/>will be used.
|
|
/// </param>
|
|
/// <param name="nameTranslator">
|
|
/// A component which will be used to translate CLR names (e.g. SomeClass) into database names (e.g. some_class).
|
|
/// Defaults to <see cref="NpgsqlSnakeCaseNameTranslator"/>
|
|
/// </param>
|
|
[Obsolete("Use NpgsqlConnection.GlobalTypeMapper.UnmapEnum() instead")]
|
|
public static void UnmapEnumGlobally<TEnum>(string? pgName = null, INpgsqlNameTranslator? nameTranslator = null)
|
|
where TEnum : struct, Enum
|
|
=> GlobalTypeMapper.UnmapEnum<TEnum>(pgName, nameTranslator);
|
|
|
|
#endregion
|
|
|
|
#region Composite registration
|
|
|
|
/// <summary>
|
|
/// Maps a CLR type to a PostgreSQL composite type for use with this connection.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// CLR fields and properties by string to PostgreSQL enum labels.
|
|
/// The translation strategy can be controlled by the <paramref name="nameTranslator"/> parameter,
|
|
/// which defaults to <see cref="NpgsqlSnakeCaseNameTranslator"/>.
|
|
/// You can also use the <see cref="PgNameAttribute"/> on your members to manually specify a PostgreSQL enum label.
|
|
/// If there is a discrepancy between the .NET and database labels while a composite is read or written,
|
|
/// an exception will be raised.
|
|
///
|
|
/// Can only be invoked on an open connection; if the connection is closed the mapping is lost.
|
|
///
|
|
/// To avoid mapping the type for each connection, use the <see cref="MapCompositeGlobally{T}"/> method.
|
|
/// </remarks>
|
|
/// <param name="pgName">
|
|
/// A PostgreSQL type name for the corresponding enum type in the database.
|
|
/// If null, the name translator given in <paramref name="nameTranslator"/>will be used.
|
|
/// </param>
|
|
/// <param name="nameTranslator">
|
|
/// A component which will be used to translate CLR names (e.g. SomeClass) into database names (e.g. some_class).
|
|
/// Defaults to <see cref="NpgsqlSnakeCaseNameTranslator"/>
|
|
/// </param>
|
|
/// <typeparam name="T">The .NET type to be mapped</typeparam>
|
|
[Obsolete("Use NpgsqlConnection.TypeMapper.MapComposite() instead")]
|
|
[RequiresUnreferencedCode("Composite type mapping currently isn't trimming-safe.")]
|
|
public void MapComposite<T>(string? pgName = null, INpgsqlNameTranslator? nameTranslator = null) where T : new()
|
|
=> TypeMapper.MapComposite<T>(pgName, nameTranslator);
|
|
|
|
/// <summary>
|
|
/// Maps a CLR type to a PostgreSQL composite type for use with all connections created from now on. Existing connections aren't affected.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// CLR fields and properties by string to PostgreSQL enum labels.
|
|
/// The translation strategy can be controlled by the <paramref name="nameTranslator"/> parameter,
|
|
/// which defaults to <see cref="NpgsqlSnakeCaseNameTranslator"/>.
|
|
/// You can also use the <see cref="PgNameAttribute"/> on your members to manually specify a PostgreSQL enum label.
|
|
/// If there is a discrepancy between the .NET and database labels while a composite is read or written,
|
|
/// an exception will be raised.
|
|
///
|
|
/// To map the type for a specific connection, use the <see cref="MapEnum{T}"/> method.
|
|
/// </remarks>
|
|
/// <param name="pgName">
|
|
/// A PostgreSQL type name for the corresponding enum type in the database.
|
|
/// If null, the name translator given in <paramref name="nameTranslator"/>will be used.
|
|
/// </param>
|
|
/// <param name="nameTranslator">
|
|
/// A component which will be used to translate CLR names (e.g. SomeClass) into database names (e.g. some_class).
|
|
/// Defaults to <see cref="NpgsqlSnakeCaseNameTranslator"/>
|
|
/// </param>
|
|
/// <typeparam name="T">The .NET type to be mapped</typeparam>
|
|
[Obsolete("Use NpgsqlConnection.GlobalTypeMapper.MapComposite() instead")]
|
|
[RequiresUnreferencedCode("Composite type mapping currently isn't trimming-safe.")]
|
|
public static void MapCompositeGlobally<T>(string? pgName = null, INpgsqlNameTranslator? nameTranslator = null) where T : new()
|
|
=> GlobalTypeMapper.MapComposite<T>(pgName, nameTranslator);
|
|
|
|
/// <summary>
|
|
/// Removes a previous global enum mapping.
|
|
/// </summary>
|
|
/// <param name="pgName">
|
|
/// A PostgreSQL type name for the corresponding enum type in the database.
|
|
/// If null, the name translator given in <paramref name="nameTranslator"/>will be used.
|
|
/// </param>
|
|
/// <param name="nameTranslator">
|
|
/// A component which will be used to translate CLR names (e.g. SomeClass) into database names (e.g. some_class).
|
|
/// Defaults to <see cref="NpgsqlSnakeCaseNameTranslator"/>
|
|
/// </param>
|
|
[Obsolete("Use NpgsqlConnection.GlobalTypeMapper.UnmapComposite() instead")]
|
|
[RequiresUnreferencedCode("Composite type mapping currently isn't trimming-safe.")]
|
|
public static void UnmapCompositeGlobally<T>(string pgName, INpgsqlNameTranslator? nameTranslator = null) where T : new()
|
|
=> GlobalTypeMapper.UnmapComposite<T>(pgName, nameTranslator);
|
|
|
|
#endregion
|
|
|
|
#region Wait
|
|
|
|
/// <summary>
|
|
/// Waits until an asynchronous PostgreSQL messages (e.g. a notification) arrives, and
|
|
/// exits immediately. The asynchronous message is delivered via the normal events
|
|
/// (<see cref="Notification"/>, <see cref="Notice"/>).
|
|
/// </summary>
|
|
/// <param name="timeout">
|
|
/// The time-out value, in milliseconds, passed to <see cref="Socket.ReceiveTimeout"/>.
|
|
/// The default value is 0, which indicates an infinite time-out period.
|
|
/// Specifying -1 also indicates an infinite time-out period.
|
|
/// </param>
|
|
/// <returns>true if an asynchronous message was received, false if timed out.</returns>
|
|
public bool Wait(int timeout)
|
|
{
|
|
if (timeout != -1 && timeout < 0)
|
|
throw new ArgumentException("Argument must be -1, 0 or positive", nameof(timeout));
|
|
if (Settings.Multiplexing)
|
|
throw new NotSupportedException($"{nameof(Wait)} isn't supported in multiplexing mode");
|
|
|
|
CheckReady();
|
|
|
|
Log.Debug($"Starting to wait (timeout={timeout})...", Connector!.Id);
|
|
return Connector!.Wait(async: false, timeout, CancellationToken.None).GetAwaiter().GetResult();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Waits until an asynchronous PostgreSQL messages (e.g. a notification) arrives, and
|
|
/// exits immediately. The asynchronous message is delivered via the normal events
|
|
/// (<see cref="Notification"/>, <see cref="Notice"/>).
|
|
/// </summary>
|
|
/// <param name="timeout">
|
|
/// The time-out value is passed to <see cref="Socket.ReceiveTimeout"/>.
|
|
/// </param>
|
|
/// <returns>true if an asynchronous message was received, false if timed out.</returns>
|
|
public bool Wait(TimeSpan timeout) => Wait((int)timeout.TotalMilliseconds);
|
|
|
|
/// <summary>
|
|
/// Waits until an asynchronous PostgreSQL messages (e.g. a notification) arrives, and
|
|
/// exits immediately. The asynchronous message is delivered via the normal events
|
|
/// (<see cref="Notification"/>, <see cref="Notice"/>).
|
|
/// </summary>
|
|
public void Wait() => Wait(0);
|
|
|
|
/// <summary>
|
|
/// Waits asynchronously until an asynchronous PostgreSQL messages (e.g. a notification)
|
|
/// arrives, and exits immediately. The asynchronous message is delivered via the normal events
|
|
/// (<see cref="Notification"/>, <see cref="Notice"/>).
|
|
/// </summary>
|
|
/// <param name="timeout">
|
|
/// 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.
|
|
/// </param>
|
|
/// <param name="cancellationToken">
|
|
/// An optional token to cancel the asynchronous operation. The default value is <see cref="CancellationToken.None"/>.
|
|
/// </param>
|
|
/// <returns>true if an asynchronous message was received, false if timed out.</returns>
|
|
public Task<bool> WaitAsync(int timeout, CancellationToken cancellationToken = default)
|
|
{
|
|
if (Settings.Multiplexing)
|
|
throw new NotSupportedException($"{nameof(Wait)} isn't supported in multiplexing mode");
|
|
|
|
CheckReady();
|
|
|
|
Log.Debug("Starting to wait asynchronously...", Connector!.Id);
|
|
using (NoSynchronizationContextScope.Enter())
|
|
return Connector!.Wait(async: true, timeout, cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Waits asynchronously until an asynchronous PostgreSQL messages (e.g. a notification)
|
|
/// arrives, and exits immediately. The asynchronous message is delivered via the normal events
|
|
/// (<see cref="Notification"/>, <see cref="Notice"/>).
|
|
/// </summary>
|
|
/// <param name="timeout">
|
|
/// The time-out value as <see cref="TimeSpan"/>
|
|
/// </param>
|
|
/// <param name="cancellationToken">
|
|
/// An optional token to cancel the asynchronous operation. The default value is <see cref="CancellationToken.None"/>.
|
|
/// </param>
|
|
/// <returns>true if an asynchronous message was received, false if timed out.</returns>
|
|
public Task<bool> WaitAsync(TimeSpan timeout, CancellationToken cancellationToken = default) => WaitAsync((int)timeout.TotalMilliseconds, cancellationToken);
|
|
|
|
/// <summary>
|
|
/// Waits asynchronously until an asynchronous PostgreSQL messages (e.g. a notification)
|
|
/// arrives, and exits immediately. The asynchronous message is delivered via the normal events
|
|
/// (<see cref="Notification"/>, <see cref="Notice"/>).
|
|
/// </summary>
|
|
/// <param name="cancellationToken">
|
|
/// An optional token to cancel the asynchronous operation. The default value is <see cref="CancellationToken.None"/>.
|
|
/// </param>
|
|
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:
|
|
break;
|
|
case ConnectionState.Closed:
|
|
case ConnectionState.Broken:
|
|
throw new InvalidOperationException("Connection is not open");
|
|
default:
|
|
throw new ArgumentOutOfRangeException();
|
|
}
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
void CheckClosed()
|
|
{
|
|
CheckDisposed();
|
|
|
|
switch (FullState)
|
|
{
|
|
case ConnectionState.Closed:
|
|
case ConnectionState.Broken:
|
|
break;
|
|
case ConnectionState.Open:
|
|
case ConnectionState.Connecting:
|
|
case ConnectionState.Open | ConnectionState.Executing:
|
|
case ConnectionState.Open | ConnectionState.Fetching:
|
|
throw new InvalidOperationException("Connection already open");
|
|
default:
|
|
throw new ArgumentOutOfRangeException();
|
|
}
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
void CheckDisposed()
|
|
{
|
|
if (_disposed)
|
|
throw new ObjectDisposedException(typeof(NpgsqlConnection).Name);
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
internal void CheckReady()
|
|
{
|
|
CheckDisposed();
|
|
|
|
switch (FullState)
|
|
{
|
|
case ConnectionState.Open:
|
|
case ConnectionState.Connecting: // We need to do type loading as part of connecting
|
|
break;
|
|
case ConnectionState.Closed:
|
|
case ConnectionState.Broken:
|
|
throw new InvalidOperationException("Connection is not open");
|
|
case ConnectionState.Open | ConnectionState.Executing:
|
|
case ConnectionState.Open | ConnectionState.Fetching:
|
|
throw new InvalidOperationException("Connection is busy");
|
|
default:
|
|
throw new ArgumentOutOfRangeException();
|
|
}
|
|
}
|
|
|
|
#endregion State checks
|
|
|
|
#region Connector binding
|
|
|
|
/// <summary>
|
|
/// Checks whether the connection is currently bound to a connector, and if so, returns it via
|
|
/// <paramref name="connector"/>.
|
|
/// </summary>
|
|
internal bool TryGetBoundConnector([NotNullWhen(true)] out NpgsqlConnector? connector)
|
|
{
|
|
if (ConnectorBindingScope == ConnectorBindingScope.None)
|
|
{
|
|
Debug.Assert(Connector == null, $"Binding scope is None but {Connector} exists");
|
|
connector = null;
|
|
return false;
|
|
}
|
|
Debug.Assert(Connector != null, $"Binding scope is {ConnectorBindingScope} but {Connector} is null");
|
|
Debug.Assert(Connector.Connection == this, $"Bound connector {Connector} does not reference this connection");
|
|
connector = Connector;
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Binds this connection to a physical connector. This happens when opening a non-multiplexing connection,
|
|
/// or when starting a transaction on a multiplexed connection.
|
|
/// </summary>
|
|
internal ValueTask<NpgsqlConnector> StartBindingScope(
|
|
ConnectorBindingScope scope, NpgsqlTimeout timeout, bool async, CancellationToken cancellationToken)
|
|
{
|
|
// If the connection is around bound at a higher scope, we do nothing (e.g. copy operation started
|
|
// within a transaction on a multiplexing connection).
|
|
// Note that if we're in an ambient transaction, that means we're already bound and so we do nothing here.
|
|
if (ConnectorBindingScope != ConnectorBindingScope.None)
|
|
{
|
|
Debug.Assert(Connector != null, $"Connection bound with scope {ConnectorBindingScope} but has no connector");
|
|
Debug.Assert(scope != ConnectorBindingScope, $"Binding scopes aren't reentrant ({ConnectorBindingScope})");
|
|
return new ValueTask<NpgsqlConnector>(Connector);
|
|
}
|
|
|
|
return StartBindingScopeAsync();
|
|
|
|
async ValueTask<NpgsqlConnector> StartBindingScopeAsync()
|
|
{
|
|
try
|
|
{
|
|
Debug.Assert(Settings.Multiplexing);
|
|
Debug.Assert(_pool != null);
|
|
|
|
var connector = await _pool.Get(this, timeout, async, cancellationToken);
|
|
Connector = connector;
|
|
connector.Connection = this;
|
|
ConnectorBindingScope = scope;
|
|
return connector;
|
|
}
|
|
catch
|
|
{
|
|
FullState = ConnectionState.Broken;
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
|
|
internal NpgsqlConnector StartBindingScope(ConnectorBindingScope scope)
|
|
=> StartBindingScope(scope, NpgsqlTimeout.Infinite, async: false, CancellationToken.None)
|
|
.GetAwaiter().GetResult();
|
|
|
|
internal EndScopeDisposable StartTemporaryBindingScope(out NpgsqlConnector connector)
|
|
{
|
|
connector = StartBindingScope(ConnectorBindingScope.Temporary);
|
|
return new EndScopeDisposable(this);
|
|
}
|
|
|
|
internal T CheckOpenAndRunInTemporaryScope<T>(Func<NpgsqlConnector, T> func)
|
|
{
|
|
CheckOpen();
|
|
|
|
using var _ = StartTemporaryBindingScope(out var connector);
|
|
var result = func(connector);
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ends binding scope to the physical connection and returns it to the pool. Only useful with multiplexing on.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// After this method is called, under no circumstances the physical connection (connector) should ever be used if multiplexing is on.
|
|
/// See #3249.
|
|
/// </remarks>
|
|
internal void EndBindingScope(ConnectorBindingScope scope)
|
|
{
|
|
Debug.Assert(ConnectorBindingScope != ConnectorBindingScope.None || FullState == ConnectionState.Broken,
|
|
$"Ending binding scope {scope} but connection's scope is null");
|
|
|
|
if (scope != ConnectorBindingScope)
|
|
return;
|
|
|
|
Debug.Assert(Connector != null, $"Ending binding scope {scope} but connector is null");
|
|
Debug.Assert(_pool != null, $"Ending binding scope {scope} but _pool is null");
|
|
Debug.Assert(Settings.Multiplexing, $"Ending binding scope {scope} but multiplexing is disabled");
|
|
|
|
// TODO: If enlisted transaction scope is still active, need to AddPendingEnlistedConnector, just like Close
|
|
var connector = Connector;
|
|
Connector = null;
|
|
connector.Connection = null;
|
|
connector.Transaction?.UnbindIfNecessary();
|
|
connector.Return();
|
|
ConnectorBindingScope = ConnectorBindingScope.None;
|
|
}
|
|
|
|
#endregion Connector binding
|
|
|
|
#region Schema operations
|
|
|
|
/// <summary>
|
|
/// Returns the supported collections
|
|
/// </summary>
|
|
[UnconditionalSuppressMessage(
|
|
"Composite type mapping currently isn't trimming-safe, and warnings are generated at the MapComposite level.", "IL2026")]
|
|
public override DataTable GetSchema()
|
|
=> GetSchema("MetaDataCollections", null);
|
|
|
|
/// <summary>
|
|
/// Returns the schema collection specified by the collection name.
|
|
/// </summary>
|
|
/// <param name="collectionName">The collection name.</param>
|
|
/// <returns>The collection specified.</returns>
|
|
public override DataTable GetSchema(string? collectionName) => GetSchema(collectionName, null);
|
|
|
|
/// <summary>
|
|
/// Returns the schema collection specified by the collection name filtered by the restrictions.
|
|
/// </summary>
|
|
/// <param name="collectionName">The collection name.</param>
|
|
/// <param name="restrictions">
|
|
/// The restriction values to filter the results. A description of the restrictions is contained
|
|
/// in the Restrictions collection.
|
|
/// </param>
|
|
/// <returns>The collection specified.</returns>
|
|
public override DataTable GetSchema(string? collectionName, string?[]? restrictions)
|
|
=> NpgsqlSchema.GetSchema(this, collectionName, restrictions, async: false).GetAwaiter().GetResult();
|
|
|
|
/// <summary>
|
|
/// Asynchronously returns the supported collections.
|
|
/// </summary>
|
|
/// <param name="cancellationToken">
|
|
/// An optional token to cancel the asynchronous operation. The default value is <see cref="CancellationToken.None"/>.
|
|
/// </param>
|
|
/// <returns>The collection specified.</returns>
|
|
#if NET5_0_OR_GREATER
|
|
public override Task<DataTable> GetSchemaAsync(CancellationToken cancellationToken = default)
|
|
#else
|
|
public Task<DataTable> GetSchemaAsync(CancellationToken cancellationToken = default)
|
|
#endif
|
|
=> GetSchemaAsync("MetaDataCollections", null, cancellationToken);
|
|
|
|
/// <summary>
|
|
/// Asynchronously returns the schema collection specified by the collection name.
|
|
/// </summary>
|
|
/// <param name="collectionName">The collection name.</param>
|
|
/// <param name="cancellationToken">
|
|
/// An optional token to cancel the asynchronous operation. The default value is <see cref="CancellationToken.None"/>.
|
|
/// </param>
|
|
/// <returns>The collection specified.</returns>
|
|
#if NET5_0_OR_GREATER
|
|
public override Task<DataTable> GetSchemaAsync(string collectionName, CancellationToken cancellationToken = default)
|
|
#else
|
|
public Task<DataTable> GetSchemaAsync(string collectionName, CancellationToken cancellationToken = default)
|
|
#endif
|
|
=> GetSchemaAsync(collectionName, null, cancellationToken);
|
|
|
|
/// <summary>
|
|
/// Asynchronously returns the schema collection specified by the collection name filtered by the restrictions.
|
|
/// </summary>
|
|
/// <param name="collectionName">The collection name.</param>
|
|
/// <param name="restrictions">
|
|
/// The restriction values to filter the results. A description of the restrictions is contained
|
|
/// in the Restrictions collection.
|
|
/// </param>
|
|
/// <param name="cancellationToken">
|
|
/// An optional token to cancel the asynchronous operation. The default value is <see cref="CancellationToken.None"/>.
|
|
/// </param>
|
|
/// <returns>The collection specified.</returns>
|
|
#if NET5_0_OR_GREATER
|
|
public override Task<DataTable> GetSchemaAsync(string collectionName, string?[]? restrictions, CancellationToken cancellationToken = default)
|
|
#else
|
|
public Task<DataTable> GetSchemaAsync(string collectionName, string?[]? restrictions, CancellationToken cancellationToken = default)
|
|
#endif
|
|
{
|
|
using (NoSynchronizationContextScope.Enter())
|
|
return NpgsqlSchema.GetSchema(this, collectionName, restrictions, async: true, cancellationToken);
|
|
}
|
|
|
|
#endregion Schema operations
|
|
|
|
#region Misc
|
|
|
|
/// <summary>
|
|
/// Creates a closed connection with the connection string and authentication details of this message.
|
|
/// </summary>
|
|
object ICloneable.Clone()
|
|
{
|
|
CheckDisposed();
|
|
var conn = new NpgsqlConnection(_connectionString) {
|
|
ProvideClientCertificatesCallback = ProvideClientCertificatesCallback,
|
|
UserCertificateValidationCallback = UserCertificateValidationCallback,
|
|
ProvidePasswordCallback = ProvidePasswordCallback,
|
|
_userFacingConnectionString = _userFacingConnectionString
|
|
};
|
|
return conn;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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)
|
|
/// </summary>
|
|
public NpgsqlConnection CloneWith(string connectionString)
|
|
{
|
|
CheckDisposed();
|
|
var csb = new NpgsqlConnectionStringBuilder(connectionString);
|
|
if (csb.Password == null && Password != null)
|
|
csb.Password = Password;
|
|
if (csb.PersistSecurityInfo && !Settings.PersistSecurityInfo)
|
|
csb.PersistSecurityInfo = false;
|
|
return new NpgsqlConnection(csb.ToString()) {
|
|
ProvideClientCertificatesCallback = ProvideClientCertificatesCallback,
|
|
UserCertificateValidationCallback = UserCertificateValidationCallback,
|
|
ProvidePasswordCallback = ProvidePasswordCallback,
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// This method changes the current database by disconnecting from the actual
|
|
/// database and connecting to the specified.
|
|
/// </summary>
|
|
/// <param name="dbName">The name of the database to use in place of the current database.</param>
|
|
public override void ChangeDatabase(string dbName)
|
|
{
|
|
if (dbName == null)
|
|
throw new ArgumentNullException(nameof(dbName));
|
|
if (string.IsNullOrEmpty(dbName))
|
|
throw new ArgumentOutOfRangeException(nameof(dbName), dbName, $"Invalid database name: {dbName}");
|
|
|
|
CheckOpen();
|
|
Close();
|
|
|
|
_pool = null;
|
|
Settings = Settings.Clone();
|
|
Settings.Database = dbName;
|
|
ConnectionString = Settings.ToString();
|
|
|
|
Open();
|
|
}
|
|
|
|
/// <summary>
|
|
/// DB provider factory.
|
|
/// </summary>
|
|
protected override DbProviderFactory DbProviderFactory => NpgsqlFactory.Instance;
|
|
|
|
/// <summary>
|
|
/// 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 <see cref="ClearPool"/> was called
|
|
/// will be closed when returned to the pool.
|
|
/// </summary>
|
|
public static void ClearPool(NpgsqlConnection connection) => PoolManager.Clear(connection._connectionString);
|
|
|
|
/// <summary>
|
|
/// Clear all connection pools. All idle physical connections in all pools are immediately closed, and any busy
|
|
/// connections which were opened before <see cref="ClearAllPools"/> was called will be closed when returned
|
|
/// to their pool.
|
|
/// </summary>
|
|
public static void ClearAllPools() => PoolManager.ClearAll();
|
|
|
|
/// <summary>
|
|
/// Unprepares all prepared statements on this connection.
|
|
/// </summary>
|
|
public void UnprepareAll()
|
|
{
|
|
if (Settings.Multiplexing)
|
|
throw new NotSupportedException("Explicit preparation not supported with multiplexing");
|
|
|
|
CheckReady();
|
|
|
|
using (Connector!.StartUserAction())
|
|
Connector.UnprepareAll();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public void ReloadTypes()
|
|
{
|
|
CheckReady();
|
|
using var scope = StartTemporaryBindingScope(out var connector);
|
|
using var _ = connector.StartUserAction(ConnectorState.Executing);
|
|
connector.LoadDatabaseInfo(
|
|
forceReload: true,
|
|
NpgsqlTimeout.Infinite,
|
|
async: false,
|
|
CancellationToken.None).GetAwaiter().GetResult();
|
|
|
|
// Increment the change counter on the global type mapper. This will make conn.Open() pick up the
|
|
// new DatabaseInfo and set up a new connection type mapper
|
|
TypeMapping.GlobalTypeMapper.Instance.RecordChange();
|
|
|
|
if (Settings.Multiplexing)
|
|
{
|
|
var multiplexingTypeMapper = ((MultiplexingConnectorPool)Pool).MultiplexingTypeMapper!;
|
|
Debug.Assert(multiplexingTypeMapper == connector.TypeMapper,
|
|
"A connector must reference the exact same TypeMapper the MultiplexingConnectorPool does");
|
|
// It's very probable that we've called ReloadTypes on the different connection than
|
|
// the MultiplexingConnectorPool references.
|
|
// Which means, we have to explicitly call Reset after we change the connector's DatabaseInfo to reload type mappings.
|
|
multiplexingTypeMapper.Connector.DatabaseInfo = connector.TypeMapper.DatabaseInfo;
|
|
multiplexingTypeMapper.Reset();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This event is unsupported by Npgsql. Use <see cref="DbConnection.StateChange"/> instead.
|
|
/// </summary>
|
|
[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
|
|
}
|
|
|
|
enum ConnectorBindingScope
|
|
{
|
|
/// <summary>
|
|
/// The connection is currently not bound to a connector.
|
|
/// </summary>
|
|
None,
|
|
|
|
/// <summary>
|
|
/// The connection is bound to its connector for the scope of the entire connection
|
|
/// (i.e. non-multiplexed connection).
|
|
/// </summary>
|
|
Connection,
|
|
|
|
/// <summary>
|
|
/// The connection is bound to its connector for the scope of a transaction.
|
|
/// </summary>
|
|
Transaction,
|
|
|
|
/// <summary>
|
|
/// The connection is bound to its connector for the scope of a COPY operation.
|
|
/// </summary>
|
|
Copy,
|
|
|
|
/// <summary>
|
|
/// The connection is bound to its connector for the scope of a single reader.
|
|
/// </summary>
|
|
Reader,
|
|
|
|
/// <summary>
|
|
/// The connection is bound to its connector for an unspecified, temporary scope; the code that initiated
|
|
/// the binding is also responsible to unbind it.
|
|
/// </summary>
|
|
Temporary
|
|
}
|
|
|
|
readonly struct EndScopeDisposable : IDisposable
|
|
{
|
|
readonly NpgsqlConnection _connection;
|
|
public EndScopeDisposable(NpgsqlConnection connection) => _connection = connection;
|
|
public void Dispose() => _connection.EndBindingScope(ConnectorBindingScope.Temporary);
|
|
}
|
|
|
|
#region Delegates
|
|
|
|
/// <summary>
|
|
/// Represents a method that handles the <see cref="NpgsqlConnection.Notice"/> event.
|
|
/// </summary>
|
|
/// <param name="sender">The source of the event.</param>
|
|
/// <param name="e">A <see cref="NpgsqlNoticeEventArgs"/> that contains the notice information (e.g. message, severity...).</param>
|
|
public delegate void NoticeEventHandler(object sender, NpgsqlNoticeEventArgs e);
|
|
|
|
/// <summary>
|
|
/// Represents a method that handles the <see cref="NpgsqlConnection.Notification"/> event.
|
|
/// </summary>
|
|
/// <param name="sender">The source of the event.</param>
|
|
/// <param name="e">A <see cref="NpgsqlNotificationEventArgs"/> that contains the notification payload.</param>
|
|
public delegate void NotificationEventHandler(object sender, NpgsqlNotificationEventArgs e);
|
|
|
|
/// <summary>
|
|
/// Represents a method that allows the application to provide a certificate collection to be used for SSL client authentication
|
|
/// </summary>
|
|
/// <param name="certificates">
|
|
/// A <see cref="X509CertificateCollection"/> to be filled with one or more client
|
|
/// certificates.
|
|
/// </param>
|
|
public delegate void ProvideClientCertificatesCallback(X509CertificateCollection certificates);
|
|
|
|
/// <summary>
|
|
/// Represents a method that allows the application to provide a password at connection time in code rather than configuration
|
|
/// </summary>
|
|
/// <param name="host">Hostname</param>
|
|
/// <param name="port">Port</param>
|
|
/// <param name="database">Database Name</param>
|
|
/// <param name="username">User</param>
|
|
/// <returns>A valid password for connecting to the database</returns>
|
|
public delegate string ProvidePasswordCallback(string host, int port, string database, string username);
|
|
|
|
/// <summary>
|
|
/// Represents a method that allows the application to setup a connection with custom commands.
|
|
/// </summary>
|
|
/// <param name="connection">Physical connection to the database</param>
|
|
[RequiresPreviewFeatures("Physical open callback is an experimental API, and its exact shape may change in the future")]
|
|
public delegate void PhysicalOpenCallback(NpgsqlConnector connection);
|
|
|
|
/// <summary>
|
|
/// Represents an asynchronous method that allows the application to setup a connection with custom commands.
|
|
/// </summary>
|
|
/// <param name="connection">Physical connection to the database</param>
|
|
[RequiresPreviewFeatures("Physical open callback is an experimental API, and its exact shape may change in the future")]
|
|
public delegate Task PhysicalOpenAsyncCallback(NpgsqlConnector connection);
|
|
|
|
#endregion
|