#region License
// The PostgreSQL License
//
// Copyright (C) 2017 The Npgsql Development Team
//
// Permission to use, copy, modify, and distribute this software and its
// documentation for any purpose, without fee, and without a written
// agreement is hereby granted, provided that the above copyright notice
// and this paragraph and the following two paragraphs appear in all copies.
//
// IN NO EVENT SHALL THE NPGSQL DEVELOPMENT TEAM BE LIABLE TO ANY PARTY
// FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
// INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
// DOCUMENTATION, EVEN IF THE NPGSQL DEVELOPMENT TEAM HAS BEEN ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
//
// THE NPGSQL DEVELOPMENT TEAM SPECIFICALLY DISCLAIMS ANY WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
// ON AN "AS IS" BASIS, AND THE NPGSQL DEVELOPMENT TEAM HAS NO OBLIGATIONS
// TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#endregion
using System;
using System.ComponentModel;
using System.Data;
using System.Data.Common;
using System.Diagnostics;
using System.IO;
using System.Net.Security;
using System.Net.Sockets;
using System.Reflection;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Npgsql.Logging;
using Npgsql.NameTranslation;
using Npgsql.TypeMapping;
using NpgsqlTypes;
using IsolationLevel = System.Data.IsolationLevel;
using ThreadState = System.Threading.ThreadState;
#if !NETSTANDARD1_3
using System.Transactions;
#endif
namespace Npgsql
{
///
/// This class represents a connection to a PostgreSQL server.
///
#if NETSTANDARD1_3
public sealed class NpgsqlConnection : DbConnection
#else
// ReSharper disable once RedundantNameQualifier
[System.ComponentModel.DesignerCategory("")]
public sealed class NpgsqlConnection : DbConnection, ICloneable
#endif
{
#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;
///
/// The original connection string provided by the user, including the password.
///
string _connectionString;
internal string OriginalConnectionString => _connectionString;
///
/// The connector object connected to the backend.
///
[CanBeNull]
internal NpgsqlConnector Connector { get; set; }
///
/// The parsed connection string set by the user
///
internal NpgsqlConnectionStringBuilder Settings { get; private set; }
[CanBeNull]
ConnectorPool _pool;
bool _wasBroken;
#if !NETSTANDARD1_3
[CanBeNull]
internal Transaction EnlistedTransaction { get; set; }
#endif
///
/// The global type mapper, which contains defaults used by all new connections.
/// Modify mappings on this mapper to affect your entire application.
///
public static INpgsqlTypeMapper GlobalTypeMapper => TypeMapping.GlobalTypeMapper.Instance;
///
/// The connection-specific type mapper - all modifications affect this connection only,
/// and are lost when it is closed.
///
public INpgsqlTypeMapper TypeMapper
{
get
{
CheckConnectionOpen();
return Connector.TypeMapper;
}
}
///
///
/// The default TCP/IP port for PostgreSQL.
///
public const int DefaultPort = 5432;
///
/// Maximum value for connection timeout.
///
internal const int TimeoutLimit = 1024;
static readonly NpgsqlLogger Log = NpgsqlLogManager.GetCurrentClassLogger();
static bool _countersInitialized;
#endregion Fields
#region Constructors / Init / Open
///
/// Initializes a new instance of the
/// NpgsqlConnection class.
///
public NpgsqlConnection() : this("") {}
///
/// Initializes a new instance of with the given connection string.
///
/// The connection used to open the PostgreSQL database.
public NpgsqlConnection(string connectionString)
{
GC.SuppressFinalize(this);
ConnectionString = connectionString;
#if !NETSTANDARD1_3
// Fix authentication problems. See https://bugzilla.novell.com/show_bug.cgi?id=MONO77559 and
// http://pgfoundry.org/forum/message.php?msg_id=1002377 for more info.
RSACryptoServiceProvider.UseMachineKeyStore = true;
#endif
}
///
/// Opens a database connection with the property settings specified by the
/// ConnectionString.
///
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.
///
/// The cancellation instruction.
/// A task representing the asynchronous operation.
public override Task OpenAsync(CancellationToken cancellationToken)
=> SynchronizationContextSwitcher.NoContext(async () => await Open(true, cancellationToken));
void GetPoolAndSettings()
{
var pools = PoolManager.Pools;
lock (pools)
{
if (pools.TryGetValue(_connectionString, out _pool))
Settings = _pool.Settings; // Great, we already have a pool
else
{
// Connection string hasn't been seen before. Parse it.
Settings = new NpgsqlConnectionStringBuilder(_connectionString);
if (!_countersInitialized)
{
_countersInitialized = true;
Counters.Initialize(Settings.UsePerfCounters);
}
// Maybe pooling is off
if (Settings.Pooling)
{
// Connstring may be equivalent to one that has already been seen though (e.g. different
// ordering). Have NpgsqlConnectionStringBuilder produce a canonical string representation
// and recheck.
var canonical = Settings.ConnectionString;
if (pools.TryGetValue(canonical, out _pool))
pools[_connectionString] = _pool;
else
{
// Really unseen, need to create a new pool
_pool = pools[_connectionString] = new ConnectorPool(Settings, canonical);
if (_connectionString != canonical)
pools[canonical] = _pool;
}
}
}
}
}
async Task Open(bool async, CancellationToken cancellationToken)
{
CheckConnectionClosed();
Log.Trace("Opening connection...");
_wasBroken = false;
try
{
Debug.Assert(Settings != null);
var timeout = new NpgsqlTimeout(TimeSpan.FromSeconds(ConnectionTimeout));
if (_pool == null) // Unpooled connection
{
if (!Settings.PersistSecurityInfo)
_userFacingConnectionString = Settings.ToStringWithoutPassword();
Connector = new NpgsqlConnector(this);
await Connector.Open(timeout, async, cancellationToken);
Counters.NumberOfNonPooledConnections.Increment();
}
else
{
_userFacingConnectionString = _pool.UserFacingConnectionString;
#if !NETSTANDARD1_3
if (Settings.Enlist)
{
if (Transaction.Current != null)
{
// First, check to see if 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.
Connector = _pool.TryAllocateEnlistedPending(Transaction.Current);
if (Connector != null)
EnlistedTransaction = Transaction.Current;
}
if (Connector == null)
Connector = await _pool.Allocate(this, timeout, async, cancellationToken);
}
else // No enlist
#endif
Connector = await _pool.Allocate(this, timeout, async, cancellationToken);
Counters.SoftConnectsPerSecond.Increment();
// Since this pooled connector was opened, global mappings may have
// changed. Bring this up to date if needed.
var mapper = Connector.TypeMapper;
if (mapper.IsModified ||
mapper.ChangeCounter != TypeMapping.GlobalTypeMapper.Instance.ChangeCounter)
{
mapper.Reset();
}
}
#if !NETSTANDARD1_3
// We may have gotten an already enlisted pending connector above, no need to enlist in that case
if (Settings.Enlist && Transaction.Current != null && EnlistedTransaction == null)
EnlistTransaction(Transaction.Current);
#endif
}
catch
{
Connector = null;
throw;
}
Log.Debug("Connection opened", Connector.Id);
OnStateChange(new StateChangeEventArgs(ConnectionState.Closed, ConnectionState.Open));
}
#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.
///
[CanBeNull]
public override string ConnectionString
{
get => _userFacingConnectionString;
set
{
CheckConnectionClosed();
if (value == null)
value = string.Empty;
_userFacingConnectionString = _connectionString = value;
GetPoolAndSettings();
}
}
#endregion Connection string management
#region Configuration settings
///
/// Backend server host name.
///
[Browsable(true)]
[PublicAPI]
public string Host => Settings.Host;
///
/// Backend server port.
///
[Browsable(true)]
[PublicAPI]
public int Port => Settings.Port;
///
/// Gets the time 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 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 20 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.
[CanBeNull]
public override string Database => Settings.Database ?? Settings.Username;
///
/// Gets the string identifying the database server (host and port)
///
public override string DataSource => $"tcp://{Host}:{Port}";
///
/// Whether to use Windows integrated security to log in.
///
[PublicAPI]
public bool IntegratedSecurity => Settings.IntegratedSecurity;
///
/// User name.
///
[PublicAPI]
[CanBeNull]
public string UserName => Settings.Username;
[CanBeNull]
internal string Password => Settings.Password;
// The following two lines are here for backwards compatibility with the EF6 provider
internal string EntityTemplateDatabase => Settings.EntityTemplateDatabase;
internal string EntityAdminDatabase => Settings.EntityAdminDatabase;
#endregion Configuration settings
#region State management
///
/// Gets the current state of the connection.
///
/// A bitwise combination of the ConnectionState values. The default is Closed.
[Browsable(false)]
public ConnectionState FullState
{
get
{
if (Connector == null || _disposed)
{
return _wasBroken ? ConnectionState.Broken : ConnectionState.Closed;
}
switch (Connector.State)
{
case ConnectorState.Closed:
return ConnectionState.Closed;
case ConnectorState.Connecting:
return ConnectionState.Connecting;
case ConnectorState.Ready:
return ConnectionState.Open;
case ConnectorState.Executing:
return ConnectionState.Open | ConnectionState.Executing;
case ConnectorState.Copy:
case ConnectorState.Fetching:
case ConnectorState.Waiting:
return ConnectionState.Open | ConnectionState.Fetching;
case ConnectorState.Broken:
return ConnectionState.Broken;
default:
throw new InvalidOperationException($"Internal Npgsql bug: unexpected value {Connector.State} of enum {nameof(ConnectorState)}. Please file a bug.");
}
}
}
///
/// 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 s = FullState;
if ((s & ConnectionState.Open) != 0)
return ConnectionState.Open;
if ((s & ConnectionState.Connecting) != 0)
return ConnectionState.Connecting;
return ConnectionState.Closed;
}
}
#endregion State management
#region Commands
///
/// Creates and returns a DbCommand
/// object associated with the IDbConnection.
///
/// A DbCommand object.
protected override DbCommand CreateDbCommand()
{
return CreateCommand();
}
///
/// Creates and returns a NpgsqlCommand
/// object associated with the NpgsqlConnection.
///
/// A NpgsqlCommand object.
public new NpgsqlCommand CreateCommand()
{
CheckDisposed();
return new NpgsqlCommand("", this);
}
#endregion Commands
#region Transactions
///
/// Begins a database transaction with the specified isolation level.
///
/// The isolation level under which the transaction should run.
/// An DbTransaction
/// object representing the new transaction.
///
/// Currently the IsolationLevel ReadCommitted and Serializable are supported by the PostgreSQL backend.
/// There's no support for nested transactions.
///
protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel)
{
return BeginTransaction(isolationLevel);
}
///
/// Begins a database transaction.
///
/// A NpgsqlTransaction
/// object representing the new transaction.
///
/// Currently there's no support for nested transactions. Transactions created by this method will have Read Committed 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 NpgsqlTransaction
/// object representing the new transaction.
///
/// Currently the IsolationLevel ReadCommitted and Serializable are supported by the PostgreSQL backend.
/// There's no support for nested transactions.
///
public new NpgsqlTransaction BeginTransaction(IsolationLevel level)
{
if (level == IsolationLevel.Chaos)
throw new NotSupportedException("Unsupported IsolationLevel: " + level);
var connector = CheckReadyAndGetConnector();
Debug.Assert(Connector != null);
// Note that beginning a transaction doesn't actually send anything to the backend
// (only prepends), so strictly speaking we don't have to start a user action.
// However, we do this for consistency as if we did (for the checks and exceptions)
using (connector.StartUserAction())
{
if (connector.InTransaction)
throw new NotSupportedException("Nested/Concurrent transactions aren't supported.");
return new NpgsqlTransaction(this, level);
}
}
#if !NETSTANDARD1_3
///
/// Enlist transation.
///
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.
}
}
var connector = CheckReadyAndGetConnector();
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.
transaction.EnlistVolatile(new VolatileResourceManager(this, transaction), EnlistmentOptions.None);
Log.Debug($"Enlisted volatile resource manager (localid={transaction.TransactionInformation.LocalIdentifier})", connector.Id);
}
#endif
#endregion
#region Close
///
/// releases the connection to the database. If the connection is pooled, it will be
/// made available for re-use. If it is non-pooled, the actual connection will be shutdown.
///
public override void Close() => Close(false);
internal void Close(bool wasBroken)
{
if (Connector == null)
return;
var connectorId = Connector.Id;
Log.Trace("Closing connection...", connectorId);
_wasBroken = wasBroken;
CloseOngoingOperations();
if (!Settings.Pooling)
Connector.Close();
else
{
#if NETSTANDARD1_3
_pool.Release(Connector);
#else
if (EnlistedTransaction == null)
_pool.Release(Connector);
else
{
// A System.Transactions transaction is still in progress, we need to wait for it to complete.
// Close the connection and disconnect it from the resource manager but leave the connector
// in a enlisted pending list in the pool.
_pool.AddPendingEnlistedConnector(Connector, EnlistedTransaction);
Connector.Connection = null;
EnlistedTransaction = null;
}
#endif
}
Log.Debug("Connection closed", connectorId);
Connector = null;
OnStateChange(new StateChangeEventArgs(ConnectionState.Open, ConnectionState.Closed));
}
///
/// Closes ongoing operations, i.e. an open reader exists or a COPY operation still in progress, as
/// part of a connection close.
/// Does nothing if the thread has been aborted - the connector will be closed immediately.
///
void CloseOngoingOperations()
{
if ((Thread.CurrentThread.ThreadState & (ThreadState.Aborted | ThreadState.AbortRequested)) != 0)
return;
Debug.Assert(Connector != null);
Connector.CurrentReader?.Close(true, false);
var currentCopyOperation = Connector.CurrentCopyOperation;
if (currentCopyOperation != null)
{
// TODO: There's probably a race condition as the COPY operation may finish on its own during the next few lines
// Note: we only want to cancel import operations, since in these cases cancel is safe.
// Export cancellations go through the PostgreSQL "asynchronous" cancel mechanism and are
// therefore vulnerable to the race condition in #615.
if (currentCopyOperation is NpgsqlBinaryImporter ||
currentCopyOperation is NpgsqlCopyTextWriter ||
(currentCopyOperation is NpgsqlRawCopyStream && ((NpgsqlRawCopyStream)currentCopyOperation).CanWrite))
{
try
{
currentCopyOperation.Cancel();
}
catch (Exception e)
{
Log.Warn("Error while cancelling COPY on connector close", e, Connector.Id);
}
}
try
{
currentCopyOperation.Dispose();
}
catch (Exception e)
{
Log.Warn("Error while disposing cancelled COPY on connector close", e, Connector.Id);
}
}
}
///
/// Releases all resources used by the
/// NpgsqlConnection.
///
/// true when called from Dispose();
/// false when being called from the finalizer.
protected override void Dispose(bool disposing)
{
if (_disposed)
return;
if (disposing)
Close();
base.Dispose(disposing);
_disposed = true;
}
#endregion
#region Notifications
///
/// Occurs on NoticeResponses from the PostgreSQL backend.
///
public event NoticeEventHandler Notice;
///
/// Occurs on NotificationResponses from the PostgreSQL backend.
///
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
#region SSL
///
/// Returns whether SSL is being used for the connection.
///
internal bool IsSecure
{
get
{
CheckConnectionOpen();
Debug.Assert(Connector != null);
return Connector.IsSecure;
}
}
///
/// Selects the local Secure Sockets Layer (SSL) certificate used for authentication.
///
///
/// See
///
[CanBeNull]
public ProvideClientCertificatesCallback ProvideClientCertificatesCallback { get; set; }
///
/// Verifies the remote Secure Sockets Layer (SSL) certificate used for authentication.
/// Ignored if is set.
///
///
/// See
///
[CanBeNull]
public RemoteCertificateValidationCallback UserCertificateValidationCallback { get; set; }
#endregion SSL
#region Backend version, capabilities, settings
///
/// Version of the PostgreSQL backend.
/// This can only be called when there is an active connection.
///
[Browsable(false)]
public Version PostgreSqlVersion
{
get
{
CheckConnectionOpen();
Debug.Assert(Connector != null);
return Connector.ServerVersion;
}
}
///
/// PostgreSQL server version.
///
public override string ServerVersion => PostgreSqlVersion.ToString();
///
/// 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
{
CheckConnectionOpen();
Debug.Assert(Connector != null);
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)]
[PublicAPI]
public bool HasIntegerDateTimes
{
get
{
CheckConnectionOpen();
Debug.Assert(Connector != null);
return Connector.IntegerDateTimes;
}
}
///
/// The connection's timezone as reported by PostgreSQL, in the IANA/Olson database format.
///
[Browsable(false)]
[PublicAPI]
public string Timezone
{
get
{
CheckConnectionOpen();
Debug.Assert(Connector != null);
return Connector.Timezone;
}
}
#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 http://www.postgresql.org/docs/current/static/sql-copy.html.
///
public NpgsqlBinaryImporter BeginBinaryImport(string copyFromCommand)
{
if (copyFromCommand == null)
throw new ArgumentNullException(nameof(copyFromCommand));
if (!copyFromCommand.TrimStart().ToUpper().StartsWith("COPY"))
throw new ArgumentException("Must contain a COPY FROM STDIN command!", nameof(copyFromCommand));
var connector = CheckReadyAndGetConnector();
Log.Debug("Starting binary import", connector.Id);
connector.StartUserAction(ConnectorState.Copy);
try
{
var importer = new NpgsqlBinaryImporter(connector, copyFromCommand);
connector.CurrentCopyOperation = importer;
return importer;
}
catch
{
connector.EndUserAction();
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 http://www.postgresql.org/docs/current/static/sql-copy.html.
///
public NpgsqlBinaryExporter BeginBinaryExport(string copyToCommand)
{
if (copyToCommand == null)
throw new ArgumentNullException(nameof(copyToCommand));
if (!copyToCommand.TrimStart().ToUpper().StartsWith("COPY"))
throw new ArgumentException("Must contain a COPY TO STDOUT command!", nameof(copyToCommand));
var connector = CheckReadyAndGetConnector();
Log.Debug("Starting binary export", connector.Id);
connector.StartUserAction(ConnectorState.Copy);
try
{
var exporter = new NpgsqlBinaryExporter(Connector, copyToCommand);
Connector.CurrentCopyOperation = exporter;
return exporter;
}
catch
{
connector.EndUserAction();
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 http://www.postgresql.org/docs/current/static/sql-copy.html.
///
public TextWriter BeginTextImport(string copyFromCommand)
{
if (copyFromCommand == null)
throw new ArgumentNullException(nameof(copyFromCommand));
if (!copyFromCommand.TrimStart().ToUpper().StartsWith("COPY"))
throw new ArgumentException("Must contain a COPY FROM STDIN command!", nameof(copyFromCommand));
var connector = CheckReadyAndGetConnector();
Log.Debug("Starting text import", connector.Id);
connector.StartUserAction(ConnectorState.Copy);
try
{
var writer = new NpgsqlCopyTextWriter(new NpgsqlRawCopyStream(connector, copyFromCommand));
connector.CurrentCopyOperation = writer;
return writer;
}
catch
{
connector.EndUserAction();
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 http://www.postgresql.org/docs/current/static/sql-copy.html.
///
public TextReader BeginTextExport(string copyToCommand)
{
if (copyToCommand == null)
throw new ArgumentNullException(nameof(copyToCommand));
if (!copyToCommand.TrimStart().ToUpper().StartsWith("COPY"))
throw new ArgumentException("Must contain a COPY TO STDOUT command!", nameof(copyToCommand));
var connector = CheckReadyAndGetConnector();
Log.Debug("Starting text export", connector.Id);
connector.StartUserAction(ConnectorState.Copy);
try
{
var reader = new NpgsqlCopyTextReader(new NpgsqlRawCopyStream(connector, copyToCommand));
connector.CurrentCopyOperation = reader;
return reader;
}
catch
{
connector.EndUserAction();
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 http://www.postgresql.org/docs/current/static/sql-copy.html.
///
public NpgsqlRawCopyStream BeginRawBinaryCopy(string copyCommand)
{
if (copyCommand == null)
throw new ArgumentNullException(nameof(copyCommand));
if (!copyCommand.TrimStart().ToUpper().StartsWith("COPY"))
throw new ArgumentException("Must contain a COPY TO STDOUT OR COPY FROM STDIN command!", nameof(copyCommand));
var connector = CheckReadyAndGetConnector();
Log.Debug("Starting raw COPY operation", connector.Id);
connector.StartUserAction(ConnectorState.Copy);
try
{
var stream = new NpgsqlRawCopyStream(connector, copyCommand);
if (!stream.IsBinary)
{
// TODO: Stop the COPY operation gracefully, no breaking
connector.Break();
throw new ArgumentException("copyToCommand triggered a text transfer, only binary is allowed", nameof(copyCommand));
}
connector.CurrentCopyOperation = stream;
return stream;
}
catch
{
connector.EndUserAction();
throw;
}
}
#endregion
#region Enum mapping
///
/// Maps a CLR enum to a PostgreSQL enum type for use with this connection.
///
///
/// CLR enum labels are mapped by name to PostgreSQL enum labels.
/// The translation strategy can be controlled by the parameter,
/// which defaults to .
/// You can also use the 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 method.
///
///
/// A PostgreSQL type name for the corresponding enum type in the database.
/// If null, the name translator given in will be used.
///
///
/// A component which will be used to translate CLR names (e.g. SomeClass) into database names (e.g. some_class).
/// Defaults to
///
/// The .NET enum type to be mapped
[PublicAPI]
[Obsolete("Use NpgsqlConnection.TypeMapper.MapEnum() instead")]
public void MapEnum(string pgName = null, INpgsqlNameTranslator nameTranslator = null) where TEnum : struct
=> TypeMapper.MapEnum(pgName, nameTranslator);
///
/// Maps a CLR enum to a PostgreSQL enum type for use with all connections created from now on. Existing connections aren't affected.
///
///
/// CLR enum labels are mapped by name to PostgreSQL enum labels.
/// The translation strategy can be controlled by the parameter,
/// which defaults to .
/// You can also use the 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 method.
///
///
/// A PostgreSQL type name for the corresponding enum type in the database.
/// If null, the name translator given in will be used.
///
///
/// A component which will be used to translate CLR names (e.g. SomeClass) into database names (e.g. some_class).
/// Defaults to
///
/// The .NET enum type to be mapped
[PublicAPI]
[Obsolete("Use NpgsqlConnection.GlobalTypeMapper.MapEnum() instead")]
public static void MapEnumGlobally(string pgName = null, INpgsqlNameTranslator nameTranslator = null) where TEnum : struct
=> NpgsqlConnection.GlobalTypeMapper.MapEnum(pgName, nameTranslator);
///
/// Removes a previous global enum mapping.
///
///
/// A PostgreSQL type name for the corresponding enum type in the database.
/// If null, the name translator given in will be used.
///
///
/// A component which will be used to translate CLR names (e.g. SomeClass) into database names (e.g. some_class).
/// Defaults to
///
[Obsolete("Use NpgsqlConnection.GlobalTypeMapper.UnmapEnum() instead")]
public static void UnmapEnumGlobally(string pgName = null, INpgsqlNameTranslator nameTranslator = null) where TEnum : struct
=> NpgsqlConnection.GlobalTypeMapper.UnmapEnum(pgName, nameTranslator);
#endregion
#region Composite registration
///
/// Maps a CLR type to a PostgreSQL composite type for use with this connection.
///
///
/// CLR fields and properties by string to PostgreSQL enum labels.
/// The translation strategy can be controlled by the parameter,
/// which defaults to .
/// You can also use the 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 method.
///
///
/// A PostgreSQL type name for the corresponding enum type in the database.
/// If null, the name translator given in will be used.
///
///
/// A component which will be used to translate CLR names (e.g. SomeClass) into database names (e.g. some_class).
/// Defaults to
///
/// The .NET type to be mapped
[Obsolete("Use NpgsqlConnection.TypeMapper.MapComposite() instead")]
public void MapComposite(string pgName = null, INpgsqlNameTranslator nameTranslator = null) where T : new()
=> TypeMapper.MapComposite(pgName, nameTranslator);
///
/// Maps a CLR type to a PostgreSQL composite type for use with all connections created from now on. Existing connections aren't affected.
///
///
/// CLR fields and properties by string to PostgreSQL enum labels.
/// The translation strategy can be controlled by the parameter,
/// which defaults to .
/// You can also use the 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 method.
///
///
/// A PostgreSQL type name for the corresponding enum type in the database.
/// If null, the name translator given in will be used.
///
///
/// A component which will be used to translate CLR names (e.g. SomeClass) into database names (e.g. some_class).
/// Defaults to
///
/// The .NET type to be mapped
[Obsolete("Use NpgsqlConnection.GlobalTypeMapper.MapComposite() instead")]
public static void MapCompositeGlobally(string pgName = null, INpgsqlNameTranslator nameTranslator = null) where T : new()
=> NpgsqlConnection.GlobalTypeMapper.MapComposite(pgName, nameTranslator);
///
/// Removes a previous global enum mapping.
///
///
/// A PostgreSQL type name for the corresponding enum type in the database.
/// If null, the name translator given in will be used.
///
///
/// A component which will be used to translate CLR names (e.g. SomeClass) into database names (e.g. some_class).
/// Defaults to
///
[Obsolete("Use NpgsqlConnection.GlobalTypeMapper.UnmapComposite() instead")]
public static void UnmapCompositeGlobally(string pgName, INpgsqlNameTranslator nameTranslator = null) where T : new()
=> NpgsqlConnection.GlobalTypeMapper.UnmapComposite(pgName, nameTranslator);
#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));
CheckConnectionOpen();
Debug.Assert(Connector != null);
Log.Debug($"Starting to wait (timeout={timeout})...", Connector.Id);
return Connector.Wait(timeout);
}
///
/// 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.
[PublicAPI]
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
/// (, ).
///
[PublicAPI]
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
/// (, ).
/// CancelationToken can not cancel wait operation if underlying NetworkStream does not support it
/// (see https://stackoverflow.com/questions/12421989/networkstream-readasync-with-a-cancellation-token-never-cancels ).
///
[PublicAPI]
public Task WaitAsync(CancellationToken cancellationToken)
{
CheckConnectionOpen();
Debug.Assert(Connector != null);
Log.Debug("Starting to wait asynchronously...", Connector.Id);
return Connector.WaitAsync(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
/// (, ).
///
public Task WaitAsync() => WaitAsync(CancellationToken.None);
#endregion
#region State checks
void CheckConnectionOpen()
{
CheckDisposed();
if (Connector == null)
throw new InvalidOperationException("Connection is not open");
}
void CheckConnectionClosed()
{
CheckDisposed();
if (Connector != null)
throw new InvalidOperationException("Connection already open");
}
void CheckDisposed()
{
if (_disposed)
throw new ObjectDisposedException(typeof(NpgsqlConnection).Name);
}
internal NpgsqlConnector CheckReadyAndGetConnector()
{
CheckDisposed();
// This method gets called outside any lock, and might be in a race condition
// with an ongoing keepalive, which may break the connector (setting the connection's
// Connector to null). We capture the connector to the stack and return it here.
var conn = Connector;
if (conn == null)
throw new InvalidOperationException("Connection is not open");
return conn;
}
#endregion State checks
#region Schema operations
#if !NETSTANDARD1_3
///
/// Returns the supported collections
///
public override DataTable GetSchema()
{
return NpgsqlSchema.GetMetaDataCollections();
}
///
/// Returns the schema collection specified by the collection name.
///
/// The collection name.
/// The collection specified.
public override DataTable GetSchema([CanBeNull] string collectionName)
{
return 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([CanBeNull] string collectionName, [CanBeNull] string[] restrictions)
{
if (string.IsNullOrEmpty(collectionName))
throw new ArgumentException("Collection name cannot be null or empty", nameof(collectionName));
switch (collectionName.ToUpperInvariant())
{
case "METADATACOLLECTIONS":
return NpgsqlSchema.GetMetaDataCollections();
case "RESTRICTIONS":
return NpgsqlSchema.GetRestrictions();
case "DATASOURCEINFORMATION":
return NpgsqlSchema.GetDataSourceInformation();
case "DATATYPES":
throw new NotSupportedException();
case "RESERVEDWORDS":
return NpgsqlSchema.GetReservedWords();
// custom collections for npgsql
case "DATABASES":
return NpgsqlSchema.GetDatabases(this, restrictions);
case "SCHEMATA":
return NpgsqlSchema.GetSchemata(this, restrictions);
case "TABLES":
return NpgsqlSchema.GetTables(this, restrictions);
case "COLUMNS":
return NpgsqlSchema.GetColumns(this, restrictions);
case "VIEWS":
return NpgsqlSchema.GetViews(this, restrictions);
case "USERS":
return NpgsqlSchema.GetUsers(this, restrictions);
case "INDEXES":
return NpgsqlSchema.GetIndexes(this, restrictions);
case "INDEXCOLUMNS":
return NpgsqlSchema.GetIndexColumns(this, restrictions);
case "CONSTRAINTS":
case "PRIMARYKEY":
case "UNIQUEKEYS":
case "FOREIGNKEYS":
return NpgsqlSchema.GetConstraints(this, restrictions, collectionName);
case "CONSTRAINTCOLUMNS":
return NpgsqlSchema.GetConstraintColumns(this, restrictions);
default:
throw new ArgumentOutOfRangeException(nameof(collectionName), collectionName, "Invalid collection name");
}
}
#endif
#endregion Schema operations
#region Misc
///
/// Creates a closed connection with the connection string and authentication details of this message.
///
#if !NETSTANDARD1_3
object ICloneable.Clone()
#else
public NpgsqlConnection Clone()
#endif
{
CheckDisposed();
var conn = new NpgsqlConnection(_connectionString) {
ProvideClientCertificatesCallback = ProvideClientCertificatesCallback,
UserCertificateValidationCallback = UserCertificateValidationCallback,
_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)
///
[PublicAPI]
public NpgsqlConnection CloneWith(string connectionString)
{
CheckDisposed();
var csb = new NpgsqlConnectionStringBuilder(connectionString);
if (csb.Password == null && Password != null)
csb.Password = Password;
return new NpgsqlConnection(csb.ToString()) {
ProvideClientCertificatesCallback = ProvideClientCertificatesCallback,
UserCertificateValidationCallback = UserCertificateValidationCallback
};
}
///
/// 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)
{
if (dbName == null)
throw new ArgumentNullException(nameof(dbName));
if (string.IsNullOrEmpty(dbName))
throw new ArgumentOutOfRangeException(nameof(dbName), dbName, $"Invalid database name: {dbName}");
CheckConnectionOpen();
Close();
_pool = null;
Settings = Settings.Clone();
Settings.Database = dbName;
ConnectionString = Settings.ToString();
Open();
}
#if !NETSTANDARD1_3
///
/// DB provider factory.
///
protected override DbProviderFactory DbProviderFactory => NpgsqlFactory.Instance;
#endif
///
/// Clear connection pool.
///
public static void ClearPool(NpgsqlConnection connection) => PoolManager.Clear(connection._connectionString);
///
/// Clear all connection pools.
///
public static void ClearAllPools() => PoolManager.ClearAll();
///
/// Unprepares all prepared statements on this connection.
///
[PublicAPI]
public void UnprepareAll()
{
var connector = CheckReadyAndGetConnector();
using (connector.StartUserAction())
connector.UnprepareAll();
}
///
/// Flushes the type cache for this connection's connection string and reloads the
/// types for this connection only.
///
public void ReloadTypes()
{
var conn = CheckReadyAndGetConnector();
DatabaseInfo.Cache.TryRemove(_connectionString, out var _);
ConnectorTypeMapper.Bind(conn, new NpgsqlTimeout(TimeSpan.FromSeconds(ConnectionTimeout)), false)
.GetAwaiter().GetResult();
}
#endregion Misc
}
#region Delegates
///
/// Represents the method that handles the Notice events.
///
/// The source of the event.
/// A NpgsqlNoticeEventArgs that contains the event data.
public delegate void NoticeEventHandler(object sender, NpgsqlNoticeEventArgs e);
///
/// Represents the method that handles the Notification events.
///
/// The source of the event.
/// A NpgsqlNotificationEventArgs that contains the event data.
public delegate void NotificationEventHandler(object sender, NpgsqlNotificationEventArgs e);
///
/// Represents the method that allows the application to provide a certificate collection to be used for SSL client authentication
///
/// A X509CertificateCollection to be filled with one or more client certificates.
public delegate void ProvideClientCertificatesCallback(X509CertificateCollection certificates);
#endregion
}