psycopg2/psycopg/microprotocols.c
2010-11-08 01:35:06 +00:00

232 lines
7.1 KiB
C

/* microprotocols.c - minimalist and non-validating protocols implementation
*
* Copyright (C) 2003-2010 Federico Di Gregorio <fog@debian.org>
*
* This file is part of psycopg.
*
* psycopg2 is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In addition, as a special exception, the copyright holders give
* permission to link this program with the OpenSSL library (or with
* modified versions of OpenSSL that use the same license as OpenSSL),
* and distribute linked combinations including the two.
*
* You must obey the GNU Lesser General Public License in all respects for
* all of the code used other than OpenSSL.
*
* psycopg2 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 Lesser General Public
* License for more details.
*/
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <structmember.h>
#define PSYCOPG_MODULE
#include "psycopg/config.h"
#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"
/** the adapters registry **/
PyObject *psyco_adapters;
/* microprotocols_init - initialize the adapters dictionary */
int
microprotocols_init(PyObject *dict)
{
/* create adapters dictionary and put it in module namespace */
if ((psyco_adapters = PyDict_New()) == NULL) {
return -1;
}
PyDict_SetItemString(dict, "adapters", psyco_adapters);
return 0;
}
/* microprotocols_add - add a reverse type-caster to the dictionary */
int
microprotocols_add(PyTypeObject *type, PyObject *proto, PyObject *cast)
{
PyObject *key;
if (proto == NULL) proto = (PyObject*)&isqlquoteType;
Dprintf("microprotocols_add: cast %p for (%s, ?)", cast, type->tp_name);
key = PyTuple_Pack(2, (PyObject*)type, proto);
PyDict_SetItem(psyco_adapters, key, cast);
Py_DECREF(key);
return 0;
}
/* Check if one of `obj` superclasses has an adapter for `proto`.
*
* If it does, return a *borrowed reference* to the adapter, else NULL.
*/
static PyObject *
_get_superclass_adapter(PyObject *obj, PyObject *proto)
{
PyTypeObject *type;
PyObject *mro, *st;
PyObject *key, *adapter;
Py_ssize_t i, ii;
type = (PyTypeObject *)Py_TYPE(obj);
if (!(Py_TPFLAGS_HAVE_CLASS & type->tp_flags)) {
/* has no mro */
return NULL;
}
/* Walk the mro from the most specific subclass. */
mro = type->tp_mro;
for (i = 1, ii = PyTuple_GET_SIZE(mro); i < ii; ++i) {
st = PyTuple_GET_ITEM(mro, i);
key = PyTuple_Pack(2, st, proto);
adapter = PyDict_GetItem(psyco_adapters, key);
Py_DECREF(key);
if (adapter) {
Dprintf(
"microprotocols_adapt: using '%s' adapter to adapt '%s'",
((PyTypeObject *)st)->tp_name, type->tp_name);
/* register this adapter as good for the subclass too,
* so that the next time it will be found in the fast path */
/* Well, no, maybe this is not a good idea.
* It would become a leak in case of dynamic
* classes generated in a loop (think namedtuples). */
/* key = PyTuple_Pack(2, (PyObject*)type, proto);
* PyDict_SetItem(psyco_adapters, key, adapter);
* Py_DECREF(key);
*/
return adapter;
}
}
return NULL;
}
/* microprotocols_adapt - adapt an object to the built-in protocol */
PyObject *
microprotocols_adapt(PyObject *obj, PyObject *proto, PyObject *alt)
{
PyObject *adapter, *adapted, *key;
char buffer[256];
/* we don't check for exact type conformance as specified in PEP 246
because the ISQLQuote type is abstract and there is no way to get a
quotable object to be its instance */
/* None is always adapted to NULL */
if (obj == Py_None)
return PyString_FromString("NULL");
Dprintf("microprotocols_adapt: trying to adapt %s", obj->ob_type->tp_name);
/* look for an adapter in the registry */
key = PyTuple_Pack(2, Py_TYPE(obj), proto);
adapter = PyDict_GetItem(psyco_adapters, key);
Py_DECREF(key);
if (adapter) {
adapted = PyObject_CallFunctionObjArgs(adapter, obj, NULL);
return adapted;
}
/* Check if a superclass can be adapted and use the same adapter. */
if (NULL != (adapter = _get_superclass_adapter(obj, proto))) {
adapted = PyObject_CallFunctionObjArgs(adapter, obj, NULL);
return adapted;
}
/* try to have the protocol adapt this object*/
if (PyObject_HasAttrString(proto, "__adapt__")) {
adapted = PyObject_CallMethod(proto, "__adapt__", "O", obj);
if (adapted && adapted != Py_None) return adapted;
Py_XDECREF(adapted);
if (PyErr_Occurred() && !PyErr_ExceptionMatches(PyExc_TypeError))
return NULL;
}
/* and finally try to have the object adapt itself */
if (PyObject_HasAttrString(obj, "__conform__")) {
adapted = PyObject_CallMethod(obj, "__conform__","O", proto);
if (adapted && adapted != Py_None) return adapted;
Py_XDECREF(adapted);
if (PyErr_Occurred() && !PyErr_ExceptionMatches(PyExc_TypeError))
return NULL;
}
/* else set the right exception and return NULL */
PyOS_snprintf(buffer, 255, "can't adapt type '%s'", obj->ob_type->tp_name);
psyco_set_error(ProgrammingError, NULL, buffer, NULL, NULL);
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 *
psyco_microprotocols_adapt(cursorObject *self, PyObject *args)
{
PyObject *obj, *alt = NULL;
PyObject *proto = (PyObject*)&isqlquoteType;
if (!PyArg_ParseTuple(args, "O|OO", &obj, &proto, &alt)) return NULL;
return microprotocols_adapt(obj, proto, alt);
}