//
        // Summary:
        //     Enlists in the specified transaction. TODO
        //
        // Parameters:
        //   transaction:
        //     A reference to an existing System.Transactions.Transaction in which to enlist.
        //public override void EnlistTransaction(PqsqlTransaction transaction)
        //{
        //	throw new NotImplementedException();
        //}

        //
        // Summary:
        //     Opens a database connection with the settings specified by the System.Data.Common.DbConnection.ConnectionString.
        public override void Open()
        {
            if (mConnection != IntPtr.Zero && !mNewConnectionString)
            {
                // close and open with current connection setting
                PqsqlWrapper.PQreset(mConnection);

                // update connection and transaction status
                if (Status == ConnStatusType.CONNECTION_BAD || TransactionStatus != PGTransactionStatusType.PQTRANS_IDLE)
                {
                    string err = GetErrorMessage();
                    PqsqlWrapper.PQfinish(mConnection);                     // force release of mConnection memory
                    Init();
                    var connStr = mConnectionStringBuilder.GetConnectionStringWithObfuscatedPassword();
                    throw new PqsqlException("Could not reset connection with connection string «" + connStr + "»: " + err, (int)PqsqlState.CONNECTION_FAILURE);
                }

                OnStateChange(new StateChangeEventArgs(ConnectionState.Closed, ConnectionState.Open));

                // always set application_name, after a DISCARD ALL (usually issued by PqsqlConnectionPool / pgbouncer)
                // the session information is gone forever, and the shared connection will drop application_name
                SetApplicationName();

                // successfully reestablished connection
                return;
            }

            if (mStatus != ConnStatusType.CONNECTION_BAD)
            {
                Close();                 // force release of mConnection memory
            }

            // check connection pool for a connection
            mConnection = PqsqlConnectionPool.GetPGConn(mConnectionStringBuilder, out mStatus, out mTransStatus);

            if (mConnection == IntPtr.Zero)
            {
                throw new PqsqlException("libpq: unable to allocate struct PGconn");
            }

            // check connection and transaction status
            if (mStatus == ConnStatusType.CONNECTION_BAD || mTransStatus != PGTransactionStatusType.PQTRANS_IDLE)
            {
                string err = GetErrorMessage();
                PqsqlWrapper.PQfinish(mConnection);                 // force release of mConnection memory
                Init();
                var connStr = mConnectionStringBuilder.GetConnectionStringWithObfuscatedPassword();
                throw new PqsqlException("Could not create connection with connection string «" + connStr + "»: " + err);
            }

            mNewConnectionString = false;

            OnStateChange(new StateChangeEventArgs(ConnectionState.Closed, ConnectionState.Open));

            // always set application_name, after a DISCARD ALL (usually issued by PqsqlConnectionPool / pgbouncer)
            // the session information is gone forever, and the shared connection will drop application_name
            SetApplicationName();
        }
        public static void ReleasePGConn(PqsqlConnectionStringBuilder connStringBuilder, IntPtr pgConnHandle)
        {
#if CODECONTRACTS
            Contract.Requires <ArgumentNullException>(connStringBuilder != null);
#else
            if (connStringBuilder == null)
            {
                throw new ArgumentNullException(nameof(connStringBuilder));
            }
#endif

            if (pgConnHandle == IntPtr.Zero)
            {
                return;
            }

            Queue <ConnectionInfo> queue;

            lock (mPooledConnsLock)
            {
                mPooledConns.TryGetValue(connStringBuilder, out queue);
            }

            bool closeConnection = true;

            if (queue == null || !DiscardConnection(pgConnHandle))
            {
                goto close;                 // just cleanup connection and restart timer
            }

            lock (queue)
            {
                if (queue.Count < MaxQueue)
                {
                    queue.Enqueue(new ConnectionInfo {
                        pgconn = pgConnHandle, visited = 0
                    });
                    closeConnection = false;                     // keep connection
                }
            }

close:
            if (closeConnection)
            {
                PqsqlWrapper.PQfinish(pgConnHandle);                 // close connection and release memory
            }
        }
        private static void PoolService(object o)
        {
#if CODECONTRACTS
            Contract.Requires <ArgumentNullException>(o != null);
#else
            if (o == null)
            {
                throw new ArgumentNullException(nameof(o));
            }
#endif

            List <IntPtr> closeConnections = o as List <IntPtr>;

            // we assume that we run PoolService in less than IdleTimeout msecs
            lock (mPooledConnsLock)
            {
#if PQSQL_DEBUG
                mLogger.Debug("Running PoolService");
#endif

                using (Dictionary <PqsqlConnectionStringBuilder, Queue <ConnectionInfo> > .Enumerator e = mPooledConns.GetEnumerator())
                {
                    while (e.MoveNext())
                    {
                        KeyValuePair <PqsqlConnectionStringBuilder, Queue <ConnectionInfo> > item = e.Current;
#if PQSQL_DEBUG
                        PqsqlConnectionStringBuilder csb = item.Key;
#endif
                        Queue <ConnectionInfo> queue = item.Value;

                        lock (queue)
                        {
                            int count = queue.Count;

#if PQSQL_DEBUG
                            mLogger.DebugFormat("ConnectionPool {0}: {1} waiting connections", csb.ConnectionString, count);
#endif

                            if (count == 0)
                            {
                                continue;
                            }

                            int maxRelease = count / 2 + 1;

                            ConnectionInfo i = queue.Peek();
#if CODECONTRACTS
                            Contract.Assume(i != null);
#endif
                            i.visited++;

#if PQSQL_DEBUG
                            if (i.visited <= VisitedThreshold)
                            {
                                mLogger.DebugFormat("ConnectionPool {0}: {1} visits", csb.ConnectionString, i.visited);
                            }
#endif

                            if (i.visited > VisitedThreshold)
                            {
#if PQSQL_DEBUG
                                mLogger.DebugFormat("ConnectionPool {0}: visit threshold {1} reached, releasing {2} connections", csb.ConnectionString, i.visited, maxRelease);
#endif
                                while (maxRelease > 0)
                                {
                                    // clean maxRelease connections
                                    i = queue.Dequeue();
#if CODECONTRACTS
                                    Contract.Assume(i != null);
#endif
                                    closeConnections.Add(i.pgconn);                                     // close connections outside of queue lock
                                    maxRelease--;
                                }
                            }
                        }
                    }
                }
            }

            // now close old connections
            foreach (IntPtr conn in closeConnections)
            {
                PqsqlWrapper.PQfinish(conn);                 // close connection and release memory
            }

            closeConnections.Clear();
        }
        private static bool CheckOrRelease(IntPtr pgConn, out ConnStatusType connStatus, out PGTransactionStatusType tranStatus)
        {
            if (pgConn == IntPtr.Zero)
            {
                connStatus = ConnStatusType.CONNECTION_BAD;
                tranStatus = PGTransactionStatusType.PQTRANS_UNKNOWN;
                return(false);
            }

            // is connection reusable?
            connStatus = PqsqlWrapper.PQstatus(pgConn);
            if (connStatus != ConnStatusType.CONNECTION_OK)
            {
                goto broken;
            }

            tranStatus = PqsqlWrapper.PQtransactionStatus(pgConn);
            if (tranStatus != PGTransactionStatusType.PQTRANS_IDLE)
            {
                goto broken;
            }

            //
            // now check the connection: first try to fix the client encoding if it is not utf8 for some reason.
            // if client_encoding is utf8, we send the empty query to check whether the socket is still usable
            // for the backend communiction. here, we could make use of tcp_keepalive settings.
            //
            int client_encoding = PqsqlWrapper.PQclientEncoding(pgConn);

            if (client_encoding == -1)
            {
                goto broken;
            }

            if (client_encoding == (int)PgEnc.PG_UTF8)              // client_encoding == utf8
            {
                // send empty query to test whether we are really connected (tcp_keepalive might have closed socket)
                unsafe
                {
                    byte[] empty = { 0 };                   // empty query string

                    fixed(byte *eq = empty)
                    {
                        if (PqsqlWrapper.PQsendQuery(pgConn, eq) == 0)                         // could not send query
                        {
                            goto broken;
                        }
                    }
                }

                // Reading result: consume and clear remaining results until we reach the NULL result.
                // PQgetResult will block here
                IntPtr         res;
                ExecStatusType st = ExecStatusType.PGRES_EMPTY_QUERY;
                while ((res = PqsqlWrapper.PQgetResult(pgConn)) != IntPtr.Zero)
                {
                    ExecStatusType st0 = PqsqlWrapper.PQresultStatus(res);

                    if (st0 != ExecStatusType.PGRES_EMPTY_QUERY)
                    {
                        st = st0;
                    }

                    // always free res
                    PqsqlWrapper.PQclear(res);
                }

                if (st != ExecStatusType.PGRES_EMPTY_QUERY)                 // received wrong exec status
                {
                    goto broken;
                }
            }
            else             // set client_encoding to utf8
            {
                // set client_encoding to test whether we are really connected (tcp_keepalive might have closed socket)
                if (PqsqlWrapper.PQsetClientEncoding(pgConn, PgEncName.PG_UTF8) != 0)
                {
                    goto broken;
                }
            }

            return(true);            // successfully reused connection

broken:
            // reconnect with current connection setting
            PqsqlWrapper.PQreset(pgConn);

            connStatus = PqsqlWrapper.PQstatus(pgConn);
            if (connStatus == ConnStatusType.CONNECTION_OK)
            {
                tranStatus = PqsqlWrapper.PQtransactionStatus(pgConn);
                if (tranStatus == PGTransactionStatusType.PQTRANS_IDLE)
                {
                    return(true);                    // successfully reconnected
                }
            }
            else
            {
                tranStatus = PGTransactionStatusType.PQTRANS_UNKNOWN;
            }

            // could not reconnect: finally give up and clean up memory
            PqsqlWrapper.PQfinish(pgConn);
            return(false);
        }