mirror of
https://github.com/psycopg/psycopg2.git
synced 2024-11-14 04:56:33 +03:00
232 lines
7.1 KiB
C
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);
|
|
}
|