Skip to content

Commit def14d5

Browse files
committed
Array support works, at least for INTEGERS.
1 parent 07a38c3 commit def14d5

File tree

8 files changed

+184
-30
lines changed

8 files changed

+184
-30
lines changed

ChangeLog

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
2005-03-23 Federico Di Gregorio <fog@debian.org>
2+
3+
* psycopg/typecast_basic.c: all the basic casters now respect the
4+
passed string length.
5+
6+
* psycopg/typecast.c (typecast_cast): set curs->caster to self
7+
during the type-casting.
8+
9+
* psycopg/cursor_type.c: added "typecaster" attribute to the
10+
cursor (this is safe, cursors can't be shared among threads and
11+
the attribute is RO.)
12+
113
2005-03-22 Federico Di Gregorio <fog@debian.org>
214

315
* psycopg/typecast_array.c: added some more structure to implement

psycopg/cursor.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ typedef struct {
5656
Oid lastoid; /* last oid from an insert or InvalidOid */
5757

5858
PyObject *casts; /* an array (tuple) of typecast functions */
59+
PyObject *caster; /* the current typecaster object */
5960

6061
PyObject *copyfile; /* file-like used during COPY TO/FROM ops */
6162
long int copysize; /* size of the copy buffer during COPY TO/FROM ops */

psycopg/cursor_type.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1217,6 +1217,7 @@ static struct PyMemberDef cursorObject_members[] = {
12171217
{"query", T_STRING, OFFSETOF(query), RO},
12181218
{"row_factory", T_OBJECT, OFFSETOF(tuple_factory), 0},
12191219
{"tzinfo_factory", T_OBJECT, OFFSETOF(tzinfo_factory), 0},
1220+
{"typecaster", T_OBJECT, OFFSETOF(caster), RO},
12201221
#endif
12211222
{NULL}
12221223
};

psycopg/typecast.c

Lines changed: 58 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,25 @@ typecast_init(PyObject *dict)
154154
return 0;
155155
}
156156

157+
/* typecast_get_by_name - get a type object by name (slow!) */
158+
159+
static PyObject*
160+
typecast_get_by_name(unsigned char *name)
161+
{
162+
PyObject *value, *res = NULL;
163+
int ppos = 0;
164+
165+
while (PyDict_Next(psyco_types, &ppos, NULL, &value)) {
166+
if (strcmp(PyString_AsString(((typecastObject*)value)->name),
167+
name) == 0) {
168+
res = value;
169+
break;
170+
}
171+
}
172+
173+
/* borrowed reference */
174+
return res;
175+
}
157176

158177
/* typecast_add - add a type object to the dictionary */
159178
int
@@ -179,6 +198,8 @@ typecast_add(PyObject *obj, int binary)
179198
}
180199
}
181200

201+
Dprintf("typecast_add: base caster: %p", type->bcast);
202+
182203
return 0;
183204
}
184205

@@ -196,7 +217,7 @@ static struct memberlist typecastObject_memberlist[] = {
196217
/* numeric methods */
197218

198219
static PyObject *
199-
typecast_new(PyObject *name, PyObject *values, PyObject *cast);
220+
typecast_new(PyObject *name, PyObject *values, PyObject *cast, PyObject *base);
200221

201222
static int
202223
typecast_coerce(PyObject **pv, PyObject **pw)
@@ -207,7 +228,7 @@ typecast_coerce(PyObject **pv, PyObject **pw)
207228
args = PyTuple_New(1);
208229
Py_INCREF(*pw);
209230
PyTuple_SET_ITEM(args, 0, *pw);
210-
coer = typecast_new(NULL, args, NULL);
231+
coer = typecast_new(NULL, args, NULL, NULL);
211232
*pw = coer;
212233
Py_DECREF(args);
213234
Py_INCREF(*pv);
@@ -347,7 +368,7 @@ PyTypeObject typecastType = {
347368
};
348369

349370
static PyObject *
350-
typecast_new(PyObject *name, PyObject *values, PyObject *cast)
371+
typecast_new(PyObject *name, PyObject *values, PyObject *cast, PyObject *base)
351372
{
352373
typecastObject *obj;
353374

@@ -368,7 +389,11 @@ typecast_new(PyObject *name, PyObject *values, PyObject *cast)
368389

369390
obj->pcast = NULL;
370391
obj->ccast = NULL;
371-
392+
obj->bcast = base;
393+
394+
if (obj->bcast) Py_INCREF(obj->bcast);
395+
396+
/* FIXME: raise an exception when None is passed as Python caster */
372397
if (cast && cast != Py_None) {
373398
Py_INCREF(cast);
374399
obj->pcast = cast;
@@ -382,27 +407,36 @@ typecast_new(PyObject *name, PyObject *values, PyObject *cast)
382407
PyObject *
383408
typecast_from_python(PyObject *self, PyObject *args, PyObject *keywds)
384409
{
385-
PyObject *v, *name, *cast = NULL;
410+
PyObject *v, *name, *cast = NULL, *base = NULL;
386411

387-
static char *kwlist[] = {"values", "name", "castobj", NULL};
412+
static char *kwlist[] = {"values", "name", "castobj", "baseobj", NULL};
388413

389-
if (!PyArg_ParseTupleAndKeywords(args, keywds, "O!|O!O", kwlist,
414+
if (!PyArg_ParseTupleAndKeywords(args, keywds, "O!|O!OO", kwlist,
390415
&PyTuple_Type, &v,
391416
&PyString_Type, &name,
392-
&cast)) {
417+
&cast, &base)) {
393418
return NULL;
394419
}
395420

396-
return typecast_new(name, v, cast);
421+
return typecast_new(name, v, cast, base);
397422
}
398423

399424
PyObject *
400425
typecast_from_c(typecastObject_initlist *type)
401426
{
402-
PyObject *tuple;
427+
PyObject *tuple, *base = NULL;
403428
typecastObject *obj;
404429
int i, len = 0;
405430

431+
/* before doing anything else we look for the base */
432+
if (type->base) {
433+
base = typecast_get_by_name(type->base);
434+
if (!base) {
435+
PyErr_Format(Error, "typecast base not found: %s", type->base);
436+
return NULL;
437+
}
438+
}
439+
406440
while (type->values[len] != 0) len++;
407441

408442
tuple = PyTuple_New(len);
@@ -411,9 +445,10 @@ typecast_from_c(typecastObject_initlist *type)
411445
for (i = 0; i < len ; i++) {
412446
PyTuple_SET_ITEM(tuple, i, PyInt_FromLong(type->values[i]));
413447
}
414-
448+
449+
415450
obj = (typecastObject *)
416-
typecast_new(PyString_FromString(type->name), tuple, NULL);
451+
typecast_new(PyString_FromString(type->name), tuple, NULL, base);
417452

418453
if (obj) {
419454
obj->ccast = type->cast;
@@ -425,18 +460,26 @@ typecast_from_c(typecastObject_initlist *type)
425460
PyObject *
426461
typecast_cast(PyObject *obj, unsigned char *str, int len, PyObject *curs)
427462
{
463+
PyObject *old, *res = NULL;
428464
typecastObject *self = (typecastObject *)obj;
429465

466+
/* we don't incref, the caster *can't* die at this point */
467+
old = ((cursorObject*)curs)->caster;
468+
((cursorObject*)curs)->caster = obj;
469+
430470
if (self->ccast) {
431471
Dprintf("typecast_call: calling C cast function");
432-
return self->ccast(str, len, curs);
472+
res = self->ccast(str, len, curs);
433473
}
434474
else if (self->pcast) {
435475
Dprintf("typecast_call: calling python callable");
436-
return PyObject_CallFunction(self->pcast, "s#O", str, len, curs);
476+
res = PyObject_CallFunction(self->pcast, "s#O", str, len, curs);
437477
}
438478
else {
439479
PyErr_SetString(Error, "internal error: no casting function found");
440-
return NULL;
441480
}
481+
482+
((cursorObject*)curs)->caster = old;
483+
484+
return res;
442485
}

psycopg/typecast.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ typedef struct {
4343

4444
typecast_function ccast; /* the C casting function */
4545
PyObject *pcast; /* the python casting function */
46+
PyObject *bcast; /* base cast, used by array typecasters */
4647
} typecastObject;
4748

4849
/* the initialization values are stored here */

psycopg/typecast_array.c

Lines changed: 83 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,23 +20,97 @@
2020
*/
2121

2222

23-
/* the pointer to the datetime module API is initialized by the module init
24-
code, we just need to grab it */
25-
extern PyObject* pyDateTimeModuleP;
26-
extern PyObject *pyDateTypeP;
27-
extern PyObject *pyTimeTypeP;
28-
extern PyObject *pyDateTimeTypeP;
29-
extern PyObject *pyDeltaTypeP;
23+
/** typecast_array_scan - scan a string looking for array items **/
24+
25+
#define ASCAN_EOF 0
26+
#define ASCAN_BEGIN 1
27+
#define ASCAN_END 2
28+
#define ASCAN_TOKEN 3
29+
30+
static int
31+
typecast_array_tokenize(unsigned char *str, int strlength,
32+
int *pos, unsigned char** token, int *length)
33+
{
34+
int i = *pos;
35+
36+
Dprintf("TOKENIZE for %d, pos = %d", strlength, *pos);
37+
38+
while (i < strlength) {
39+
switch (str[i]) {
40+
case '{':
41+
*pos = i+1;
42+
return ASCAN_BEGIN;
43+
44+
case '}':
45+
*pos = i+1;
46+
return ASCAN_END;
47+
48+
case ',':
49+
*token = &str[*pos];
50+
*length = i - *pos;
51+
*pos = i+1;
52+
Dprintf("TOKENIZE pos = %d, length = %d", *pos, *length);
53+
return ASCAN_TOKEN;
54+
55+
default:
56+
i++;
57+
}
58+
}
59+
60+
*token = &str[*pos];
61+
*length = i - *pos;
62+
63+
return ASCAN_EOF;
64+
}
65+
66+
static int
67+
typecast_array_scan(unsigned char *str, int strlength,
68+
PyObject *curs, PyObject *base, PyObject *array)
69+
{
70+
int state, length, pos = 0;
71+
unsigned char *token;
72+
73+
while (1) {
74+
state = typecast_array_tokenize(str, strlength, &pos, &token, &length);
75+
76+
if (state == ASCAN_TOKEN || state == ASCAN_EOF) {
77+
PyObject *obj = typecast_cast(base, token, length, curs);
78+
if (obj == NULL) return 0;
79+
PyList_Append(array, obj);
80+
Py_DECREF(obj);
81+
}
82+
else {
83+
Dprintf("** RECURSION (not supported right now)!!");
84+
}
85+
86+
if (state == ASCAN_EOF) break;
87+
}
88+
89+
return 1;
90+
}
3091

3192
/** LONGINTEGERARRAY and INTEGERARRAY - cast integers arrays **/
3293

3394
static PyObject *
3495
typecast_INTEGERARRAY_cast(unsigned char *str, int len, PyObject *curs)
3596
{
36-
PyObject* obj = NULL;
37-
97+
PyObject *obj = NULL;
98+
PyObject *base = ((typecastObject*)((cursorObject*)curs)->caster)->bcast;
99+
38100
if (str == NULL) {Py_INCREF(Py_None); return Py_None;}
101+
if (str[0] != '{') {
102+
PyErr_SetString(Error, "array does not start with '{'");
103+
return NULL;
104+
}
105+
106+
obj = PyList_New(0);
39107

108+
/* scan the array skipping the first level of {} */
109+
if (typecast_array_scan(&str[1], len-2, curs, base, obj) == 0) {
110+
Py_DECREF(obj);
111+
obj = NULL;
112+
}
113+
40114
return obj;
41115
}
42116

psycopg/typecast_basic.c

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,22 @@
2727
static PyObject *
2828
typecast_INTEGER_cast(unsigned char *s, int len, PyObject *curs)
2929
{
30+
unsigned char buffer[12];
31+
3032
if (s == NULL) {Py_INCREF(Py_None); return Py_None;}
31-
return PyInt_FromString(s, NULL, 0);
33+
strncpy(buffer, s, len); buffer[len] = '\0';
34+
return PyInt_FromString(buffer, NULL, 0);
3235
}
3336

3437
/** LONGINTEGER - cast long integers (8 bytes) to python long **/
3538

3639
static PyObject *
3740
typecast_LONGINTEGER_cast(unsigned char *s, int len, PyObject *curs)
3841
{
42+
unsigned char buffer[24];
43+
3944
if (s == NULL) {Py_INCREF(Py_None); return Py_None;}
45+
strncpy(buffer, s, len); buffer[len] = '\0';
4046
return PyLong_FromString(s, NULL, 0);
4147
}
4248

@@ -45,7 +51,10 @@ typecast_LONGINTEGER_cast(unsigned char *s, int len, PyObject *curs)
4551
static PyObject *
4652
typecast_FLOAT_cast(unsigned char *s, int len, PyObject *curs)
4753
{
54+
unsigned char buffer[64];
55+
4856
if (s == NULL) {Py_INCREF(Py_None); return Py_None;}
57+
strncpy(buffer, s, len); buffer[len] = '\0';
4958
return PyFloat_FromDouble(atof(s));
5059
}
5160

@@ -70,7 +79,7 @@ typecast_UNICODE_cast(unsigned char *s, int len, PyObject *curs)
7079
enc = PyDict_GetItemString(psycoEncodings,
7180
((cursorObject*)curs)->conn->encoding);
7281
if (enc) {
73-
return PyUnicode_Decode(s, strlen(s), PyString_AsString(enc), NULL);
82+
return PyUnicode_Decode(s, len, PyString_AsString(enc), NULL);
7483
}
7584
else {
7685
PyErr_Format(InterfaceError,
@@ -135,13 +144,17 @@ static PyObject *
135144
typecast_BINARY_cast(unsigned char *s, int l, PyObject *curs)
136145
{
137146
PyObject *res;
138-
unsigned char *str;
147+
unsigned char *str, saved;
139148
size_t len;
140149

141150
if (s == NULL) {Py_INCREF(Py_None); return Py_None;}
142-
151+
152+
/* PQunescapeBytea absolutely wants a 0-terminated string and we don't
153+
want to copy the whole buffer, right? Wrong... :/ */
154+
saved = s[l]; s[l] = '\0';
143155
str = PQunescapeBytea(s, &len);
144156
Dprintf("typecast_BINARY_cast: unescaped %d bytes", len);
157+
s[l] = saved;
145158

146159
/* TODO: using a PyBuffer would make this a zero-copy operation but we'll
147160
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)
178191
static PyObject *
179192
typecast_DECIMAL_cast(unsigned char *s, int len, PyObject *curs)
180193
{
194+
PyObject *res = NULL;
195+
unsigned char *buffer;
196+
181197
if (s == NULL) {Py_INCREF(Py_None); return Py_None;}
182-
return PyObject_CallFunction(decimalType, "s", s);
198+
199+
buffer = PyMem_Malloc(len+1);
200+
strncpy(buffer, s, len); buffer[len] = '\0';
201+
res = PyObject_CallFunction(decimalType, "s", buffer);
202+
PyMem_Free(buffer);
203+
204+
return res;
183205
}
184206
#else
185207
#define typecast_DECIMAL_cast typecast_FLOAT_cast

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[build_ext]
2-
define=PSYCOPG_EXTENSIONS,PSYCOPG_DISPLAY_SIZE,HAVE_PQFREEMEM,HAVE_PQPROTOCOL3
2+
define=PSYCOPG_EXTENSIONS,PSYCOPG_DISPLAY_SIZE,HAVE_PQFREEMEM,HAVE_PQPROTOCOL3,PSYCOPG_DEBUG
33
# PSYCOPG_DEBUG can be added to enable verbose debug information
44
# PSYCOPG_OWN_QUOTING can be added above but it is deprecated
55

0 commit comments

Comments
 (0)