From 64933f2004350b7c3d9221c1a8d002e0f5ce3cc0 Mon Sep 17 00:00:00 2001 From: Federico Di Gregorio Date: Fri, 1 Sep 2006 13:46:57 +0000 Subject: [PATCH] Merge from 2_0_x branch up to r814. --- ChangeLog | 111 +++++++++++++++++++++++++++++++++++++ NEWS | 26 +++++++++ ZPsycopgDA/DA.py | 14 ++++- ZPsycopgDA/db.py | 34 +++++------- lib/extensions.py | 14 ++++- lib/pool.py | 16 ++++++ psycopg/adapter_binary.c | 18 +++--- psycopg/adapter_datetime.c | 11 ++-- psycopg/adapter_list.c | 2 +- psycopg/adapter_qstring.c | 10 ++-- psycopg/connection_int.c | 2 +- psycopg/connection_type.c | 3 + psycopg/cursor_type.c | 74 ++++++++++++++++++++----- psycopg/pqpath.c | 2 +- psycopg/psycopgmodule.c | 27 +++++++-- psycopg/typecast_array.c | 8 ++- psycopg/typecast_basic.c | 13 ++--- sandbox/array.py | 35 +++++++----- sandbox/gtk.py | 19 +++++++ sandbox/textfloat.py | 9 +++ setup.py | 3 +- tests/types_basic.py | 3 + 22 files changed, 364 insertions(+), 90 deletions(-) create mode 100644 sandbox/gtk.py create mode 100644 sandbox/textfloat.py diff --git a/ChangeLog b/ChangeLog index d6f5c494..6588a987 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,115 @@ +2006-09-01 Federico Di Gregorio + + * Release 2.0.5. + + * Fixed patch from #119, see tracker for details. + + * Preparing release 2.0.5. + + * psycopg/psycopgmodule.c: fixed filling of connection errors + to include OperationalError. + + * setup.py: removed pydatetime option from initialize_options + to make sure that the value in setup.cfg is used. + + * psycopg/psycopgmodule.c: applied patch from jdahlin (#120) + to have .connect() accept either a string or int as the port + parameter. + + * psycopg/adapter_binary.c: applied patch from jdahlin (#119) + to fix the segfault on empty binary buffers. + + * psycopg/connection_type.c: added .status attribute to expose + the internal status. + + * psycopg/pqpath.c: applied patch from intgr (#117) to fix + segfault on null queries. + + * psycopg/cursor_type.c: applied patch from intgr (#116) to + fix bad keyword naming and segfault in .executemany(). + + * ZPsycopgDA/DA.py: applied ImageFile patch from Charlie + Clark. + + * lib/pool.py: applied logging patch from Charlie Clark. + It will probably get a makeup and be moved to the top-level + module later. + +2006-08-02 Federico Di Gregorio + + * Release 2.0.4. + + * Fixed bug in float conversion (check for NULL string was + erroneously removed in 2.0.3!) + +2006-07-31 Federico Di Gregorio + + * Release 2.0.3. + + * psycopg/cursor_type.c: applied patch from jbellis (#113) to + allow column selection in .copy_from(). + + * psycopg/psycopgmodule.c: fixed memory leak in custom exceptions + (applied patch from #114). + +2006-07-26 Federico Di Gregorio + + * psycopg/adapter_datetime.c (pydatetime_str): fixed error + in conversion of microseconds for intervals and better algo + (thanks to Mario Frasca.) + +2006-06-18 Federico Di Gregorio + + * psycopg/adapter_binary.c: same as below. + + * psycopg/adapter_qstring.c: does not segfault anymore if + .getquoted() is called without preparing the qstring with + the connection. + +2006-06-15 Federico Di Gregorio + + * psycopg/typecast_basic.c: fixed problem with bogus + conversion when importing gtk (that was crazy, I didn't + understand why it happened but the new code just fixes it.) + + * ZPsycopgDA/db.py: better type analisys, using an hash + instead of a series of if (variation on patch from Charlie + Clark.) + +2006-06-11 Federico Di Gregorio + + * Release 2.0.2. + + * psycopg/typecast_array.c (typecast_array_cleanup): fixed a + problem with typecast_array_cleanup always returning the new + string length shorter by 1 (Closes: #93). + + * psycopg/adapter_binary.c: as below. + + * psycopg/adapter_qstring.c: wrapped #warning in #ifdef __GCC__ + because other compilers don't have it and it will just break + compilation (patch from jason, our great win32 builder). + + * psycopg/adapter_list.c: applied patch to adapt an empty list + into an empty array and not to NULL (from iGGy, closes: #108). + + * psycopg/cursor_type.c: applied patch from wkv to avoid + under-allocating query space when the parameters are not of the + right type (Closes: #110). + + * psycopg/connection_int.c: applied patch from wkv to avoid off + by one allocation of connection encoding string (Closes: #109). + +2006-06-09 Federico Di Gregorio + + * Release 2.0.1. + + * Fixed some buglets in ZPsycopgDA (was unable to load due + to shorter version number in psycopg module.) + 2006-06-08 Federico Di Gregorio + + * Release 2.0. * ZPsycopgDA/DA.py: removed Browse table for 2.0 release; we'll add it back later. diff --git a/NEWS b/NEWS index 84f022d4..99ae6661 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,29 @@ +What's new in psycopg 2.0.4 +--------------------------- + +* Fixed float conversion bug introduced in 2.0.3. + +What's new in psycopg 2.0.3 +--------------------------- + +* Fixed various buglets and a memory leak (see ChangeLog for details) + +What's new in psycopg 2.0.2 +--------------------------- + +* Fixed a bug in array typecasting that sometimes made psycopg forget about + the last element in the array. + +* Fixed some minor buglets in string memory allocations. + +* Builds again with compilers different from gcc (#warning about PostgreSQL + version is issued only if __GCC__ is defined.) + +What's new in psycopg 2.0.1 +--------------------------- + +* ZPsycopgDA now actually loads. + What's new in psycopg 2.0 ------------------------- diff --git a/ZPsycopgDA/DA.py b/ZPsycopgDA/DA.py index 91159e86..2a0f4dd1 100644 --- a/ZPsycopgDA/DA.py +++ b/ZPsycopgDA/DA.py @@ -18,7 +18,7 @@ # See the LICENSE file for details. -ALLOWED_PSYCOPG_VERSIONS = ('2.0',) +ALLOWED_PSYCOPG_VERSIONS = ('2.0.1', '2.0.2', '2.0.3', '2.0.4', '2.0.5') import sys import time @@ -30,11 +30,21 @@ import Shared.DC.ZRDB.Connection from db import DB from Globals import HTMLFile -from ImageFile import ImageFile from ExtensionClass import Base from App.Dialogs import MessageDialog from DateTime import DateTime +# Build Zope version in a float for later cheks +import App +zope_version = App.version_txt.getZopeVersion() +zope_version = float("%s.%s" %(zope_version[:2])) + +# ImageFile is deprecated in Zope >= 2.9 +if zope_version < 2.9: + from ImageFile import ImageFile +else: + from App.ImageFile import ImageFile + # import psycopg and functions/singletons needed for date/time conversions import psycopg2 diff --git a/ZPsycopgDA/db.py b/ZPsycopgDA/db.py index 2c90ab8c..9a0b4b02 100644 --- a/ZPsycopgDA/db.py +++ b/ZPsycopgDA/db.py @@ -26,7 +26,7 @@ import site import pool import psycopg2 -from psycopg2.extensions import INTEGER, LONGINTEGER, FLOAT, BOOLEAN, DATE +from psycopg2.extensions import INTEGER, LONGINTEGER, FLOAT, BOOLEAN, DATE, TIME from psycopg2 import NUMBER, STRING, ROWID, DATETIME @@ -42,6 +42,7 @@ class DB(TM, dbi_db.DB): self.encoding = enc self.failures = 0 self.calls = 0 + self.make_mappings() def getconn(self, create=True): conn = pool.getconn(self.dsn) @@ -89,32 +90,23 @@ class DB(TM, dbi_db.DB): def sortKey(self): return 1 + def make_mappings(self): + """Generate the mappings used later by self.convert_description().""" + self.type_mappings = {} + for t, s in [(INTEGER,'i'), (LONGINTEGER, 'i'), (NUMBER, 'n'), + (BOOLEAN,'n'), (ROWID, 'i'), + (DATETIME, 'd'), (DATE, 'd'), (TIME, 'd')]: + for v in t.values: + self.type_mappings[v] = (t, s) + def convert_description(self, desc, use_psycopg_types=False): """Convert DBAPI-2.0 description field to Zope format.""" items = [] for name, typ, width, ds, p, scale, null_ok in desc: - if typ == NUMBER: - if typ == INTEGER or typ == LONGINTEGER: - typs = 'i' - else: - typs = 'n' - typp = NUMBER - elif typ == BOOLEAN: - typs = 'n' - typp = BOOLEAN - elif typ == ROWID: - typs = 'i' - typp = ROWID - # FIXME: shouldn't DATETIME include other types? - elif typ == DATETIME or typ == DATE or typ == TIME: - typs = 'd' - typp = DATETIME - else: - typs = 's' - typp = STRING + m = self.type_mappings.get(typ, (STRING, 's')) items.append({ 'name': name, - 'type': use_psycopg_types and typp or typs, + 'type': use_psycopg_types and m[0] or m[1], 'width': width, 'precision': p, 'scale': scale, diff --git a/lib/extensions.py b/lib/extensions.py index b4ade2ca..9233d1d7 100644 --- a/lib/extensions.py +++ b/lib/extensions.py @@ -43,14 +43,24 @@ from _psycopg import string_types, binary_types, new_type, register_type from _psycopg import ISQLQuote """Isolation level values.""" -ISOLATION_LEVEL_AUTOCOMMIT = 0 +ISOLATION_LEVEL_AUTOCOMMIT = 0 ISOLATION_LEVEL_READ_COMMITTED = 1 -ISOLATION_LEVEL_SERIALIZABLE = 2 +ISOLATION_LEVEL_SERIALIZABLE = 2 # PostgreSQL maps the the other standard values to already defined levels ISOLATION_LEVEL_REPEATABLE_READ = ISOLATION_LEVEL_SERIALIZABLE ISOLATION_LEVEL_READ_UNCOMMITTED = ISOLATION_LEVEL_READ_COMMITTED +"""Transaction status values.""" +STATUS_SETUP = 0 +STATUS_READY = 1 +STATUS_BEGIN = 2 +STATUS_SYNC = 3 +STATUS_ASYNC = 4 + +# This is a usefull mnemonic to check if the connection is in a transaction +STATUS_IN_TRANSACTION = STATUS_BEGIN + def register_adapter(typ, callable): """Register 'callable' as an ISQLQuote adapter for type 'typ'.""" diff --git a/lib/pool.py b/lib/pool.py index b51d2555..0468db6d 100644 --- a/lib/pool.py +++ b/lib/pool.py @@ -19,11 +19,27 @@ This module implements thread-safe (and not) connection pools. import psycopg2 try: + import logging + # do basic initialization if the module is not already initialized + logging.basicConfig(level=logging.INFO, + format='%(asctime)s %(levelname)s %(message)s') + # create logger object for psycopg2 module and sub-modules + _logger = logging.getLogger("psycopg2") + def dbg(*args): + _logger.debug("psycopg2", ' '.join([str(x) for x in args])) + try: + import App # does this make sure that we're running in Zope? + _logger.info("installed. Logging using Python logging module") + except: + _logger.debug("installed. Logging using Python logging module") + +except ImportError: from zLOG import LOG, DEBUG, INFO def dbg(*args): LOG('ZPsycopgDA', DEBUG, "", ' '.join([str(x) for x in args])+'\n') LOG('ZPsycopgDA', INFO, "Installed", "Logging using Zope's zLOG\n") + except: import sys def dbg(*args): diff --git a/psycopg/adapter_binary.c b/psycopg/adapter_binary.c index 852e6289..0fc8101e 100644 --- a/psycopg/adapter_binary.c +++ b/psycopg/adapter_binary.c @@ -44,11 +44,11 @@ binary_escape(unsigned char *from, unsigned int from_length, #if PG_MAJOR_VERSION > 8 || \ (PG_MAJOR_VERSION == 8 && PG_MINOR_VERSION > 1) || \ (PG_MAJOR_VERSION == 8 && PG_MINOR_VERSION == 1 && PG_PATCH_VERSION >= 4) - return PQescapeByteaConn(conn, from, from_length, to_length); -#else -#warning "YOUR POSTGRESQL VERSION IS TOO OLD AND IT CAN BE INSECURE" - return PQescapeBytea(from, from_length, to_length); + if (conn) + return PQescapeByteaConn(conn, from, from_length, to_length); + else #endif + return PQescapeBytea(from, from_length, to_length); } #else static unsigned char * @@ -136,19 +136,23 @@ binary_quote(binaryObject *self) const char *buffer; int buffer_len; size_t len = 0; - + /* if we got a plain string or a buffer we escape it and save the buffer */ if (PyString_Check(self->wrapped) || PyBuffer_Check(self->wrapped)) { /* escape and build quoted buffer */ PyObject_AsCharBuffer(self->wrapped, &buffer, &buffer_len); + to = (char *)binary_escape((unsigned char*)buffer, buffer_len, &len, - ((connectionObject*)self->conn)->pgconn); + self->conn ? ((connectionObject*)self->conn)->pgconn : NULL); if (to == NULL) { PyErr_NoMemory(); return NULL; } - self->buffer = PyString_FromFormat("'%s'", to); + if (len > 0) + self->buffer = PyString_FromFormat("'%s'", to); + else + self->buffer = PyString_FromString("''"); PQfreemem(to); } diff --git a/psycopg/adapter_datetime.c b/psycopg/adapter_datetime.c index 50796387..6409ceff 100644 --- a/psycopg/adapter_datetime.c +++ b/psycopg/adapter_datetime.c @@ -64,15 +64,14 @@ pydatetime_str(pydatetimeObject *self) PyDateTime_Delta *obj = (PyDateTime_Delta*)self->wrapped; char buffer[8]; - int i, j, x; + int i; int a = obj->microseconds; - for (i=1000000, j=0; i > 0 ; i /= 10) { - x = a/i; - a -= x*i; - buffer[j++] = '0'+x; + for (i=0; i < 6 ; i++) { + buffer[5-i] = '0' + (a % 10); + a /= 10; } - buffer[j] = '\0'; + buffer[6] = '\0'; return PyString_FromFormat("'%d days %d.%s seconds'", obj->days, obj->seconds, buffer); diff --git a/psycopg/adapter_list.c b/psycopg/adapter_list.c index d03626d8..29ef1167 100644 --- a/psycopg/adapter_list.c +++ b/psycopg/adapter_list.c @@ -46,7 +46,7 @@ list_quote(listObject *self) /* empty arrays are converted to NULLs (still searching for a way to insert an empty array in postgresql */ - if (len == 0) return PyString_FromString("NULL"); + if (len == 0) return PyString_FromString("'{}'"); tmp = PyTuple_New(len); diff --git a/psycopg/adapter_qstring.c b/psycopg/adapter_qstring.c index ca627d75..2eaf7f34 100644 --- a/psycopg/adapter_qstring.c +++ b/psycopg/adapter_qstring.c @@ -45,11 +45,11 @@ qstring_escape(char *to, char *from, size_t len, PGconn *conn) (PG_MAJOR_VERSION == 8 && PG_MINOR_VERSION > 1) || \ (PG_MAJOR_VERSION == 8 && PG_MINOR_VERSION == 1 && PG_PATCH_VERSION >= 4) int err; - return PQescapeStringConn(conn, to, from, len, &err); -#else -#warning "YOUR POSTGRESQL VERSION IS TOO OLD AND IT CAN BE INSECURE" - return PQescapeString(to, from, len); + if (conn) + return PQescapeStringConn(conn, to, from, len, &err); + else #endif + return PQescapeString(to, from, len); } #else static size_t @@ -147,7 +147,7 @@ qstring_quote(qstringObject *self) Py_BEGIN_ALLOW_THREADS; len = qstring_escape(buffer+1, s, len, - ((connectionObject*)self->conn)->pgconn); + self->conn ? ((connectionObject*)self->conn)->pgconn : NULL); buffer[0] = '\'' ; buffer[len+1] = '\''; Py_END_ALLOW_THREADS; diff --git a/psycopg/connection_int.c b/psycopg/connection_int.c index 1aa20fb7..3e61ddf1 100644 --- a/psycopg/connection_int.c +++ b/psycopg/connection_int.c @@ -112,7 +112,7 @@ conn_connect(connectionObject *self) return -1; } tmp = PQgetvalue(pgres, 0, 0); - self->encoding = PyMem_Malloc(strlen(tmp)); + self->encoding = PyMem_Malloc(strlen(tmp)+1); if (self->encoding == NULL) { /* exception already set by PyMem_Malloc() */ PQfinish(pgconn); diff --git a/psycopg/connection_type.c b/psycopg/connection_type.c index 195e6ec7..f8cbd806 100644 --- a/psycopg/connection_type.c +++ b/psycopg/connection_type.c @@ -260,6 +260,9 @@ static struct PyMemberDef connectionObject_members[] = { {"notifies", T_OBJECT, offsetof(connectionObject, notifies), RO}, {"dsn", T_STRING, offsetof(connectionObject, dsn), RO, "The current connection string."}, + {"status", T_LONG, + offsetof(connectionObject, status), RO, + "The current transaction status."}, #endif {NULL} }; diff --git a/psycopg/cursor_type.c b/psycopg/cursor_type.c index 82ce82f4..58cb76b5 100644 --- a/psycopg/cursor_type.c +++ b/psycopg/cursor_type.c @@ -434,7 +434,7 @@ psyco_curs_execute(cursorObject *self, PyObject *args, PyObject *kwargs) } #define psyco_curs_executemany_doc \ -"executemany(query, vars_list=(), async=0) -- Execute many queries with bound vars." +"executemany(query, vars_list) -- Execute many queries with bound vars." static PyObject * psyco_curs_executemany(cursorObject *self, PyObject *args, PyObject *kwargs) @@ -442,9 +442,9 @@ psyco_curs_executemany(cursorObject *self, PyObject *args, PyObject *kwargs) PyObject *operation = NULL, *vars = NULL; PyObject *v, *iter = NULL; - static char *kwlist[] = {"query", "vars", NULL}; + static char *kwlist[] = {"query", "vars_list", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|O", kwlist, + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO", kwlist, &operation, &vars)) { return NULL; } @@ -891,6 +891,7 @@ psyco_curs_callproc(cursorObject *self, PyObject *args, PyObject *kwargs) if(parameters && parameters != Py_None) { nparameters = PyObject_Length(parameters); + if (nparameters < 0) nparameters = 0; } /* allocate some memory, build the SQL and create a PyString from it */ @@ -1047,7 +1048,7 @@ psyco_curs_scroll(cursorObject *self, PyObject *args, PyObject *kwargs) /* extension: copy_from - implements COPY FROM */ #define psyco_curs_copy_from_doc \ -"copy_from(file, table, sep='\\t', null='\\N') -- Copy table from file." +"copy_from(file, table, sep='\\t', null='\\N', columns=None) -- Copy table from file." static int _psyco_curs_has_read_check(PyObject* o, void* var) @@ -1068,29 +1069,76 @@ _psyco_curs_has_read_check(PyObject* o, void* var) static PyObject * psyco_curs_copy_from(cursorObject *self, PyObject *args, PyObject *kwargs) { - char query[256]; + char query[1024]; char *table_name; char *sep = "\t", *null = NULL; long int bufsize = DEFAULT_COPYSIZE; - PyObject *file, *columns, *res = NULL; + PyObject *file, *columns = NULL, *res = NULL; + char columnlist[1024] = ""; - static char *kwlist[] = {"file", "table", "sep", "null", "size", NULL}; + static char *kwlist[] = {"file", "table", "sep", "null", "size", + "columns", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&s|ssi", kwlist, + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&s|ssiO", kwlist, _psyco_curs_has_read_check, &file, - &table_name, &sep, &null, &bufsize)) { + &table_name, &sep, &null, &bufsize, + &columns)) { return NULL; } + if (columns != NULL && columns != Py_None) { + PyObject* collistiter = PyObject_GetIter(columns); + if (collistiter == NULL) { + return NULL; + } + PyObject* col; + int collistlen = 2; + int colitemlen; + char* colname; + strcpy(columnlist, " ("); + while ((col = PyIter_Next(collistiter)) != NULL) { + if (!PyString_Check(col)) { + Py_DECREF(col); + Py_DECREF(collistiter); + PyErr_SetString(PyExc_ValueError, + "Elements in column list must be strings"); + return NULL; + } + PyString_AsStringAndSize(col, &colname, &colitemlen); + if (collistlen + colitemlen > 1022) { + Py_DECREF(col); + Py_DECREF(collistiter); + PyErr_SetString(PyExc_ValueError, "Column list too long"); + return NULL; + } + strncpy(&columnlist[collistlen], colname, colitemlen); + collistlen += colitemlen; + columnlist[collistlen++] = ','; + Py_DECREF(col); + } + Py_DECREF(collistiter); + + if (collistlen == 2) { /* empty list; we printed no comma */ + collistlen++; + } + + columnlist[collistlen - 1] = ')'; + columnlist[collistlen] = '\0'; + } + + if (PyErr_Occurred()) { + return NULL; + } + EXC_IF_CURS_CLOSED(self); if (null) { - PyOS_snprintf(query, 255, "COPY %s FROM stdin USING DELIMITERS '%s'" - " WITH NULL AS '%s'", table_name, sep, null); + PyOS_snprintf(query, 1023, "COPY %s%s FROM stdin USING DELIMITERS '%s'" + " WITH NULL AS '%s'", table_name, columnlist, sep, null); } else { - PyOS_snprintf(query, 255, "COPY %s FROM stdin USING DELIMITERS '%s'", - table_name, sep); + PyOS_snprintf(query, 1023, "COPY %s%s FROM stdin USING DELIMITERS '%s'", + table_name, columnlist, sep); } Dprintf("psyco_curs_copy_from: query = %s", query); diff --git a/psycopg/pqpath.c b/psycopg/pqpath.c index 7bceed20..1ebb569c 100644 --- a/psycopg/pqpath.c +++ b/psycopg/pqpath.c @@ -85,7 +85,7 @@ pq_raise(connectionObject *conn, cursorObject *curs, PyObject *exc, char *msg) #ifdef HAVE_PQPROTOCOL3 char *pgstate = PQresultErrorField(curs->pgres, PG_DIAG_SQLSTATE); - if (!strncmp(pgstate, "23", 2)) + if (pgstate != NULL && !strncmp(pgstate, "23", 2)) exc = IntegrityError; else exc = ProgrammingError; diff --git a/psycopg/psycopgmodule.c b/psycopg/psycopgmodule.c index f711df80..426fb27a 100644 --- a/psycopg/psycopgmodule.c +++ b/psycopg/psycopgmodule.c @@ -118,12 +118,15 @@ _psyco_connect_fill_exc(connectionObject *conn) Py_INCREF(DataError); conn->exc_NotSupportedError = NotSupportedError; Py_INCREF(NotSupportedError); + conn->exc_OperationalError = OperationalError; + Py_INCREF(OperationalError); } static PyObject * psyco_connect(PyObject *self, PyObject *args, PyObject *keywds) { PyObject *conn, *factory = NULL; + PyObject *pyport = NULL; int idsn=-1, iport=-1; char *dsn=NULL, *database=NULL, *user=NULL, *password=NULL; @@ -134,15 +137,28 @@ psyco_connect(PyObject *self, PyObject *args, PyObject *keywds) "user", "password", "sslmode", "connection_factory", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, keywds, "|sssisssO", kwlist, - &dsn, &database, &host, &iport, + if (!PyArg_ParseTupleAndKeywords(args, keywds, "|sssOsssO", kwlist, + &dsn, &database, &host, &pyport, &user, &password, &sslmode, &factory)) { return NULL; } - if (iport > 0) - PyOS_snprintf(port, 16, "%d", iport); - + if (pyport && PyString_Check(pyport)) { + PyObject *pyint = PyInt_FromString(PyString_AsString(pyport), NULL, 10); + if (!pyint) return NULL; + iport = PyInt_AsLong(pyint); + } + else if (pyport && PyInt_Check(pyport)) { + iport = PyInt_AsLong(pyport); + } + else if (pyport != NULL) { + PyErr_SetString(PyExc_TypeError, "port must be a string or int"); + return NULL; + } + + if (iport > 0) + PyOS_snprintf(port, 16, "%d", iport); + if (dsn == NULL) { int l = 45; /* len("dbname= user= password= host= port= sslmode=\0") */ @@ -429,6 +445,7 @@ psyco_set_error(PyObject *exc, PyObject *curs, char *msg, PyObject_SetAttrString(err, "cursor", Py_None); PyErr_SetObject(exc, err); + Py_DECREF(err); } } diff --git a/psycopg/typecast_array.c b/psycopg/typecast_array.c index 65055334..7dcb3a3d 100644 --- a/psycopg/typecast_array.c +++ b/psycopg/typecast_array.c @@ -39,7 +39,7 @@ typecast_array_cleanup(char **str, int *len) if ((*str)[i] != '=') return -1; *str = &((*str)[i+1]); - *len = *len - i - 2; + *len = *len - i - 1; return 0; } @@ -217,7 +217,9 @@ typecast_GENERIC_ARRAY_cast(char *str, int len, PyObject *curs) { PyObject *obj = NULL; PyObject *base = ((typecastObject*)((cursorObject*)curs)->caster)->bcast; - + + Dprintf("typecast_GENERIC_ARRAY_cast: str = '%s', len = %d", str, len); + if (str == NULL) {Py_INCREF(Py_None); return Py_None;} if (str[0] == '[') typecast_array_cleanup(&str, &len); @@ -226,7 +228,7 @@ typecast_GENERIC_ARRAY_cast(char *str, int len, PyObject *curs) return NULL; } - Dprintf("typecast_GENERIC_ARRAY_cast: scanning %s", str); + Dprintf("typecast_GENERIC_ARRAY_cast: str = '%s', len = %d", str, len); obj = PyList_New(0); diff --git a/psycopg/typecast_basic.c b/psycopg/typecast_basic.c index d3ebb0fc..e9ac7f49 100644 --- a/psycopg/typecast_basic.c +++ b/psycopg/typecast_basic.c @@ -54,15 +54,14 @@ typecast_LONGINTEGER_cast(char *s, int len, PyObject *curs) static PyObject * typecast_FLOAT_cast(char *s, int len, PyObject *curs) { - /* FIXME: is 64 large enough for any float? */ - char buffer[64]; + PyObject *str = NULL, *flo = NULL; + char *pend; if (s == NULL) {Py_INCREF(Py_None); return Py_None;} - if (s[len] != '\0') { - strncpy(buffer, s, len); buffer[len] = '\0'; - s = buffer; - } - return PyFloat_FromDouble(atof(s)); + str = PyString_FromStringAndSize(s, len); + flo = PyFloat_FromString(str, &pend); + Py_DECREF(str); + return flo; } /** STRING - cast strings of any type to python string **/ diff --git a/sandbox/array.py b/sandbox/array.py index 786cbe87..183c89b1 100644 --- a/sandbox/array.py +++ b/sandbox/array.py @@ -1,21 +1,28 @@ -import psycopg +import psycopg2 -conn = psycopg.connect("dbname=test") +conn = psycopg2.connect("dbname=test") curs = conn.cursor() -curs.execute("SELECT ARRAY[1,2,3] AS foo") -print curs.fetchone()[0] +#curs.execute("SELECT ARRAY[1,2,3] AS foo") +#print curs.fetchone()[0] -curs.execute("SELECT ARRAY['1','2','3'] AS foo") -print curs.fetchone()[0] +#curs.execute("SELECT ARRAY['1','2','3'] AS foo") +#print curs.fetchone()[0] -curs.execute("""SELECT ARRAY[',','"','\\\\'] AS foo""") -d = curs.fetchone()[0] -print d, '->', d[0], d[1], d[2] +#curs.execute("""SELECT ARRAY[',','"','\\\\'] AS foo""") +#d = curs.fetchone()[0] +#print d, '->', d[0], d[1], d[2] -curs.execute("SELECT ARRAY[ARRAY[1,2],ARRAY[3,4]] AS foo") -print curs.fetchone()[0] +#curs.execute("SELECT ARRAY[ARRAY[1,2],ARRAY[3,4]] AS foo") +#print curs.fetchone()[0] + +#curs.execute("SELECT ARRAY[ARRAY[now(), now()], ARRAY[now(), now()]] AS foo") +#print curs.description +#print curs.fetchone()[0] + +curs.execute("SELECT 1 AS foo, ARRAY[1,2] AS bar") +print curs.fetchone() + +curs.execute("SELECT * FROM test()") +print curs.fetchone() -curs.execute("SELECT ARRAY[ARRAY[now(), now()], ARRAY[now(), now()]] AS foo") -print curs.description -print curs.fetchone()[0] diff --git a/sandbox/gtk.py b/sandbox/gtk.py new file mode 100644 index 00000000..f96e1a32 --- /dev/null +++ b/sandbox/gtk.py @@ -0,0 +1,19 @@ +import psycopg2 + +o = psycopg2.connect("dbname=test") +c = o.cursor() + +def sql(): + c.execute("SELECT 1.23 AS foo") + print 1, c.fetchone() + #print c.description + c.execute("SELECT 1.23::float AS foo") + print 2, c.fetchone() + #print c.description + +print "BEFORE" +sql() +import gtk +print "AFTER" +sql() + diff --git a/sandbox/textfloat.py b/sandbox/textfloat.py new file mode 100644 index 00000000..22d65b8b --- /dev/null +++ b/sandbox/textfloat.py @@ -0,0 +1,9 @@ +import gtk +import psycopg2 + +o = psycopg2.connect("dbname=test") +c = o.cursor() +c.execute("SELECT 1.23::float AS foo") +x = c.fetchone()[0] +print x, type(x) + diff --git a/setup.py b/setup.py index 105a15f3..948cb51d 100644 --- a/setup.py +++ b/setup.py @@ -52,7 +52,7 @@ from distutils.command.build_ext import build_ext from distutils.sysconfig import get_python_inc from distutils.ccompiler import get_default_compiler -PSYCOPG_VERSION = '2.0' +PSYCOPG_VERSION = '2.0.5' version_flags = [] # to work around older distutil limitations @@ -95,7 +95,6 @@ class psycopg_build_ext(build_ext): def initialize_options(self): build_ext.initialize_options(self) - self.use_pydatetime = 1 self.use_pg_dll = 1 self.pgdir = None self.pg_config = self.DEFAULT_PG_CONFIG diff --git a/tests/types_basic.py b/tests/types_basic.py index 3abaf927..446ad153 100644 --- a/tests/types_basic.py +++ b/tests/types_basic.py @@ -62,6 +62,9 @@ class TypesBasicTests(TestCase): b = psycopg2.Binary(s) r = str(self.execute("SELECT %s::bytea AS foo", (b,))) self.failUnless(r == s, "wrong binary quoting") + # test to make sure an empty Binary is converted to an empty string + b = psycopg2.Binary('') + self.assertEqual(str(b), "''") def testArray(self): s = self.execute("SELECT %s AS foo", ([[1,2],[3,4]],))