From b3b225a9da450ed11ffbe8a61ba28ca6b343af32 Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Thu, 11 Oct 2018 02:59:45 +0100 Subject: Added C implementation for a Column type Currently behaving exactly like the previous (named)tuple. --- psycopg/column.h | 44 ++++++ psycopg/column_type.c | 353 ++++++++++++++++++++++++++++++++++++++++++++++++ psycopg/pqpath.c | 52 +++---- psycopg/psycopgmodule.c | 67 +-------- setup.py | 4 +- 5 files changed, 422 insertions(+), 98 deletions(-) create mode 100644 psycopg/column.h create mode 100644 psycopg/column_type.c diff --git a/psycopg/column.h b/psycopg/column.h new file mode 100644 index 0000000..b551345 --- /dev/null +++ b/psycopg/column.h @@ -0,0 +1,44 @@ +/* column.h - definition for a column in cursor.description type + * + * Copyright (C) 2018 Daniele Varrazzo + * + * 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. + */ + +#ifndef PSYCOPG_COLUMN_H +#define PSYCOPG_COLUMN_H 1 + +extern HIDDEN PyTypeObject columnType; + +typedef struct { + PyObject_HEAD + + PyObject *name; + PyObject *type_code; + PyObject *display_size; + PyObject *internal_size; + PyObject *precision; + PyObject *scale; + PyObject *null_ok; + +} columnObject; + +#endif /* PSYCOPG_COLUMN_H */ diff --git a/psycopg/column_type.c b/psycopg/column_type.c new file mode 100644 index 0000000..f8476a4 --- /dev/null +++ b/psycopg/column_type.c @@ -0,0 +1,353 @@ +/* column_type.c - python interface to cursor.description objects + * + * Copyright (C) 2018 Daniele Varrazzo + * + * 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/column.h" + + +static const char column_doc[] = + "Description of a column returned by a query.\n\n" + "The DBAPI demands this object to be a 7-items sequence. This object\n" + "respects this interface, but adds names for the exposed attributes\n" + "and adds attribute not requested by the DBAPI."; + +static const char name_doc[] = + "The name of the column returned."; + +static const char type_code_doc[] = + "The PostgreSQL OID of the column.\n\n" + "You can use the pg_type system table to get more informations about the\n" + "type. This is the value used by Psycopg to decide what Python type use\n" + "to represent the value"; + +static const char display_size_doc[] = + "The actual length of the column in bytes.\n\n" + "Obtaining this value is computationally intensive, so it is always None\n" + "unless the PSYCOPG_DISPLAY_SIZE parameter is set at compile time."; + +static const char internal_size_doc[] = + "The size in bytes of the column associated to this column on the server.\n\n" + "Set to a negative value for variable-size types."; + +static const char precision_doc[] = + "Total number of significant digits in columns of type NUMERIC.\n\n" + "None for other types."; + +static const char scale_doc[] = + "Count of decimal digits in the fractional part in columns of type NUMERIC.\n\n" + "None for other types."; + +static const char null_ok_doc[] = + "Always none.\n\n"; + + +static PyMemberDef column_members[] = { + { "name", T_OBJECT, offsetof(columnObject, name), READONLY, (char *)name_doc }, + { "type_code", T_OBJECT, offsetof(columnObject, type_code), READONLY, (char *)type_code_doc }, + { "display_size", T_OBJECT, offsetof(columnObject, display_size), READONLY, (char *)display_size_doc }, + { "internal_size", T_OBJECT, offsetof(columnObject, internal_size), READONLY, (char *)internal_size_doc }, + { "precision", T_OBJECT, offsetof(columnObject, precision), READONLY, (char *)precision_doc }, + { "scale", T_OBJECT, offsetof(columnObject, scale), READONLY, (char *)scale_doc }, + { "null_ok", T_OBJECT, offsetof(columnObject, null_ok), READONLY, (char *)null_ok_doc }, + { NULL } +}; + + +static PyObject * +column_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + return type->tp_alloc(type, 0); +} + + +static int +column_init(columnObject *self, PyObject *args, PyObject *kwargs) +{ + static char *kwlist[] = { + "name", "type_code", "display_size", "internal_size", + "precision", "scale", "null_ok", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|OOOOOOO", kwlist, + &self->name, &self->type_code, &self->display_size, + &self->internal_size, &self->precision, &self->scale, + &self->null_ok)) { + return -1; + } + + return 0; +} + + +static void +column_dealloc(columnObject *self) +{ + Py_CLEAR(self->name); + Py_CLEAR(self->type_code); + Py_CLEAR(self->display_size); + Py_CLEAR(self->internal_size); + Py_CLEAR(self->precision); + Py_CLEAR(self->scale); + Py_CLEAR(self->null_ok); + + Py_TYPE(self)->tp_free((PyObject *)self); +} + + +static PyObject* +column_repr(columnObject *self) +{ + PyObject *rv = NULL; + PyObject *format = NULL; + PyObject *args = NULL; + PyObject *tmp; + + if (!(format = Text_FromUTF8("Column(name=%r, type_code=%r)"))) { + goto exit; + } + + if (!(args = PyTuple_New(2))) { goto exit; } + + tmp = self->name ? self->name : Py_None; + Py_INCREF(tmp); + PyTuple_SET_ITEM(args, 0, tmp); + + tmp = self->type_code ? self->type_code : Py_None; + Py_INCREF(tmp); + PyTuple_SET_ITEM(args, 1, tmp); + + rv = Text_Format(format, args); + +exit: + Py_XDECREF(args); + Py_XDECREF(format); + + return rv; +} + + +static PyObject * +column_richcompare(columnObject *self, PyObject *other, int op) +{ + PyObject *rv = NULL; + PyObject *tself = NULL; + + if (!(tself = PyObject_CallFunctionObjArgs( + (PyObject *)&PyTuple_Type, (PyObject *)self, NULL))) { + goto exit; + } + + rv = PyObject_RichCompare(tself, other, op); + +exit: + Py_XDECREF(tself); + return rv; +} + + +/* column description can be accessed as a 7 items tuple for DBAPI compatibility */ + +static Py_ssize_t +column_len(columnObject *self) +{ + return 7; +} + + +static PyObject * +column_getitem(columnObject *self, Py_ssize_t item) +{ + PyObject *rv = NULL; + + if (item < 0) + item += 7; + + switch (item) { + case 0: + rv = self->name; + break; + case 1: + rv = self->type_code; + break; + case 2: + rv = self->display_size; + break; + case 3: + rv = self->internal_size; + break; + case 4: + rv = self->precision; + break; + case 5: + rv = self->scale; + break; + case 6: + rv = self->null_ok; + break; + default: + PyErr_SetString(PyExc_IndexError, "index out of range"); + return NULL; + } + + if (!rv) { + rv = Py_None; + } + + Py_INCREF(rv); + return rv; +} + + +static PySequenceMethods column_sequence = { + (lenfunc)column_len, /* sq_length */ + 0, /* sq_concat */ + 0, /* sq_repeat */ + (ssizeargfunc)column_getitem, /* sq_item */ + 0, /* sq_slice */ + 0, /* sq_ass_item */ + 0, /* sq_ass_slice */ + 0, /* sq_contains */ + 0, /* sq_inplace_concat */ + 0, /* sq_inplace_repeat */ +}; + + +static PyObject * +column_getstate(columnObject *self) +{ + return PyObject_CallFunctionObjArgs( + (PyObject *)&PyTuple_Type, (PyObject *)self, NULL); +} + + +PyObject * +column_setstate(columnObject *self, PyObject *state) +{ + Py_ssize_t size; + PyObject *rv = NULL; + + if (state == Py_None) { + goto exit; + } + if (!PyTuple_Check(state)) { + PyErr_SetString(PyExc_TypeError, "state is not a tuple"); + goto error; + } + + size = PyTuple_GET_SIZE(state); + + if (size > 0) { + Py_CLEAR(self->name); + self->name = PyTuple_GET_ITEM(state, 0); + Py_INCREF(self->name); + } + if (size > 1) { + Py_CLEAR(self->type_code); + self->type_code = PyTuple_GET_ITEM(state, 1); + Py_INCREF(self->type_code); + } + if (size > 2) { + Py_CLEAR(self->display_size); + self->display_size = PyTuple_GET_ITEM(state, 2); + Py_INCREF(self->display_size); + } + if (size > 3) { + Py_CLEAR(self->internal_size); + self->internal_size = PyTuple_GET_ITEM(state, 3); + Py_INCREF(self->internal_size); + } + if (size > 4) { + Py_CLEAR(self->precision); + self->precision = PyTuple_GET_ITEM(state, 4); + Py_INCREF(self->precision); + } + if (size > 5) { + Py_CLEAR(self->scale); + self->scale = PyTuple_GET_ITEM(state, 5); + Py_INCREF(self->scale); + } + if (size > 6) { + Py_CLEAR(self->null_ok); + self->null_ok = PyTuple_GET_ITEM(state, 6); + Py_INCREF(self->null_ok); + } + +exit: + rv = Py_None; + Py_INCREF(rv); + +error: + return rv; +} + + +static PyMethodDef column_methods[] = { + /* Make Column picklable. */ + {"__getstate__", (PyCFunction)column_getstate, METH_NOARGS }, + {"__setstate__", (PyCFunction)column_setstate, METH_O }, + {NULL} +}; + + +PyTypeObject columnType = { + PyVarObject_HEAD_INIT(NULL, 0) + "psycopg2.extensions.Column", + sizeof(columnObject), 0, + (destructor)column_dealloc, /* tp_dealloc */ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + (reprfunc)column_repr, /*tp_repr*/ + 0, /*tp_as_number*/ + &column_sequence, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/ + column_doc, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + (richcmpfunc)column_richcompare, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + column_methods, /*tp_methods*/ + column_members, /*tp_members*/ + 0, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + (initproc)column_init, /*tp_init*/ + 0, /*tp_alloc*/ + column_new, /*tp_new*/ +}; diff --git a/psycopg/pqpath.c b/psycopg/pqpath.c index 287262c..e318802 100644 --- a/psycopg/pqpath.c +++ b/psycopg/pqpath.c @@ -41,6 +41,7 @@ #include "psycopg/typecast.h" #include "psycopg/pgtypes.h" #include "psycopg/error.h" +#include "psycopg/column.h" #include "psycopg/libpq_support.h" #include "libpq-fe.h" @@ -1207,11 +1208,14 @@ _pq_fetch_tuples(cursorObject *curs) int fsize = PQfsize(curs->pgres, i); int fmod = PQfmod(curs->pgres, i); - PyObject *dtitem = NULL; + columnObject *column = NULL; PyObject *type = NULL; PyObject *cast = NULL; - if (!(dtitem = PyTuple_New(7))) { goto exit; } + if (!(column = (columnObject *)PyObject_CallObject( + (PyObject *)&columnType, NULL))) { + goto exit; + } /* fill the right cast function by accessing three different dictionaries: - the per-cursor dictionary, if available (can be NULL or None) @@ -1248,20 +1252,16 @@ _pq_fetch_tuples(cursorObject *curs) curs->conn, PQfname(curs->pgres, i)))) { goto err_for; } - PyTuple_SET_ITEM(dtitem, 0, tmp); + column->name = tmp; } - PyTuple_SET_ITEM(dtitem, 1, type); + column->type_code = type; type = NULL; /* 2/ display size is the maximum size of this field result tuples. */ if (dsize && dsize[i] >= 0) { PyObject *tmp; if (!(tmp = PyInt_FromLong(dsize[i]))) { goto err_for; } - PyTuple_SET_ITEM(dtitem, 2, tmp); - } - else { - Py_INCREF(Py_None); - PyTuple_SET_ITEM(dtitem, 2, Py_None); + column->display_size = tmp; } /* 3/ size on the backend */ @@ -1270,18 +1270,18 @@ _pq_fetch_tuples(cursorObject *curs) if (ftype == NUMERICOID) { PyObject *tmp; if (!(tmp = PyInt_FromLong((fmod >> 16)))) { goto err_for; } - PyTuple_SET_ITEM(dtitem, 3, tmp); + column->internal_size = tmp; } else { /* If variable length record, return maximum size */ PyObject *tmp; if (!(tmp = PyInt_FromLong(fmod))) { goto err_for; } - PyTuple_SET_ITEM(dtitem, 3, tmp); + column->internal_size = tmp; } } else { PyObject *tmp; if (!(tmp = PyInt_FromLong(fsize))) { goto err_for; } - PyTuple_SET_ITEM(dtitem, 3, tmp); + column->internal_size = tmp; } /* 4,5/ scale and precision */ @@ -1291,40 +1291,24 @@ _pq_fetch_tuples(cursorObject *curs) if (!(tmp = PyInt_FromLong((fmod >> 16) & 0xFFFF))) { goto err_for; } - PyTuple_SET_ITEM(dtitem, 4, tmp); + column->precision = tmp; if (!(tmp = PyInt_FromLong(fmod & 0xFFFF))) { - PyTuple_SET_ITEM(dtitem, 5, tmp); + goto err_for; } - PyTuple_SET_ITEM(dtitem, 5, tmp); - } - else { - Py_INCREF(Py_None); - PyTuple_SET_ITEM(dtitem, 4, Py_None); - Py_INCREF(Py_None); - PyTuple_SET_ITEM(dtitem, 5, Py_None); + column->scale = tmp; } /* 6/ FIXME: null_ok??? */ - Py_INCREF(Py_None); - PyTuple_SET_ITEM(dtitem, 6, Py_None); - - /* Convert into a namedtuple if available */ - if (Py_None != psyco_DescriptionType) { - PyObject *tmp = dtitem; - dtitem = PyObject_CallObject(psyco_DescriptionType, tmp); - Py_DECREF(tmp); - if (NULL == dtitem) { goto err_for; } - } - PyTuple_SET_ITEM(description, i, dtitem); - dtitem = NULL; + PyTuple_SET_ITEM(description, i, (PyObject *)column); + column = NULL; continue; err_for: Py_XDECREF(type); - Py_XDECREF(dtitem); + Py_XDECREF(column); goto exit; } diff --git a/psycopg/psycopgmodule.c b/psycopg/psycopgmodule.c index 23e648d..ff04b25 100644 --- a/psycopg/psycopgmodule.c +++ b/psycopg/psycopgmodule.c @@ -32,6 +32,7 @@ #include "psycopg/replication_cursor.h" #include "psycopg/replication_message.h" #include "psycopg/green.h" +#include "psycopg/column.h" #include "psycopg/lobject.h" #include "psycopg/notify.h" #include "psycopg/xid.h" @@ -69,9 +70,6 @@ HIDDEN int psycopg_debug_enabled = 0; /* Python representation of SQL NULL */ HIDDEN PyObject *psyco_null = NULL; -/* The type of the cursor.description items */ -HIDDEN PyObject *psyco_DescriptionType = NULL; - /* macro trick to stringify a macro expansion */ #define xstr(s) str(s) #define str(s) #s @@ -839,63 +837,6 @@ psyco_GetDecimalType(void) } -/* Create a namedtuple for cursor.description items - * - * Return None in case of expected errors (e.g. namedtuples not available) - * NULL in case of errors to propagate. - */ -static PyObject * -psyco_make_description_type(void) -{ - PyObject *coll = NULL; - PyObject *nt = NULL; - PyTypeObject *t = NULL; - PyObject *s = NULL; - PyObject *rv = NULL; - - /* Try to import collections.namedtuple */ - if (!(coll = PyImport_ImportModule("collections"))) { - Dprintf("psyco_make_description_type: collections import failed"); - goto error; - } - if (!(nt = PyObject_GetAttrString(coll, "namedtuple"))) { - Dprintf("psyco_make_description_type: no collections.namedtuple"); - goto error; - } - - /* Build the namedtuple */ - if(!(t = (PyTypeObject *)PyObject_CallFunction(nt, "ss", "Column", - "name type_code display_size internal_size precision scale null_ok"))) { - goto exit; - } - - /* Export the tuple on the extensions module - * Required to guarantee picklability on Py > 3.3 (see Python issue 21374) - * for previous Py version the module is psycopg2 anyway but for consistency - * we'd rather expose it from the extensions module. */ - if (!(s = Text_FromUTF8("psycopg2.extensions"))) { goto exit; } - if (0 > PyDict_SetItemString(t->tp_dict, "__module__", s)) { goto exit; } - - rv = (PyObject *)t; - t = NULL; - -exit: - Py_XDECREF(coll); - Py_XDECREF(nt); - Py_XDECREF((PyObject *)t); - Py_XDECREF(s); - - return rv; - -error: - /* controlled error: we will fall back to regular tuples. Return None. */ - PyErr_Clear(); - rv = Py_None; - Py_INCREF(rv); - goto exit; -} - - /** method table and module initialization **/ static PyMethodDef psycopgMethods[] = { @@ -1041,6 +982,9 @@ INIT_MODULE(_psycopg)(void) Py_TYPE(&chunkType) = &PyType_Type; if (PyType_Ready(&chunkType) == -1) goto exit; + Py_TYPE(&columnType) = &PyType_Type; + if (PyType_Ready(&columnType) == -1) goto exit; + Py_TYPE(¬ifyType) = &PyType_Type; if (PyType_Ready(¬ifyType) == -1) goto exit; @@ -1119,7 +1063,6 @@ INIT_MODULE(_psycopg)(void) if (!(psycoEncodings = PyDict_New())) { goto exit; } if (0 != psyco_encodings_fill(psycoEncodings)) { goto exit; } psyco_null = Bytes_FromString("NULL"); - if (!(psyco_DescriptionType = psyco_make_description_type())) { goto exit; } /* set some module's parameters */ PyModule_AddStringConstant(module, "__version__", xstr(PSYCOPG_VERSION)); @@ -1138,6 +1081,7 @@ INIT_MODULE(_psycopg)(void) PyModule_AddObject(module, "ReplicationCursor", (PyObject*)&replicationCursorType); PyModule_AddObject(module, "ReplicationMessage", (PyObject*)&replicationMessageType); PyModule_AddObject(module, "ISQLQuote", (PyObject*)&isqlquoteType); + PyModule_AddObject(module, "Column", (PyObject*)&columnType); PyModule_AddObject(module, "Notify", (PyObject*)¬ifyType); PyModule_AddObject(module, "Xid", (PyObject*)&xidType); PyModule_AddObject(module, "Diagnostics", (PyObject*)&diagnosticsType); @@ -1150,7 +1094,6 @@ INIT_MODULE(_psycopg)(void) PyModule_AddObject(module, "List", (PyObject*)&listType); PyModule_AddObject(module, "QuotedString", (PyObject*)&qstringType); PyModule_AddObject(module, "lobject", (PyObject*)&lobjectType); - PyModule_AddObject(module, "Column", psyco_DescriptionType); /* encodings dictionary in module dictionary */ PyModule_AddObject(module, "encodings", psycoEncodings); diff --git a/setup.py b/setup.py index db85fdb..ded5a05 100644 --- a/setup.py +++ b/setup.py @@ -486,7 +486,7 @@ sources = [ 'libpq_support.c', 'win32_support.c', 'solaris_support.c', 'connection_int.c', 'connection_type.c', - 'cursor_int.c', 'cursor_type.c', + 'cursor_int.c', 'cursor_type.c', 'column_type.c', 'replication_connection_type.c', 'replication_cursor_type.c', 'replication_message_type.c', @@ -508,7 +508,7 @@ depends = [ 'replication_connection.h', 'replication_cursor.h', 'replication_message.h', - 'notify.h', 'pqpath.h', 'xid.h', + 'notify.h', 'pqpath.h', 'xid.h', 'column.h', 'libpq_support.h', 'win32_support.h', 'adapter_asis.h', 'adapter_binary.h', 'adapter_datetime.h', -- cgit v1.2.1