/*
   A Python interface to the MySQL database.

      Python: http://www.python.org
      MySQL:  http://www.tcx.se

   Copyright (C) 1998 Joerg Senekowitsch <senekow@ibm.net>

   Based on mySQLmodule-0.1.4 (see below)

   WARNING: This code is not compatible with mySQLmodule-0.1.4!!!

   This interface tested with Python 1.5.1 and MySQL 3.21.30
   on Linux RH5.0 (kernel 2.0.33). It works for me, YMMV.

   JS, May 1998

   Version 1.4 (JS 10/03/1998):
      - Another segfault fix from Richard Fish (rjf@estinc.com).
        STH reference counting off by one for server side storage.
      - Patch to return unsigned longs from Ladislav Lhotka (lhotka@jcu.cz).
        If the mysql field has the UNSIGNED attribute set, we need to suppress
	the sign bit evaluation and return a PyLong instead of a PyInt.
      - The home-made escape_chars() has been replaced by a call to
        mysql_escape_string(), requiring at least 3.21.30 for correct
        operation.
      - listfields() and fields() now return the information:
         o usign     - field has the UNSIGNED attribute set
      - Unsigned fields return PyLongs instead of PyInts.
      - Support compilation on NT systems by Nigel Head (nhead@houbits.com).
        See README.NT for details.
        Note: The NT port only works with MySQL version 3.22.8, while this
	      module has been developed for the 3.21 API. Thus, some of the
	      3.22 functionality is missing. In particular, the return
	      values of mysql_affected_rows() and mysql_insert_id() have
	      changed to my_ulonglong in 3.22, which will be represented by 
	      this module as standard signed PyInt potentially resulting in 
	      some odd behavior for DBs with more than 2**31-1 rows and/or
	      insert operations.

   Version 1.3 (JS 08/18/1998):
      - Fixed segfaults due to uninitialized field STH->res in DBH_query()
        (Thanks to Grisha Trubetskoy)
      - Added instructions on how to compile MySQLmodule as a shared module
        (Thanks to Trond Eivind Glomsrød)

   Version 1.2 (JS 07/16/1998):
      - Error checking on all mysql API calls
      - Tossed out useless includes which should make FreeBSD happy
      - Added __doc__ string and changed DbType and ResType to DBH_Type and STH_Type
      - DBH.do and DBH[...] now return affected_rows() if no result
      - listdbs(), listtables(), listfields(), and listprocesses() now return
        empty lists instead of None if there is no data.
      - Added DBHObjects:
         o close()
	 o insert_id()
      - listfields() and fields() now return more information:
         o pri      - primary key field
	 o notnull  - not null field
         o auto_inc - auto_increment field
	 o ukey     - unique key field
         o mkey     - multiple key field

   Version 1.1a (JS 06/30/98) [never released]:
      - Fixed bug in protoinfo()
      - Additional error checking in 
        listdbs(), listtables(), listfields(), listprocesses()

   Version 1.1 (JS 06/11/98):
      - Cleaner error handling after discussion with Monty (thanks!)
      - New feature:
         o DBH.selectdb(string[,int]) will now take optional integer
	   parameter to switch between client side (0, default)
	   and server side (>0) result set storage. This should
	   improve the behavior of low memory clients. Note that
	   this affects server performance negatively.
	 o DBH.query(string[,int]) will allow overriding the DB
	   selected result set storage method on a per cursor basis.

   Version 1.0a:
      - Hopefully all possible errors (Python & MySQL) are caught and handled.

   New features in version 1.0:
      - Name change to 'MySQL' so that this module does not clash with the
        old (incompatible) 'mySQL' module.
      - Plugged huge memory leak in listdbs, listtables, and listfields
      - Added wildcard argument to listdbs, listtables, and listfields
      - Allow \0 in queries (for inserting images!)
      - Added MySQLObjects:
         o escape()
      - Added DBHObjects:
         o stat()
         o clientinfo() [overwritten with module version string]
         o serverinfo()
         o hostinfo()
         o protoinfo()
         o listprocesses()
	 o do()         [former 'query']
	 o query()      [former 'querycursor']
      - Added STHObjects:
         o fetchrows([n])   [n<0: all records (default), n>0: only n records]
	 o fetchdict([n])   [same as fetchrows(). Returns list of dicts]
      - Removed STHObjects:
         o fetchall()
	 o fetchone()
	 o fetchmany()

   All database results are returned in a 'table', i.e. a list of lists
   (or list of dictionaries for fetchdict()).

********************************************************************************

 Copyright:
         Permission is granted to anyone to use this software for any purpose,
	 including commercial applications, and to alter it and redistribute it
	 freely, subject to the following restrictions:

	 1. The origin of this software must not be misrepresented;
	    you must not claim that you wrote the original software.
	 2. Altered source versions must be plainly marked as such, and must 
            not be misrepresented as being the original software.
	 3. This notice may not be removed from any source distribution.

 Joerg Senekowitsch <senekow@ibm.net>


 Notice: This code is based on mySQLmodule-0.1.4, which is
 
         Copyright (C) 1997  Joseph Skinner <joe@earthlink.co.nz>
	 Copyright (C) 1997  James Henstridge <james@daa.com.au>

	 mySQLmodule-0.1.4 is based on mSQLmodule, which is

	 Portions copyright (C) 1995  Thawte Consulting, cc
	 Portions copyright (C) 1994  Anthony Baxter.

	 See the file 'Credits' for details.

Disclaimer:
	 THE  AUTHOR  MAKES  NO  REPRESENTATIONS ABOUT  THE  SUITABILITY  OF
	 THE  SOFTWARE FOR  ANY  PURPOSE.  IT IS  PROVIDED  "AS IS"  WITHOUT
	 EXPRESS OR  IMPLIED WARRANTY.  THE AUTHOR DISCLAIMS  ALL WARRANTIES
	 WITH  REGARD TO  THIS  SOFTWARE, INCLUDING  ALL IMPLIED  WARRANTIES
	 OF   MERCHANTABILITY,  FITNESS   FOR  A   PARTICULAR  PURPOSE   AND
	 NON-INFRINGEMENT  OF THIRD  PARTY  RIGHTS. IN  NO  EVENT SHALL  THE
	 AUTHOR  BE LIABLE  TO  YOU  OR ANY  OTHER  PARTY  FOR ANY  SPECIAL,
	 INDIRECT,  OR  CONSEQUENTIAL  DAMAGES  OR  ANY  DAMAGES  WHATSOEVER
	 WHETHER IN AN  ACTION OF CONTRACT, NEGLIGENCE,  STRICT LIABILITY OR
	 ANY OTHER  ACTION ARISING OUT OF  OR IN CONNECTION WITH  THE USE OR
	 PERFORMANCE OF THIS SOFTWARE.

*/

#ifdef WIN32
typedef void *HANDLE;
#include "winsock.h"
#endif

#include "Python.h"
#include "mysql.h"

static char MySQL_Version[] = "MySQLmodule-1.4: A Python interface to the MySQL database.";

typedef struct MySQL_DBH {
    PyObject_HEAD
    MYSQL *handle;
    MYSQL connection;
    int dbh_use_result;
    struct MySQL_STH *sth;
} DBHObject;

typedef struct MySQL_STH {
    PyObject_HEAD
    MYSQL_RES *res;
#ifdef WIN32
    my_ulonglong affected_rows;
    my_ulonglong insert_id;
#else
    int affected_rows;
    int insert_id;
#endif
    int sth_use_result;
    struct MySQL_DBH *dbh;
} STHObject;

staticforward PyTypeObject DBH_Type;
staticforward PyTypeObject STH_Type;
static PyObject *MySQLError;

#define STHObject_Check(v)	((v)->ob_type == &STH_Type)
#define DBHObject_Check(v)	((v)->ob_type == &DBH_Type)


/*********************************************************
 ****** Helper routines
 *********************************************************/

static int clear_channel(self)
     STHObject *self;
{
    if (self->res) { /* only if we have a result */
        if (!mysql_eof(self->res)) {
            MYSQL_ROW dummy;
	    while (dummy = mysql_fetch_row(self->res)) continue;
	    /* check for error. make sure handle exists, though */
	    if (self->res->handle && mysql_error(self->res->handle)[0] != 0) {
	        PyErr_SetString(MySQLError,mysql_error(self->res->handle));
		return 1;
	    }
	}
    }
    return 0;
}

/* catch empty cursors (STH) */
static int no_response(self)
     STHObject *self;
{
    if (self->res == NULL) {
        PyErr_SetString(MySQLError,"no response body");
	return 1;
    }
    return 0;
}

/* Take a MYSQL_ROW and turn it into a list */
static PyObject *
pythonify_row(res, thisrow)
     MYSQL_RES *res;
     MYSQL_ROW thisrow;
{
    PyObject *rowlist, *fieldobj;
    MYSQL_FIELD *tf;
    int i, n;
    unsigned int *lengths;

    n = mysql_num_fields(res);
    lengths = mysql_fetch_lengths(res);
    if (lengths == NULL) {
        if (res->handle && mysql_error(res->handle)[0] != 0) {
	    PyErr_SetString(MySQLError,mysql_error(res->handle));
	} else {
	    PyErr_SetString(MySQLError, "pythonify_row: mysql_fetch_lengths() failed");
	}
        return NULL;
    }
    rowlist = PyList_New(n);
    if (rowlist == NULL) return NULL;
    mysql_field_seek(res, 0);
    for (i = 0; i < n; i++) {
        tf = mysql_fetch_field(res);
	if (tf == NULL) {
	    if (res->handle && mysql_error(res->handle)[0] != 0) {
	        PyErr_SetString(MySQLError,mysql_error(res->handle));
	    } else {
	        PyErr_SetString(MySQLError, "pythonify_row: mysql_fetch_field() failed");
	    }
	    Py_XDECREF(rowlist);
	    return NULL;
	}
        if (thisrow[i])
            switch (tf->type) {
                case FIELD_TYPE_SHORT:
                case FIELD_TYPE_LONG:
                    if (tf->flags & UNSIGNED_FLAG) {
                        fieldobj = PyLong_FromString(thisrow[i], (char **)NULL, 10);
                    } else {
                        fieldobj = PyInt_FromLong(atol(thisrow[i]));
                    }
		    if (fieldobj == NULL) {
		        Py_XDECREF(rowlist);
		        return NULL;
		    }
                    break;
                case FIELD_TYPE_CHAR:
                case FIELD_TYPE_STRING:
                case FIELD_TYPE_VAR_STRING:
                case FIELD_TYPE_DATE:
                case FIELD_TYPE_TIME:
                case FIELD_TYPE_DATETIME:
                case FIELD_TYPE_TIMESTAMP:
                    fieldobj = PyString_FromString(thisrow[i]);
		    if (fieldobj == NULL) {
		        Py_XDECREF(rowlist);
		        return NULL;
		    }
                    break;
                case FIELD_TYPE_TINY_BLOB:
                case FIELD_TYPE_MEDIUM_BLOB:
                case FIELD_TYPE_LONG_BLOB:
                case FIELD_TYPE_BLOB:
                    fieldobj = PyString_FromStringAndSize(thisrow[i],lengths[i]);
		    if (fieldobj == NULL) {
		        Py_XDECREF(rowlist);
		        return NULL;
		    }
                    break;
                case FIELD_TYPE_DECIMAL:
                case FIELD_TYPE_DOUBLE:
                case FIELD_TYPE_FLOAT:
                    fieldobj = PyFloat_FromDouble(atof(thisrow[i]));
		    if (fieldobj == NULL) {
		        Py_XDECREF(rowlist);
		        return NULL;
		    }
                    break;
                default:
                    fieldobj = PyString_FromString(thisrow[i]);
		    if (fieldobj == NULL) {
		        Py_XDECREF(rowlist);
		        return NULL;
		    }
                    break;
        } else {
            Py_INCREF(Py_None);
            fieldobj = Py_None;
        }
        if (PyList_SetItem(rowlist, i, fieldobj) == -1) {
	    Py_XDECREF(rowlist);
	    return NULL;
	}
    }
    return rowlist;
}

/* Take a MYSQL_RES and turn it into a table (list of lists).
   Depending on whether (res) is local (from a mysql_store_result)
   or remote (from mysql_use_result) we might see client/server
   errors on the mysql_fetch_row() call. If we receive a NULL
   from mysql_fetch_row() we must check whether this is "no more
   data" or indeed an error.

   If the error was generated by pythonify_row, rowlist will be
   NULL and PyErr will already be set.
*/

static PyObject *
pythonify_res(res, num)
     MYSQL_RES *res;
     int num;
{
    PyObject *reslist;
    PyObject *rowlist = NULL;
    MYSQL_ROW thisrow;
    int i = 0;

    reslist = PyList_New(0);
    if (reslist == NULL) return NULL;
    while ((i != num) && (thisrow = mysql_fetch_row(res))) {
        i++;
        rowlist = pythonify_row(res, thisrow);
	if (rowlist == NULL) goto error;
        if (PyList_Append(reslist, rowlist) == -1) goto error;
        Py_XDECREF(rowlist);
	rowlist = NULL;
    }
    if (thisrow == NULL && res->handle && mysql_error(res->handle)[0] != 0) {
        PyErr_SetString(MySQLError,mysql_error(res->handle));
        Py_XDECREF(reslist);
	return NULL;
    }
    return (reslist);

 error:
    Py_XDECREF(rowlist);
    Py_XDECREF(reslist);
    return NULL;
}

/* Take a MYSQL_RES and return a table of field data */
static PyObject *
pythonify_res_fields(res)
     MYSQL_RES *res;
{
    PyObject *reslist, *thislist;
    int i, n;
    char *type, flags[32];
    MYSQL_FIELD *tf;

    reslist = PyList_New(0);
    if (reslist == NULL) return NULL;
    n = mysql_num_fields(res);
    for (i = 0; i < n; i++) {
        tf = &(mysql_fetch_field_direct(res, i));
	if (tf == NULL) {
	    if (res->handle && mysql_error(res->handle)[0] != 0) {
	        PyErr_SetString(MySQLError,mysql_error(res->handle));
	    } else {
	        PyErr_SetString(MySQLError, "pythonify_res_fields: mysql_fetch_field_direct() failed");
	    }
	    Py_XDECREF(reslist);
	    return NULL;
	}
        switch (tf->type) {
            case FIELD_TYPE_SHORT:
                type = "short";
                break;
            case FIELD_TYPE_LONG:
                type = "long";
                break;
            case FIELD_TYPE_CHAR:
                type = "char";
                break;
            case FIELD_TYPE_DOUBLE:
                type = "double";
                break;
            case FIELD_TYPE_DECIMAL:
                type = "decimal";
                break;
            case FIELD_TYPE_FLOAT:
                type = "float";
                break;
            case FIELD_TYPE_TINY_BLOB:
                type = "tiny blob";
                break;
            case FIELD_TYPE_MEDIUM_BLOB:
                type = "medium blob";
                break;
            case FIELD_TYPE_LONG_BLOB:
                type = "long blob";
                break;
            case FIELD_TYPE_BLOB:
                type = "blob";
                break;
            case FIELD_TYPE_DATE:
                type = "date";
                break;
            case FIELD_TYPE_TIME:
                type = "time";
                break;
            case FIELD_TYPE_DATETIME:
                type = "datetime";
                break;
            case FIELD_TYPE_TIMESTAMP:
                type = "timestamp";
                break;
            case FIELD_TYPE_NULL:
            case FIELD_TYPE_LONGLONG:
            case FIELD_TYPE_INT24:
                type = "unhandled";
                break;
            case FIELD_TYPE_VAR_STRING:
                type = "varchar";
                break;
            case FIELD_TYPE_STRING:
                type = "string";
                break;
            default:
                type = "????";
                break;
        }
        flags[0] = 0;
        if (IS_PRI_KEY(tf->flags)) (void) strcpy(flags, "pri");
        if (IS_NOT_NULL(tf->flags))
	    flags[0] ? (void) strcat(flags, " notnull") : (void) strcpy(flags, "notnull");
        if ((tf->flags) & AUTO_INCREMENT_FLAG)
	    flags[0] ? (void) strcat(flags, " auto_inc") : (void) strcpy(flags, "auto_inc");
        if ((tf->flags) & UNSIGNED_FLAG)
	    flags[0] ? (void) strcat(flags, " usign") : (void) strcpy(flags, "usign");
        if ((tf->flags) & UNIQUE_KEY_FLAG)
            flags[0] ? (void) strcat(flags, " ukey") : (void) strcpy(flags, "ukey");
        if ((tf->flags) & MULTIPLE_KEY_FLAG)
            flags[0] ? (void) strcat(flags, " mkey") : (void) strcpy(flags, "mkey");
        thislist = Py_BuildValue("[sssis]", tf->name, tf->table, type, 
				 tf->length, flags);
	if (thislist == NULL) {
	    Py_XDECREF(reslist);
	    return NULL;
	}
        if (PyList_Append(reslist, thislist) == -1) {
	    Py_XDECREF(thislist);
	    Py_XDECREF(reslist);
	    return NULL;
	}
        Py_DECREF(thislist);
    }
    return (reslist);
}

/* concat three strings */
static void 
mystrcpy(field, table, sep, name)
     char *field;
     char *table;
     char *sep;
     char *name;
{
    char *s;
    s = field;
    while (!(*table == 0)) *s++ = *table++;
    while (!(*sep == 0)) *s++ = *sep++;
    while (!(*name == 0)) *s++ = *name++;
    *s = 0;
}

/********************************************************
 ***** MySQL methods
 ********************************************************/

static PyObject *
MySQL_connect(self, args)
     PyObject *self, *args;
{
    char *dbhost = NULL;
    char *dbuser = NULL;
    char *dbpass = NULL;
    DBHObject *DBH;

    if (!PyArg_ParseTuple(args, "|sss:connect", &dbhost, &dbuser, &dbpass)) return NULL;
    DBH = PyObject_NEW(DBHObject, &DBH_Type);
    if (DBH == NULL) return NULL;
    DBH->dbh_use_result = 0;
    DBH->handle = &DBH->connection;
    DBH->sth = NULL;
    if (!(mysql_connect(DBH->handle, dbhost, dbuser, dbpass))) {
        if (mysql_error(DBH->handle)[0] != 0) {
	    PyErr_SetString(MySQLError,mysql_error(DBH->handle));
	} else {
	    /* safety. should not be reached */
	    PyErr_SetString(MySQLError, "connect(): could not connect to MySQL");
	}
	Py_XDECREF(DBH);
	return NULL;
    }
    return ((PyObject *) DBH);
}

static PyObject *
MySQL_escape(self,args)
     PyObject *self, *args;
{
    char *in = NULL;
    char *out = NULL;
    PyObject *str;
    unsigned int size, len;
    if (!PyArg_ParseTuple(args, "s#:escape", &in, &size)) return NULL;
    /* wonder whether one should use PyMem_* routines instead of malloc/free */
    out = (char *) malloc(size*2+1);
    if (!out) {
        PyErr_SetString(MySQLError, "escape(): no memory");
	return NULL;
    }
    len = mysql_escape_string(out,in,size);
    str = Py_BuildValue("s#",out,len);
    free(out);
    return (str);
}

static struct PyMethodDef MySQL_Methods[] =
{
    {"connect", MySQL_connect, METH_VARARGS},
    {"escape", MySQL_escape, METH_VARARGS},
    {NULL, NULL}
};

/************************************************************
 ****** DBH methods
 ************************************************************/

static PyObject *
DBH_selectdb(self, args)
     DBHObject *self;
     PyObject *args;
{
    char *dbname;
    if (!PyArg_ParseTuple(args, "s|i:selectdb", &dbname, &(self->dbh_use_result))) return NULL;
    if (self->sth && clear_channel(self->sth)) return NULL;
    Py_XDECREF(self->sth);
    self->sth = NULL;
    if (mysql_select_db(self->handle, dbname)) {
        PyErr_SetString(MySQLError, mysql_error(self->handle));
        return NULL;
    }
    Py_INCREF(Py_None);
    return (Py_None);
}

static PyObject *
DBH_listdbs(self, args)
     DBHObject *self;
     PyObject *args;
{
    MYSQL_RES *res;
    PyObject *resobj;
    char *wildcard = NULL;

    if (!PyArg_ParseTuple(args, "|s:listdbs", &wildcard)) return NULL;
    if (self->sth && clear_channel(self->sth)) return NULL;
    Py_XDECREF(self->sth);
    self->sth = NULL;
    if ((res = mysql_list_dbs(self->handle, wildcard)) == NULL) {
        if (mysql_error(self->handle)[0] != 0) {
	    PyErr_SetString(MySQLError, mysql_error(self->handle));
	    return NULL;
	} else {
	    resobj = PyList_New(0);
	    return (resobj);
	}
    }
    resobj = pythonify_res(res,-1);
    mysql_free_result(res);
    return (resobj);
}

static PyObject *
DBH_listtables(self, args)
     DBHObject *self;
     PyObject *args;
{
    MYSQL_RES *res;
    PyObject *resobj;
    char *wildcard = NULL;

    if (!PyArg_ParseTuple(args, "|s:listtables", &wildcard)) return NULL;
    if (self->sth && clear_channel(self->sth)) return NULL;
    Py_XDECREF(self->sth);
    self->sth = NULL;
    if ((res = mysql_list_tables(self->handle, wildcard)) == NULL) {
        if (mysql_error(self->handle)[0] != 0) {
	    PyErr_SetString(MySQLError, mysql_error(self->handle));
	    return NULL;
	} else {
	    resobj = PyList_New(0);
	    return (resobj);
	}
    }
    resobj = pythonify_res(res,-1);
    mysql_free_result(res);
    return (resobj);
}

static PyObject *
DBH_listfields(self, args)
     DBHObject *self;
     PyObject *args;
{
    char *tname;
    char *wildcard = NULL;
    MYSQL_RES *res;
    PyObject *resobj;

    if (!PyArg_ParseTuple(args, "s|s:listfields", &tname, &wildcard)) return NULL;
    if (self->sth && clear_channel(self->sth)) return NULL;
    Py_XDECREF(self->sth);
    self->sth = NULL;
    if ((res = mysql_list_fields(self->handle, tname, wildcard)) == NULL) {
        if (mysql_error(self->handle)[0] != 0) {
	    PyErr_SetString(MySQLError, mysql_error(self->handle));
	    return NULL;
	} else {
	    resobj = PyList_New(0);
	    return (resobj);
	}
    }
    resobj = pythonify_res_fields(res);
    mysql_free_result(res);
    return (resobj);
}

static PyObject *
DBH_listprocesses(self, args)
     DBHObject *self;
     PyObject *args;
{
    MYSQL_RES *res;
    PyObject *resobj;

    if (!PyArg_ParseTuple(args, ":listprocesses")) return NULL;
    if (self->sth && clear_channel(self->sth)) return NULL;
    Py_XDECREF(self->sth);
    self->sth = NULL;
    if ((res = mysql_list_processes(self->handle)) == NULL) {
        if (mysql_error(self->handle)[0] != 0) {
	    PyErr_SetString(MySQLError, mysql_error(self->handle));
	    return NULL;
	} else {
	    resobj = PyList_New(0);
	    return (resobj);
	}
    }
    resobj = pythonify_res(res,-1);
    mysql_free_result(res);
    return (resobj);
}

static PyObject *
DBH_query_helper(self, query, size)
     DBHObject *self;
     char *query;
     unsigned int size;
{
    MYSQL_RES *res;
    PyObject *resobj;

    if (self->sth && clear_channel(self->sth)) return NULL;
    Py_XDECREF(self->sth);
    self->sth = NULL;
    if (mysql_real_query(self->handle, query, size)) {
        PyErr_SetString(MySQLError, mysql_error(self->handle));
        return NULL;
    }
    if (self->dbh_use_result) {
        res = mysql_use_result(self->handle);
    } else {
        res = mysql_store_result(self->handle);
    }
    if (mysql_error(self->handle)[0] != 0) {
        PyErr_SetString(MySQLError,mysql_error(self->handle));
	if (res) mysql_free_result(res);
	return NULL;
    }
    if (res == NULL) { /* query did not return a result, return affected_rows */
        return (PyInt_FromLong((long) mysql_affected_rows(self->handle)));
    }
    resobj = pythonify_res(res,-1);
    mysql_free_result(res);
    return (resobj);
}

static PyObject *
DBH_do(self, args)
     DBHObject *self;
     PyObject *args;
{
    char *query;
    unsigned int size;

    if (!PyArg_ParseTuple(args, "s#:do", &query, &size)) return NULL;
    return DBH_query_helper(self, query, size);
}

static PyObject *
DBH_create(self, args)
     DBHObject *self;
     PyObject *args;
{
    char *dbname;

    if (!PyArg_ParseTuple(args, "s:create", &dbname)) return NULL;
    if (self->sth && clear_channel(self->sth)) return NULL;
    Py_XDECREF(self->sth);
    self->sth = NULL;
    if (mysql_create_db(self->handle, dbname)) {
        PyErr_SetString(MySQLError, mysql_error(self->handle));
        return NULL;
    }
    Py_INCREF(Py_None);
    return (Py_None);
}

static PyObject *
DBH_close(self, args)
     DBHObject *self;
     PyObject *args;
{
    if (!PyArg_ParseTuple(args, ":close")) return NULL;
    if (self->sth && clear_channel(self->sth)) return NULL;
    Py_XDECREF(self->sth);
    self->sth = NULL;
    mysql_close(self->handle); /* unconditional */
    Py_INCREF(Py_None);
    return (Py_None);
}

static PyObject *
DBH_insertid(self, args)
     DBHObject *self;
     PyObject *args;
{
    if (!PyArg_ParseTuple(args, ":insertid")) return NULL;
    if (self->sth && clear_channel(self->sth)) return NULL;
    Py_XDECREF(self->sth);
    self->sth = NULL;
    return (PyInt_FromLong((long) mysql_insert_id(self->handle)));
}

static PyObject *
DBH_stat(self, args)
     DBHObject *self;
     PyObject *args;
{
    char *stat;
    if (!PyArg_ParseTuple(args, ":stat")) return NULL;
    if (self->sth && clear_channel(self->sth)) return NULL;
    Py_XDECREF(self->sth);
    self->sth = NULL;
    if ((stat = mysql_stat(self->handle)) == NULL) {
        PyErr_SetString(MySQLError, mysql_error(self->handle));
        return NULL;
    }
    return (PyString_FromString(stat));
}

static PyObject *
DBH_clientinfo(self, args)
     DBHObject *self;
     PyObject *args;
{
    char *info;
    if (!PyArg_ParseTuple(args, ":clientinfo")) return NULL;
/* MySQL returns the server version on this call :-(
    if (self->sth && clear_channel(self->sth)) return NULL;
    Py_XDECREF(self->sth);
    self->sth = NULL;
    if ((info = mysql_get_client_info()) == NULL) {
        PyErr_SetString(MySQLError, mysql_error(self->handle));
        return NULL;
    }
*/
    return (PyString_FromString(MySQL_Version));
}

static PyObject *
DBH_hostinfo(self, args)
     DBHObject *self;
     PyObject *args;
{
    char *info;
    if (!PyArg_ParseTuple(args, ":hostinfo")) return NULL;
    if (self->sth && clear_channel(self->sth)) return NULL;
    Py_XDECREF(self->sth);
    self->sth = NULL;
    if ((info = mysql_get_host_info(self->handle)) == NULL) {
        PyErr_SetString(MySQLError, mysql_error(self->handle));
        return NULL;
    }
    return (PyString_FromString(info));
}

static PyObject *
DBH_serverinfo(self, args)
     DBHObject *self;
     PyObject *args;
{
    char *info;
    if (!PyArg_ParseTuple(args, ":serverinfo")) return NULL;
    if (self->sth && clear_channel(self->sth)) return NULL;
    Py_XDECREF(self->sth);
    self->sth = NULL;
    if ((info = mysql_get_server_info(self->handle)) == NULL) {
        PyErr_SetString(MySQLError, mysql_error(self->handle));
        return NULL;
    }
    return (PyString_FromString(info));
}

static PyObject *
DBH_protoinfo(self, args)
     DBHObject *self;
     PyObject *args;
{
    int info;
    if (!PyArg_ParseTuple(args, ":protoinfo")) return NULL;
    if (self->sth && clear_channel(self->sth)) return NULL;
    Py_XDECREF(self->sth);
    self->sth = NULL;
    if (!(info = mysql_get_proto_info(self->handle))) {
        PyErr_SetString(MySQLError, mysql_error(self->handle));
        return NULL;
    }
    return (PyInt_FromLong((long) info));
}

static PyObject *
DBH_drop(self, args)
     DBHObject *self;
     PyObject *args;
{
    char *dbname;

    if (!PyArg_ParseTuple(args, "s:drop", &dbname)) return NULL;
    if (self->sth && clear_channel(self->sth)) return NULL;
    Py_XDECREF(self->sth);
    self->sth = NULL;
    if (mysql_drop_db(self->handle, dbname)) {
        PyErr_SetString(MySQLError, mysql_error(self->handle));
        return NULL;
    }
    Py_INCREF(Py_None);
    return (Py_None);
}

static PyObject *
DBH_reload(self, args)
     DBHObject *self;
     PyObject *args;
{
    if (!PyArg_ParseTuple(args, ":reload")) return NULL;
    if (self->sth && clear_channel(self->sth)) return NULL;
    Py_XDECREF(self->sth);
    self->sth = NULL;
    if (mysql_reload(self->handle)) {
        PyErr_SetString(MySQLError, mysql_error(self->handle));
        return NULL;
    }
    Py_INCREF(Py_None);
    return (Py_None);
}

static PyObject *
DBH_shutdown(self, args)
     DBHObject *self;
     PyObject *args;
{
    if (!PyArg_ParseTuple(args, ":shutdown")) return NULL;
    if (self->sth && clear_channel(self->sth)) return NULL;
    Py_XDECREF(self->sth);
    self->sth = NULL;
    if (mysql_shutdown(self->handle)) {
        PyErr_SetString(MySQLError, mysql_error(self->handle));
        return NULL;
    }
    Py_INCREF(Py_None);
    return (Py_None);
}

/* return a cursor object (STH) for the given query */
static PyObject *
DBH_query(self, args)
     DBHObject *self;
     PyObject *args;
{
    STHObject *sth;
    char *query;
    unsigned int size;
    sth = PyObject_NEW(STHObject, &STH_Type);
    if (sth == NULL) return NULL;
    sth->sth_use_result = self->dbh_use_result;
    sth->dbh = NULL;
    sth->res = NULL;
    if (!PyArg_ParseTuple(args, "s#|i:query", &query, &size, &(sth->sth_use_result))) {
        Py_XDECREF(sth);
	return NULL;
    }
    if (self->sth && clear_channel(self->sth)) {
        Py_XDECREF(sth);
        return NULL;
    }
    Py_XDECREF(self->sth);
    self->sth = NULL;
    if (mysql_real_query(self->handle, query, size)) {
        PyErr_SetString(MySQLError, mysql_error(self->handle));
	Py_XDECREF(sth);
        return NULL;
    }
    if (sth->sth_use_result) {
        sth->res = mysql_use_result(self->handle);
	if (mysql_error(self->handle)[0] != 0) {
	    PyErr_SetString(MySQLError,mysql_error(self->handle));
	    Py_XDECREF(sth);
	    return NULL;
	}
	sth->dbh = self;
	Py_XINCREF(self);
	self->sth = sth;
	Py_XINCREF(sth);
    } else {
        sth->res = mysql_store_result(self->handle);
	if (mysql_error(self->handle)[0] != 0) {
	    PyErr_SetString(MySQLError,mysql_error(self->handle));
	    Py_XDECREF(sth);
	    return NULL;
	}
    }
    sth->affected_rows = mysql_affected_rows(self->handle);
    sth->insert_id = mysql_insert_id(self->handle);
    return (PyObject *) sth;
}

/* For DBH['query'] syntax */
static PyObject *
DBH_subscript(self, subs)
     PyObject *self, *subs;
{
    char *query;
    unsigned int size;
    if (!PyArg_Parse(subs, "s#:subscript", &query, &size)) {
        PyErr_SetString(MySQLError, "subscript expects a query string");
        return NULL;
    }
    return DBH_query_helper(self, query, size);
}

static int
DBH_subscript_assign(self, subs, val)
     PyObject *self, *subs, *val;
{
    PyErr_SetString(MySQLError, "MySQL handle is readonly");
    return -1;  /* -1 is error code in interpreter main loop! (ceval.c) */
}

static int
DBH_len(self, subs)
     PyObject *self, *subs;
{
    PyErr_SetString(MySQLError, "len() of unsized object");
    return -1;
}

static struct PyMethodDef DBH_methods[] =
{
    {"selectdb", (PyCFunction) DBH_selectdb, METH_VARARGS},
    {"do", (PyCFunction) DBH_do, METH_VARARGS},
    {"query", (PyCFunction) DBH_query, METH_VARARGS},
    {"listdbs", (PyCFunction) DBH_listdbs, METH_VARARGS},
    {"listtables", (PyCFunction) DBH_listtables, METH_VARARGS},
    {"listfields", (PyCFunction) DBH_listfields, METH_VARARGS},
    {"listprocesses", (PyCFunction) DBH_listprocesses, METH_VARARGS},
    {"create", (PyCFunction) DBH_create, METH_VARARGS},
    {"stat", (PyCFunction) DBH_stat, METH_VARARGS},
    {"clientinfo", (PyCFunction) DBH_clientinfo, METH_VARARGS},
    {"hostinfo", (PyCFunction) DBH_hostinfo, METH_VARARGS},
    {"serverinfo", (PyCFunction) DBH_serverinfo, METH_VARARGS},
    {"protoinfo", (PyCFunction) DBH_protoinfo, METH_VARARGS},
    {"drop", (PyCFunction) DBH_drop, METH_VARARGS},
    {"reload", (PyCFunction) DBH_reload, METH_VARARGS},
    {"insert_id", (PyCFunction) DBH_insertid, METH_VARARGS},
    {"close", (PyCFunction) DBH_close, METH_VARARGS},
    {"shutdown", (PyCFunction) DBH_shutdown, METH_VARARGS},
    {NULL, NULL}                       /* sentinel */
};

static PyMappingMethods mysql_as_mapping =
{
    (inquiry) DBH_len,                     /*length */
    (binaryfunc) DBH_subscript,            /*subscript */
    (objobjargproc) DBH_subscript_assign,  /*assign subscript */
};

static PyObject *
DBH_getattr(self, name)
     DBHObject *self;
     char *name;
{
    return Py_FindMethod(DBH_methods, (PyObject *) self, name);
}

static void
DBH_dealloc(self)
     register DBHObject *self;
{
    mysql_close(self->handle);
    PyMem_DEL(self);
}

static PyTypeObject DBH_Type =
{
    PyObject_HEAD_INIT(NULL)
    0,
    "DBHObject",
    sizeof(DBHObject),
    0,
    (destructor) DBH_dealloc,          /*tp_dealloc */
    0,                                 /*tp_print */
    (getattrfunc) DBH_getattr,         /*tp_getattr */
    0,                                 /*tp_setattr */
    0,                                 /*tp_compare */
    0,                                 /*tp_repr */
    0,                                 /*tp_as_number */
    0,                                 /*tp_as_sequence */
    &mysql_as_mapping,                 /*tp_as_mapping */
};

/*************************************************************
****   STH methods 
**************************************************************/

static PyObject *
STH_fields(self, args)
     STHObject *self;
     PyObject *args;
{
    PyObject *resobj;
    if ((!PyArg_ParseTuple(args, ":fields")) || no_response(self)) return NULL;
    resobj = pythonify_res_fields(self->res);
    if (resobj == NULL) {
	mysql_free_result(self->res);
	self->res = NULL;
    }
    return (resobj);
}

static PyObject *
STH_fetchrows(self, args)
     STHObject *self;
     PyObject *args;
{
    PyObject *resobj;
    int i = -1;
    if ((!PyArg_ParseTuple(args, "|i:fetchrows", &i)) || no_response(self)) return NULL;
    if (i < 0 && self->sth_use_result == 0) mysql_data_seek(self->res,0);
    resobj = pythonify_res(self->res, i);
    if (resobj == NULL) {
	mysql_free_result(self->res);
	self->res = NULL;
    }
    return (resobj);
}

static PyObject *
STH_fetchdict(self,args)
     STHObject *self;
     PyObject *args;
{
    int i = -1;
    int tlen = 0;
    int j, flen, rows, cols;
    char *fieldname = NULL;
    PyObject *rowdict = NULL;
    PyObject *datalist, *rowlist, *value;
    MYSQL_FIELD *tf;

    if ((!PyArg_ParseTuple(args, "|i:fetchdict", &i)) || no_response(self)) return NULL;
    if (i < 0 && self->sth_use_result == 0) mysql_data_seek(self->res,0);
    datalist = pythonify_res(self->res,i);
    if (datalist == NULL) {
        mysql_free_result(self->res);
	self->res = NULL;
	return NULL;
    }
    rows = PyList_Size(datalist);
    if (rows > 0) {
        cols = mysql_num_fields(self->res);
        for (j=0; j<cols; j++) {
 	    tf = &(mysql_fetch_field_direct(self->res,j));
	    if (tf == NULL) {
		if (self->res->handle && mysql_error(self->res->handle)[0] != 0) {
		    PyErr_SetString(MySQLError,mysql_error(self->res->handle));
		} else {
		    PyErr_SetString(MySQLError, "fetchdict: mysql_fetch_field_direct() failed");
		}
		goto error;
	    }
	    flen = strlen(tf->table) + strlen(tf->name);
	    tlen = flen > tlen ? flen : tlen;
	}
	if ((fieldname = (char *) malloc(tlen+2)) == NULL) {
	    PyErr_SetString(MySQLError,"fetchdict(): no memory (fieldname)");
	    goto error;
	}
	for (i=0; i<rows; i++) {
	    rowdict = PyDict_New();
	    if (rowdict == NULL) goto error;
	    rowlist = PyList_GetItem(datalist,i);
	    if (rowlist == NULL) goto error;
	    for (j=0; j<cols; j++) {
	        tf = &(mysql_fetch_field_direct(self->res,j));
		if (tf == NULL) {
		    if (self->res->handle && mysql_error(self->res->handle)[0] != 0) {
		        PyErr_SetString(MySQLError,mysql_error(self->res->handle));
		    } else {
		        PyErr_SetString(MySQLError,"fetchdict(): mysql_fetch_field_direct() failed");
		    }
		    goto error;
		}
		mystrcpy(fieldname,tf->table,".",tf->name);
		value = PyList_GetItem(rowlist,j);
		if (value == NULL) goto error;
		if (PyDict_SetItemString(rowdict,fieldname,value)) goto error;
	    }
	    if (PyList_SetItem(datalist,i,rowdict)) goto error;
	}
	free(fieldname);
    }
    return (datalist);

 error:
    Py_XDECREF(datalist);
    Py_XDECREF(rowdict);
    if (fieldname) free(fieldname);
    return NULL;
}

static PyObject *
STH_seek(self, args)
     STHObject *self;
     PyObject *args;
{
    unsigned int i;
    if ((!PyArg_ParseTuple(args, "i:seek", &i)) || no_response(self)) return NULL;
    if (self->sth_use_result) {
        PyErr_SetString(MySQLError, "STH_seek: cannot seek on server");
	return NULL;
    }
    mysql_data_seek(self->res, i);
    Py_INCREF(Py_None);
    return Py_None;
}

static PyObject *
STH_numrows(self, args)
     STHObject *self;
     PyObject *args;
{
    if ((!PyArg_ParseTuple(args, ":numrows")) || no_response(self)) return NULL;
    return PyInt_FromLong((long) mysql_num_rows(self->res));
}

static PyObject *
STH_numfields(self, args)
     STHObject *self;
     PyObject *args;
{
    if ((!PyArg_ParseTuple(args, ":numfields")) || no_response(self)) return NULL;
    return PyInt_FromLong((long) mysql_num_fields(self->res));
}

static PyObject *
STH_eof(self, args)
     STHObject *self;
     PyObject *args;
{
    if ((!PyArg_ParseTuple(args, ":eof")) || no_response(self)) return NULL;
    if (mysql_eof(self->res)) {   /* only useful if mysql_use_result() has been used */
        Py_INCREF(Py_True);
        return Py_True;
    } else {
        Py_INCREF(Py_False);
        return Py_False;
    }
}

static PyObject *
STH_affectedrows(self, args)
     STHObject *self;
     PyObject *args;
{
    if (!PyArg_ParseTuple(args, ":affectedrows")) return NULL;
    return PyInt_FromLong((long) self->affected_rows);
}

static PyObject *
STH_insertid(self, args)
    STHObject *self;
    PyObject *args;
{
    if (!PyArg_ParseTuple(args, ":insertid")) return NULL;
    return PyInt_FromLong((long) self->insert_id);
}

static struct PyMethodDef STH_methods[] =
{
    {"fields", (PyCFunction) STH_fields, METH_VARARGS},
    {"fetchrows", (PyCFunction) STH_fetchrows, METH_VARARGS},
    {"fetchdict", (PyCFunction) STH_fetchdict, METH_VARARGS},
    {"seek", (PyCFunction) STH_seek, METH_VARARGS},
    {"numrows", (PyCFunction) STH_numrows, METH_VARARGS},
    {"numfields", (PyCFunction) STH_numfields, METH_VARARGS},
    {"eof", (PyCFunction) STH_eof, METH_VARARGS},
    {"affectedrows", (PyCFunction) STH_affectedrows, METH_VARARGS},
    {"insert_id", (PyCFunction) STH_insertid, METH_VARARGS},
    {NULL, NULL}
};

static PyObject *
STH_getattr(self, name)
     STHObject *self;
     char *name;
{
    return Py_FindMethod(STH_methods, (PyObject *) self, name);
}

static void
STH_dealloc(self)
     register STHObject *self;
{
    if (self->res) mysql_free_result(self->res);
    Py_XDECREF(self->dbh);
    PyMem_DEL(self);
}

static PyTypeObject STH_Type =
{
    PyObject_HEAD_INIT(NULL)
    0,
    "STHObject",
    sizeof(STHObject),
    0,
    (destructor) STH_dealloc,          /*tp_dealloc */
    0,                                 /*tp_print */
    (getattrfunc) STH_getattr,         /*tp_getattr */
    0,                                 /*tp_setattr */
    0,                                 /*tp_compare */
    0,                                 /*tp_repr */
    0,                                 /*tp_as_number */
    0,                                 /*tp_as_sequence */
    0,                                 /*tp_as_mapping */
};

/***********************************************************
 ******** Module initialization
 ***********************************************************/

void
initMySQL()
{
    PyObject *module, *dict, *str;

    DBH_Type.ob_type = &PyType_Type;
    STH_Type.ob_type = &PyType_Type;

    module = Py_InitModule("MySQL", MySQL_Methods);
    dict = PyModule_GetDict(module);

    if (PyDict_SetItemString(dict, "DBH_Type", (PyObject *) & DBH_Type) != 0)
        Py_FatalError("Cannot add to MySQL dictionary");
    if (PyDict_SetItemString(dict, "STH_Type", (PyObject *) & STH_Type) != 0)
        Py_FatalError("Cannot add to MySQL dictionary");

    str = PyString_FromString(MySQL_Version);
    if (PyDict_SetItemString(dict, "__doc__", str) != 0)
        Py_FatalError("Cannot add to MySQL dictionary");
    Py_XDECREF(str);

    MySQLError = PyErr_NewException("MySQL.error",NULL,NULL);

    if (PyDict_SetItemString(dict, "error", MySQLError) != 0)
        Py_FatalError("Cannot add to MySQL dictionary");
}
