Added support for per-cursor and per-connection typecasters.

This commit is contained in:
Federico Di Gregorio 2007-02-22 15:16:54 +00:00
parent c2e16b8901
commit 6598a279e2
10 changed files with 145 additions and 30 deletions

View File

@ -1,3 +1,7 @@
2007-02-22 Federico Di Gregorio <fog@initd.org>
* Added support for per-connection and per-cursor typecasters.
2007-02-11 Federico Di Gregorio <fog@initd.org>
* psycopg/pqpath.c: ported psycopg1 patch from #135.

67
examples/typecast.py Normal file
View File

@ -0,0 +1,67 @@
# typecast.py - example of per-cursor and per-connection typecasters.
#
# Copyright (C) 2001-2007 Federico Di Gregorio <fog@debian.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# for more details.
## put in DSN your DSN string
DSN = 'dbname=test'
## don't modify anything below this line (except for experimenting)
class SimpleQuoter(object):
def sqlquote(x=None):
return "'bar'"
import sys
import psycopg2
import psycopg2.extensions
if len(sys.argv) > 1:
DSN = sys.argv[1]
print "Opening connection using dns:", DSN
conn = psycopg2.connect(DSN)
print "Encoding for this connection is", conn.encoding
curs = conn.cursor()
curs.execute("SELECT 'text'::text AS foo")
textoid = curs.description[0][1]
print "Oid for the text datatype is", textoid
def castA(s, curs):
if s is not None: return "(A) " + s
TYPEA = psycopg2.extensions.new_type((textoid,), "TYPEA", castA)
def castB(s, curs):
if s is not None: return "(B) " + s
TYPEB = psycopg2.extensions.new_type((textoid,), "TYPEB", castB)
curs = conn.cursor()
curs.execute("SELECT 'some text.'::text AS foo")
print "Some text from plain connection:", curs.fetchone()[0]
psycopg2.extensions.register_type(TYPEA, conn)
curs = conn.cursor()
curs.execute("SELECT 'some text.'::text AS foo")
print "Some text from connection with typecaster:", curs.fetchone()[0]
curs = conn.cursor()
psycopg2.extensions.register_type(TYPEB, curs)
curs.execute("SELECT 'some text.'::text AS foo")
print "Some text from cursor with typecaster:", curs.fetchone()[0]
curs = conn.cursor()
curs.execute("SELECT 'some text.'::text AS foo")
print "Some text from connection with typecaster again:", curs.fetchone()[0]

View File

@ -75,6 +75,10 @@ typedef struct {
PyObject *exc_DataError;
PyObject *exc_NotSupportedError;
/* per-connection typecasters */
PyObject *string_types; /* a set of typecasters for string types */
PyObject *binary_types; /* a set of typecasters for binary types */
} connectionObject;
/* C-callable functions in connection_int.c and connection_ext.c */

View File

@ -277,6 +277,10 @@ static struct PyMemberDef connectionObject_members[] = {
{"status", T_LONG,
offsetof(connectionObject, status), RO,
"The current transaction status."},
{"string_types", T_OBJECT, offsetof(connectionObject, string_types), RO,
"A set of typecasters to convert textual values."},
{"binary_types", T_OBJECT, offsetof(connectionObject, binary_types), RO,
"A set of typecasters to convert binary values."},
#endif
{NULL}
};
@ -301,7 +305,8 @@ connection_setup(connectionObject *self, char *dsn)
self->async_cursor = NULL;
self->pgconn = NULL;
self->mark = 0;
self->string_types = PyDict_New();
self->binary_types = PyDict_New();
pthread_mutex_init(&(self->lock), NULL);
@ -339,6 +344,8 @@ connection_dealloc(PyObject* obj)
Py_XDECREF(self->notice_list);
Py_XDECREF(self->notifies);
Py_XDECREF(self->async_cursor);
Py_XDECREF(self->string_types);
Py_XDECREF(self->binary_types);
pthread_mutex_destroy(&(self->lock));

View File

@ -1448,6 +1448,8 @@ cursor_dealloc(PyObject* obj)
Py_XDECREF(self->tuple_factory);
Py_XDECREF(self->tzinfo_factory);
Py_XDECREF(self->query);
Py_XDECREF(self->string_types);
Py_XDECREF(self->binary_types);
IFCLEARPGRES(self->pgres);

View File

@ -501,34 +501,44 @@ _pq_fetch_tuples(cursorObject *curs)
PyTuple_SET_ITEM(curs->description, i, dtitem);
/* fill the right cast function by accessing the global dictionary of
casting objects. if we got no defined cast use the default
one */
if (!(cast = PyDict_GetItem(curs->casts, type))) {
Dprintf("_pq_fetch_tuples: cast %d not in per-cursor dict", ftype);
if (!(cast = PyDict_GetItem(psyco_types, type))) {
Dprintf("_pq_fetch_tuples: cast %d not found, using default",
PQftype(curs->pgres,i));
cast = psyco_default_cast;
/* fill the right cast function by accessing three different dictionaries:
- the per-cursor dictionary, if available (can be NULL or None)
- the per-connection dictionary (always exists but can be null)
- the global dictionary (at module level)
if we get no defined cast use the default one */
Dprintf("_pq_fetch_tuples: looking for cast %d:", ftype);
if (curs->string_types != NULL && curs->string_types != Py_None) {
cast = PyDict_GetItem(curs->string_types, type);
Dprintf("_pq_fetch_tuples: per-cursor dict: %p", cast);
}
if (cast == NULL) {
cast = PyDict_GetItem(curs->conn->string_types, type);
Dprintf("_pq_fetch_tuples: per-connection dict: %p", cast);
}
if (cast == NULL) {
cast = PyDict_GetItem(psyco_types, type);
Dprintf("_pq_fetch_tuples: global dict: %p", cast);
}
if (cast == NULL) cast = psyco_default_cast;
/* else if we got binary tuples and if we got a field that
is binary use the default cast
FIXME: what the hell am I trying to do here? This just can't work..
*/
else if (pgbintuples && cast == psyco_default_binary_cast) {
if (pgbintuples && cast == psyco_default_binary_cast) {
Dprintf("_pq_fetch_tuples: Binary cursor and "
"binary field: %i using default cast",
PQftype(curs->pgres,i));
cast = psyco_default_cast;
}
Dprintf("_pq_fetch_tuples: using cast at %p (%s) for type %d",
cast, PyString_AS_STRING(((typecastObject*)cast)->name),
PQftype(curs->pgres,i));
Py_INCREF(cast);
PyTuple_SET_ITEM(curs->casts, i, cast);
/* 1/ fill the other fields */
PyTuple_SET_ITEM(dtitem, 0,
PyString_FromString(PQfname(curs->pgres, i)));

View File

@ -228,16 +228,39 @@ psyco_connect(PyObject *self, PyObject *args, PyObject *keywds)
" the string representation returned by PostgreSQL (`None` if ``NULL``)\n" \
" and ``cur`` is the cursor from which data are read."
static void
_psyco_register_type_set(PyObject **dict, PyObject *type)
{
if (*dict == NULL)
*dict = PyDict_New();
typecast_add(type, *dict, 0);
}
static PyObject *
psyco_register_type(PyObject *self, PyObject *args)
{
PyObject *type;
PyObject *type, *obj;
if (!PyArg_ParseTuple(args, "O!", &typecastType, &type)) {
if (!PyArg_ParseTuple(args, "O!|O", &typecastType, &type, &obj)) {
return NULL;
}
typecast_add(type, 0);
if (obj != NULL) {
if (obj->ob_type == &cursorType) {
_psyco_register_type_set(&(((cursorObject*)obj)->string_types), type);
}
else if (obj->ob_type == &connectionType) {
typecast_add(type, ((connectionObject*)obj)->string_types, 0);
}
else {
PyErr_SetString(PyExc_TypeError,
"argument 2 must be a connection, cursor or None");
return NULL;
}
}
else {
typecast_add(type, NULL, 0);
}
Py_INCREF(Py_None);
return Py_None;

View File

@ -226,7 +226,7 @@ typecast_init(PyObject *dict)
t = (typecastObject *)typecast_from_c(&(typecast_builtins[i]), dict);
if (t == NULL) return -1;
if (typecast_add((PyObject *)t, 0) != 0) return -1;
if (typecast_add((PyObject *)t, NULL, 0) != 0) return -1;
PyDict_SetItem(dict, t->name, (PyObject *)t);
@ -264,7 +264,7 @@ typecast_init(PyObject *dict)
/* typecast_add - add a type object to the dictionary */
int
typecast_add(PyObject *obj, int binary)
typecast_add(PyObject *obj, PyObject *dict, int binary)
{
PyObject *val;
Py_ssize_t len, i;
@ -274,16 +274,14 @@ typecast_add(PyObject *obj, int binary)
Dprintf("typecast_add: object at %p, values refcnt = %d",
obj, type->values->ob_refcnt);
if (dict == NULL)
dict = (binary ? psyco_binary_types : psyco_types);
len = PyTuple_Size(type->values);
for (i = 0; i < len; i++) {
val = PyTuple_GetItem(type->values, i);
Dprintf("typecast_add: adding val: %ld", PyInt_AsLong(val));
if (binary) {
PyDict_SetItem(psyco_binary_types, val, obj);
}
else {
PyDict_SetItem(psyco_types, val, obj);
}
PyDict_SetItem(dict, val, obj);
}
Dprintf("typecast_add: base caster: %p", type->bcast);

View File

@ -69,7 +69,7 @@ extern PyObject *psyco_default_binary_cast;
/* used by module.c to init the type system and register types */
extern int typecast_init(PyObject *dict);
extern int typecast_add(PyObject *obj, int binary);
extern int typecast_add(PyObject *obj, PyObject *dict, int binary);
/* the C callable typecastObject creator function */
extern PyObject *typecast_from_c(typecastObject_initlist *type, PyObject *d);

View File

@ -1,5 +1,5 @@
[build_ext]
define=PSYCOPG_DEBUG,PSYCOPG_EXTENSIONS,PSYCOPG_DISPLAY_SIZE,PSYCOPG_NEW_BOOLEAN,HAVE_PQFREEMEM,HAVE_PQPROTOCOL3
define=PSYCOPG_EXTENSIONS,PSYCOPG_DISPLAY_SIZE,PSYCOPG_NEW_BOOLEAN,HAVE_PQFREEMEM,HAVE_PQPROTOCOL3
# PSYCOPG_EXTENSIONS enables extensions to PEP-249 (you really want this)
# PSYCOPG_DISPLAY_SIZE enable display size calculation (a little slower)
# HAVE_PQFREEMEM should be defined on PostgreSQL >= 7.4