/* psycopgmodule.c - psycopg module (will import other C classes)
 *
 * Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
 * Copyright (C) 2020-2021 The Psycopg Team
 *
 * 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/replication_connection.h"
#include "psycopg/replication_cursor.h"
#include "psycopg/replication_message.h"
#include "psycopg/green.h"
#include "psycopg/column.h"
#include "psycopg/lobject.h"
#include "psycopg/notify.h"
#include "psycopg/xid.h"
#include "psycopg/typecast.h"
#include "psycopg/microprotocols.h"
#include "psycopg/microprotocols_proto.h"
#include "psycopg/conninfo.h"
#include "psycopg/diagnostics.h"

#include "psycopg/adapter_qstring.h"
#include "psycopg/adapter_binary.h"
#include "psycopg/adapter_pboolean.h"
#include "psycopg/adapter_pint.h"
#include "psycopg/adapter_pfloat.h"
#include "psycopg/adapter_pdecimal.h"
#include "psycopg/adapter_asis.h"
#include "psycopg/adapter_list.h"
#include "psycopg/typecast_binary.h"

/* some module-level variables, like the datetime module */
#include <datetime.h>
#include "psycopg/adapter_datetime.h"

#include <stdlib.h>

HIDDEN PyObject *psycoEncodings = NULL;
HIDDEN PyObject *sqlstate_errors = NULL;

#ifdef PSYCOPG_DEBUG
HIDDEN int psycopg_debug_enabled = 0;
#endif

/* Python representation of SQL NULL */
HIDDEN PyObject *psyco_null = NULL;

/* macro trick to stringify a macro expansion */
#define xstr(s) str(s)
#define str(s) #s

/** connect module-level function **/
#define psyco_connect_doc \
"_connect(dsn, [connection_factory], [async]) -- New database connection.\n\n"

static PyObject *
psyco_connect(PyObject *self, PyObject *args, PyObject *keywds)
{
    PyObject *conn = NULL;
    PyObject *factory = NULL;
    const char *dsn = NULL;
    int async = 0, async_ = 0;

    static char *kwlist[] = {"dsn", "connection_factory", "async", "async_", NULL};

    if (!PyArg_ParseTupleAndKeywords(args, keywds, "s|Oii", kwlist,
            &dsn, &factory, &async, &async_)) {
        return NULL;
    }

    if (async_) { async = async_; }

    Dprintf("psyco_connect: dsn = '%s', async = %d", dsn, async);

    /* allocate connection, fill with errors and return it */
    if (factory == NULL || factory == Py_None) {
        factory = (PyObject *)&connectionType;
    }

    /* Here we are breaking the connection.__init__ interface defined
     * by psycopg2. So, if not requiring an async conn, avoid passing
     * the async parameter. */
    /* TODO: would it be possible to avoid an additional parameter
     * to the conn constructor? A subclass? (but it would require mixins
     * to further subclass) Another dsn parameter (but is not really
     * a connection parameter that can be configured) */
    if (!async) {
        conn = PyObject_CallFunction(factory, "s", dsn);
    } else {
        conn = PyObject_CallFunction(factory, "si", dsn, async);
    }

    return conn;
}


#define parse_dsn_doc \
"parse_dsn(dsn) -> dict -- parse a connection string into parameters"

static PyObject *
parse_dsn(PyObject *self, PyObject *args, PyObject *kwargs)
{
    char *err = NULL;
    PQconninfoOption *options = NULL;
    PyObject *res = NULL, *dsn;

    static char *kwlist[] = {"dsn", NULL};
    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O", kwlist, &dsn)) {
        return NULL;
    }

    Py_INCREF(dsn); /* for ensure_bytes */
    if (!(dsn = psyco_ensure_bytes(dsn))) { goto exit; }

    options = PQconninfoParse(Bytes_AS_STRING(dsn), &err);
    if (options == NULL) {
        if (err != NULL) {
            PyErr_Format(ProgrammingError, "invalid dsn: %s", err);
            PQfreemem(err);
        } else {
            PyErr_SetString(OperationalError, "PQconninfoParse() failed");
        }
        goto exit;
    }

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

exit:
    PQconninfoFree(options);    /* safe on null */
    Py_XDECREF(dsn);

    return res;
}


#define quote_ident_doc \
"quote_ident(str, conn_or_curs) -> str -- wrapper around PQescapeIdentifier\n\n" \
":Parameters:\n" \
"  * `str`: A bytes or unicode object\n" \
"  * `conn_or_curs`: A connection or cursor, required"

static PyObject *
quote_ident(PyObject *self, PyObject *args, PyObject *kwargs)
{
    PyObject *ident = NULL, *obj = NULL, *result = NULL;
    connectionObject *conn;
    char *quoted = NULL;

    static char *kwlist[] = {"ident", "scope", NULL};
    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO", kwlist, &ident, &obj)) {
        return NULL;
    }

    if (PyObject_TypeCheck(obj, &cursorType)) {
        conn = ((cursorObject*)obj)->conn;
    }
    else if (PyObject_TypeCheck(obj, &connectionType)) {
        conn = (connectionObject*)obj;
    }
    else {
        PyErr_SetString(PyExc_TypeError,
                        "argument 2 must be a connection or a cursor");
        return NULL;
    }

    Py_INCREF(ident); /* for ensure_bytes */
    if (!(ident = psyco_ensure_bytes(ident))) { goto exit; }

    if (!(quoted = psyco_escape_identifier(conn,
        Bytes_AS_STRING(ident), Bytes_GET_SIZE(ident)))) { goto exit; }

    result = conn_text_from_chars(conn, quoted);

exit:
    PQfreemem(quoted);
    Py_XDECREF(ident);

    return result;
}

/** type registration **/
#define register_type_doc \
"register_type(obj, conn_or_curs) -> None -- register obj with psycopg type system\n\n" \
":Parameters:\n" \
"  * `obj`: A type adapter created by `new_type()`\n" \
"  * `conn_or_curs`: A connection, cursor or None"

#define typecast_from_python_doc \
"new_type(oids, name, castobj) -> new type object\n\n" \
"Create a new binding object. The object can be used with the\n" \
"`register_type()` function to bind PostgreSQL objects to python objects.\n\n" \
":Parameters:\n" \
"  * `oids`: Tuple of ``oid`` of the PostgreSQL types to convert.\n" \
"  * `name`: Name for the new type\n" \
"  * `adapter`: Callable to perform type conversion.\n" \
"    It must have the signature ``fun(value, cur)`` where ``value`` is\n" \
"    the string representation returned by PostgreSQL (`!None` if ``NULL``)\n" \
"    and ``cur`` is the cursor from which data are read."

#define typecast_array_from_python_doc \
"new_array_type(oids, name, baseobj) -> new type object\n\n" \
"Create a new binding object to parse an array.\n\n" \
"The object can be used with `register_type()`.\n\n" \
":Parameters:\n" \
"  * `oids`: Tuple of ``oid`` of the PostgreSQL types to convert.\n" \
"  * `name`: Name for the new type\n" \
"  * `baseobj`: Adapter to perform type conversion of a single array item."

static PyObject *
register_type(PyObject *self, PyObject *args)
{
    PyObject *type, *obj = NULL;

    if (!PyArg_ParseTuple(args, "O!|O", &typecastType, &type, &obj)) {
        return NULL;
    }

    if (obj != NULL && obj != Py_None) {
        if (PyObject_TypeCheck(obj, &cursorType)) {
            PyObject **dict = &(((cursorObject*)obj)->string_types);
            if (*dict == NULL) {
                if (!(*dict = PyDict_New())) { return NULL; }
            }
            if (0 > typecast_add(type, *dict, 0)) { return NULL; }
        }
        else if (PyObject_TypeCheck(obj, &connectionType)) {
            if (0 > typecast_add(type, ((connectionObject*)obj)->string_types, 0)) {
                return NULL;
            }
        }
        else {
            PyErr_SetString(PyExc_TypeError,
                "argument 2 must be a connection, cursor or None");
            return NULL;
        }
    }
    else {
        if (0 > typecast_add(type, NULL, 0)) { return NULL; }
    }

    Py_RETURN_NONE;
}



/* Make sure libcrypto thread callbacks are set up. */
static void
libcrypto_threads_init(void)
{
    PyObject *m;

    Dprintf("psycopgmodule: configuring libpq libcrypto callbacks ");

    /* importing the ssl module sets up Python's libcrypto callbacks */
    if ((m = PyImport_ImportModule("ssl"))) {
        /* disable libcrypto setup in libpq, so it won't stomp on the callbacks
           that have already been set up */
        PQinitOpenSSL(1, 0);
        Py_DECREF(m);
    }
    else {
        /* might mean that Python has been compiled without OpenSSL support,
           fall back to relying on libpq's libcrypto locking */
        PyErr_Clear();
    }
}

/* Initialize the default adapters map
 *
 * Return 0 on success, else -1 and set an exception.
 */
RAISES_NEG static int
adapters_init(PyObject *module)
{
    PyObject *dict = NULL, *obj = NULL;
    int rv = -1;

    if (0 > microprotocols_init(module)) { goto exit; }

    Dprintf("psycopgmodule: initializing adapters");

    if (0 > microprotocols_add(&PyFloat_Type, NULL, (PyObject*)&pfloatType)) {
        goto exit;
    }
    if (0 > microprotocols_add(&PyLong_Type, NULL, (PyObject*)&pintType)) {
        goto exit;
    }
    if (0 > microprotocols_add(&PyBool_Type, NULL, (PyObject*)&pbooleanType)) {
        goto exit;
    }

    /* strings */
    if (0 > microprotocols_add(&PyUnicode_Type, NULL, (PyObject*)&qstringType)) {
        goto exit;
    }

    /* binary */
    if (0 > microprotocols_add(&PyBytes_Type, NULL, (PyObject*)&binaryType)) {
        goto exit;
    }

    if (0 > microprotocols_add(&PyByteArray_Type, NULL, (PyObject*)&binaryType)) {
        goto exit;
    }

    if (0 > microprotocols_add(&PyMemoryView_Type, NULL, (PyObject*)&binaryType)) {
        goto exit;
    }

    if (0 > microprotocols_add(&PyList_Type, NULL, (PyObject*)&listType)) {
        goto exit;
    }

    /* the module has already been initialized, so we can obtain the callable
       objects directly from its dictionary :) */
    if (!(dict = PyModule_GetDict(module))) { goto exit; }

    if (!(obj = PyMapping_GetItemString(dict, "DateFromPy"))) { goto exit; }
    if (0 > microprotocols_add(PyDateTimeAPI->DateType, NULL, obj)) { goto exit; }
    Py_CLEAR(obj);

    if (!(obj = PyMapping_GetItemString(dict, "TimeFromPy"))) { goto exit; }
    if (0 > microprotocols_add(PyDateTimeAPI->TimeType, NULL, obj)) { goto exit; }
    Py_CLEAR(obj);

    if (!(obj = PyMapping_GetItemString(dict, "TimestampFromPy"))) { goto exit; }
    if (0 > microprotocols_add(PyDateTimeAPI->DateTimeType, NULL, obj)) { goto exit; }
    Py_CLEAR(obj);

    if (!(obj = PyMapping_GetItemString(dict, "IntervalFromPy"))) { goto exit; }
    if (0 > microprotocols_add(PyDateTimeAPI->DeltaType, NULL, obj)) { goto exit; }
    Py_CLEAR(obj);

    /* Success! */
    rv = 0;

exit:
    Py_XDECREF(obj);

    return rv;
}

#define libpq_version_doc "Query actual libpq version loaded."

static PyObject*
libpq_version(PyObject *self, PyObject *dummy)
{
    return PyInt_FromLong(PQlibVersion());
}

/* encrypt_password - Prepare the encrypted password form */
#define encrypt_password_doc \
"encrypt_password(password, user, [scope], [algorithm]) -- Prepares the encrypted form of a PostgreSQL password.\n\n"

static PyObject *
encrypt_password(PyObject *self, PyObject *args, PyObject *kwargs)
{
    char *encrypted = NULL;
    PyObject *password = NULL, *user = NULL;
    PyObject *scope = Py_None, *algorithm = Py_None;
    PyObject *res = NULL;
    connectionObject *conn = NULL;

    static char *kwlist[] = {"password", "user", "scope", "algorithm", NULL};

    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO|OO", kwlist,
            &password, &user, &scope, &algorithm)) {
        return NULL;
    }

    /* for ensure_bytes */
    Py_INCREF(user);
    Py_INCREF(password);
    Py_INCREF(algorithm);

    if (scope != Py_None) {
        if (PyObject_TypeCheck(scope, &cursorType)) {
            conn = ((cursorObject*)scope)->conn;
        }
        else if (PyObject_TypeCheck(scope, &connectionType)) {
            conn = (connectionObject*)scope;
        }
        else {
            PyErr_SetString(PyExc_TypeError,
                "the scope must be a connection or a cursor");
            goto exit;
        }
    }

    if (!(user = psyco_ensure_bytes(user))) { goto exit; }
    if (!(password = psyco_ensure_bytes(password))) { goto exit; }
    if (algorithm != Py_None) {
        if (!(algorithm = psyco_ensure_bytes(algorithm))) {
            goto exit;
        }
    }

    /* If we have to encrypt md5 we can use the libpq < 10 API */
    if (algorithm != Py_None &&
            strcmp(Bytes_AS_STRING(algorithm), "md5") == 0) {
        encrypted = PQencryptPassword(
            Bytes_AS_STRING(password), Bytes_AS_STRING(user));
    }

    /* If the algorithm is not md5 we have to use the API available from
     * libpq 10. */
    else {
#if PG_VERSION_NUM >= 100000
        if (!conn) {
            PyErr_SetString(ProgrammingError,
                "password encryption (other than 'md5' algorithm)"
                " requires a connection or cursor");
            goto exit;
        }

        /* TODO: algo = None will block: forbid on async/green conn? */
        encrypted = PQencryptPasswordConn(conn->pgconn,
            Bytes_AS_STRING(password), Bytes_AS_STRING(user),
            algorithm != Py_None ? Bytes_AS_STRING(algorithm) : NULL);
#else
        PyErr_SetString(NotSupportedError,
            "password encryption (other than 'md5' algorithm)"
            " requires libpq 10");
        goto exit;
#endif
    }

    if (encrypted) {
        res = Text_FromUTF8(encrypted);
    }
    else {
        const char *msg = PQerrorMessage(conn->pgconn);
        PyErr_Format(ProgrammingError,
            "password encryption failed: %s", msg ? msg : "no reason given");
        goto exit;
    }

exit:
    if (encrypted) {
        PQfreemem(encrypted);
    }
    Py_XDECREF(user);
    Py_XDECREF(password);
    Py_XDECREF(algorithm);

    return res;
}


/* Fill the module's postgresql<->python encoding table */
static struct {
    char *pgenc;
    char *pyenc;
} enctable[] = {
    {"ABC",          "cp1258"},
    {"ALT",          "cp866"},
    {"BIG5",         "big5"},
    {"EUC_CN",       "euccn"},
    {"EUC_JIS_2004", "euc_jis_2004"},
    {"EUC_JP",       "euc_jp"},
    {"EUC_KR",       "euc_kr"},
    {"GB18030",      "gb18030"},
    {"GBK",          "gbk"},
    {"ISO_8859_1",   "iso8859_1"},
    {"ISO_8859_2",   "iso8859_2"},
    {"ISO_8859_3",   "iso8859_3"},
    {"ISO_8859_5",   "iso8859_5"},
    {"ISO_8859_6",   "iso8859_6"},
    {"ISO_8859_7",   "iso8859_7"},
    {"ISO_8859_8",   "iso8859_8"},
    {"ISO_8859_9",   "iso8859_9"},
    {"ISO_8859_10",  "iso8859_10"},
    {"ISO_8859_13",  "iso8859_13"},
    {"ISO_8859_14",  "iso8859_14"},
    {"ISO_8859_15",  "iso8859_15"},
    {"ISO_8859_16",  "iso8859_16"},
    {"JOHAB",        "johab"},
    {"KOI8",         "koi8_r"},
    {"KOI8R",        "koi8_r"},
    {"KOI8U",        "koi8_u"},
    {"LATIN1",       "iso8859_1"},
    {"LATIN2",       "iso8859_2"},
    {"LATIN3",       "iso8859_3"},
    {"LATIN4",       "iso8859_4"},
    {"LATIN5",       "iso8859_9"},
    {"LATIN6",       "iso8859_10"},
    {"LATIN7",       "iso8859_13"},
    {"LATIN8",       "iso8859_14"},
    {"LATIN9",       "iso8859_15"},
    {"LATIN10",      "iso8859_16"},
    {"Mskanji",      "cp932"},
    {"ShiftJIS",     "cp932"},
    {"SHIFT_JIS_2004", "shift_jis_2004"},
    {"SJIS",         "cp932"},
    {"SQL_ASCII",    "ascii"},  /* XXX this is wrong: SQL_ASCII means "no
                                 *  encoding" we should fix the unicode
                                 *  typecaster to return a str or bytes in Py3
                                 */
    {"TCVN",         "cp1258"},
    {"TCVN5712",     "cp1258"},
    {"UHC",          "cp949"},
    {"UNICODE",      "utf_8"}, /* Not valid in 8.2, backward compatibility */
    {"UTF8",         "utf_8"},
    {"VSCII",        "cp1258"},
    {"WIN",          "cp1251"},
    {"WIN866",       "cp866"},
    {"WIN874",       "cp874"},
    {"WIN932",       "cp932"},
    {"WIN936",       "gbk"},
    {"WIN949",       "cp949"},
    {"WIN950",       "cp950"},
    {"WIN1250",      "cp1250"},
    {"WIN1251",      "cp1251"},
    {"WIN1252",      "cp1252"},
    {"WIN1253",      "cp1253"},
    {"WIN1254",      "cp1254"},
    {"WIN1255",      "cp1255"},
    {"WIN1256",      "cp1256"},
    {"WIN1257",      "cp1257"},
    {"WIN1258",      "cp1258"},
    {"Windows932",   "cp932"},
    {"Windows936",   "gbk"},
    {"Windows949",   "cp949"},
    {"Windows950",   "cp950"},

/* those are missing from Python:                */
/*    {"EUC_TW", "?"},                           */
/*    {"MULE_INTERNAL", "?"},                    */
    {NULL, NULL}
};

/* Initialize the encodings table.
 *
 * Return 0 on success, else -1 and set an exception.
 */
RAISES_NEG static int
encodings_init(PyObject *module)
{
    PyObject *value = NULL;
    int i;
    int rv = -1;

    Dprintf("psycopgmodule: initializing encodings table");
    if (psycoEncodings) {
        Dprintf("encodings_init(): already called");
        return 0;
    }

    if (!(psycoEncodings = PyDict_New())) { goto exit; }
    Py_INCREF(psycoEncodings);
    if (0 > PyModule_AddObject(module, "encodings", psycoEncodings)) {
        Py_DECREF(psycoEncodings);
        goto exit;
    }

    for (i = 0; enctable[i].pgenc != NULL; i++) {
        if (!(value = Text_FromUTF8(enctable[i].pyenc))) { goto exit; }
        if (0 > PyDict_SetItemString(
                psycoEncodings, enctable[i].pgenc, value)) {
            goto exit;
        }
        Py_CLEAR(value);
    }
    rv = 0;

exit:
    Py_XDECREF(value);

    return rv;
}

/* Initialize the module's exceptions and after that a dictionary with a full
   set of exceptions. */

PyObject *Error, *Warning, *InterfaceError, *DatabaseError,
    *InternalError, *OperationalError, *ProgrammingError,
    *IntegrityError, *DataError, *NotSupportedError;
PyObject *QueryCanceledError, *TransactionRollbackError;

/* mapping between exception names and their PyObject */
static struct {
    char *name;
    PyObject **exc;
    PyObject **base;
    const char *docstr;
} exctable[] = {
    { "psycopg2.Error", &Error, NULL, Error_doc },
    { "psycopg2.Warning", &Warning, NULL, Warning_doc },
    { "psycopg2.InterfaceError", &InterfaceError, &Error, InterfaceError_doc },
    { "psycopg2.DatabaseError", &DatabaseError, &Error, DatabaseError_doc },
    { "psycopg2.InternalError", &InternalError, &DatabaseError, InternalError_doc },
    { "psycopg2.OperationalError", &OperationalError, &DatabaseError,
        OperationalError_doc },
    { "psycopg2.ProgrammingError", &ProgrammingError, &DatabaseError,
        ProgrammingError_doc },
    { "psycopg2.IntegrityError", &IntegrityError, &DatabaseError,
        IntegrityError_doc },
    { "psycopg2.DataError", &DataError, &DatabaseError, DataError_doc },
    { "psycopg2.NotSupportedError", &NotSupportedError, &DatabaseError,
        NotSupportedError_doc },
    { "psycopg2.extensions.QueryCanceledError", &QueryCanceledError,
      &OperationalError, QueryCanceledError_doc },
    { "psycopg2.extensions.TransactionRollbackError",
      &TransactionRollbackError, &OperationalError,
      TransactionRollbackError_doc },
    {NULL}  /* Sentinel */
};


RAISES_NEG static int
basic_errors_init(PyObject *module)
{
    /* the names of the exceptions here reflect the organization of the
       psycopg2 module and not the fact the original error objects live in
       _psycopg */

    int i;
    PyObject *dict = NULL;
    PyObject *str = NULL;
    PyObject *errmodule = NULL;
    int rv = -1;

    Dprintf("psycopgmodule: initializing basic exceptions");

    /* 'Error' has been defined elsewhere: only init the other classes */
    Error = (PyObject *)&errorType;

    for (i = 1; exctable[i].name; i++) {
        if (!(dict = PyDict_New())) { goto exit; }

        if (exctable[i].docstr) {
            if (!(str = Text_FromUTF8(exctable[i].docstr))) { goto exit; }
            if (0 > PyDict_SetItemString(dict, "__doc__", str)) { goto exit; }
            Py_CLEAR(str);
        }

        /* can't put PyExc_StandardError in the static exctable:
         * windows build will fail */
        if (!(*exctable[i].exc = PyErr_NewException(
                exctable[i].name,
                exctable[i].base ? *exctable[i].base : PyExc_StandardError,
                dict))) {
            goto exit;
        }
        Py_CLEAR(dict);
    }

    if (!(errmodule = PyImport_ImportModule("psycopg2.errors"))) {
        /* don't inject the exceptions into the errors module */
        PyErr_Clear();
    }

    for (i = 0; exctable[i].name; i++) {
        char *name;
        if (NULL == exctable[i].exc) { continue; }

        /* the name is the part after the last dot */
        name = strrchr(exctable[i].name, '.');
        name = name ? name + 1 : exctable[i].name;

        Py_INCREF(*exctable[i].exc);
        if (0 > PyModule_AddObject(module, name, *exctable[i].exc)) {
            Py_DECREF(*exctable[i].exc);
            goto exit;
        }
        if (errmodule) {
            Py_INCREF(*exctable[i].exc);
            if (0 > PyModule_AddObject(errmodule, name, *exctable[i].exc)) {
                Py_DECREF(*exctable[i].exc);
                goto exit;
            }
        }
    }

    rv = 0;

exit:
    Py_XDECREF(errmodule);
    Py_XDECREF(str);
    Py_XDECREF(dict);
    return rv;
}


/* mapping between sqlstate and exception name */
static struct {
    char *sqlstate;
    char *name;
} sqlstate_table[] = {
#include "sqlstate_errors.h"
    {NULL}  /* Sentinel */
};


RAISES_NEG static int
sqlstate_errors_init(PyObject *module)
{
    int i;
    char namebuf[120];
    char prefix[] = "psycopg2.errors.";
    char *suffix;
    size_t bufsize;
    PyObject *exc = NULL;
    PyObject *errmodule = NULL;
    int rv = -1;

    Dprintf("psycopgmodule: initializing sqlstate exceptions");

    if (sqlstate_errors) {
		Dprintf("sqlstate_errors_init(): already called");
        return 0;
    }
    if (!(errmodule = PyImport_ImportModule("psycopg2.errors"))) {
        /* don't inject the exceptions into the errors module */
        PyErr_Clear();
    }
    if (!(sqlstate_errors = PyDict_New())) {
        goto exit;
    }
    Py_INCREF(sqlstate_errors);
    if (0 > PyModule_AddObject(module, "sqlstate_errors", sqlstate_errors)) {
        Py_DECREF(sqlstate_errors);
        return -1;
    }

    strcpy(namebuf, prefix);
    suffix = namebuf + sizeof(prefix) - 1;
    bufsize = sizeof(namebuf) - sizeof(prefix) - 1;
    /* If this 0 gets deleted the buffer was too small. */
    namebuf[sizeof(namebuf) - 1] = '\0';

    for (i = 0; sqlstate_table[i].sqlstate; i++) {
        PyObject *base;

        base = base_exception_from_sqlstate(sqlstate_table[i].sqlstate);
        strncpy(suffix, sqlstate_table[i].name, bufsize);
        if (namebuf[sizeof(namebuf) - 1] != '\0') {
            PyErr_SetString(
                PyExc_SystemError, "sqlstate_errors_init(): buffer too small");
            goto exit;
        }
        if (!(exc = PyErr_NewException(namebuf, base, NULL))) {
            goto exit;
        }
        if (0 > PyDict_SetItemString(
                sqlstate_errors, sqlstate_table[i].sqlstate, exc)) {
            goto exit;
        }

        /* Expose the exceptions to psycopg2.errors */
        if (errmodule) {
            if (0 > PyModule_AddObject(
                    errmodule, sqlstate_table[i].name, exc)) {
                goto exit;
            }
            else {
                exc = NULL;     /* ref stolen by the module */
            }
        }
        else {
            Py_CLEAR(exc);
        }
    }

    rv = 0;

exit:
    Py_XDECREF(errmodule);
    Py_XDECREF(exc);
    return rv;
}


RAISES_NEG static int
add_module_constants(PyObject *module)
{
    PyObject *tmp;
    Dprintf("psycopgmodule: initializing module constants");

    if (0 > PyModule_AddStringConstant(module,
        "__version__", xstr(PSYCOPG_VERSION)))
    { return -1; }

    if (0 > PyModule_AddStringConstant(module,
        "__doc__", "psycopg2 PostgreSQL driver"))
    { return -1; }

    if (0 > PyModule_AddIntConstant(module,
        "__libpq_version__", PG_VERSION_NUM))
    { return -1; }

    if (0 > PyModule_AddObject(module,
        "apilevel", tmp = Text_FromUTF8(APILEVEL)))
    {
        Py_XDECREF(tmp);
        return -1;
    }

    if (0 > PyModule_AddObject(module,
        "threadsafety", tmp = PyInt_FromLong(THREADSAFETY)))
    {
        Py_XDECREF(tmp);
        return -1;
    }

    if (0 > PyModule_AddObject(module,
        "paramstyle", tmp = Text_FromUTF8(PARAMSTYLE)))
    {
        Py_XDECREF(tmp);
        return -1;
    }

    if (0 > PyModule_AddIntMacro(module, REPLICATION_PHYSICAL)) { return -1; }
    if (0 > PyModule_AddIntMacro(module, REPLICATION_LOGICAL)) { return -1; }

    return 0;
}


static struct {
    char *name;
    PyTypeObject *type;
} typetable[] = {
    { "connection", &connectionType },
    { "cursor", &cursorType },
    { "ReplicationConnection", &replicationConnectionType },
    { "ReplicationCursor", &replicationCursorType },
    { "ReplicationMessage", &replicationMessageType },
    { "ISQLQuote", &isqlquoteType },
    { "Column", &columnType },
    { "Notify", &notifyType },
    { "Xid", &xidType },
    { "ConnectionInfo", &connInfoType },
    { "Diagnostics", &diagnosticsType },
    { "AsIs", &asisType },
    { "Binary", &binaryType },
    { "Boolean", &pbooleanType },
    { "Decimal", &pdecimalType },
    { "Int", &pintType },
    { "Float", &pfloatType },
    { "List", &listType },
    { "QuotedString", &qstringType },
    { "lobject", &lobjectType },
    {NULL}  /* Sentinel */
};

RAISES_NEG static int
add_module_types(PyObject *module)
{
    int i;

    Dprintf("psycopgmodule: initializing module types");

    for (i = 0; typetable[i].name; i++) {
        PyObject *type = (PyObject *)typetable[i].type;

        Py_SET_TYPE(typetable[i].type, &PyType_Type);
        if (0 > PyType_Ready(typetable[i].type)) { return -1; }

        Py_INCREF(type);
        if (0 > PyModule_AddObject(module, typetable[i].name, type)) {
            Py_DECREF(type);
            return -1;
        }
    }
    return 0;
}


RAISES_NEG static int
datetime_init(void)
{
    PyObject *dt = NULL;

    Dprintf("psycopgmodule: initializing datetime module");

    /* import python builtin datetime module, if available */
    if (!(dt = PyImport_ImportModule("datetime"))) {
        return -1;
    }
    Py_DECREF(dt);

    /* Initialize the PyDateTimeAPI everywhere is used */
    PyDateTime_IMPORT;
    if (0 > adapter_datetime_init()) { return -1; }
    if (0 > repl_curs_datetime_init()) { return -1; }
    if (0 > replmsg_datetime_init()) { return -1; }

    Py_SET_TYPE(&pydatetimeType, &PyType_Type);
    if (0 > PyType_Ready(&pydatetimeType)) { return -1; }

    return 0;
}

/** method table and module initialization **/

static PyMethodDef psycopgMethods[] = {
    {"_connect",  (PyCFunction)psyco_connect,
     METH_VARARGS|METH_KEYWORDS, psyco_connect_doc},
    {"parse_dsn",  (PyCFunction)parse_dsn,
     METH_VARARGS|METH_KEYWORDS, parse_dsn_doc},
    {"quote_ident", (PyCFunction)quote_ident,
     METH_VARARGS|METH_KEYWORDS, quote_ident_doc},
    {"adapt",  (PyCFunction)psyco_microprotocols_adapt,
     METH_VARARGS, psyco_microprotocols_adapt_doc},

    {"register_type", (PyCFunction)register_type,
     METH_VARARGS, register_type_doc},
    {"new_type", (PyCFunction)typecast_from_python,
     METH_VARARGS|METH_KEYWORDS, typecast_from_python_doc},
    {"new_array_type", (PyCFunction)typecast_array_from_python,
     METH_VARARGS|METH_KEYWORDS, typecast_array_from_python_doc},
    {"libpq_version", (PyCFunction)libpq_version,
     METH_NOARGS, libpq_version_doc},

    {"Date",  (PyCFunction)psyco_Date,
     METH_VARARGS, psyco_Date_doc},
    {"Time",  (PyCFunction)psyco_Time,
     METH_VARARGS, psyco_Time_doc},
    {"Timestamp",  (PyCFunction)psyco_Timestamp,
     METH_VARARGS, psyco_Timestamp_doc},
    {"DateFromTicks",  (PyCFunction)psyco_DateFromTicks,
     METH_VARARGS, psyco_DateFromTicks_doc},
    {"TimeFromTicks",  (PyCFunction)psyco_TimeFromTicks,
     METH_VARARGS, psyco_TimeFromTicks_doc},
    {"TimestampFromTicks",  (PyCFunction)psyco_TimestampFromTicks,
     METH_VARARGS, psyco_TimestampFromTicks_doc},

    {"DateFromPy",  (PyCFunction)psyco_DateFromPy,
     METH_VARARGS, psyco_DateFromPy_doc},
    {"TimeFromPy",  (PyCFunction)psyco_TimeFromPy,
     METH_VARARGS, psyco_TimeFromPy_doc},
    {"TimestampFromPy",  (PyCFunction)psyco_TimestampFromPy,
     METH_VARARGS, psyco_TimestampFromPy_doc},
    {"IntervalFromPy",  (PyCFunction)psyco_IntervalFromPy,
     METH_VARARGS, psyco_IntervalFromPy_doc},

    {"set_wait_callback",  (PyCFunction)psyco_set_wait_callback,
     METH_O, psyco_set_wait_callback_doc},
    {"get_wait_callback",  (PyCFunction)psyco_get_wait_callback,
     METH_NOARGS, psyco_get_wait_callback_doc},
    {"encrypt_password", (PyCFunction)encrypt_password,
     METH_VARARGS|METH_KEYWORDS, encrypt_password_doc},

    {NULL, NULL, 0, NULL}        /* Sentinel */
};

static struct PyModuleDef psycopgmodule = {
        PyModuleDef_HEAD_INIT,
        "_psycopg",
        NULL,
        -1,
        psycopgMethods,
        NULL,
        NULL,
        NULL,
        NULL
};

#ifndef PyMODINIT_FUNC	/* declarations for DLL import/export */
#define PyMODINIT_FUNC void
#endif
PyMODINIT_FUNC
INIT_MODULE(_psycopg)(void)
{
    PyObject *module = NULL;

#ifdef PSYCOPG_DEBUG
    if (getenv("PSYCOPG_DEBUG"))
        psycopg_debug_enabled = 1;
#endif

    Dprintf("psycopgmodule: initializing psycopg %s", xstr(PSYCOPG_VERSION));

    /* initialize libcrypto threading callbacks */
    libcrypto_threads_init();

    /* initialize types and objects not exposed to the module */
    Py_SET_TYPE(&typecastType, &PyType_Type);
    if (0 > PyType_Ready(&typecastType)) { goto exit; }

    Py_SET_TYPE(&chunkType, &PyType_Type);
    if (0 > PyType_Ready(&chunkType)) { goto exit; }

    Py_SET_TYPE(&errorType, &PyType_Type);
    errorType.tp_base = (PyTypeObject *)PyExc_StandardError;
    if (0 > PyType_Ready(&errorType)) { goto exit; }

    if (!(psyco_null = Bytes_FromString("NULL"))) { goto exit; }

    /* initialize the module */
    module = PyModule_Create(&psycopgmodule);
    if (!module) { goto exit; }

    if (0 > add_module_constants(module)) { goto exit; }
    if (0 > add_module_types(module)) { goto exit; }
    if (0 > datetime_init()) { goto exit; }
    if (0 > encodings_init(module)) { goto exit; }
    if (0 > typecast_init(module)) { goto exit; }
    if (0 > adapters_init(module)) { goto exit; }
    if (0 > basic_errors_init(module)) { goto exit; }
    if (0 > sqlstate_errors_init(module)) { goto exit; }

    Dprintf("psycopgmodule: module initialization complete");

exit:
    return module;
}
