diff --git a/ChangeLog b/ChangeLog index 6c6bec28..1bef4973 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,11 @@ 2005-04-10 Federico Di Gregorio + * psycopg/adapter_list.*: added list adapter. + + * psycopg/microprotocols.c (microprotocol_getquoted): moved + _mogrify_getquoted into utility function in the microprotocols + library. + * setup.py: Added extensive error message on missing datetime headers. diff --git a/psycopg/adapter_list.c b/psycopg/adapter_list.c new file mode 100644 index 00000000..88db602b --- /dev/null +++ b/psycopg/adapter_list.c @@ -0,0 +1,301 @@ +/* adapter_list.c - python list objects + * + * Copyright (C) 2004-2005 Federico Di Gregorio + * + * This file is part of psycopg. + * + * 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 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include + +#include +#include + +#define PSYCOPG_MODULE +#include "psycopg/config.h" +#include "psycopg/python.h" +#include "psycopg/psycopg.h" +#include "psycopg/adapter_list.h" +#include "psycopg/microprotocols.h" +#include "psycopg/microprotocols_proto.h" + + +/* list_str, list_getquoted - return result of quoting */ + +static PyObject * +list_quote(listObject *self) +{ + /* adapt the list by calling adapt() recursively and then wrapping + everything into "ARRAY[]" */ + PyObject *tmp = NULL, *str = NULL, *joined = NULL, *res = NULL; + int i, len; + + len = PyList_GET_SIZE(self->wrapped); + + /* 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"); + + tmp = PyTuple_New(len); + + for (i=0; iwrapped, i), + (connectionObject*)self->connection); + if (quoted == NULL) goto error; + + /* here we don't loose a refcnt: SET_ITEM does not change the + reference count and we are just transferring ownership of the tmp + object to the tuple */ + PyTuple_SET_ITEM(tmp, i, quoted); + } + + /* now that we have a tuple of adapted objects we just need to join them + and put "ARRAY[] around the result */ + str = PyString_FromString(", "); + joined = PyObject_CallMethod(str, "join", "(O)", tmp); + if (joined == NULL) goto error; + + res = PyString_FromFormat("ARRAY[%s]", PyString_AsString(joined)); + + error: + Py_XDECREF(tmp); + Py_XDECREF(str); + Py_XDECREF(joined); + return res; +} + +PyObject * +list_str(listObject *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, "")) return NULL; + return list_quote(self); +} + +PyObject * +list_getquoted(listObject *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, "")) return NULL; + return list_quote(self); +} + +PyObject * +list_prepare(listObject *self, PyObject *args) +{ + connectionObject *conn; + + if (!PyArg_ParseTuple(args, "O", &conn)) + return NULL; + + /* note that we don't copy the encoding from the connection, but take a + reference to it; we'll need it during the recursive adapt() call (the + encoding is here for a future expansion that will make .getquoted() + work even without a connection to the backend. */ + Py_XDECREF(self->connection); + self->connection = (PyObject*)conn; + Py_INCREF(self->connection); + + Py_INCREF(Py_None); + return Py_None; +} + +PyObject * +list_conform(listObject *self, PyObject *args) +{ + PyObject *res, *proto; + + if (!PyArg_ParseTuple(args, "O", &proto)) return NULL; + + if (proto == (PyObject*)&isqlquoteType) + res = (PyObject*)self; + else + res = Py_None; + + Py_INCREF(res); + return res; +} + +/** the DateTime wrapper object **/ + +/* object member list */ + +static struct PyMemberDef listObject_members[] = { + {"adapted", T_OBJECT, offsetof(listObject, wrapped), RO}, + {NULL} +}; + +/* object method table */ + +static PyMethodDef listObject_methods[] = { + {"getquoted", (PyCFunction)list_getquoted, METH_VARARGS, + "getquoted() -> wrapped object value as SQL date/time"}, + {"prepare", (PyCFunction)list_prepare, METH_VARARGS, + "prepare(conn) -> set encoding to conn->encoding"}, + {"__conform__", (PyCFunction)list_conform, METH_VARARGS, NULL}, + {NULL} /* Sentinel */ +}; + +/* initialization and finalization methods */ + +static int +list_setup(listObject *self, PyObject *obj, char *enc) +{ + Dprintf("list_setup: init list object at %p, refcnt = %d", + self, ((PyObject *)self)->ob_refcnt); + + if (!PyList_Check(obj)) + return -1; + + /* FIXME: remove this orrible strdup */ + if (enc) self->encoding = strdup(enc); + + self->connection = NULL; + self->wrapped = obj; + Py_INCREF(self->wrapped); + + Dprintf("list_setup: good list object at %p, refcnt = %d", + self, ((PyObject *)self)->ob_refcnt); + return 0; +} + +static void +list_dealloc(PyObject* obj) +{ + listObject *self = (listObject *)obj; + + Py_XDECREF(self->wrapped); + Py_XDECREF(self->connection); + if (self->encoding) free(self->encoding); + + Dprintf("list_dealloc: deleted list object at %p, " + "refcnt = %d", obj, obj->ob_refcnt); + + obj->ob_type->tp_free(obj); +} + +static int +list_init(PyObject *obj, PyObject *args, PyObject *kwds) +{ + PyObject *l; + char *enc = "latin-1"; /* default encoding as in Python */ + + if (!PyArg_ParseTuple(args, "O|s", &l, &enc)) + return -1; + + return list_setup((listObject *)obj, l, enc); +} + +static PyObject * +list_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + return type->tp_alloc(type, 0); +} + +static void +list_del(PyObject* self) +{ + PyObject_Del(self); +} + +static PyObject * +list_repr(listObject *self) +{ + return PyString_FromFormat("", self); +} + +/* object type */ + +#define listType_doc \ +"psycopg.List(list) -> new list wrapper object" + +PyTypeObject listType = { + PyObject_HEAD_INIT(NULL) + 0, + "psycopg._psycopg.datetime", + sizeof(listObject), + 0, + list_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + + 0, /*tp_compare*/ + (reprfunc)list_repr, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + + 0, /*tp_call*/ + (reprfunc)list_str, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/ + + listType_doc, /*tp_doc*/ + + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + + /* Attribute descriptor and subclassing stuff */ + + listObject_methods, /*tp_methods*/ + listObject_members, /*tp_members*/ + 0, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + + list_init, /*tp_init*/ + 0, /*tp_alloc will be set to PyType_GenericAlloc in module init*/ + list_new, /*tp_new*/ + (freefunc)list_del, /*tp_free Low-level free-memory routine */ + 0, /*tp_is_gc For PyObject_IS_GC */ + 0, /*tp_bases*/ + 0, /*tp_mro method resolution order */ + 0, /*tp_cache*/ + 0, /*tp_subclasses*/ + 0 /*tp_weaklist*/ +}; + + +/** module-level functions **/ + +PyObject * +psyco_List(PyObject *module, PyObject *args) +{ + PyObject *str; + char *enc = "latin-1"; /* default encoding as in Python */ + + if (!PyArg_ParseTuple(args, "O|s", &str, &enc)) + return NULL; + + return PyObject_CallFunction((PyObject *)&listType, "Os", str, enc); +} diff --git a/psycopg/adapter_list.h b/psycopg/adapter_list.h new file mode 100644 index 00000000..a6850284 --- /dev/null +++ b/psycopg/adapter_list.h @@ -0,0 +1,49 @@ +/* adapter_list.h - definition for the python list types + * + * Copyright (C) 2004-2005 Federico Di Gregorio + * + * This file is part of psycopg. + * + * 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 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef PSYCOPG_LIST_H +#define PSYCOPG_LIST_H 1 + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern PyTypeObject listType; + +typedef struct { + PyObject HEAD; + + PyObject *wrapped; + PyObject *connection; + char *encoding; +} listObject; + +extern PyObject *psyco_List(PyObject *module, PyObject *args); +#define psyco_List_doc \ + "psycopg.List(list, enc) -> new quoted list" + +#ifdef __cplusplus +} +#endif + +#endif /* !defined(PSYCOPG_LIST_H) */ diff --git a/psycopg/cursor_type.c b/psycopg/cursor_type.c index 8c239aee..cf73ccf2 100644 --- a/psycopg/cursor_type.c +++ b/psycopg/cursor_type.c @@ -62,38 +62,6 @@ psyco_curs_close(cursorObject *self, PyObject *args) /* mogrify a query string and build argument array or dict */ -static PyObject* -_mogrify_getquoted(PyObject *obj, connectionObject *conn) -{ - PyObject *res = NULL; - PyObject *tmp = microprotocols_adapt( - obj, (PyObject*)&isqlquoteType, NULL); - - if (tmp != NULL) { - Dprintf("_mogrify: adapted to %s", tmp->ob_type->tp_name); - - /* if requested prepare the object passing it the connection */ - if (PyObject_HasAttrString(tmp, "prepare")) { - res = PyObject_CallMethod(tmp, "prepare", "O", (PyObject*)conn); - if (res == NULL) { - Py_DECREF(tmp); - return NULL; - } - else { - Py_DECREF(res); - } - } - - /* call the getquoted method on tmp (that should exist because we - adapted to the right protocol) */ - res = PyObject_CallMethod(tmp, "getquoted", NULL); - Py_DECREF(tmp); - } - - /* we return res with one extra reference, the caller shall free it */ - return res; -} - static int _mogrify(PyObject *var, PyObject *fmt, connectionObject *conn, PyObject **new) { @@ -164,7 +132,7 @@ _mogrify(PyObject *var, PyObject *fmt, connectionObject *conn, PyObject **new) if (*d) *d = 's'; } else { - t = _mogrify_getquoted(value, conn); + t = microprotocol_getquoted(value, conn); if (t != NULL) { PyDict_SetItem(n, key, t); @@ -222,7 +190,7 @@ _mogrify(PyObject *var, PyObject *fmt, connectionObject *conn, PyObject **new) Py_DECREF(value); } else { - PyObject *t = _mogrify_getquoted(value, conn); + PyObject *t = microprotocol_getquoted(value, conn); if (t != NULL) { PyTuple_SET_ITEM(n, index, t); diff --git a/psycopg/microprotocols.c b/psycopg/microprotocols.c index 9675d914..6f75f250 100644 --- a/psycopg/microprotocols.c +++ b/psycopg/microprotocols.c @@ -27,6 +27,7 @@ #include "psycopg/python.h" #include "psycopg/psycopg.h" #include "psycopg/cursor.h" +#include "psycopg/connection.h" #include "psycopg/microprotocols.h" #include "psycopg/microprotocols_proto.h" @@ -109,6 +110,42 @@ microprotocols_adapt(PyObject *obj, PyObject *proto, PyObject *alt) return NULL; } +/* microprotocol_getquoted - utility function that adapt and call getquoted */ + +PyObject * +microprotocol_getquoted(PyObject *obj, connectionObject *conn) +{ + PyObject *res = NULL; + PyObject *tmp = microprotocols_adapt( + obj, (PyObject*)&isqlquoteType, NULL); + + if (tmp != NULL) { + Dprintf("microprotocol_getquoted: adapted to %s", + tmp->ob_type->tp_name); + + /* if requested prepare the object passing it the connection */ + if (PyObject_HasAttrString(tmp, "prepare") && conn) { + res = PyObject_CallMethod(tmp, "prepare", "O", (PyObject*)conn); + if (res == NULL) { + Py_DECREF(tmp); + return NULL; + } + else { + Py_DECREF(res); + } + } + + /* call the getquoted method on tmp (that should exist because we + adapted to the right protocol) */ + res = PyObject_CallMethod(tmp, "getquoted", NULL); + Py_DECREF(tmp); + } + + /* we return res with one extra reference, the caller shall free it */ + return res; +} + + /** module-level functions **/ PyObject * diff --git a/psycopg/microprotocols.h b/psycopg/microprotocols.h index 4cdeaad9..d6df2545 100644 --- a/psycopg/microprotocols.h +++ b/psycopg/microprotocols.h @@ -23,6 +23,8 @@ #define PSYCOPG_MICROPROTOCOLS_H 1 #include +#include "psycopg/connection.h" +#include "psycopg/cursor.h" #ifdef __cplusplus extern "C" { @@ -44,9 +46,12 @@ extern PyObject *psyco_adapters; extern int microprotocols_init(PyObject *dict); extern int microprotocols_add( PyTypeObject *type, PyObject *proto, PyObject *cast); + extern PyObject *microprotocols_adapt( PyObject *obj, PyObject *proto, PyObject *alt); - +extern PyObject *microprotocol_getquoted( + PyObject *obj, connectionObject *conn); + extern PyObject * psyco_microprotocols_adapt(cursorObject *self, PyObject *args); #define psyco_microprotocols_adapt_doc \ diff --git a/psycopg/psycopgmodule.c b/psycopg/psycopgmodule.c index 59a97d22..4e5f9ced 100644 --- a/psycopg/psycopgmodule.c +++ b/psycopg/psycopgmodule.c @@ -35,7 +35,7 @@ #include "psycopg/adapter_binary.h" #include "psycopg/adapter_pboolean.h" #include "psycopg/adapter_asis.h" - +#include "psycopg/adapter_list.h" #ifdef HAVE_MXDATETIME #include @@ -192,11 +192,12 @@ psyco_adapters_init(PyObject *mod) microprotocols_add(&PyFloat_Type, NULL, (PyObject*)&asisType); microprotocols_add(&PyInt_Type, NULL, (PyObject*)&asisType); microprotocols_add(&PyLong_Type, NULL, (PyObject*)&asisType); - + microprotocols_add(&PyString_Type, NULL, (PyObject*)&qstringType); microprotocols_add(&PyUnicode_Type, NULL, (PyObject*)&qstringType); microprotocols_add(&PyBuffer_Type, NULL, (PyObject*)&binaryType); - + microprotocols_add(&PyList_Type, NULL, (PyObject*)&listType); + #ifdef HAVE_MXDATETIME /* the module has already been initialized, so we can obtain the callable objects directly from its dictionary :) */ @@ -358,6 +359,8 @@ static PyMethodDef psycopgMethods[] = { METH_VARARGS, psyco_TimeFromTicks_doc}, {"TimestampFromTicks", (PyCFunction)psyco_TimestampFromTicks, METH_VARARGS, psyco_TimestampFromTicks_doc}, + {"List", (PyCFunction)psyco_List, + METH_VARARGS, psyco_List_doc}, #ifdef HAVE_MXDATETIME {"DateFromMx", (PyCFunction)psyco_DateFromMx, @@ -402,6 +405,7 @@ init_psycopg(void) binaryType.ob_type = &PyType_Type; isqlquoteType.ob_type = &PyType_Type; asisType.ob_type = &PyType_Type; + listType.ob_type = &PyType_Type; if (PyType_Ready(&connectionType) == -1) return; if (PyType_Ready(&cursorType) == -1) return; @@ -410,6 +414,7 @@ init_psycopg(void) if (PyType_Ready(&binaryType) == -1) return; if (PyType_Ready(&isqlquoteType) == -1) return; if (PyType_Ready(&asisType) == -1) return; + if (PyType_Ready(&listType) == -1) return; #ifdef HAVE_PYBOOL pbooleanType.ob_type = &PyType_Type; @@ -495,6 +500,7 @@ init_psycopg(void) connectionType.tp_alloc = PyType_GenericAlloc; asisType.tp_alloc = PyType_GenericAlloc; qstringType.tp_alloc = PyType_GenericAlloc; - + listType.tp_alloc = PyType_GenericAlloc; + Dprintf("initpsycopg: module initialization complete"); } diff --git a/setup.py b/setup.py index b79af28e..6a11c1a6 100644 --- a/setup.py +++ b/setup.py @@ -230,7 +230,7 @@ sources = [ 'microprotocols.c', 'microprotocols_proto.c', 'connection_type.c', 'connection_int.c', 'cursor_type.c', 'cursor_int.c', 'adapter_qstring.c', 'adapter_pboolean.c', 'adapter_binary.c', - 'adapter_asis.c'] + 'adapter_asis.c', 'adapter_list.c'] from ConfigParser import ConfigParser parser = ConfigParser()