// Copyright (C) 2002 The Npgsql Development Team
// npgsql-general@gborg.postgresql.org
// http://gborg.postgresql.org/project/npgsql/projdisplay.php
//
// 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.
//
// Connector.cs
// ------------------------------------------------------------------
// Project
// Npgsql
// Status
// 0.00.0000 - 06/17/2002 - ulrich sprick - created
// - 06/??/2004 - Glen Parker rewritten
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using Mono.Security.Protocol.Tls;
using NpgsqlTypes;
using System.Text;
namespace Npgsql
{
///
/// Represents the method that allows the application to provide a certificate collection to be used for SSL clien authentication
///
/// A X509CertificateCollection to be filled with one or more client certificates.
public delegate void ProvideClientCertificatesCallback(X509CertificateCollection certificates);
///
/// Represents the method that is called to validate the certificate provided by the server during an SSL handshake
///
/// The server's certificate
/// The certificate chain containing the certificate's CA and any intermediate authorities
/// Any errors that were detected
public delegate bool ValidateRemoteCertificateCallback(X509Certificate cert, X509Chain chain, SslPolicyErrors errors);
///
/// !!! Helper class, for compilation only.
/// Connector implements the logic for the Connection Objects to
/// access the physical connection to the database, and isolate
/// the application developer from connection pooling internals.
///
internal class NpgsqlConnector
{
// Immutable.
private readonly NpgsqlConnectionStringBuilder settings;
///
/// Occurs on NoticeResponses from the PostgreSQL backend.
///
internal event NoticeEventHandler Notice;
///
/// Occurs on NotificationResponses from the PostgreSQL backend.
///
internal event NotificationEventHandler Notification;
///
/// Called to provide client certificates for SSL handshake.
///
internal event ProvideClientCertificatesCallback ProvideClientCertificatesCallback;
///
/// Mono.Security.Protocol.Tls.CertificateSelectionCallback delegate.
///
internal event CertificateSelectionCallback CertificateSelectionCallback;
///
/// Mono.Security.Protocol.Tls.CertificateValidationCallback delegate.
///
internal event CertificateValidationCallback CertificateValidationCallback;
///
/// Mono.Security.Protocol.Tls.PrivateKeySelectionCallback delegate.
///
internal event PrivateKeySelectionCallback PrivateKeySelectionCallback;
///
/// Called to validate server's certificate during SSL handshake
///
internal event ValidateRemoteCertificateCallback ValidateRemoteCertificateCallback;
private ConnectionState _connection_state;
// The physical network connection socket and stream to the backend.
private Socket _socket;
private NpgsqlNetworkStream _baseStream;
// The top level stream to the backend.
// This is a BufferedStream.
// With SSL, this stream sits on top of the SSL stream, which sits on top of _baseStream.
// Otherwise, this stream sits directly on top of _baseStream.
private BufferedStream _stream;
// Mediator which will hold data generated from backend.
private readonly NpgsqlMediator _mediator;
private ProtocolVersion _backendProtocolVersion;
private Version _serverVersion;
// Values for possible CancelRequest messages.
private NpgsqlBackEndKeyData _backend_keydata;
// Flag for transaction status.
// private Boolean _inTransaction = false;
private NpgsqlTransaction _transaction = null;
private Boolean _supportsPrepare = false;
private Boolean _supportsSavepoint = false;
private Boolean _supportsDiscard = false;
private Boolean _supportsApplicationName = false;
private Boolean _supportsExtraFloatDigits3 = false;
private Boolean _supportsExtraFloatDigits = false;
private Boolean _supportsSslRenegotiationLimit = false;
private Boolean _isInitialized;
private readonly Boolean _pooled;
private readonly Boolean _shared;
private NpgsqlState _state;
private Int32 _planIndex;
private Int32 _portalIndex;
private const String _planNamePrefix = "s";
private const String _portalNamePrefix = "p";
private NativeToBackendTypeConverterOptions _NativeToBackendTypeConverterOptions;
private Thread _notificationThread;
// Counter of notification thread start/stop requests in order to
internal Int16 _notificationThreadStopCount;
private Exception _notificationException;
internal ForwardsOnlyDataReader CurrentReader;
// Some kinds of messages only get one response, and do not
// expect a ready_for_query response.
private bool _requireReadyForQuery = true;
private readonly Dictionary _serverParameters =
new Dictionary(StringComparer.InvariantCultureIgnoreCase);
// For IsValid test
private readonly RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
private string initQueries;
#if WINDOWS && UNMANAGED
private SSPIHandler _sspi;
internal SSPIHandler SSPI
{
get { return _sspi; }
set { _sspi = value; }
}
#endif
public NpgsqlConnector(NpgsqlConnection Connection)
: this(Connection.CopyConnectionStringBuilder(), Connection.Pooling, false)
{}
///
/// Constructor.
///
/// Connection string.
/// Pooled
/// Controls whether the connector can be shared.
public NpgsqlConnector(NpgsqlConnectionStringBuilder ConnectionString, bool Pooled, bool Shared)
{
this.settings = ConnectionString;
_connection_state = ConnectionState.Closed;
_pooled = Pooled;
_shared = Shared;
_isInitialized = false;
_state = NpgsqlClosedState.Instance;
_mediator = new NpgsqlMediator();
_NativeToBackendTypeConverterOptions = NativeToBackendTypeConverterOptions.Default.Clone(new NpgsqlBackendTypeMapping());
_planIndex = 0;
_portalIndex = 0;
_notificationThreadStopCount = 1;
}
//Finalizer should never be used, but if some incident has left to a connector being abandoned (most likely
//case being a user not cleaning up a connection properly) then this way we can at least reduce the damage.
//~NpgsqlConnector()
//{
// Close();
//}
internal String Host
{
get { return settings.Host; }
}
internal Int32 Port
{
get { return settings.Port; }
}
internal String Database
{
get { return settings.ContainsKey(Keywords.Database) ? settings.Database : settings.UserName; }
}
internal String UserName
{
get { return settings.UserName; }
}
internal byte[] Password
{
get { return settings.PasswordAsByteArray; }
}
internal Boolean SSL
{
get { return settings.SSL; }
}
internal SslMode SslMode
{
get { return settings.SslMode; }
}
internal Boolean UseMonoSsl
{
get { return ValidateRemoteCertificateCallback == null; }
}
internal Int32 ConnectionTimeout
{
get { return settings.Timeout; }
}
internal Int32 DefaultCommandTimeout
{
get { return settings.CommandTimeout; }
}
internal Boolean Enlist
{
get { return settings.Enlist; }
}
public bool UseExtendedTypes
{
get { return settings.UseExtendedTypes; }
}
internal Boolean IntegratedSecurity
{
get { return settings.IntegratedSecurity; }
}
internal Boolean AlwaysPrepare
{
get { return SupportsPrepare && settings.AlwaysPrepare; }
}
///
/// Gets the current state of the connection.
///
internal ConnectionState State
{
get
{
if (_connection_state != ConnectionState.Closed && CurrentReader != null && !CurrentReader._cleanedUp)
{
return ConnectionState.Open | ConnectionState.Fetching;
}
return _connection_state;
}
}
///
/// Return Connection String.
///
internal string ConnectionString
{
get { return settings.ConnectionString; }
}
internal void Query(NpgsqlQuery query)
{
CurrentState.Query(this, query);
}
internal void Authenticate(byte[] password)
{
CurrentState.Authenticate(this, password);
}
internal void Parse(NpgsqlParse parse)
{
CurrentState.Parse(this, parse);
}
internal void TestConnector()
{
CurrentState.TestConnector(this);
}
internal void Sync()
{
CurrentState.Sync(this);
}
internal void Bind(NpgsqlBind bind)
{
CurrentState.Bind(this, bind);
}
internal void Describe(NpgsqlDescribe describe)
{
CurrentState.Describe(this, describe);
}
internal void Execute(NpgsqlExecute execute)
{
CurrentState.Execute(this, execute);
}
internal void ProcessAndDiscardBackendResponses()
{
CurrentState.ProcessAndDiscardBackendResponses(this);
}
internal IEnumerable ProcessBackendResponsesEnum()
{
return CurrentState.ProcessBackendResponsesEnum(this);
}
///
/// This method checks if the connector is still ok.
/// We try to send a simple query text, select 1 as ConnectionTest;
///
internal Boolean IsValid()
{
try
{
// Here we use a fake NpgsqlCommand, just to send the test query string.
// Get random test value.
Byte[] testBytes = new Byte[2];
rng.GetNonZeroBytes(testBytes);
String testValue = String.Format("Npgsql{0}{1}", testBytes[0], testBytes[1]);
//Query(new NpgsqlCommand("select 1 as ConnectionTest", this));
string compareValue = string.Empty;
string sql = "select '" + testValue + "'";
// restore initial connection parameters resetted by "Discard ALL"
if (SupportsDiscard)
{
sql = this.initQueries + sql;
}
using(NpgsqlCommand cmd = new NpgsqlCommand(sql, this))
{
compareValue = (string) cmd.ExecuteScalar();
}
if (compareValue != testValue)
{
return false;
}
this.RequireReadyForQuery = true;
}
catch
{
return false;
}
return true;
}
///
/// This method is responsible for releasing all resources associated with this Connector.
///
internal void ReleaseResources()
{
if (_connection_state != ConnectionState.Closed)
{
if (SupportsDiscard)
{
ReleaseWithDiscard();
}
else
{
ReleasePlansPortals();
ReleaseRegisteredListen();
}
}
}
internal void ReleaseWithDiscard()
{
NpgsqlCommand.ExecuteBlind(this, "DISCARD ALL", 60);
// The initial connection parameters will be restored via IsValid() when get connector from pool later
}
internal void ReleaseRegisteredListen()
{
NpgsqlCommand.ExecuteBlind(this, "UNLISTEN *");
}
///
/// This method is responsible to release all portals used by this Connector.
///
internal void ReleasePlansPortals()
{
Int32 i = 0;
if (_planIndex > 0)
{
for (i = 1; i <= _planIndex; i++)
{
try
{
NpgsqlCommand.ExecuteBlind(this, String.Format("DEALLOCATE \"{0}{1}\";", _planNamePrefix, i), -1);
}
// Ignore any error which may occur when releasing portals as this portal name may not be valid anymore. i.e.: the portal name was used on a prepared query which had errors.
catch {}
}
}
_portalIndex = 0;
_planIndex = 0;
}
///
/// Modify the backend statement_timeout value if needed.
///
/// New timeout
internal void SetBackendCommandTimeout(int timeout)
{
if (Mediator.BackendCommandTimeout == -1 || Mediator.BackendCommandTimeout != timeout)
{
NpgsqlCommand.ExecuteSetStatementTimeoutBlind(this, timeout);
Mediator.BackendCommandTimeout = timeout;
}
}
internal void FireNotice(NpgsqlError e)
{
if (Notice != null)
{
try
{
Notice(this, new NpgsqlNoticeEventArgs(e));
}
catch
{
} //Eat exceptions from user code.
}
}
internal void FireNotification(NpgsqlNotificationEventArgs e)
{
if (Notification != null)
{
try
{
Notification(this, e);
}
catch
{
} //Eat exceptions from user code.
}
}
///
/// Default SSL CertificateSelectionCallback implementation.
///
internal X509Certificate DefaultCertificateSelectionCallback(X509CertificateCollection clientCertificates,
X509Certificate serverCertificate, string targetHost,
X509CertificateCollection serverRequestedCertificates)
{
if (CertificateSelectionCallback != null)
{
return CertificateSelectionCallback(clientCertificates, serverCertificate, targetHost, serverRequestedCertificates);
}
else
{
return null;
}
}
///
/// Default SSL CertificateValidationCallback implementation.
///
internal bool DefaultCertificateValidationCallback(X509Certificate certificate, int[] certificateErrors)
{
if (CertificateValidationCallback != null)
{
return CertificateValidationCallback(certificate, certificateErrors);
}
else
{
return true;
}
}
///
/// Default SSL PrivateKeySelectionCallback implementation.
///
internal AsymmetricAlgorithm DefaultPrivateKeySelectionCallback(X509Certificate certificate, string targetHost)
{
if (PrivateKeySelectionCallback != null)
{
return PrivateKeySelectionCallback(certificate, targetHost);
}
else
{
return null;
}
}
///
/// Default SSL ProvideClientCertificatesCallback implementation.
///
internal void DefaultProvideClientCertificatesCallback(X509CertificateCollection certificates)
{
if (ProvideClientCertificatesCallback != null)
{
ProvideClientCertificatesCallback(certificates);
}
}
///
/// Default SSL ValidateRemoteCertificateCallback implementation.
///
internal bool DefaultValidateRemoteCertificateCallback(object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors errors)
{
if (ValidateRemoteCertificateCallback != null)
{
return ValidateRemoteCertificateCallback(cert, chain, errors);
}
else
{
return false;
}
}
///
/// Version of backend server this connector is connected to.
///
internal Version ServerVersion
{
get { return _serverVersion; }
set { _serverVersion = value; }
}
///
/// Backend protocol version in use by this connector.
///
internal ProtocolVersion BackendProtocolVersion
{
get { return _backendProtocolVersion; }
set { _backendProtocolVersion = value; }
}
///
/// The physical connection socket to the backend.
///
internal Socket Socket
{
get { return _socket; }
set { _socket = value; }
}
///
/// The physical connection stream to the backend.
///
internal NpgsqlNetworkStream BaseStream
{
get { return _baseStream; }
set { _baseStream = value; }
}
///
/// The top level stream to the backend.
///
internal BufferedStream Stream
{
get { return _stream; }
set { _stream = value; }
}
///
/// Reports if this connector is fully connected.
///
internal Boolean IsInitialized
{
get { return _isInitialized; }
set { _isInitialized = value; }
}
internal NpgsqlState CurrentState
{
get { return _state; }
set { _state = value; }
}
internal bool Pooled
{
get { return _pooled; }
}
internal bool Shared
{
get { return _shared; }
}
internal NpgsqlBackEndKeyData BackEndKeyData
{
get { return _backend_keydata; }
set { _backend_keydata = value; }
}
internal NpgsqlBackendTypeMapping OidToNameMapping
{
get { return _NativeToBackendTypeConverterOptions.OidToNameMapping; }
}
internal Version CompatVersion
{
get
{
return settings.Compatible;
}
}
///
/// The connection mediator.
///
internal NpgsqlMediator Mediator
{
get { return _mediator; }
}
///
/// Report if the connection is in a transaction.
///
internal NpgsqlTransaction Transaction
{
get { return _transaction; }
set { _transaction = value; }
}
internal Boolean SupportsApplicationName
{
get { return _supportsApplicationName; }
}
internal Boolean SupportsExtraFloatDigits3
{
get { return _supportsExtraFloatDigits3; }
}
internal Boolean SupportsExtraFloatDigits
{
get { return _supportsExtraFloatDigits; }
}
internal Boolean SupportsSslRenegotiationLimit
{
get { return _supportsSslRenegotiationLimit; }
}
///
/// Report whether the current connection can support prepare functionality.
///
internal Boolean SupportsPrepare
{
get { return _supportsPrepare; }
set { _supportsPrepare = value; }
}
internal Boolean SupportsSavepoint
{
get { return _supportsSavepoint; }
set { _supportsSavepoint = value; }
}
internal Boolean SupportsDiscard
{
get { return _supportsDiscard; }
}
///
/// Options that control certain aspects of native to backend conversions that depend
/// on backend version and status.
///
public NativeToBackendTypeConverterOptions NativeToBackendTypeConverterOptions
{
get
{
return _NativeToBackendTypeConverterOptions;
}
}
///
/// This method is required to set all the version dependent features flags.
/// SupportsPrepare means the server can use prepared query plans (7.3+)
///
private void ProcessServerVersion()
{
this._supportsPrepare = (ServerVersion >= new Version(7, 3, 0));
this._supportsSavepoint = (ServerVersion >= new Version(8, 0, 0));
this._supportsDiscard = (ServerVersion >= new Version(8, 3, 0));
this._supportsApplicationName = (ServerVersion >= new Version(9, 0, 0));
this._supportsExtraFloatDigits3 =(ServerVersion >= new Version(9, 0, 0));
this._supportsExtraFloatDigits = (ServerVersion >= new Version(7, 4, 0));
this._supportsSslRenegotiationLimit = ((ServerVersion >= new Version(8, 4, 3)) ||
(ServerVersion >= new Version(8, 3, 10) && ServerVersion < new Version(8, 4, 0)) ||
(ServerVersion >= new Version(8, 2, 16) && ServerVersion < new Version(8, 3, 0)) ||
(ServerVersion >= new Version(8, 1, 20) && ServerVersion < new Version(8, 2, 0)) ||
(ServerVersion >= new Version(8, 0, 24) && ServerVersion < new Version(8, 1, 0)) ||
(ServerVersion >= new Version(7, 4, 28) && ServerVersion < new Version(8, 0, 0)) );
// Per the PG documentation, E string literal prefix support appeared in PG version 8.1.
// Note that it is possible that support for this prefix will vanish in some future version
// of Postgres, in which case this test will need to be revised.
// At that time it may also be necessary to set UseConformantStrings = true here.
NativeToBackendTypeConverterOptions.Supports_E_StringPrefix = (ServerVersion >= new Version(8, 1, 0));
// Per the PG documentation, hex string encoding format support appeared in PG version 9.0.
NativeToBackendTypeConverterOptions.SupportsHexByteFormat = (ServerVersion >= new Version(9, 0, 0));
}
/*/// Counts the numbers of Connections that share
/// this Connector. Used in Release() to decide wether this
/// connector is to be moved to the PooledConnectors list.
// internal int mShareCount;*/
///
/// Opens the physical connection to the server.
///
/// Usually called by the RequestConnector
/// Method of the connection pool manager.
internal void Open()
{
ServerVersion = null;
// If Connection.ConnectionString specifies a protocol version, we will
// not try to fall back to version 2 on failure.
_backendProtocolVersion = (settings.Protocol == ProtocolVersion.Unknown)
? ProtocolVersion.Version3
: settings.Protocol;
// Reset state to initialize new connector in pool.
CurrentState = NpgsqlClosedState.Instance;
// Keep track of time remaining; Even though there may be multiple timeout-able calls,
// this allows us to still respect the caller's timeout expectation.
int connectTimeRemaining = this.ConnectionTimeout * 1000;
DateTime attemptStart = DateTime.Now;
// Get a raw connection, possibly SSL...
CurrentState.Open(this, connectTimeRemaining);
try
{
// Establish protocol communication and handle authentication...
CurrentState.Startup(this,settings);
}
catch (NpgsqlException ne)
{
if (_stream != null)
{
try
{
_stream.Dispose();
}
catch
{
}
}
connectTimeRemaining -= Convert.ToInt32((DateTime.Now - attemptStart).TotalMilliseconds);
// Check for protocol not supported. If we have been told what protocol to use,
// we will not try this step.
if (settings.Protocol != ProtocolVersion.Unknown)
{
throw;
}
// If we attempted protocol version 3, it may be possible to drop back to version 2.
if (BackendProtocolVersion != ProtocolVersion.Version3)
{
throw;
}
NpgsqlError Error0 = (NpgsqlError) ne.Errors[0];
// If NpgsqlError..ctor() encounters a version 2 error,
// it will set its own protocol version to version 2. That way, we can tell
// easily if the error was a FATAL: protocol error.
if (Error0.BackendProtocolVersion != ProtocolVersion.Version2)
{
throw;
}
// Try using the 2.0 protocol.
BackendProtocolVersion = ProtocolVersion.Version2;
CurrentState = NpgsqlClosedState.Instance;
// Get a raw connection, possibly SSL...
CurrentState.Open(this, connectTimeRemaining);
// Establish protocol communication and handle authentication...
CurrentState.Startup(this,this.settings);
}
// Change the state of connection to open and ready.
_connection_state = ConnectionState.Open;
CurrentState = NpgsqlReadyState.Instance;
// After attachment, the stream will close the connector (this) when the stream gets disposed.
_baseStream.AttachConnector(this);
// Fall back to the old way, SELECT VERSION().
// This should not happen for protocol version 3+.
if (ServerVersion == null)
{
//NpgsqlCommand command = new NpgsqlCommand("set DATESTYLE TO ISO;select version();", this);
//ServerVersion = new Version(PGUtil.ExtractServerVersion((string) command.ExecuteScalar()));
using (NpgsqlCommand command = new NpgsqlCommand("set DATESTYLE TO ISO;select version();", this))
{
ServerVersion = new Version(PGUtil.ExtractServerVersion((string)command.ExecuteScalar()));
}
}
ProcessServerVersion();
StringWriter sbInitQueries = new StringWriter();
if (BackendProtocolVersion == ProtocolVersion.Version2)
{
sbInitQueries.WriteLine("SET DATESTYLE TO ISO;");
// Adjust client encoding.
NpgsqlParameterStatus clientEncodingParam = null;
if (
!ServerParameters.TryGetValue("client_encoding", out clientEncodingParam) ||
(!string.Equals(clientEncodingParam.ParameterValue, "UTF8", StringComparison.OrdinalIgnoreCase) && !string.Equals(clientEncodingParam.ParameterValue, "UNICODE", StringComparison.OrdinalIgnoreCase))
)
{
sbInitQueries.WriteLine("SET CLIENT_ENCODING TO UTF8;");
}
if (!string.IsNullOrEmpty(settings.SearchPath))
{
// TODO: Add proper message when finding a semicolon in search_path.
// This semicolon could lead to a sql injection security hole as someone could write in connection string:
// searchpath=public;delete from table; and it would be executed.
if (settings.SearchPath.Contains(";"))
{
throw new InvalidOperationException();
}
// This is using string concatenation because set search_path doesn't allow type casting. ::text
sbInitQueries.WriteLine("SET SEARCH_PATH = {0};", settings.SearchPath);
}
if (!string.IsNullOrEmpty(settings.ApplicationName))
{
if (!SupportsApplicationName)
{
//TODO
//throw new InvalidOperationException(resman.GetString("Exception_ApplicationNameNotSupported"));
throw new InvalidOperationException("ApplicationName not supported.");
}
if (settings.ApplicationName.Contains(";"))
{
throw new InvalidOperationException();
}
sbInitQueries.WriteLine("SET APPLICATION_NAME='{0}';", settings.ApplicationName);
}
/*
* Try to set SSL negotiation to 0. As of 2010-03-29, recent problems in SSL library implementations made
* postgresql to add a parameter to set a value when to do this renegotiation or 0 to disable it.
* Currently, Npgsql has a problem with renegotiation so, we are trying to disable it here.
* This only works on postgresql servers where the ssl renegotiation settings is supported of course.
* See http://lists.pgfoundry.org/pipermail/npgsql-devel/2010-February/001065.html for more information.
*/
if (SupportsSslRenegotiationLimit)
{
sbInitQueries.WriteLine("SET ssl_renegotiation_limit=0;");
}
/*
* Set precision digits to maximum value possible. For postgresql before 9 it was 2, after that, it is 3.
* Check bug report #1010992 for more information.
*/
if (SupportsExtraFloatDigits3)
{
sbInitQueries.WriteLine("SET extra_float_digits=3;");
}
else if (SupportsExtraFloatDigits)
{
sbInitQueries.WriteLine("SET extra_float_digits=2;");
}
/*
* Set lc_monetary format to 'C' in order to get a culture agnostic representation of money.
* I noticed that on Windows, even when the lc_monetary is English_United States.UTF-8, negative
* money is formatted as ($value) with parentheses to indicate negative value.
* By going with a culture agnostic format, we get a consistent behavior.
*/
sbInitQueries.WriteLine("SET lc_monetary='C';");
}
else
{
// Some connection parameters for protocol 3 had been sent in the startup packet.
// The rest will be setted here.
if (SupportsExtraFloatDigits3)
{
sbInitQueries.WriteLine("SET extra_float_digits=3;");
}
if (SupportsSslRenegotiationLimit)
{
sbInitQueries.WriteLine("SET ssl_renegotiation_limit=0;");
}
}
initQueries = sbInitQueries.ToString();
NpgsqlCommand.ExecuteBlind(this, initQueries, 60);
// Make a shallow copy of the type mapping that the connector will own.
// It is possible that the connector may add types to its private
// mapping that will not be valid to another connector, even
// if connected to the same backend version.
NativeToBackendTypeConverterOptions.OidToNameMapping = NpgsqlTypesHelper.CreateAndLoadInitialTypesMapping(this).Clone();
// The connector is now fully initialized. Beyond this point, it is
// safe to release it back to the pool rather than closing it.
IsInitialized = true;
}
///
/// Closes the physical connection to the server.
///
internal void Close()
{
try
{
if (_connection_state != ConnectionState.Closed)
{
_connection_state = ConnectionState.Closed;
this.CurrentState.Close(this);
_serverParameters.Clear();
ServerVersion = null;
}
}
catch
{
}
}
internal void CancelRequest()
{
NpgsqlConnector cancelConnector = new NpgsqlConnector(settings, false, false);
cancelConnector._backend_keydata = BackEndKeyData;
try
{
// Get a raw connection, possibly SSL...
cancelConnector.CurrentState.Open(cancelConnector, cancelConnector.ConnectionTimeout * 1000);
// Cancel current request.
cancelConnector.CurrentState.CancelRequest(cancelConnector);
}
finally
{
cancelConnector.CurrentState.Close(cancelConnector);
}
}
///
/// Returns next portal index.
///
internal String NextPortalName()
{
return _portalNamePrefix + (++_portalIndex).ToString();
}
///
/// Returns next plan index.
///
internal String NextPlanName()
{
return _planNamePrefix + (++_planIndex).ToString();
}
internal void RemoveNotificationThread()
{
// Wait notification thread finish its work.
lock (_socket)
{
// Kill notification thread.
_notificationThread.Abort();
_notificationThread = null;
// Special case in order to not get problems with thread synchronization.
// It will be turned to 0 when synch thread is created.
_notificationThreadStopCount = 1;
}
}
internal void AddNotificationThread()
{
_notificationThreadStopCount = 0;
NpgsqlContextHolder contextHolder = new NpgsqlContextHolder(this, CurrentState);
_notificationThread = new Thread(new ThreadStart(contextHolder.ProcessServerMessages));
_notificationThread.Start();
}
//Use with using(){} to perform the sentry pattern
//on stopping and starting notification thread
//(The sentry pattern is a generalisation of RAII where we
//have a pair of actions - one "undoing" the previous
//and we want to execute the first and second around other code,
//then we treat it much like resource mangement in RAII.
//try{}finally{} also does execute-around, but sentry classes
//have some extra flexibility (e.g. they can be "owned" by
//another object and then cleaned up when that object is
//cleaned up), and can act as the sole gate-way
//to the code in question, guaranteeing that using code can't be written
//so that the "undoing" is forgotten.
internal class NotificationThreadBlock : IDisposable
{
private NpgsqlConnector _connector;
public NotificationThreadBlock(NpgsqlConnector connector)
{
(_connector = connector).StopNotificationThread();
}
public void Dispose()
{
if (_connector != null)
{
_connector.ResumeNotificationThread();
}
_connector = null;
}
}
internal NotificationThreadBlock BlockNotificationThread()
{
return new NotificationThreadBlock(this);
}
private void StopNotificationThread()
{
// first check to see if an exception has
// been thrown by the notification thread.
if (_notificationException != null)
{
throw _notificationException;
}
_notificationThreadStopCount++;
if (_notificationThreadStopCount == 1) // If this call was the first to increment.
{
Monitor.Enter(_socket);
}
}
private void ResumeNotificationThread()
{
_notificationThreadStopCount--;
if (_notificationThreadStopCount == 0)
{
// Release the synchronization handle.
Monitor.Exit(_socket);
}
}
internal Boolean IsNotificationThreadRunning
{
get { return _notificationThreadStopCount <= 0; }
}
internal class NpgsqlContextHolder
{
private readonly NpgsqlConnector connector;
private readonly NpgsqlState state;
internal NpgsqlContextHolder(NpgsqlConnector connector, NpgsqlState state)
{
this.connector = connector;
this.state = state;
}
internal void ProcessServerMessages()
{
try
{
while (true)
{
// Mono's implementation of System.Threading.Monitor does not appear to give threads
// priority on a first come/first serve basis, as does Microsoft's. As a result,
// under mono, this loop may execute many times even after another thread has attempted
// to lock on _socket. A short Sleep() seems to solve the problem effectively.
// Note that Sleep(0) does not work.
Thread.Sleep(1);
lock (connector._socket)
{
// 20 millisecond timeout
if (this.connector.Socket.Poll(20000, SelectMode.SelectRead))
{
this.connector.ProcessAndDiscardBackendResponses();
}
}
}
}
catch (IOException ex)
{
this.connector._notificationException = ex;
}
}
}
public bool RequireReadyForQuery
{
get { return _requireReadyForQuery; }
set { _requireReadyForQuery = value; }
}
public void AddParameterStatus(NpgsqlParameterStatus ps)
{
if (_serverParameters.ContainsKey(ps.Parameter))
{
_serverParameters[ps.Parameter] = ps;
}
else
{
_serverParameters.Add(ps.Parameter, ps);
}
if (ps.Parameter == "standard_conforming_strings")
{
NativeToBackendTypeConverterOptions.UseConformantStrings = (ps.ParameterValue == "on");
}
}
public IDictionary ServerParameters
{
get { return new NpgsqlReadOnlyDictionary(_serverParameters); }
}
}
}