diff --git a/ChangeLog b/ChangeLog index efeb5345..2856d326 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,15 @@ +2005-03-23 Federico Di Gregorio + + * psycopg/typecast_basic.c: all the basic casters now respect the + passed string length. + + * psycopg/typecast.c (typecast_cast): set curs->caster to self + during the type-casting. + + * psycopg/cursor_type.c: added "typecaster" attribute to the + cursor (this is safe, cursors can't be shared among threads and + the attribute is RO.) + 2005-03-22 Federico Di Gregorio * psycopg/typecast_array.c: added some more structure to implement diff --git a/psycopg/cursor.h b/psycopg/cursor.h index 37b3ebb7..496c68b9 100644 --- a/psycopg/cursor.h +++ b/psycopg/cursor.h @@ -56,6 +56,7 @@ typedef struct { Oid lastoid; /* last oid from an insert or InvalidOid */ PyObject *casts; /* an array (tuple) of typecast functions */ + PyObject *caster; /* the current typecaster object */ PyObject *copyfile; /* file-like used during COPY TO/FROM ops */ long int copysize; /* size of the copy buffer during COPY TO/FROM ops */ diff --git a/psycopg/cursor_type.c b/psycopg/cursor_type.c index 08895bc7..4d066a4e 100644 --- a/psycopg/cursor_type.c +++ b/psycopg/cursor_type.c @@ -1217,6 +1217,7 @@ static struct PyMemberDef cursorObject_members[] = { {"query", T_STRING, OFFSETOF(query), RO}, {"row_factory", T_OBJECT, OFFSETOF(tuple_factory), 0}, {"tzinfo_factory", T_OBJECT, OFFSETOF(tzinfo_factory), 0}, + {"typecaster", T_OBJECT, OFFSETOF(caster), RO}, #endif {NULL} }; diff --git a/psycopg/typecast.c b/psycopg/typecast.c index 78e873c8..da13cb86 100644 --- a/psycopg/typecast.c +++ b/psycopg/typecast.c @@ -154,6 +154,25 @@ typecast_init(PyObject *dict) return 0; } +/* typecast_get_by_name - get a type object by name (slow!) */ + +static PyObject* +typecast_get_by_name(unsigned char *name) +{ + PyObject *value, *res = NULL; + int ppos = 0; + + while (PyDict_Next(psyco_types, &ppos, NULL, &value)) { + if (strcmp(PyString_AsString(((typecastObject*)value)->name), + name) == 0) { + res = value; + break; + } + } + + /* borrowed reference */ + return res; +} /* typecast_add - add a type object to the dictionary */ int @@ -179,6 +198,8 @@ typecast_add(PyObject *obj, int binary) } } + Dprintf("typecast_add: base caster: %p", type->bcast); + return 0; } @@ -196,7 +217,7 @@ static struct memberlist typecastObject_memberlist[] = { /* numeric methods */ static PyObject * -typecast_new(PyObject *name, PyObject *values, PyObject *cast); +typecast_new(PyObject *name, PyObject *values, PyObject *cast, PyObject *base); static int typecast_coerce(PyObject **pv, PyObject **pw) @@ -207,7 +228,7 @@ typecast_coerce(PyObject **pv, PyObject **pw) args = PyTuple_New(1); Py_INCREF(*pw); PyTuple_SET_ITEM(args, 0, *pw); - coer = typecast_new(NULL, args, NULL); + coer = typecast_new(NULL, args, NULL, NULL); *pw = coer; Py_DECREF(args); Py_INCREF(*pv); @@ -347,7 +368,7 @@ PyTypeObject typecastType = { }; static PyObject * -typecast_new(PyObject *name, PyObject *values, PyObject *cast) +typecast_new(PyObject *name, PyObject *values, PyObject *cast, PyObject *base) { typecastObject *obj; @@ -368,7 +389,11 @@ typecast_new(PyObject *name, PyObject *values, PyObject *cast) obj->pcast = NULL; obj->ccast = NULL; - + obj->bcast = base; + + if (obj->bcast) Py_INCREF(obj->bcast); + + /* FIXME: raise an exception when None is passed as Python caster */ if (cast && cast != Py_None) { Py_INCREF(cast); obj->pcast = cast; @@ -382,27 +407,36 @@ typecast_new(PyObject *name, PyObject *values, PyObject *cast) PyObject * typecast_from_python(PyObject *self, PyObject *args, PyObject *keywds) { - PyObject *v, *name, *cast = NULL; + PyObject *v, *name, *cast = NULL, *base = NULL; - static char *kwlist[] = {"values", "name", "castobj", NULL}; + static char *kwlist[] = {"values", "name", "castobj", "baseobj", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, keywds, "O!|O!O", kwlist, + if (!PyArg_ParseTupleAndKeywords(args, keywds, "O!|O!OO", kwlist, &PyTuple_Type, &v, &PyString_Type, &name, - &cast)) { + &cast, &base)) { return NULL; } - return typecast_new(name, v, cast); + return typecast_new(name, v, cast, base); } PyObject * typecast_from_c(typecastObject_initlist *type) { - PyObject *tuple; + PyObject *tuple, *base = NULL; typecastObject *obj; int i, len = 0; + /* before doing anything else we look for the base */ + if (type->base) { + base = typecast_get_by_name(type->base); + if (!base) { + PyErr_Format(Error, "typecast base not found: %s", type->base); + return NULL; + } + } + while (type->values[len] != 0) len++; tuple = PyTuple_New(len); @@ -411,9 +445,10 @@ typecast_from_c(typecastObject_initlist *type) for (i = 0; i < len ; i++) { PyTuple_SET_ITEM(tuple, i, PyInt_FromLong(type->values[i])); } - + + obj = (typecastObject *) - typecast_new(PyString_FromString(type->name), tuple, NULL); + typecast_new(PyString_FromString(type->name), tuple, NULL, base); if (obj) { obj->ccast = type->cast; @@ -425,18 +460,26 @@ typecast_from_c(typecastObject_initlist *type) PyObject * typecast_cast(PyObject *obj, unsigned char *str, int len, PyObject *curs) { + PyObject *old, *res = NULL; typecastObject *self = (typecastObject *)obj; + /* we don't incref, the caster *can't* die at this point */ + old = ((cursorObject*)curs)->caster; + ((cursorObject*)curs)->caster = obj; + if (self->ccast) { Dprintf("typecast_call: calling C cast function"); - return self->ccast(str, len, curs); + res = self->ccast(str, len, curs); } else if (self->pcast) { Dprintf("typecast_call: calling python callable"); - return PyObject_CallFunction(self->pcast, "s#O", str, len, curs); + res = PyObject_CallFunction(self->pcast, "s#O", str, len, curs); } else { PyErr_SetString(Error, "internal error: no casting function found"); - return NULL; } + + ((cursorObject*)curs)->caster = old; + + return res; } diff --git a/psycopg/typecast.h b/psycopg/typecast.h index 3e5a4c4f..09e1daee 100644 --- a/psycopg/typecast.h +++ b/psycopg/typecast.h @@ -43,6 +43,7 @@ typedef struct { typecast_function ccast; /* the C casting function */ PyObject *pcast; /* the python casting function */ + PyObject *bcast; /* base cast, used by array typecasters */ } typecastObject; /* the initialization values are stored here */ diff --git a/psycopg/typecast_array.c b/psycopg/typecast_array.c index d4a982a9..a949673c 100644 --- a/psycopg/typecast_array.c +++ b/psycopg/typecast_array.c @@ -20,23 +20,97 @@ */ -/* the pointer to the datetime module API is initialized by the module init - code, we just need to grab it */ -extern PyObject* pyDateTimeModuleP; -extern PyObject *pyDateTypeP; -extern PyObject *pyTimeTypeP; -extern PyObject *pyDateTimeTypeP; -extern PyObject *pyDeltaTypeP; +/** typecast_array_scan - scan a string looking for array items **/ + +#define ASCAN_EOF 0 +#define ASCAN_BEGIN 1 +#define ASCAN_END 2 +#define ASCAN_TOKEN 3 + +static int +typecast_array_tokenize(unsigned char *str, int strlength, + int *pos, unsigned char** token, int *length) +{ + int i = *pos; + + Dprintf("TOKENIZE for %d, pos = %d", strlength, *pos); + + while (i < strlength) { + switch (str[i]) { + case '{': + *pos = i+1; + return ASCAN_BEGIN; + + case '}': + *pos = i+1; + return ASCAN_END; + + case ',': + *token = &str[*pos]; + *length = i - *pos; + *pos = i+1; + Dprintf("TOKENIZE pos = %d, length = %d", *pos, *length); + return ASCAN_TOKEN; + + default: + i++; + } + } + + *token = &str[*pos]; + *length = i - *pos; + + return ASCAN_EOF; +} + +static int +typecast_array_scan(unsigned char *str, int strlength, + PyObject *curs, PyObject *base, PyObject *array) +{ + int state, length, pos = 0; + unsigned char *token; + + while (1) { + state = typecast_array_tokenize(str, strlength, &pos, &token, &length); + + if (state == ASCAN_TOKEN || state == ASCAN_EOF) { + PyObject *obj = typecast_cast(base, token, length, curs); + if (obj == NULL) return 0; + PyList_Append(array, obj); + Py_DECREF(obj); + } + else { + Dprintf("** RECURSION (not supported right now)!!"); + } + + if (state == ASCAN_EOF) break; + } + + return 1; +} /** LONGINTEGERARRAY and INTEGERARRAY - cast integers arrays **/ static PyObject * typecast_INTEGERARRAY_cast(unsigned char *str, int len, PyObject *curs) { - PyObject* obj = NULL; - + PyObject *obj = NULL; + PyObject *base = ((typecastObject*)((cursorObject*)curs)->caster)->bcast; + if (str == NULL) {Py_INCREF(Py_None); return Py_None;} + if (str[0] != '{') { + PyErr_SetString(Error, "array does not start with '{'"); + return NULL; + } + + obj = PyList_New(0); + /* scan the array skipping the first level of {} */ + if (typecast_array_scan(&str[1], len-2, curs, base, obj) == 0) { + Py_DECREF(obj); + obj = NULL; + } + return obj; } diff --git a/psycopg/typecast_basic.c b/psycopg/typecast_basic.c index b6e62601..f9a2fb3c 100644 --- a/psycopg/typecast_basic.c +++ b/psycopg/typecast_basic.c @@ -27,8 +27,11 @@ static PyObject * typecast_INTEGER_cast(unsigned char *s, int len, PyObject *curs) { + unsigned char buffer[12]; + if (s == NULL) {Py_INCREF(Py_None); return Py_None;} - return PyInt_FromString(s, NULL, 0); + strncpy(buffer, s, len); buffer[len] = '\0'; + return PyInt_FromString(buffer, NULL, 0); } /** LONGINTEGER - cast long integers (8 bytes) to python long **/ @@ -36,7 +39,10 @@ typecast_INTEGER_cast(unsigned char *s, int len, PyObject *curs) static PyObject * typecast_LONGINTEGER_cast(unsigned char *s, int len, PyObject *curs) { + unsigned char buffer[24]; + if (s == NULL) {Py_INCREF(Py_None); return Py_None;} + strncpy(buffer, s, len); buffer[len] = '\0'; return PyLong_FromString(s, NULL, 0); } @@ -45,7 +51,10 @@ typecast_LONGINTEGER_cast(unsigned char *s, int len, PyObject *curs) static PyObject * typecast_FLOAT_cast(unsigned char *s, int len, PyObject *curs) { + unsigned char buffer[64]; + if (s == NULL) {Py_INCREF(Py_None); return Py_None;} + strncpy(buffer, s, len); buffer[len] = '\0'; return PyFloat_FromDouble(atof(s)); } @@ -70,7 +79,7 @@ typecast_UNICODE_cast(unsigned char *s, int len, PyObject *curs) enc = PyDict_GetItemString(psycoEncodings, ((cursorObject*)curs)->conn->encoding); if (enc) { - return PyUnicode_Decode(s, strlen(s), PyString_AsString(enc), NULL); + return PyUnicode_Decode(s, len, PyString_AsString(enc), NULL); } else { PyErr_Format(InterfaceError, @@ -135,13 +144,17 @@ static PyObject * typecast_BINARY_cast(unsigned char *s, int l, PyObject *curs) { PyObject *res; - unsigned char *str; + unsigned char *str, saved; size_t len; if (s == NULL) {Py_INCREF(Py_None); return Py_None;} - + + /* PQunescapeBytea absolutely wants a 0-terminated string and we don't + want to copy the whole buffer, right? Wrong... :/ */ + saved = s[l]; s[l] = '\0'; str = PQunescapeBytea(s, &len); Dprintf("typecast_BINARY_cast: unescaped %d bytes", len); + s[l] = saved; /* TODO: using a PyBuffer would make this a zero-copy operation but we'll need to define our own buffer-derived object to keep a reference to the @@ -178,8 +191,17 @@ typecast_BOOLEAN_cast(unsigned char *s, int len, PyObject *curs) static PyObject * typecast_DECIMAL_cast(unsigned char *s, int len, PyObject *curs) { + PyObject *res = NULL; + unsigned char *buffer; + if (s == NULL) {Py_INCREF(Py_None); return Py_None;} - return PyObject_CallFunction(decimalType, "s", s); + + buffer = PyMem_Malloc(len+1); + strncpy(buffer, s, len); buffer[len] = '\0'; + res = PyObject_CallFunction(decimalType, "s", buffer); + PyMem_Free(buffer); + + return res; } #else #define typecast_DECIMAL_cast typecast_FLOAT_cast diff --git a/setup.cfg b/setup.cfg index 46bcbc0a..7409c618 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [build_ext] -define=PSYCOPG_EXTENSIONS,PSYCOPG_DISPLAY_SIZE,HAVE_PQFREEMEM,HAVE_PQPROTOCOL3 +define=PSYCOPG_EXTENSIONS,PSYCOPG_DISPLAY_SIZE,HAVE_PQFREEMEM,HAVE_PQPROTOCOL3,PSYCOPG_DEBUG # PSYCOPG_DEBUG can be added to enable verbose debug information # PSYCOPG_OWN_QUOTING can be added above but it is deprecated