X Tutup
#region License // The PostgreSQL License // // Copyright (C) 2016 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.Contracts; 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 AsyncRewriter; using JetBrains.Annotations; #if NET45 || NET451 using System.Transactions; #endif using Npgsql.Logging; using NpgsqlTypes; using IsolationLevel = System.Data.IsolationLevel; namespace Npgsql { /// /// This class represents a connection to a PostgreSQL server. /// #if WITHDESIGN [System.Drawing.ToolboxBitmapAttribute(typeof(NpgsqlConnection))] #endif #if NETSTANDARD1_3 public sealed partial class NpgsqlConnection : DbConnection #else // ReSharper disable once RedundantNameQualifier [System.ComponentModel.DesignerCategory("")] public sealed partial class NpgsqlConnection : DbConnection, ICloneable #endif { #region Fields // Set this when disposed is called. bool _disposed; // Used when we closed the connector due to an error, but are pretending it's open. bool _fakingOpen; // Used when the connection is closed but an TransactionScope is still active // (the actual close is postponed until the scope ends) bool _postponingClose; bool _postponingDispose; /// /// The parsed connection string set by the user /// internal NpgsqlConnectionStringBuilder Settings { get; private set; } /// /// The actual string provided by the user for the connection string /// string _connectionString; /// /// Contains the clear text password which was extracted from the user-provided connection string. /// internal string Password { get; private set; } /// /// The connector object connected to the backend. /// internal NpgsqlConnector Connector { get; set; } /// /// A counter that gets incremented every time the connection is (re-)opened. /// This allows us to identify an "instance" of connection, which is useful since /// some resources are released when a connection is closed (e.g. prepared statements). /// internal int OpenCounter { get; private set; } bool _wasBroken; #if NET45 || NET451 NpgsqlPromotableSinglePhaseNotification Promotable => _promotable ?? (_promotable = new NpgsqlPromotableSinglePhaseNotification(this)); NpgsqlPromotableSinglePhaseNotification _promotable; #endif /// /// 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(); #endregion Fields #region Constructors / Init / Open /// /// Initializes a new instance of the /// NpgsqlConnection class. /// public NpgsqlConnection() : this(String.Empty) {} /// /// Initializes a new instance of the /// NpgsqlConnection class /// and sets the ConnectionString. /// /// The connection used to open the PostgreSQL database. public NpgsqlConnection(NpgsqlConnectionStringBuilder builder) : this(builder.ConnectionString) { } /// /// Initializes a new instance of the /// NpgsqlConnection class /// and sets the ConnectionString. /// /// The connection used to open the PostgreSQL database. public NpgsqlConnection(string connectionString) { GC.SuppressFinalize(this); ConnectionString = connectionString; Init(); } void Init() { _noticeDelegate = OnNotice; _notificationDelegate = OnNotification; #if NET45 || NET451 // 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; _promotable = new NpgsqlPromotableSinglePhaseNotification(this); #endif } /// /// Opens a database connection with the property settings specified by the /// ConnectionString. /// public override void Open() => OpenInternal(); /// /// 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) => OpenInternalAsync(cancellationToken); [RewriteAsync] void OpenInternal() { if (string.IsNullOrWhiteSpace(Host)) throw new ArgumentException("Host can't be null"); Contract.EndContractBlock(); var timeout = new NpgsqlTimeout(TimeSpan.FromSeconds(ConnectionTimeout)); // If we're postponing a close (see doc on this variable), the connection is already // open and can be silently reused if (_postponingClose) return; CheckConnectionClosed(); Log.Trace("Opening connnection"); // Copy the password aside and remove it from the user-provided connection string // (unless PersistSecurityInfo has been requested). Note that cloned connections already // have Password populated and should not be overwritten. if (Password == null) { Password = Settings.Password; } if (!Settings.PersistSecurityInfo) { Settings.Password = null; _connectionString = Settings.ToString(); } _wasBroken = false; try { // Get a Connector, either from the pool or creating one ourselves. if (Settings.Pooling) { Connector = PoolManager.GetOrAdd(Settings).Allocate(this, timeout); // Since this pooled connector was opened, global enum/composite mappings may have // changed. Bring this up to date if needed. Connector.TypeHandlerRegistry.ActivateGlobalMappings(); } else { Connector = new NpgsqlConnector(this); Connector.Open(timeout); } Connector.Notice += _noticeDelegate; Connector.Notification += _notificationDelegate; #if NET45 || NET451 if (Settings.Enlist) { Promotable.Enlist(Transaction.Current); } #endif } catch { Connector = null; throw; } OpenCounter++; 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. /// #if WITHDESIGN [RefreshProperties(RefreshProperties.All), DefaultValue(""), RecommendedAsConfigurable(true)] [NpgsqlSysDescription("Description_ConnectionString", typeof(NpgsqlConnection)), Category("Data")] [Editor(typeof(ConnectionStringEditor), typeof(System.Drawing.Design.UITypeEditor))] #endif public override string ConnectionString { get { return _connectionString; } set { if (value == null) { value = string.Empty; } Settings = new NpgsqlConnectionStringBuilder(value); // Note that settings.ConnectionString is canonical and may therefore be different from // the provided value _connectionString = Settings.ConnectionString; } } #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; /// /// If true, the connection will attempt to use SslStream instead of an internal TlsClientStream. /// [PublicAPI] public bool UseSslStream => Settings.UseSslStream; /// /// 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. #if WITHDESIGN [NpgsqlSysDescription("Description_ConnectionTimeout", typeof(NpgsqlConnection))] #endif 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. #if WITHDESIGN [NpgsqlSysDescription("Description_Database", typeof(NpgsqlConnection))] #endif public override string Database => Settings.Database; /// /// 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] public string UserName => Settings.Username; internal int MinPoolSize => Settings.MinPoolSize; internal int MaxPoolSize => Settings.MaxPoolSize; internal int Timeout => Settings.Timeout; internal int BufferSize => Settings.BufferSize; 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 PGUtil.ThrowIfReached("Unknown connector state: " + Connector.State); } } } /// /// 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() { CheckNotDisposed(); 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() { // ReSharper disable once IntroduceOptionalParameters.Global return 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); Contract.EndContractBlock(); var connector = CheckReadyAndGetConnector(); // 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."); Log.Debug("Beginning transaction with isolation level " + level, Connector.Id); return new NpgsqlTransaction(this, level); } } /// /// When a connection is closed within an enclosing TransactionScope and the transaction /// hasn't been promoted, we defer the actual closing until the scope ends. /// internal void PromotableLocalTransactionEnded() { if (_postponingDispose) Dispose(true); else if (_postponingClose) ReallyClose(); } #if NET45 || NET451 /// /// Enlist transation. /// /// // ReSharper disable once ImplicitNotNullOverridesUnknownExternalMember public override void EnlistTransaction(Transaction transaction) { if (transaction == null) throw new ArgumentNullException(nameof(transaction)); Contract.EndContractBlock(); Promotable.Enlist(transaction); } #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() { if (Connector == null) return; Log.Trace("Closing connection", Connector.Id); #if NET45 || NET451 if (_promotable != null && _promotable.InLocalTransaction) { _postponingClose = true; return; } #endif ReallyClose(); } internal void ReallyClose(bool wasBroken=false) { var connectorId = Connector.Id; Log.Trace("Really closing connection", connectorId); _postponingClose = false; _wasBroken = wasBroken; #if NET45 || NET451 // clear the way for another promotable transaction _promotable = null; #endif Connector.Notification -= _notificationDelegate; Connector.Notice -= _noticeDelegate; CloseOngoingOperations(); if (Settings.Pooling) { PoolManager.GetOrAdd(Settings).Release(Connector); } else { Connector.Close(); } Log.Debug("Connection closed", Connector.Id); 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; } if (Connector.CurrentReader != null) { Connector.CurrentReader.Close(); } else if (Connector.State == ConnectorState.Copy) { Contract.Assert(Connector.CurrentCopyOperation != null); // 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 (Connector.CurrentCopyOperation is NpgsqlBinaryImporter || Connector.CurrentCopyOperation is NpgsqlCopyTextWriter || (Connector.CurrentCopyOperation is NpgsqlRawCopyStream && ((NpgsqlRawCopyStream)Connector.CurrentCopyOperation).CanWrite)) { try { Connector.CurrentCopyOperation.Cancel(); } catch (Exception e) { Log.Warn("Error while cancelling COPY on connector close", e); } } try { Connector.CurrentCopyOperation.Dispose(); } catch (Exception e) { Log.Warn("Error while disposing cancelled COPY on connector close", e); } } } /// /// 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; _postponingDispose = false; if (disposing) { Close(); if (_postponingClose) { _postponingDispose = true; return; } } base.Dispose(disposing); _disposed = true; } #endregion #region Notifications /// /// Occurs on NoticeResponses from the PostgreSQL backend. /// public event NoticeEventHandler Notice; NoticeEventHandler _noticeDelegate; /// /// Occurs on NotificationResponses from the PostgreSQL backend. /// public event NotificationEventHandler Notification; NotificationEventHandler _notificationDelegate; // // Internal methods and properties // void OnNotice(object o, NpgsqlNoticeEventArgs e) { Notice?.Invoke(this, e); } void OnNotification(object o, NpgsqlNotificationEventArgs e) { Notification?.Invoke(this, e); } #endregion Notifications #region SSL /// /// Returns whether SSL is being used for the connection. /// internal bool IsSecure { get { CheckConnectionOpen(); return Connector.IsSecure; } } /// /// Selects the local Secure Sockets Layer (SSL) certificate used for authentication. /// /// /// See /// public ProvideClientCertificatesCallback ProvideClientCertificatesCallback { get; set; } /// /// Verifies the remote Secure Sockets Layer (SSL) certificate used for authentication. /// Ignored if is set. /// /// /// See /// public RemoteCertificateValidationCallback UserCertificateValidationCallback { get; set; } #endregion SSL #region Backend version and capabilities /// /// Version of the PostgreSQL backend. /// This can only be called when there is an active connection. /// [Browsable(false)] public Version PostgreSqlVersion { get { CheckConnectionOpen(); 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(); return Connector.BackendProcessId; } } /// /// Report whether the backend is expecting standard conformant strings. /// In version 8.1, Postgres began reporting this value (false), but did not actually support standard conformant strings. /// In version 8.2, Postgres began supporting standard conformant strings, but defaulted this flag to false. /// As of version 9.1, this flag defaults to true. /// [Browsable(false)] [PublicAPI] public bool UseConformantStrings { get { CheckConnectionOpen(); return Connector.UseConformantStrings; } } /// /// Report whether the backend understands the string literal E prefix (>= 8.1). /// [Browsable(false)] [PublicAPI] public bool SupportsEStringPrefix { get { CheckConnectionOpen(); return Connector.SupportsEStringPrefix; } } #endregion Backend version and capabilities #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)); Contract.EndContractBlock(); var connector = CheckReadyAndGetConnector(); 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)); Contract.EndContractBlock(); var connector = CheckReadyAndGetConnector(); 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)); Contract.EndContractBlock(); var connector = CheckReadyAndGetConnector(); 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)); Contract.EndContractBlock(); var connector = CheckReadyAndGetConnector(); 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)); Contract.EndContractBlock(); var connector = CheckReadyAndGetConnector(); 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] public void MapEnum(string pgName = null, INpgsqlNameTranslator nameTranslator = null) where TEnum : struct { if (!typeof(TEnum).GetTypeInfo().IsEnum) throw new ArgumentException("An enum type must be provided"); if (pgName != null && pgName.Trim() == "") throw new ArgumentException("pgName can't be empty", nameof(pgName)); if (State != ConnectionState.Open) throw new InvalidOperationException("Connection must be open and idle to perform registration"); Contract.EndContractBlock(); Connector.TypeHandlerRegistry.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] public static void MapEnumGlobally(string pgName = null, INpgsqlNameTranslator nameTranslator = null) where TEnum : struct { if (!typeof(TEnum).GetTypeInfo().IsEnum) throw new ArgumentException("An enum type must be provided"); if (pgName != null && pgName.Trim() == "") throw new ArgumentException("pgName can't be empty", nameof(pgName)); Contract.EndContractBlock(); TypeHandlerRegistry.MapEnumGlobally(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 /// public static void UnmapEnumGlobally(string pgName = null, INpgsqlNameTranslator nameTranslator = null) where TEnum : struct { if (!typeof(TEnum).GetTypeInfo().IsEnum) throw new ArgumentException("An enum type must be provided"); if (pgName != null && pgName.Trim() == "") throw new ArgumentException("pgName can't be empty", nameof(pgName)); Contract.EndContractBlock(); TypeHandlerRegistry.UnmapEnumGlobally(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 public void MapComposite(string pgName = null, INpgsqlNameTranslator nameTranslator = null) where T : new() { if (pgName != null && pgName.Trim() == "") throw new ArgumentException("pgName can't be empty", nameof(pgName)); if (State != ConnectionState.Open) throw new InvalidOperationException("Connection must be open and idle to perform registration"); Contract.EndContractBlock(); Connector.TypeHandlerRegistry.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 public static void MapCompositeGlobally(string pgName = null, INpgsqlNameTranslator nameTranslator = null) where T : new() { if (pgName != null && pgName.Trim() == "") throw new ArgumentException("pgName can't be empty", nameof(pgName)); Contract.EndContractBlock(); TypeHandlerRegistry.MapCompositeGlobally(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 /// public static void UnmapCompositeGlobally(string pgName, INpgsqlNameTranslator nameTranslator = null) where T : new() { TypeHandlerRegistry.UnmapCompositeGlobally(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)); Contract.EndContractBlock(); CheckConnectionOpen(); Log.Debug($"Starting to wait (timeout={timeout})", Connector.Id); using (Connector.StartUserAction(ConnectorState.Waiting)) { Connector.UserTimeout = timeout; try { Connector.ReadAsyncMessage(); } catch (TimeoutException) { return false; } } return true; } /// /// Waits until an asynchronous PostgreSQL messages (e.g. a notification) arrives, and /// exits immediately. The asynchronous message is delivered via the normal events /// (, ). /// /// /// The time-out value is passed to . /// /// true if an asynchronous message was received, false if timed out. public bool Wait(TimeSpan timeout) => Wait((int)timeout.TotalMilliseconds); /// /// Waits until an asynchronous PostgreSQL messages (e.g. a notification) arrives, and /// exits immediately. The asynchronous message is delivered via the normal events /// (, ). /// public void Wait() => Wait(0); /// /// Waits asynchronously until an asynchronous PostgreSQL messages (e.g. a notification) /// arrives, and exist immediately. The asynchronous message is delivered via the normal events /// (, ). /// public async Task WaitAsync(CancellationToken cancellationToken) { CheckConnectionOpen(); Log.Debug("Starting to wait async", Connector.Id); using (Connector.StartUserAction(ConnectorState.Waiting)) await Connector.ReadAsyncMessageAsync(cancellationToken).ConfigureAwait(false); } /// /// Waits asynchronously until an asynchronous PostgreSQL messages (e.g. a notification) /// arrives, and exist immediately. The asynchronous message is delivered via the normal events /// (, ). /// public Task WaitAsync() => WaitAsync(CancellationToken.None); #endregion #region State checks void CheckConnectionOpen() { if (_disposed) { throw new ObjectDisposedException(typeof(NpgsqlConnection).Name); } if (_fakingOpen) { if (Connector != null) { try { Close(); } catch { // ignored } } Open(); _fakingOpen = false; } if (_postponingClose || Connector == null) { throw new InvalidOperationException("Connection is not open"); } } void CheckConnectionClosed() { if (_disposed) throw new ObjectDisposedException(typeof(NpgsqlConnection).Name); if (Connector != null) throw new InvalidOperationException("Connection already open"); } void CheckNotDisposed() { if (_disposed) throw new ObjectDisposedException(typeof(NpgsqlConnection).Name); } internal NpgsqlConnector CheckReadyAndGetConnector() { if (_disposed) throw new ObjectDisposedException(typeof(NpgsqlConnection).Name); // 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 NET45 || NET451 /// /// 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) { switch (collectionName) { 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 NET45 || NET451 object ICloneable.Clone() #else public NpgsqlConnection Clone() #endif { CheckNotDisposed(); return new NpgsqlConnection(ConnectionString) { Password = Password, ProvideClientCertificatesCallback = ProvideClientCertificatesCallback, UserCertificateValidationCallback = UserCertificateValidationCallback }; } /// /// Clones this connection, replacing its connection string with the given one. /// This allows creating a new connection with the same security information /// (password, SSL callbacks) while changing other connection parameters (e.g. /// database or pooling) /// public NpgsqlConnection CloneWith(string connectionString) { CheckNotDisposed(); var csb = new NpgsqlConnectionStringBuilder(connectionString); if (csb.Password == null && Password != null) { csb.Password = Password; } return new NpgsqlConnection(csb) { 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}"); Contract.EndContractBlock(); CheckNotDisposed(); Log.Debug("Changing database to " + dbName, Connector.Id); Close(); Settings = Settings.Clone(); Settings.Database = dbName; _connectionString = Settings.ConnectionString; Open(); } #if NET45 || NET451 /// /// DB provider factory. /// protected override DbProviderFactory DbProviderFactory => NpgsqlFactory.Instance; #endif /// /// Clear connection pool. /// public static void ClearPool(NpgsqlConnection connection) { ConnectorPool pool; if (PoolManager.Pools.TryGetValue(connection.Settings, out pool)) pool.Clear(); } /// /// Clear all connection pools. /// public static void ClearAllPools() => PoolManager.ClearAll(); /// /// Flushes the type cache for this connection's connection string and reloads the /// types for this connection only. /// public void ReloadTypes() { var conn = CheckReadyAndGetConnector(); TypeHandlerRegistry.ClearBackendTypeCache(ConnectionString); TypeHandlerRegistry.Setup(conn, new NpgsqlTimeout(TimeSpan.FromSeconds(ConnectionTimeout))); } #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 }
X Tutup