/* microprotocols.c - minimalist and non-validating protocols implementation * * Copyright (C) 2003-2010 Federico Di Gregorio * * 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 PSYCOPG_MODULE #include "psycopg/psycopg.h" #include "psycopg/microprotocols.h" #include "psycopg/microprotocols_proto.h" #include "psycopg/cursor.h" #include "psycopg/connection.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 * * Return 0 on success, else -1 and set an exception. */ int microprotocols_add(PyTypeObject *type, PyObject *proto, PyObject *cast) { PyObject *key = NULL; int rv = -1; if (proto == NULL) proto = (PyObject*)&isqlquoteType; Dprintf("microprotocols_add: cast %p for (%s, ?)", cast, type->tp_name); if (!(key = PyTuple_Pack(2, (PyObject*)type, proto))) { goto exit; } if (0 != PyDict_SetItem(psyco_adapters, key, cast)) { goto exit; } rv = 0; exit: Py_XDECREF(key); return rv; } /* Check if one of `obj` superclasses has an adapter for `proto`. * * If it does, return a *borrowed reference* to the adapter, else to None. */ BORROWED static PyObject * _get_superclass_adapter(PyObject *obj, PyObject *proto) { PyTypeObject *type; PyObject *mro, *st; PyObject *key, *adapter; Py_ssize_t i, ii; type = Py_TYPE(obj); if (!( #if PY_MAJOR_VERSION < 3 (Py_TPFLAGS_HAVE_CLASS & type->tp_flags) && #endif type->tp_mro)) { /* has no mro */ return Py_None; } /* 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); if (!(key = PyTuple_Pack(2, st, proto))) { return NULL; } 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 Py_None; } /* microprotocols_adapt - adapt an object to the built-in protocol */ PyObject * microprotocols_adapt(PyObject *obj, PyObject *proto, PyObject *alt) { PyObject *adapter, *adapted, *key, *meth; 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 */ Dprintf("microprotocols_adapt: trying to adapt %s", Py_TYPE(obj)->tp_name); /* look for an adapter in the registry */ if (!(key = PyTuple_Pack(2, Py_TYPE(obj), proto))) { return NULL; } adapter = PyDict_GetItem(psyco_adapters, key); Py_DECREF(key); if (adapter) { adapted = PyObject_CallFunctionObjArgs(adapter, obj, NULL); return adapted; } /* try to have the protocol adapt this object*/ if ((meth = PyObject_GetAttrString(proto, "__adapt__"))) { adapted = PyObject_CallFunctionObjArgs(meth, obj, NULL); Py_DECREF(meth); if (adapted && adapted != Py_None) return adapted; Py_XDECREF(adapted); if (PyErr_Occurred()) { if (PyErr_ExceptionMatches(PyExc_TypeError)) { PyErr_Clear(); } else { return NULL; } } } else { /* proto.__adapt__ not found. */ PyErr_Clear(); } /* then try to have the object adapt itself */ if ((meth = PyObject_GetAttrString(obj, "__conform__"))) { adapted = PyObject_CallFunctionObjArgs(meth, proto, NULL); Py_DECREF(meth); if (adapted && adapted != Py_None) return adapted; Py_XDECREF(adapted); if (PyErr_Occurred()) { if (PyErr_ExceptionMatches(PyExc_TypeError)) { PyErr_Clear(); } else { return NULL; } } } else { /* obj.__conform__ not found. */ PyErr_Clear(); } /* Finally check if a superclass can be adapted and use the same adapter. */ if (!(adapter = _get_superclass_adapter(obj, proto))) { return NULL; } if (Py_None != adapter) { adapted = PyObject_CallFunctionObjArgs(adapter, obj, NULL); return adapted; } /* else set the right exception and return NULL */ PyOS_snprintf(buffer, 255, "can't adapt type '%s'", Py_TYPE(obj)->tp_name); psyco_set_error(ProgrammingError, NULL, buffer); return NULL; } /* microprotocol_getquoted - utility function that adapt and call getquoted. * * Return a bytes string, NULL on error. */ PyObject * microprotocol_getquoted(PyObject *obj, connectionObject *conn) { PyObject *res = NULL; PyObject *prepare = NULL; PyObject *adapted; if (!(adapted = microprotocols_adapt(obj, (PyObject*)&isqlquoteType, NULL))) { goto exit; } Dprintf("microprotocol_getquoted: adapted to %s", Py_TYPE(adapted)->tp_name); /* if requested prepare the object passing it the connection */ if (conn) { if ((prepare = PyObject_GetAttrString(adapted, "prepare"))) { res = PyObject_CallFunctionObjArgs( prepare, (PyObject *)conn, NULL); if (res) { Py_DECREF(res); res = NULL; } else { goto exit; } } else { /* adapted.prepare not found */ PyErr_Clear(); } } /* call the getquoted method on adapted (that should exist because we adapted to the right protocol) */ res = PyObject_CallMethod(adapted, "getquoted", NULL); /* Convert to bytes. */ if (res && PyUnicode_CheckExact(res)) { PyObject *b; b = conn_encode(conn, res); Py_DECREF(res); res = b; } exit: Py_XDECREF(adapted); Py_XDECREF(prepare); /* 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); }