Skip to content

Commit 6598a27

Browse files
committed
Added support for per-cursor and per-connection typecasters.
1 parent c2e16b8 commit 6598a27

File tree

10 files changed

+145
-30
lines changed

10 files changed

+145
-30
lines changed

ChangeLog

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
2007-02-22 Federico Di Gregorio <fog@initd.org>
2+
3+
* Added support for per-connection and per-cursor typecasters.
4+
15
2007-02-11 Federico Di Gregorio <fog@initd.org>
26

37
* psycopg/pqpath.c: ported psycopg1 patch from #135.

examples/typecast.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# typecast.py - example of per-cursor and per-connection typecasters.
2+
#
3+
# Copyright (C) 2001-2007 Federico Di Gregorio <fog@debian.org>
4+
#
5+
# This program is free software; you can redistribute it and/or modify
6+
# it under the terms of the GNU General Public License as published by the
7+
# Free Software Foundation; either version 2, or (at your option) any later
8+
# version.
9+
#
10+
# This program is distributed in the hope that it will be useful, but
11+
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
12+
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13+
# for more details.
14+
15+
## put in DSN your DSN string
16+
17+
DSN = 'dbname=test'
18+
19+
## don't modify anything below this line (except for experimenting)
20+
21+
class SimpleQuoter(object):
22+
def sqlquote(x=None):
23+
return "'bar'"
24+
25+
import sys
26+
import psycopg2
27+
import psycopg2.extensions
28+
29+
if len(sys.argv) > 1:
30+
DSN = sys.argv[1]
31+
32+
print "Opening connection using dns:", DSN
33+
conn = psycopg2.connect(DSN)
34+
print "Encoding for this connection is", conn.encoding
35+
36+
curs = conn.cursor()
37+
curs.execute("SELECT 'text'::text AS foo")
38+
textoid = curs.description[0][1]
39+
print "Oid for the text datatype is", textoid
40+
41+
def castA(s, curs):
42+
if s is not None: return "(A) " + s
43+
TYPEA = psycopg2.extensions.new_type((textoid,), "TYPEA", castA)
44+
45+
def castB(s, curs):
46+
if s is not None: return "(B) " + s
47+
TYPEB = psycopg2.extensions.new_type((textoid,), "TYPEB", castB)
48+
49+
curs = conn.cursor()
50+
curs.execute("SELECT 'some text.'::text AS foo")
51+
print "Some text from plain connection:", curs.fetchone()[0]
52+
53+
psycopg2.extensions.register_type(TYPEA, conn)
54+
curs = conn.cursor()
55+
curs.execute("SELECT 'some text.'::text AS foo")
56+
print "Some text from connection with typecaster:", curs.fetchone()[0]
57+
58+
curs = conn.cursor()
59+
psycopg2.extensions.register_type(TYPEB, curs)
60+
curs.execute("SELECT 'some text.'::text AS foo")
61+
print "Some text from cursor with typecaster:", curs.fetchone()[0]
62+
63+
curs = conn.cursor()
64+
curs.execute("SELECT 'some text.'::text AS foo")
65+
print "Some text from connection with typecaster again:", curs.fetchone()[0]
66+
67+

psycopg/connection.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,11 @@ typedef struct {
7474
PyObject *exc_IntegrityError;
7575
PyObject *exc_DataError;
7676
PyObject *exc_NotSupportedError;
77-
77+
78+
/* per-connection typecasters */
79+
PyObject *string_types; /* a set of typecasters for string types */
80+
PyObject *binary_types; /* a set of typecasters for binary types */
81+
7882
} connectionObject;
7983

8084
/* C-callable functions in connection_int.c and connection_ext.c */

psycopg/connection_type.c

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,11 @@ static struct PyMemberDef connectionObject_members[] = {
276276
"The current connection string."},
277277
{"status", T_LONG,
278278
offsetof(connectionObject, status), RO,
279-
"The current transaction status."},
279+
"The current transaction status."},
280+
{"string_types", T_OBJECT, offsetof(connectionObject, string_types), RO,
281+
"A set of typecasters to convert textual values."},
282+
{"binary_types", T_OBJECT, offsetof(connectionObject, binary_types), RO,
283+
"A set of typecasters to convert binary values."},
280284
#endif
281285
{NULL}
282286
};
@@ -301,7 +305,8 @@ connection_setup(connectionObject *self, char *dsn)
301305
self->async_cursor = NULL;
302306
self->pgconn = NULL;
303307
self->mark = 0;
304-
308+
self->string_types = PyDict_New();
309+
self->binary_types = PyDict_New();
305310

306311
pthread_mutex_init(&(self->lock), NULL);
307312

@@ -339,6 +344,8 @@ connection_dealloc(PyObject* obj)
339344
Py_XDECREF(self->notice_list);
340345
Py_XDECREF(self->notifies);
341346
Py_XDECREF(self->async_cursor);
347+
Py_XDECREF(self->string_types);
348+
Py_XDECREF(self->binary_types);
342349

343350
pthread_mutex_destroy(&(self->lock));
344351

psycopg/cursor_type.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1448,7 +1448,9 @@ cursor_dealloc(PyObject* obj)
14481448
Py_XDECREF(self->tuple_factory);
14491449
Py_XDECREF(self->tzinfo_factory);
14501450
Py_XDECREF(self->query);
1451-
1451+
Py_XDECREF(self->string_types);
1452+
Py_XDECREF(self->binary_types);
1453+
14521454
IFCLEARPGRES(self->pgres);
14531455

14541456
Dprintf("cursor_dealloc: deleted cursor object at %p, refcnt = %d",

psycopg/pqpath.c

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -501,34 +501,44 @@ _pq_fetch_tuples(cursorObject *curs)
501501

502502
PyTuple_SET_ITEM(curs->description, i, dtitem);
503503

504-
/* fill the right cast function by accessing the global dictionary of
505-
casting objects. if we got no defined cast use the default
506-
one */
507-
if (!(cast = PyDict_GetItem(curs->casts, type))) {
508-
Dprintf("_pq_fetch_tuples: cast %d not in per-cursor dict", ftype);
509-
if (!(cast = PyDict_GetItem(psyco_types, type))) {
510-
Dprintf("_pq_fetch_tuples: cast %d not found, using default",
511-
PQftype(curs->pgres,i));
512-
cast = psyco_default_cast;
513-
}
504+
/* fill the right cast function by accessing three different dictionaries:
505+
- the per-cursor dictionary, if available (can be NULL or None)
506+
- the per-connection dictionary (always exists but can be null)
507+
- the global dictionary (at module level)
508+
if we get no defined cast use the default one */
509+
510+
Dprintf("_pq_fetch_tuples: looking for cast %d:", ftype);
511+
if (curs->string_types != NULL && curs->string_types != Py_None) {
512+
cast = PyDict_GetItem(curs->string_types, type);
513+
Dprintf("_pq_fetch_tuples: per-cursor dict: %p", cast);
514+
}
515+
if (cast == NULL) {
516+
cast = PyDict_GetItem(curs->conn->string_types, type);
517+
Dprintf("_pq_fetch_tuples: per-connection dict: %p", cast);
518+
}
519+
if (cast == NULL) {
520+
cast = PyDict_GetItem(psyco_types, type);
521+
Dprintf("_pq_fetch_tuples: global dict: %p", cast);
514522
}
523+
if (cast == NULL) cast = psyco_default_cast;
524+
515525
/* else if we got binary tuples and if we got a field that
516526
is binary use the default cast
517527
FIXME: what the hell am I trying to do here? This just can't work..
518528
*/
519-
else if (pgbintuples && cast == psyco_default_binary_cast) {
529+
if (pgbintuples && cast == psyco_default_binary_cast) {
520530
Dprintf("_pq_fetch_tuples: Binary cursor and "
521531
"binary field: %i using default cast",
522532
PQftype(curs->pgres,i));
523533
cast = psyco_default_cast;
524534
}
535+
525536
Dprintf("_pq_fetch_tuples: using cast at %p (%s) for type %d",
526537
cast, PyString_AS_STRING(((typecastObject*)cast)->name),
527538
PQftype(curs->pgres,i));
528539
Py_INCREF(cast);
529540
PyTuple_SET_ITEM(curs->casts, i, cast);
530541

531-
532542
/* 1/ fill the other fields */
533543
PyTuple_SET_ITEM(dtitem, 0,
534544
PyString_FromString(PQfname(curs->pgres, i)));

psycopg/psycopgmodule.c

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -228,17 +228,40 @@ psyco_connect(PyObject *self, PyObject *args, PyObject *keywds)
228228
" the string representation returned by PostgreSQL (`None` if ``NULL``)\n" \
229229
" and ``cur`` is the cursor from which data are read."
230230

231+
static void
232+
_psyco_register_type_set(PyObject **dict, PyObject *type)
233+
{
234+
if (*dict == NULL)
235+
*dict = PyDict_New();
236+
typecast_add(type, *dict, 0);
237+
}
238+
231239
static PyObject *
232240
psyco_register_type(PyObject *self, PyObject *args)
233241
{
234-
PyObject *type;
242+
PyObject *type, *obj;
235243

236-
if (!PyArg_ParseTuple(args, "O!", &typecastType, &type)) {
244+
if (!PyArg_ParseTuple(args, "O!|O", &typecastType, &type, &obj)) {
237245
return NULL;
238246
}
239247

240-
typecast_add(type, 0);
241-
248+
if (obj != NULL) {
249+
if (obj->ob_type == &cursorType) {
250+
_psyco_register_type_set(&(((cursorObject*)obj)->string_types), type);
251+
}
252+
else if (obj->ob_type == &connectionType) {
253+
typecast_add(type, ((connectionObject*)obj)->string_types, 0);
254+
}
255+
else {
256+
PyErr_SetString(PyExc_TypeError,
257+
"argument 2 must be a connection, cursor or None");
258+
return NULL;
259+
}
260+
}
261+
else {
262+
typecast_add(type, NULL, 0);
263+
}
264+
242265
Py_INCREF(Py_None);
243266
return Py_None;
244267
}

psycopg/typecast.c

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ typecast_init(PyObject *dict)
226226

227227
t = (typecastObject *)typecast_from_c(&(typecast_builtins[i]), dict);
228228
if (t == NULL) return -1;
229-
if (typecast_add((PyObject *)t, 0) != 0) return -1;
229+
if (typecast_add((PyObject *)t, NULL, 0) != 0) return -1;
230230

231231
PyDict_SetItem(dict, t->name, (PyObject *)t);
232232

@@ -264,7 +264,7 @@ typecast_init(PyObject *dict)
264264

265265
/* typecast_add - add a type object to the dictionary */
266266
int
267-
typecast_add(PyObject *obj, int binary)
267+
typecast_add(PyObject *obj, PyObject *dict, int binary)
268268
{
269269
PyObject *val;
270270
Py_ssize_t len, i;
@@ -274,16 +274,14 @@ typecast_add(PyObject *obj, int binary)
274274
Dprintf("typecast_add: object at %p, values refcnt = %d",
275275
obj, type->values->ob_refcnt);
276276

277+
if (dict == NULL)
278+
dict = (binary ? psyco_binary_types : psyco_types);
279+
277280
len = PyTuple_Size(type->values);
278281
for (i = 0; i < len; i++) {
279282
val = PyTuple_GetItem(type->values, i);
280283
Dprintf("typecast_add: adding val: %ld", PyInt_AsLong(val));
281-
if (binary) {
282-
PyDict_SetItem(psyco_binary_types, val, obj);
283-
}
284-
else {
285-
PyDict_SetItem(psyco_types, val, obj);
286-
}
284+
PyDict_SetItem(dict, val, obj);
287285
}
288286

289287
Dprintf("typecast_add: base caster: %p", type->bcast);

psycopg/typecast.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ extern PyObject *psyco_default_binary_cast;
6969

7070
/* used by module.c to init the type system and register types */
7171
extern int typecast_init(PyObject *dict);
72-
extern int typecast_add(PyObject *obj, int binary);
72+
extern int typecast_add(PyObject *obj, PyObject *dict, int binary);
7373

7474
/* the C callable typecastObject creator function */
7575
extern PyObject *typecast_from_c(typecastObject_initlist *type, PyObject *d);

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_DEBUG,PSYCOPG_EXTENSIONS,PSYCOPG_DISPLAY_SIZE,PSYCOPG_NEW_BOOLEAN,HAVE_PQFREEMEM,HAVE_PQPROTOCOL3
2+
define=PSYCOPG_EXTENSIONS,PSYCOPG_DISPLAY_SIZE,PSYCOPG_NEW_BOOLEAN,HAVE_PQFREEMEM,HAVE_PQPROTOCOL3
33
# PSYCOPG_EXTENSIONS enables extensions to PEP-249 (you really want this)
44
# PSYCOPG_DISPLAY_SIZE enable display size calculation (a little slower)
55
# HAVE_PQFREEMEM should be defined on PostgreSQL >= 7.4

0 commit comments

Comments
 (0)