Added C implementation for a Column type

Currently behaving exactly like the previous (named)tuple.
This commit is contained in:
Daniele Varrazzo 2018-10-11 02:59:45 +01:00
parent e00c4e2a7f
commit b3b225a9da
5 changed files with 422 additions and 98 deletions

44
psycopg/column.h Normal file
View File

@ -0,0 +1,44 @@
/* column.h - definition for a column in cursor.description type
*
* Copyright (C) 2018 Daniele Varrazzo <daniele.varrazzo@gmail.com>
*
* 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 */

353
psycopg/column_type.c Normal file
View File

@ -0,0 +1,353 @@
/* column_type.c - python interface to cursor.description objects
*
* Copyright (C) 2018 Daniele Varrazzo <daniele.varrazzo@gmail.com>
*
* 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*/
};

View File

@ -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;
}

View File

@ -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(&notifyType) = &PyType_Type;
if (PyType_Ready(&notifyType) == -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*)&notifyType);
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);

View File

@ -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',