Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
psycopg2 / psycopg / connection_type.c
Size: Mime:
/* connection_type.c - python interface to connection objects
 *
 * Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
 *
 * This file is part of psycopg.
 *
 * psycopg2 is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * In addition, as a special exception, the copyright holders give
 * permission to link this program with the OpenSSL library (or with
 * modified versions of OpenSSL that use the same license as OpenSSL),
 * and distribute linked combinations including the two.
 *
 * You must obey the GNU Lesser General Public License in all respects for
 * all of the code used other than OpenSSL.
 *
 * psycopg2 is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
 * License for more details.
 */

#define PSYCOPG_MODULE
#include "psycopg/psycopg.h"

#include "psycopg/connection.h"
#include "psycopg/cursor.h"
#include "psycopg/pqpath.h"
#include "psycopg/conninfo.h"
#include "psycopg/lobject.h"
#include "psycopg/green.h"
#include "psycopg/xid.h"

#include <string.h>
#include <ctype.h>

extern HIDDEN const char *srv_isolevels[];
extern HIDDEN const char *srv_readonly[];
extern HIDDEN const char *srv_deferrable[];
extern HIDDEN const int SRV_STATE_UNCHANGED;

/** DBAPI methods **/

/* cursor method - allocate a new cursor */

#define psyco_conn_cursor_doc \
"cursor(name=None, cursor_factory=extensions.cursor, withhold=False) -- new cursor\n\n"     \
"Return a new cursor.\n\nThe ``cursor_factory`` argument can be used to\n"  \
"create non-standard cursors by passing a class different from the\n"       \
"default. Note that the new class *should* be a sub-class of\n"             \
"`extensions.cursor`.\n\n"                                                  \
":rtype: `extensions.cursor`"

static PyObject *
psyco_conn_cursor(connectionObject *self, PyObject *args, PyObject *kwargs)
{
    PyObject *obj = NULL;
    PyObject *rv = NULL;
    PyObject *name = Py_None;
    PyObject *factory = Py_None;
    PyObject *withhold = Py_False;
    PyObject *scrollable = Py_None;

    static char *kwlist[] = {
        "name", "cursor_factory", "withhold", "scrollable", NULL};

    EXC_IF_CONN_CLOSED(self);

    if (!PyArg_ParseTupleAndKeywords(
            args, kwargs, "|OOOO", kwlist,
            &name, &factory, &withhold, &scrollable)) {
        goto exit;
    }

    if (factory == Py_None) {
        if (self->cursor_factory && self->cursor_factory != Py_None) {
            factory = self->cursor_factory;
        }
        else {
            factory = (PyObject *)&cursorType;
        }
    }

    if (self->status != CONN_STATUS_READY &&
        self->status != CONN_STATUS_BEGIN &&
        self->status != CONN_STATUS_PREPARED) {
        PyErr_SetString(OperationalError,
                        "asynchronous connection attempt underway");
        goto exit;
    }

    if (name != Py_None && self->async == 1) {
        PyErr_SetString(ProgrammingError,
                        "asynchronous connections "
                        "cannot produce named cursors");
        goto exit;
    }

    Dprintf("psyco_conn_cursor: new %s cursor for connection at %p",
        (name == Py_None ? "unnamed" : "named"), self);

    if (!(obj = PyObject_CallFunctionObjArgs(factory, self, name, NULL))) {
        goto exit;
    }

    if (PyObject_IsInstance(obj, (PyObject *)&cursorType) == 0) {
        PyErr_SetString(PyExc_TypeError,
            "cursor factory must be subclass of psycopg2.extensions.cursor");
        goto exit;
    }

    if (0 > curs_withhold_set((cursorObject *)obj, withhold)) {
        goto exit;
    }
    if (0 > curs_scrollable_set((cursorObject *)obj, scrollable)) {
        goto exit;
    }

    Dprintf("psyco_conn_cursor: new cursor at %p: refcnt = "
        FORMAT_CODE_PY_SSIZE_T,
        obj, Py_REFCNT(obj)
    );

    rv = obj;
    obj = NULL;

exit:
    Py_XDECREF(obj);
    return rv;
}


/* close method - close the connection and all related cursors */

#define psyco_conn_close_doc "close() -- Close the connection."

static PyObject *
psyco_conn_close(connectionObject *self, PyObject *dummy)
{
    Dprintf("psyco_conn_close: closing connection at %p", self);
    conn_close(self);
    Dprintf("psyco_conn_close: connection at %p closed", self);

    Py_RETURN_NONE;
}


/* commit method - commit all changes to the database */

#define psyco_conn_commit_doc "commit() -- Commit all changes to database."

static PyObject *
psyco_conn_commit(connectionObject *self, PyObject *dummy)
{
    EXC_IF_CONN_CLOSED(self);
    EXC_IF_CONN_ASYNC(self, commit);
    EXC_IF_TPC_BEGIN(self, commit);

    if (conn_commit(self) < 0)
        return NULL;

    Py_RETURN_NONE;
}


/* rollback method - roll back all changes done to the database */

#define psyco_conn_rollback_doc \
"rollback() -- Roll back all changes done to database."

static PyObject *
psyco_conn_rollback(connectionObject *self, PyObject *dummy)
{
    EXC_IF_CONN_CLOSED(self);
    EXC_IF_CONN_ASYNC(self, rollback);
    EXC_IF_TPC_BEGIN(self, rollback);

    if (conn_rollback(self) < 0)
        return NULL;

    Py_RETURN_NONE;
}


#define psyco_conn_xid_doc \
"xid(format_id, gtrid, bqual) -- create a transaction identifier."

static PyObject *
psyco_conn_xid(connectionObject *self, PyObject *args, PyObject *kwargs)
{
    EXC_IF_CONN_CLOSED(self);
    EXC_IF_TPC_NOT_SUPPORTED(self);

    return PyObject_Call((PyObject *)&xidType, args, kwargs);
}


#define psyco_conn_tpc_begin_doc \
"tpc_begin(xid) -- begin a TPC transaction with given transaction ID xid."

static PyObject *
psyco_conn_tpc_begin(connectionObject *self, PyObject *args)
{
    PyObject *rv = NULL;
    xidObject *xid = NULL;
    PyObject *oxid;

    EXC_IF_CONN_CLOSED(self);
    EXC_IF_CONN_ASYNC(self, tpc_begin);
    EXC_IF_TPC_NOT_SUPPORTED(self);
    EXC_IF_IN_TRANSACTION(self, tpc_begin);

    if (!PyArg_ParseTuple(args, "O", &oxid)) {
        goto exit;
    }

    if (NULL == (xid = xid_ensure(oxid))) {
        goto exit;
    }

    /* two phase commit and autocommit make no point */
    if (self->autocommit) {
        PyErr_SetString(ProgrammingError,
            "tpc_begin can't be called in autocommit mode");
        goto exit;
    }

    if (conn_tpc_begin(self, xid) < 0) {
        goto exit;
    }

    Py_INCREF(Py_None);
    rv = Py_None;

exit:
    Py_XDECREF(xid);
    return rv;
}


#define psyco_conn_tpc_prepare_doc \
"tpc_prepare() -- perform the first phase of a two-phase transaction."

static PyObject *
psyco_conn_tpc_prepare(connectionObject *self, PyObject *dummy)
{
    EXC_IF_CONN_CLOSED(self);
    EXC_IF_CONN_ASYNC(self, tpc_prepare);
    EXC_IF_TPC_PREPARED(self, tpc_prepare);

    if (NULL == self->tpc_xid) {
        PyErr_SetString(ProgrammingError,
            "prepare must be called inside a two-phase transaction");
        return NULL;
    }

    if (0 > conn_tpc_command(self, "PREPARE TRANSACTION", self->tpc_xid)) {
        return NULL;
    }

    /* transaction prepared: set the state so that no operation
     * can be performed until commit. */
    self->status = CONN_STATUS_PREPARED;

    Py_RETURN_NONE;
}


/* the type of conn_commit/conn_rollback */
typedef int (*_finish_f)(connectionObject *self);

/* Implement tpc_commit/tpc_rollback.
 *
 * This is a common framework performing the chechs and state manipulation
 * common to the two functions.
 *
 * Parameters are:
 * - self, args: passed by Python
 * - opc_f: the function to call in case of one-phase commit/rollback
 *          one of conn_commit/conn_rollback
 * - tpc_cmd: the command to execute for a two-phase commit/rollback
 *
 * The function can be called in three cases:
 * - If xid is specified, the status must be "ready";
 *   issue the commit/rollback prepared.
 * - if xid is not specified and status is "begin" with a xid,
 *   issue a normal commit/rollback.
 * - if xid is not specified and status is "prepared",
 *   issue the commit/rollback prepared.
 */
static PyObject *
_psyco_conn_tpc_finish(connectionObject *self, PyObject *args,
    _finish_f opc_f, char *tpc_cmd)
{
    PyObject *oxid = NULL;
    xidObject *xid = NULL;
    PyObject *rv = NULL;

    if (!PyArg_ParseTuple(args, "|O", &oxid)) { goto exit; }

    if (oxid) {
        if (!(xid = xid_ensure(oxid))) { goto exit; }
    }

    if (xid) {
        /* committing/aborting a recovered transaction. */
        if (self->status != CONN_STATUS_READY) {
            PyErr_SetString(ProgrammingError,
                "tpc_commit/tpc_rollback with a xid "
                "must be called outside a transaction");
            goto exit;
        }
        if (0 > conn_tpc_command(self, tpc_cmd, xid)) {
            goto exit;
        }
    } else {
        /* committing/aborting our own transaction. */
        if (!self->tpc_xid) {
            PyErr_SetString(ProgrammingError,
                "tpc_commit/tpc_rollback with no parameter "
                "must be called in a two-phase transaction");
            goto exit;
        }

        switch (self->status) {
          case CONN_STATUS_BEGIN:
            if (0 > opc_f(self)) { goto exit; }
            break;

          case CONN_STATUS_PREPARED:
            if (0 > conn_tpc_command(self, tpc_cmd, self->tpc_xid)) {
                goto exit;
            }
            break;

          default:
            PyErr_SetString(InterfaceError,
                "unexpected state in tpc_commit/tpc_rollback");
            goto exit;
        }

        Py_CLEAR(self->tpc_xid);

        /* connection goes ready */
        self->status = CONN_STATUS_READY;
    }

    Py_INCREF(Py_None);
    rv = Py_None;

exit:
    Py_XDECREF(xid);
    return rv;
}

#define psyco_conn_tpc_commit_doc \
"tpc_commit([xid]) -- commit a transaction previously prepared."

static PyObject *
psyco_conn_tpc_commit(connectionObject *self, PyObject *args)
{
    EXC_IF_CONN_CLOSED(self);
    EXC_IF_CONN_ASYNC(self, tpc_commit);
    EXC_IF_TPC_NOT_SUPPORTED(self);

    return _psyco_conn_tpc_finish(self, args,
                                  conn_commit, "COMMIT PREPARED");
}

#define psyco_conn_tpc_rollback_doc \
"tpc_rollback([xid]) -- abort a transaction previously prepared."

static PyObject *
psyco_conn_tpc_rollback(connectionObject *self, PyObject *args)
{
    EXC_IF_CONN_CLOSED(self);
    EXC_IF_CONN_ASYNC(self, tpc_rollback);
    EXC_IF_TPC_NOT_SUPPORTED(self);

    return _psyco_conn_tpc_finish(self, args,
                                  conn_rollback, "ROLLBACK PREPARED");
}

#define psyco_conn_tpc_recover_doc \
"tpc_recover() -- returns a list of pending transaction IDs."

static PyObject *
psyco_conn_tpc_recover(connectionObject *self, PyObject *dummy)
{
    EXC_IF_CONN_CLOSED(self);
    EXC_IF_CONN_ASYNC(self, tpc_recover);
    EXC_IF_TPC_PREPARED(self, tpc_recover);
    EXC_IF_TPC_NOT_SUPPORTED(self);

    return conn_tpc_recover(self);
}


#define psyco_conn_enter_doc \
"__enter__ -> self"

static PyObject *
psyco_conn_enter(connectionObject *self, PyObject *dummy)
{
    EXC_IF_CONN_CLOSED(self);

    Py_INCREF(self);
    return (PyObject *)self;
}


#define psyco_conn_exit_doc \
"__exit__ -- commit if no exception, else roll back"

static PyObject *
psyco_conn_exit(connectionObject *self, PyObject *args)
{
    PyObject *type, *name, *tb;
    PyObject *tmp = NULL;
    PyObject *rv = NULL;

    if (!PyArg_ParseTuple(args, "OOO", &type, &name, &tb)) {
        goto exit;
    }

    if (type == Py_None) {
        if (!(tmp = PyObject_CallMethod((PyObject *)self, "commit", NULL))) {
            goto exit;
        }
    } else {
        if (!(tmp = PyObject_CallMethod((PyObject *)self, "rollback", NULL))) {
            goto exit;
        }
    }

    /* success (of the commit or rollback, there may have been an exception in
     * the block). Return None to avoid swallowing the exception */
    rv = Py_None;
    Py_INCREF(rv);

exit:
    Py_XDECREF(tmp);
    return rv;
}


/* parse a python object into one of the possible isolation level values */

RAISES_NEG static int
_psyco_conn_parse_isolevel(PyObject *pyval)
{
    int rv = -1;
    long level;

    Py_INCREF(pyval);   /* for ensure_bytes */

    /* None is default. This is only used when setting the property, because
     * set_session() has None used as "don't change" */
    if (pyval == Py_None) {
        rv = ISOLATION_LEVEL_DEFAULT;
    }

    /* parse from one of the level constants */
    else if (PyInt_Check(pyval)) {
        level = PyInt_AsLong(pyval);
        if (level == -1 && PyErr_Occurred()) { goto exit; }
        if (level < 1 || level > 4) {
            PyErr_SetString(PyExc_ValueError,
                "isolation_level must be between 1 and 4");
            goto exit;
        }

        rv = level;
    }

    /* parse from the string -- this includes "default" */
    else {
        if (!(pyval = psyco_ensure_bytes(pyval))) {
            goto exit;
        }
        for (level = 1; level <= 4; level++) {
            if (0 == strcasecmp(srv_isolevels[level], Bytes_AS_STRING(pyval))) {
                rv = level;
                break;
            }
        }
        if (rv < 0 && 0 == strcasecmp("default", Bytes_AS_STRING(pyval))) {
            rv = ISOLATION_LEVEL_DEFAULT;
        }
        if (rv < 0) {
            PyErr_Format(PyExc_ValueError,
                "bad value for isolation_level: '%s'", Bytes_AS_STRING(pyval));
            goto exit;
        }
    }

exit:
    Py_XDECREF(pyval);

    return rv;
}

/* convert False/True/"default" -> 0/1/2 */

RAISES_NEG static int
_psyco_conn_parse_onoff(PyObject *pyval)
{
    int rv = -1;

    Py_INCREF(pyval);   /* for ensure_bytes */

    if (pyval == Py_None) {
        rv = STATE_DEFAULT;
    }
    else if (PyUnicode_CheckExact(pyval) || Bytes_CheckExact(pyval)) {
        if (!(pyval = psyco_ensure_bytes(pyval))) {
            goto exit;
        }
        if (0 == strcasecmp("default", Bytes_AS_STRING(pyval))) {
            rv = STATE_DEFAULT;
        }
        else {
            PyErr_Format(PyExc_ValueError,
                "the only string accepted is 'default'; got %s",
                Bytes_AS_STRING(pyval));
            goto exit;
        }
    }
    else {
        int istrue;
        if (0 > (istrue = PyObject_IsTrue(pyval))) { goto exit; }
        rv = istrue ? STATE_ON : STATE_OFF;
    }

exit:
    Py_XDECREF(pyval);

    return rv;
}

#define _set_session_checks(self,what) \
do { \
    EXC_IF_CONN_CLOSED(self); \
    EXC_IF_CONN_ASYNC(self, what); \
    EXC_IF_IN_TRANSACTION(self, what); \
    EXC_IF_TPC_PREPARED(self, what); \
} while(0)

/* set_session - set default transaction characteristics */

#define psyco_conn_set_session_doc \
"set_session(...) -- Set one or more parameters for the next transactions.\n\n" \
"Accepted arguments are 'isolation_level', 'readonly', 'deferrable', 'autocommit'."

static PyObject *
psyco_conn_set_session(connectionObject *self, PyObject *args, PyObject *kwargs)
{
    PyObject *isolevel = Py_None;
    PyObject *readonly = Py_None;
    PyObject *deferrable = Py_None;
    PyObject *autocommit = Py_None;

    int c_isolevel = SRV_STATE_UNCHANGED;
    int c_readonly = SRV_STATE_UNCHANGED;
    int c_deferrable = SRV_STATE_UNCHANGED;
    int c_autocommit = SRV_STATE_UNCHANGED;

    static char *kwlist[] =
        {"isolation_level", "readonly", "deferrable", "autocommit", NULL};

    _set_session_checks(self, set_session);

    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|OOOO", kwlist,
            &isolevel, &readonly, &deferrable, &autocommit)) {
        return NULL;
    }

    if (Py_None != isolevel) {
        if (0 > (c_isolevel = _psyco_conn_parse_isolevel(isolevel))) {
            return NULL;
        }
    }

    if (Py_None != readonly) {
        if (0 > (c_readonly = _psyco_conn_parse_onoff(readonly))) {
            return NULL;
        }
    }
    if (Py_None != deferrable) {
        if (0 > (c_deferrable = _psyco_conn_parse_onoff(deferrable))) {
            return NULL;
        }
    }

    if (Py_None != autocommit) {
        if (-1 == (c_autocommit = PyObject_IsTrue(autocommit))) { return NULL; }
    }

    if (0 > conn_set_session(
                self, c_autocommit, c_isolevel, c_readonly, c_deferrable)) {
        return NULL;
    }

    Py_RETURN_NONE;
}


/* autocommit - return or set the current autocommit status */

#define psyco_conn_autocommit_doc \
"Set or return the autocommit status."

static PyObject *
psyco_conn_autocommit_get(connectionObject *self)
{
    return PyBool_FromLong(self->autocommit);
}

BORROWED static PyObject *
_psyco_set_session_check_setter_wrapper(connectionObject *self)
{
    /* wrapper to use the EXC_IF macros.
     * return NULL in case of error, else whatever */
    _set_session_checks(self, set_session);
    return Py_None;     /* borrowed */
}

static int
psyco_conn_autocommit_set(connectionObject *self, PyObject *pyvalue)
{
    int value;

    if (!_psyco_set_session_check_setter_wrapper(self)) { return -1; }
    if (-1 == (value = PyObject_IsTrue(pyvalue))) { return -1; }
    if (0 > conn_set_session(self, value,
                SRV_STATE_UNCHANGED, SRV_STATE_UNCHANGED, SRV_STATE_UNCHANGED)) {
        return -1;
    }

    return 0;
}


/* isolation_level - return or set the current isolation level */

#define psyco_conn_isolation_level_doc \
"Set or return the connection transaction isolation level."

static PyObject *
psyco_conn_isolation_level_get(connectionObject *self)
{
    if (self->isolevel == ISOLATION_LEVEL_DEFAULT) {
        Py_RETURN_NONE;
    } else {
        return PyInt_FromLong((long)self->isolevel);
    }
}


static int
psyco_conn_isolation_level_set(connectionObject *self, PyObject *pyvalue)
{
    int value;

    if (!_psyco_set_session_check_setter_wrapper(self)) { return -1; }
    if (0 > (value = _psyco_conn_parse_isolevel(pyvalue))) { return -1; }
    if (0 > conn_set_session(self, SRV_STATE_UNCHANGED,
                value, SRV_STATE_UNCHANGED, SRV_STATE_UNCHANGED)) {
        return -1;
    }

    return 0;
}


/* set_isolation_level method - switch connection isolation level */

#define psyco_conn_set_isolation_level_doc \
"set_isolation_level(level) -- Switch isolation level to ``level``."

static PyObject *
psyco_conn_set_isolation_level(connectionObject *self, PyObject *args)
{
    int level = 1;
    PyObject *pyval = NULL;

    EXC_IF_CONN_CLOSED(self);
    EXC_IF_CONN_ASYNC(self, "isolation_level");
    EXC_IF_TPC_PREPARED(self, "isolation_level");

    if (!PyArg_ParseTuple(args, "O", &pyval)) return NULL;

    if (pyval == Py_None) {
        level = ISOLATION_LEVEL_DEFAULT;
    }

    /* parse from one of the level constants */
    else if (PyInt_Check(pyval)) {
        level = PyInt_AsLong(pyval);

        if (level < 0 || level > 4) {
            PyErr_SetString(PyExc_ValueError,
                "isolation level must be between 0 and 4");
            return NULL;
        }
    }

    if (0 > conn_rollback(self)) {
        return NULL;
    }

    if (level == 0) {
        if (0 > conn_set_session(self, 1,
                SRV_STATE_UNCHANGED, SRV_STATE_UNCHANGED, SRV_STATE_UNCHANGED)) {
            return NULL;
        }
    }
    else {
        if (0 > conn_set_session(self, 0,
                level, SRV_STATE_UNCHANGED, SRV_STATE_UNCHANGED)) {
            return NULL;
        }
    }

    Py_RETURN_NONE;
}


/* readonly - return or set the current read-only status */

#define psyco_conn_readonly_doc \
"Set or return the connection read-only status."

static PyObject *
psyco_conn_readonly_get(connectionObject *self)
{
    PyObject *rv = NULL;

    switch (self->readonly) {
        case STATE_OFF:
            rv = Py_False;
            break;
        case STATE_ON:
            rv = Py_True;
            break;
        case STATE_DEFAULT:
            rv = Py_None;
            break;
        default:
            PyErr_Format(InternalError,
                "bad internal value for readonly: %d", self->readonly);
            break;
    }

    Py_XINCREF(rv);
    return rv;
}


static int
psyco_conn_readonly_set(connectionObject *self, PyObject *pyvalue)
{
    int value;

    if (!_psyco_set_session_check_setter_wrapper(self)) { return -1; }
    if (0 > (value = _psyco_conn_parse_onoff(pyvalue))) { return -1; }
    if (0 > conn_set_session(self, SRV_STATE_UNCHANGED,
                SRV_STATE_UNCHANGED, value, SRV_STATE_UNCHANGED)) {
        return -1;
    }

    return 0;
}


/* deferrable - return or set the current deferrable status */

#define psyco_conn_deferrable_doc \
"Set or return the connection deferrable status."

static PyObject *
psyco_conn_deferrable_get(connectionObject *self)
{
    PyObject *rv = NULL;

    switch (self->deferrable) {
        case STATE_OFF:
            rv = Py_False;
            break;
        case STATE_ON:
            rv = Py_True;
            break;
        case STATE_DEFAULT:
            rv = Py_None;
            break;
        default:
            PyErr_Format(InternalError,
                "bad internal value for deferrable: %d", self->deferrable);
            break;
    }

    Py_XINCREF(rv);
    return rv;
}


static int
psyco_conn_deferrable_set(connectionObject *self, PyObject *pyvalue)
{
    int value;

    if (!_psyco_set_session_check_setter_wrapper(self)) { return -1; }
    if (0 > (value = _psyco_conn_parse_onoff(pyvalue))) { return -1; }
    if (0 > conn_set_session(self, SRV_STATE_UNCHANGED,
                SRV_STATE_UNCHANGED, SRV_STATE_UNCHANGED, value)) {
        return -1;
    }

    return 0;
}

/* psyco_get_native_connection - expose PGconn* as a Python capsule */

#define psyco_get_native_connection_doc \
"get_native_connection() -- Return the internal PGconn* as a Python Capsule."

static PyObject *
psyco_get_native_connection(connectionObject *self, PyObject *dummy)
{
    EXC_IF_CONN_CLOSED(self);

    return PyCapsule_New(self->pgconn, "psycopg2.connection.native_connection", NULL);
}


/* set_client_encoding method - set client encoding */

#define psyco_conn_set_client_encoding_doc \
"set_client_encoding(encoding) -- Set client encoding to ``encoding``."

static PyObject *
psyco_conn_set_client_encoding(connectionObject *self, PyObject *args)
{
    const char *enc;
    PyObject *rv = NULL;

    EXC_IF_CONN_CLOSED(self);
    EXC_IF_CONN_ASYNC(self, set_client_encoding);
    EXC_IF_TPC_PREPARED(self, set_client_encoding);

    if (!PyArg_ParseTuple(args, "s", &enc)) return NULL;

    if (conn_set_client_encoding(self, enc) >= 0) {
        Py_INCREF(Py_None);
        rv = Py_None;
    }
    return rv;
}

/* get_transaction_status method - Get backend transaction status */

#define psyco_conn_get_transaction_status_doc \
"get_transaction_status() -- Get backend transaction status."

static PyObject *
psyco_conn_get_transaction_status(connectionObject *self, PyObject *dummy)
{
    return PyInt_FromLong((long)PQtransactionStatus(self->pgconn));
}

/* get_parameter_status method - Get server parameter status */

#define psyco_conn_get_parameter_status_doc \
"get_parameter_status(parameter) -- Get backend parameter status.\n\n" \
"Potential values for ``parameter``:\n" \
"  server_version, server_encoding, client_encoding, is_superuser,\n" \
"  session_authorization, DateStyle, TimeZone, integer_datetimes,\n" \
"  and standard_conforming_strings\n" \
"If server did not report requested parameter, None is returned.\n\n" \
"See libpq docs for PQparameterStatus() for further details."

static PyObject *
psyco_conn_get_parameter_status(connectionObject *self, PyObject *args)
{
    const char *param = NULL;
    const char *val = NULL;

    EXC_IF_CONN_CLOSED(self);

    if (!PyArg_ParseTuple(args, "s", &param)) return NULL;

    val = PQparameterStatus(self->pgconn, param);
    if (!val) {
        Py_RETURN_NONE;
    }
    return conn_text_from_chars(self, val);
}

/* get_dsn_parameters method - Get connection parameters */

#define psyco_conn_get_dsn_parameters_doc \
"get_dsn_parameters() -- Get effective connection parameters.\n\n"

static PyObject *
psyco_conn_get_dsn_parameters(connectionObject *self, PyObject *dummy)
{
#if PG_VERSION_NUM >= 90300
    PyObject *res = NULL;
    PQconninfoOption *options = NULL;

    EXC_IF_CONN_CLOSED(self);

    if (!(options = PQconninfo(self->pgconn))) {
        PyErr_NoMemory();
        goto exit;
    }

    res = psyco_dict_from_conninfo_options(options, /* include_password = */ 0);

exit:
    PQconninfoFree(options);

    return res;
#else
    PyErr_SetString(NotSupportedError, "PQconninfo not available in libpq < 9.3");
    return NULL;
#endif
}


/* lobject method - allocate a new lobject */

#define psyco_conn_lobject_doc \
"lobject(oid=0, mode=0, new_oid=0, new_file=None,\n"                        \
"       lobject_factory=extensions.lobject) -- new lobject\n\n"             \
"Return a new lobject.\n\nThe ``lobject_factory`` argument can be used\n"   \
"to create non-standard lobjects by passing a class different from the\n"   \
"default. Note that the new class *should* be a sub-class of\n"             \
"`extensions.lobject`.\n\n"                                                 \
":rtype: `extensions.lobject`"

static PyObject *
psyco_conn_lobject(connectionObject *self, PyObject *args, PyObject *keywds)
{
    Oid oid = InvalidOid, new_oid = InvalidOid;
    const char *new_file = NULL;
    const char *smode = "";
    PyObject *factory = (PyObject *)&lobjectType;
    PyObject *obj;

    static char *kwlist[] = {"oid", "mode", "new_oid", "new_file",
                             "lobject_factory", NULL};

    if (!PyArg_ParseTupleAndKeywords(args, keywds, "|IzIzO", kwlist,
                                     &oid, &smode, &new_oid, &new_file,
                                     &factory)) {
        return NULL;
    }

    EXC_IF_CONN_CLOSED(self);
    EXC_IF_CONN_ASYNC(self, lobject);
    EXC_IF_GREEN(lobject);
    EXC_IF_TPC_PREPARED(self, lobject);

    Dprintf("psyco_conn_lobject: new lobject for connection at %p", self);
    Dprintf("psyco_conn_lobject:     parameters: oid = %u, mode = %s",
            oid, smode);
    Dprintf("psyco_conn_lobject:     parameters: new_oid = %u, new_file = %s",
            new_oid, new_file);

    if (new_file)
        obj = PyObject_CallFunction(factory, "OIsIs",
            self, oid, smode, new_oid, new_file);
    else
        obj = PyObject_CallFunction(factory, "OIsI",
            self, oid, smode, new_oid);

    if (obj == NULL) return NULL;
    if (PyObject_IsInstance(obj, (PyObject *)&lobjectType) == 0) {
        PyErr_SetString(PyExc_TypeError,
            "lobject factory must be subclass of psycopg2.extensions.lobject");
        Py_DECREF(obj);
        return NULL;
    }

    Dprintf("psyco_conn_lobject: new lobject at %p: refcnt = "
            FORMAT_CODE_PY_SSIZE_T,
            obj, Py_REFCNT(obj));
    return obj;
}

/* get the current backend pid */

#define psyco_conn_get_backend_pid_doc \
"get_backend_pid() -- Get backend process id."

static PyObject *
psyco_conn_get_backend_pid(connectionObject *self, PyObject *dummy)
{
    EXC_IF_CONN_CLOSED(self);

    return PyInt_FromLong((long)PQbackendPID(self->pgconn));
}


/* get info about the connection */

#define psyco_conn_info_doc \
"info -- Get connection info."

static PyObject *
psyco_conn_info_get(connectionObject *self)
{
    return PyObject_CallFunctionObjArgs(
        (PyObject *)&connInfoType, (PyObject *)self, NULL);
}


/* return the pointer to the PGconn structure */

#define psyco_conn_pgconn_ptr_doc \
"pgconn_ptr -- Get the PGconn structure pointer."

static PyObject *
psyco_conn_pgconn_ptr_get(connectionObject *self)
{
    if (self->pgconn) {
        return PyLong_FromVoidPtr((void *)self->pgconn);
    }
    else {
        Py_RETURN_NONE;
    }
}


/* reset the currect connection */

#define psyco_conn_reset_doc \
"reset() -- Reset current connection to defaults."

static PyObject *
psyco_conn_reset(connectionObject *self, PyObject *dummy)
{
    int res;

    EXC_IF_CONN_CLOSED(self);
    EXC_IF_CONN_ASYNC(self, reset);

    if (pq_reset(self) < 0)
        return NULL;

    res = conn_setup(self);
    if (res < 0)
        return NULL;

    Py_RETURN_NONE;
}

static PyObject *
psyco_conn_get_exception(PyObject *self, void *closure)
{
    PyObject *exception = *(PyObject **)closure;

    Py_INCREF(exception);
    return exception;
}


#define psyco_conn_poll_doc \
"poll() -> int -- Advance the connection or query process without blocking."

static PyObject *
psyco_conn_poll(connectionObject *self, PyObject *dummy)
{
    int res;

    EXC_IF_CONN_CLOSED(self);

    res = conn_poll(self);
    if (res != PSYCO_POLL_ERROR || !PyErr_Occurred()) {
        return PyInt_FromLong(res);
    } else {
        /* There is an error and an exception is already in place */
        return NULL;
    }
}


#define psyco_conn_fileno_doc \
"fileno() -> int -- Return file descriptor associated to database connection."

static PyObject *
psyco_conn_fileno(connectionObject *self, PyObject *dummy)
{
    long int socket;

    EXC_IF_CONN_CLOSED(self);

    socket = (long int)PQsocket(self->pgconn);

    return PyInt_FromLong(socket);
}


#define psyco_conn_isexecuting_doc                           \
"isexecuting() -> bool -- Return True if the connection is " \
 "executing an asynchronous operation."

static PyObject *
psyco_conn_isexecuting(connectionObject *self, PyObject *dummy)
{
    /* synchronous connections will always return False */
    if (self->async == 0) {
        Py_RETURN_FALSE;
    }

    /* check if the connection is still being built */
    if (self->status != CONN_STATUS_READY) {
        Py_RETURN_TRUE;
    }

    /* check if there is a query being executed */
    if (self->async_cursor != NULL) {
        Py_RETURN_TRUE;
    }

    /* otherwise it's not executing */
    Py_RETURN_FALSE;
}


#define psyco_conn_cancel_doc                           \
"cancel() -- cancel the current operation"

static PyObject *
psyco_conn_cancel(connectionObject *self, PyObject *dummy)
{
    char errbuf[256];

    EXC_IF_CONN_CLOSED(self);
    EXC_IF_TPC_PREPARED(self, cancel);

    /* do not allow cancellation while the connection is being built */
    Dprintf("psyco_conn_cancel: cancelling with key %p", self->cancel);
    if (self->status != CONN_STATUS_READY &&
        self->status != CONN_STATUS_BEGIN) {
        PyErr_SetString(OperationalError,
                        "asynchronous connection attempt underway");
        return NULL;
    }

    if (PQcancel(self->cancel, errbuf, sizeof(errbuf)) == 0) {
        Dprintf("psyco_conn_cancel: cancelling failed: %s", errbuf);
        PyErr_SetString(OperationalError, errbuf);
        return NULL;
    }
    Py_RETURN_NONE;
}


/** the connection object **/


/* object method list */

static struct PyMethodDef connectionObject_methods[] = {
    {"cursor", (PyCFunction)psyco_conn_cursor,
     METH_VARARGS|METH_KEYWORDS, psyco_conn_cursor_doc},
    {"close", (PyCFunction)psyco_conn_close,
     METH_NOARGS, psyco_conn_close_doc},
    {"commit", (PyCFunction)psyco_conn_commit,
     METH_NOARGS, psyco_conn_commit_doc},
    {"rollback", (PyCFunction)psyco_conn_rollback,
     METH_NOARGS, psyco_conn_rollback_doc},
    {"xid", (PyCFunction)psyco_conn_xid,
     METH_VARARGS|METH_KEYWORDS, psyco_conn_xid_doc},
    {"tpc_begin", (PyCFunction)psyco_conn_tpc_begin,
     METH_VARARGS, psyco_conn_tpc_begin_doc},
    {"tpc_prepare", (PyCFunction)psyco_conn_tpc_prepare,
     METH_NOARGS, psyco_conn_tpc_prepare_doc},
    {"tpc_commit", (PyCFunction)psyco_conn_tpc_commit,
     METH_VARARGS, psyco_conn_tpc_commit_doc},
    {"tpc_rollback", (PyCFunction)psyco_conn_tpc_rollback,
     METH_VARARGS, psyco_conn_tpc_rollback_doc},
    {"tpc_recover", (PyCFunction)psyco_conn_tpc_recover,
     METH_NOARGS, psyco_conn_tpc_recover_doc},
    {"__enter__", (PyCFunction)psyco_conn_enter,
     METH_NOARGS, psyco_conn_enter_doc},
    {"__exit__", (PyCFunction)psyco_conn_exit,
     METH_VARARGS, psyco_conn_exit_doc},
    {"set_session", (PyCFunction)psyco_conn_set_session,
     METH_VARARGS|METH_KEYWORDS, psyco_conn_set_session_doc},
    {"set_isolation_level", (PyCFunction)psyco_conn_set_isolation_level,
     METH_VARARGS, psyco_conn_set_isolation_level_doc},
    {"set_client_encoding", (PyCFunction)psyco_conn_set_client_encoding,
     METH_VARARGS, psyco_conn_set_client_encoding_doc},
    {"get_transaction_status", (PyCFunction)psyco_conn_get_transaction_status,
     METH_NOARGS, psyco_conn_get_transaction_status_doc},
    {"get_parameter_status", (PyCFunction)psyco_conn_get_parameter_status,
     METH_VARARGS, psyco_conn_get_parameter_status_doc},
    {"get_dsn_parameters", (PyCFunction)psyco_conn_get_dsn_parameters,
     METH_NOARGS, psyco_conn_get_dsn_parameters_doc},
    {"get_backend_pid", (PyCFunction)psyco_conn_get_backend_pid,
     METH_NOARGS, psyco_conn_get_backend_pid_doc},
    {"lobject", (PyCFunction)psyco_conn_lobject,
     METH_VARARGS|METH_KEYWORDS, psyco_conn_lobject_doc},
    {"reset", (PyCFunction)psyco_conn_reset,
     METH_NOARGS, psyco_conn_reset_doc},
    {"poll", (PyCFunction)psyco_conn_poll,
     METH_NOARGS, psyco_conn_poll_doc},
    {"fileno", (PyCFunction)psyco_conn_fileno,
     METH_NOARGS, psyco_conn_fileno_doc},
    {"isexecuting", (PyCFunction)psyco_conn_isexecuting,
     METH_NOARGS, psyco_conn_isexecuting_doc},
    {"cancel", (PyCFunction)psyco_conn_cancel,
     METH_NOARGS, psyco_conn_cancel_doc},
    {"get_native_connection", (PyCFunction)psyco_get_native_connection,
     METH_NOARGS, psyco_get_native_connection_doc},
    {NULL}
};

/* object member list */

static struct PyMemberDef connectionObject_members[] = {
    {"closed", T_LONG, offsetof(connectionObject, closed), READONLY,
        "True if the connection is closed."},
    {"encoding", T_STRING, offsetof(connectionObject, encoding), READONLY,
        "The current client encoding."},
    {"notices", T_OBJECT, offsetof(connectionObject, notice_list), 0},
    {"notifies", T_OBJECT, offsetof(connectionObject, notifies), 0},
    {"dsn", T_STRING, offsetof(connectionObject, dsn), READONLY,
        "The current connection string."},
    {"async", T_LONG, offsetof(connectionObject, async), READONLY,
        "True if the connection is asynchronous."},
    {"async_", T_LONG, offsetof(connectionObject, async), READONLY,
        "True if the connection is asynchronous."},
    {"status", T_INT,
        offsetof(connectionObject, status), READONLY,
        "The current transaction status."},
    {"cursor_factory", T_OBJECT, offsetof(connectionObject, cursor_factory), 0,
        "Default cursor_factory for cursor()."},
    {"string_types", T_OBJECT, offsetof(connectionObject, string_types), READONLY,
        "A set of typecasters to convert textual values."},
    {"binary_types", T_OBJECT, offsetof(connectionObject, binary_types), READONLY,
        "A set of typecasters to convert binary values."},
    {"protocol_version", T_INT,
        offsetof(connectionObject, protocol), READONLY,
        "Protocol version used for this connection. Currently always 3."},
    {"server_version", T_INT,
        offsetof(connectionObject, server_version), READONLY,
        "Server version."},
    {NULL}
};

#define EXCEPTION_GETTER(exc) \
    { #exc, psyco_conn_get_exception, NULL, exc ## _doc, &exc }

static struct PyGetSetDef connectionObject_getsets[] = {
    EXCEPTION_GETTER(Error),
    EXCEPTION_GETTER(Warning),
    EXCEPTION_GETTER(InterfaceError),
    EXCEPTION_GETTER(DatabaseError),
    EXCEPTION_GETTER(InternalError),
    EXCEPTION_GETTER(OperationalError),
    EXCEPTION_GETTER(ProgrammingError),
    EXCEPTION_GETTER(IntegrityError),
    EXCEPTION_GETTER(DataError),
    EXCEPTION_GETTER(NotSupportedError),
    { "autocommit",
        (getter)psyco_conn_autocommit_get,
        (setter)psyco_conn_autocommit_set,
        psyco_conn_autocommit_doc },
    { "isolation_level",
        (getter)psyco_conn_isolation_level_get,
        (setter)psyco_conn_isolation_level_set,
        psyco_conn_isolation_level_doc },
    { "readonly",
        (getter)psyco_conn_readonly_get,
        (setter)psyco_conn_readonly_set,
        psyco_conn_readonly_doc },
    { "deferrable",
        (getter)psyco_conn_deferrable_get,
        (setter)psyco_conn_deferrable_set,
        psyco_conn_deferrable_doc },
    { "info",
        (getter)psyco_conn_info_get, NULL,
        psyco_conn_info_doc },
    { "pgconn_ptr",
        (getter)psyco_conn_pgconn_ptr_get, NULL,
        psyco_conn_pgconn_ptr_doc },
    {NULL}
};
#undef EXCEPTION_GETTER

/* initialization and finalization methods */

static int
connection_setup(connectionObject *self, const char *dsn, long int async)
{
    int rv = -1;

    Dprintf("connection_setup: init connection object at %p, "
	    "async %ld, refcnt = " FORMAT_CODE_PY_SSIZE_T,
            self, async, Py_REFCNT(self)
      );

    if (!(self->dsn = conn_obscure_password(dsn))) { goto exit; }
    if (!(self->notice_list = PyList_New(0))) { goto exit; }
    if (!(self->notifies = PyList_New(0))) { goto exit; }
    self->async = async;
    self->status = CONN_STATUS_SETUP;
    self->async_status = ASYNC_DONE;
    if (!(self->string_types = PyDict_New())) { goto exit; }
    if (!(self->binary_types = PyDict_New())) { goto exit; }
    self->isolevel = ISOLATION_LEVEL_DEFAULT;
    self->readonly = STATE_DEFAULT;
    self->deferrable = STATE_DEFAULT;
#ifdef CONN_CHECK_PID
    self->procpid = getpid();
#endif

    /* other fields have been zeroed by tp_alloc */

    if (0 != pthread_mutex_init(&(self->lock), NULL)) {
        PyErr_SetString(InternalError, "lock initialization failed");
        goto exit;
    }

    if (conn_connect(self, dsn, async) != 0) {
        Dprintf("connection_init: FAILED");
        goto exit;
    }

    rv = 0;

    Dprintf("connection_setup: good connection object at %p, refcnt = "
        FORMAT_CODE_PY_SSIZE_T,
        self, Py_REFCNT(self));

exit:
    return rv;
}


static int
connection_clear(connectionObject *self)
{
    Py_CLEAR(self->tpc_xid);
    Py_CLEAR(self->async_cursor);
    Py_CLEAR(self->notice_list);
    Py_CLEAR(self->notifies);
    Py_CLEAR(self->string_types);
    Py_CLEAR(self->binary_types);
    Py_CLEAR(self->cursor_factory);
    Py_CLEAR(self->pyencoder);
    Py_CLEAR(self->pydecoder);
    return 0;
}

static void
connection_dealloc(PyObject* obj)
{
    connectionObject *self = (connectionObject *)obj;

    /* Make sure to untrack the connection before calling conn_close, which may
     * allow a different thread to try and dealloc the connection again,
     * resulting in a double-free segfault (ticket #166). */
    PyObject_GC_UnTrack(self);

    /* close the connection only if this is the same process it was created
     * into, otherwise using multiprocessing we may close the connection
     * belonging to another process. */
#ifdef CONN_CHECK_PID
    if (self->procpid == getpid())
#endif
    {
        conn_close(self);
    }

    if (self->weakreflist) {
        PyObject_ClearWeakRefs(obj);
    }

    conn_notice_clean(self);

    PyMem_Free(self->dsn);
    PyMem_Free(self->encoding);
    if (self->error) free(self->error);
    if (self->cancel) PQfreeCancel(self->cancel);
    PQclear(self->pgres);

    connection_clear(self);

    pthread_mutex_destroy(&(self->lock));

    Dprintf("connection_dealloc: deleted connection object at %p, refcnt = "
        FORMAT_CODE_PY_SSIZE_T,
        obj, Py_REFCNT(obj)
      );

    Py_TYPE(obj)->tp_free(obj);
}

static int
connection_init(PyObject *obj, PyObject *args, PyObject *kwds)
{
    const char *dsn;
    long int async = 0, async_ = 0;
    static char *kwlist[] = {"dsn", "async", "async_", NULL};

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|ll", kwlist,
            &dsn, &async, &async_))
        return -1;

    if (async_) { async = async_; }
    return connection_setup((connectionObject *)obj, dsn, async);
}

static PyObject *
connection_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    return type->tp_alloc(type, 0);
}

static PyObject *
connection_repr(connectionObject *self)
{
    return PyString_FromFormat(
        "<connection object at %p; dsn: '%s', closed: %ld>",
        self, (self->dsn ? self->dsn : "<unintialized>"), self->closed);
}

static int
connection_traverse(connectionObject *self, visitproc visit, void *arg)
{
    Py_VISIT((PyObject *)(self->tpc_xid));
    Py_VISIT(self->async_cursor);
    Py_VISIT(self->notice_list);
    Py_VISIT(self->notifies);
    Py_VISIT(self->string_types);
    Py_VISIT(self->binary_types);
    Py_VISIT(self->cursor_factory);
    Py_VISIT(self->pyencoder);
    Py_VISIT(self->pydecoder);
    return 0;
}


/* object type */

#define connectionType_doc \
"connection(dsn, ...) -> new connection object\n\n" \
":Groups:\n" \
"  * `DBAPI-2.0 errors`: Error, Warning, InterfaceError,\n" \
"    DatabaseError, InternalError, OperationalError,\n" \
"    ProgrammingError, IntegrityError, DataError, NotSupportedError"

PyTypeObject connectionType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "psycopg2.extensions.connection",
    sizeof(connectionObject), 0,
    connection_dealloc, /*tp_dealloc*/
    0,          /*tp_print*/
    0,          /*tp_getattr*/
    0,          /*tp_setattr*/
    0,          /*tp_compare*/
    (reprfunc)connection_repr, /*tp_repr*/
    0,          /*tp_as_number*/
    0,          /*tp_as_sequence*/
    0,          /*tp_as_mapping*/
    0,          /*tp_hash */
    0,          /*tp_call*/
    (reprfunc)connection_repr, /*tp_str*/
    0,          /*tp_getattro*/
    0,          /*tp_setattro*/
    0,          /*tp_as_buffer*/
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC |
        Py_TPFLAGS_HAVE_WEAKREFS,
                /*tp_flags*/
    connectionType_doc, /*tp_doc*/
    (traverseproc)connection_traverse, /*tp_traverse*/
    (inquiry)connection_clear, /*tp_clear*/
    0,          /*tp_richcompare*/
    offsetof(connectionObject, weakreflist), /* tp_weaklistoffset */
    0,          /*tp_iter*/
    0,          /*tp_iternext*/
    connectionObject_methods, /*tp_methods*/
    connectionObject_members, /*tp_members*/
    connectionObject_getsets, /*tp_getset*/
    0,          /*tp_base*/
    0,          /*tp_dict*/
    0,          /*tp_descr_get*/
    0,          /*tp_descr_set*/
    0,          /*tp_dictoffset*/
    connection_init, /*tp_init*/
    0,          /*tp_alloc*/
    connection_new, /*tp_new*/
};