mirror of
https://github.com/psycopg/psycopg2.git
synced 2024-11-10 19:16:34 +03:00
Merge branch 'description-extra-attrs'
This commit is contained in:
commit
7619c91d62
3
NEWS
3
NEWS
|
@ -7,6 +7,9 @@ What's new in psycopg 2.8
|
|||
New features:
|
||||
|
||||
- Added `~psycopg2.extensions.encrypt_password()` function (:ticket:`#576`).
|
||||
- Added `~psycopg2.extensions.Column.table_oid` and
|
||||
`~psycopg2.extensions.Column.table_column` attributes on `cursor.description`
|
||||
items (:ticket:`#661`).
|
||||
- Added `connection.host` property (:ticket:`#726`).
|
||||
- `~psycopg2.sql.Identifier` can represent qualified names in SQL composition
|
||||
(:ticket:`#732`).
|
||||
|
|
|
@ -37,46 +37,49 @@ The ``cursor`` class
|
|||
|
||||
.. attribute:: description
|
||||
|
||||
This read-only attribute is a sequence of 7-item sequences.
|
||||
Read-only attribute describing the result of a query. It is a
|
||||
sequence of `~psycopg2.extensions.Column` instances, each one
|
||||
describing one result column in order. The attribute is `!None` for
|
||||
operations that do not return rows or if the cursor has not had an
|
||||
operation invoked via the |execute*|_ methods yet.
|
||||
|
||||
Each of these sequences is a named tuple (a regular tuple if
|
||||
:func:`collections.namedtuple` is not available) containing information
|
||||
describing one result column:
|
||||
For compatibility with the DB-API, every object can be unpacked as a
|
||||
7-items sequence: the attributes retuned this way are the following.
|
||||
For further details and other attributes available check the
|
||||
`~psycopg2.extensions.Column` documentation.
|
||||
|
||||
0. `!name`: the name of the column returned.
|
||||
1. `!type_code`: the PostgreSQL OID of the column. You can use the
|
||||
|pg_type|_ system table to get more informations about the type.
|
||||
This is the value used by Psycopg to decide what Python type use
|
||||
to represent the value. See also
|
||||
:ref:`type-casting-from-sql-to-python`.
|
||||
2. `!display_size`: the actual length of the column in bytes.
|
||||
Obtaining this value is computationally intensive, so it is
|
||||
always `!None` unless the :envvar:`PSYCOPG_DISPLAY_SIZE` parameter
|
||||
is set at compile time. See also PQgetlength_.
|
||||
3. `!internal_size`: the size in bytes of the column associated to
|
||||
this column on the server. Set to a negative value for
|
||||
variable-size types See also PQfsize_.
|
||||
4. `!precision`: total number of significant digits in columns of
|
||||
type |NUMERIC|_. `!None` for other types.
|
||||
5. `!scale`: count of decimal digits in the fractional part in
|
||||
columns of type |NUMERIC|. `!None` for other types.
|
||||
6. `!null_ok`: always `!None` as not easy to retrieve from the libpq.
|
||||
0. `~psycopg2.extensions.Column.name`: the name of the column returned.
|
||||
|
||||
This attribute will be `!None` for operations that do not return rows
|
||||
or if the cursor has not had an operation invoked via the
|
||||
|execute*|_ methods yet.
|
||||
1. `~psycopg2.extensions.Column.type_code`: the PostgreSQL OID of the
|
||||
column.
|
||||
|
||||
.. |pg_type| replace:: :sql:`pg_type`
|
||||
.. _pg_type: https://www.postgresql.org/docs/current/static/catalog-pg-type.html
|
||||
.. _PQgetlength: https://www.postgresql.org/docs/current/static/libpq-exec.html#LIBPQ-PQGETLENGTH
|
||||
.. _PQfsize: https://www.postgresql.org/docs/current/static/libpq-exec.html#LIBPQ-PQFSIZE
|
||||
.. _NUMERIC: https://www.postgresql.org/docs/current/static/datatype-numeric.html#DATATYPE-NUMERIC-DECIMAL
|
||||
.. |NUMERIC| replace:: :sql:`NUMERIC`
|
||||
2. `~psycopg2.extensions.Column.display_size`: the actual length of
|
||||
the column in bytes.
|
||||
|
||||
3. `~psycopg2.extensions.Column.internal_size`: the size in bytes of
|
||||
the column associated to this column on the server.
|
||||
|
||||
4. `~psycopg2.extensions.Column.precision`: total number of
|
||||
significant digits in columns of type |NUMERIC|. `!None`
|
||||
for other types.
|
||||
|
||||
5. `~psycopg2.extensions.Column.scale`: count of decimal digits in
|
||||
the fractional part in columns of type |NUMERIC|. `!None`
|
||||
for other types.
|
||||
|
||||
6. `~psycopg2.extensions.Column.null_ok`: always `!None` as not easy
|
||||
to retrieve from the libpq.
|
||||
|
||||
.. versionchanged:: 2.4
|
||||
if possible, columns descriptions are named tuple instead of
|
||||
regular tuples.
|
||||
|
||||
.. versionchanged:: 2.8
|
||||
columns descriptions are instances of `!Column`, exposing extra
|
||||
attributes.
|
||||
|
||||
.. |NUMERIC| replace:: :sql:`NUMERIC`
|
||||
|
||||
.. method:: close()
|
||||
|
||||
Close the cursor now (rather than whenever `del` is executed).
|
||||
|
|
|
@ -154,6 +154,80 @@ introspection etc.
|
|||
Close the object and remove it from the database.
|
||||
|
||||
|
||||
.. class:: Column
|
||||
|
||||
Description of one result column, exposed as items of the
|
||||
`cursor.description` sequence.
|
||||
|
||||
.. versionadded:: 2.8
|
||||
|
||||
in previous version the `!description` attribute was a sequence of
|
||||
simple tuples or namedtuples.
|
||||
|
||||
.. attribute:: name
|
||||
|
||||
The name of the column returned.
|
||||
|
||||
.. attribute:: type_code
|
||||
|
||||
The PostgreSQL OID of the column. You can use the |pg_type|_ system
|
||||
table to get more informations about the type. This is the value used
|
||||
by Psycopg to decide what Python type use to represent the value. See
|
||||
also :ref:`type-casting-from-sql-to-python`.
|
||||
|
||||
.. attribute:: display_size
|
||||
|
||||
The actual length of the column in bytes. Obtaining this value is
|
||||
computationally intensive, so it is always `!None` unless the
|
||||
:envvar:`PSYCOPG_DISPLAY_SIZE` parameter is set at compile time. See
|
||||
also PQgetlength_.
|
||||
|
||||
.. attribute:: internal_size
|
||||
|
||||
The size in bytes of the column associated to this column on the
|
||||
server. Set to a negative value for variable-size types See also
|
||||
PQfsize_.
|
||||
|
||||
.. attribute:: precision
|
||||
|
||||
Total number of significant digits in columns of type |NUMERIC|_.
|
||||
`!None` for other types.
|
||||
|
||||
.. attribute:: scale
|
||||
|
||||
Count of decimal digits in the fractional part in columns of type
|
||||
|NUMERIC|. `!None` for other types.
|
||||
|
||||
.. attribute:: null_ok
|
||||
|
||||
Always `!None` as not easy to retrieve from the libpq.
|
||||
|
||||
.. attribute:: table_oid
|
||||
|
||||
The oid of the table from which the column was fetched (matching
|
||||
:sql:`pg_class.oid`). `!None` if the column is not a simple reference
|
||||
to a table column. See also PQftable_.
|
||||
|
||||
.. versionadded:: 2.8
|
||||
|
||||
.. attribute:: table_column
|
||||
|
||||
The number of the column (within its table) making up the result
|
||||
(matching :sql:`pg_attribute.attnum`, so it will start from 1).
|
||||
`!None` if the column is not a simple reference to a table column. See
|
||||
also PQftablecol_.
|
||||
|
||||
.. versionadded:: 2.8
|
||||
|
||||
.. |pg_type| replace:: :sql:`pg_type`
|
||||
.. _pg_type: https://www.postgresql.org/docs/current/static/catalog-pg-type.html
|
||||
.. _PQgetlength: https://www.postgresql.org/docs/current/static/libpq-exec.html#LIBPQ-PQGETLENGTH
|
||||
.. _PQfsize: https://www.postgresql.org/docs/current/static/libpq-exec.html#LIBPQ-PQFSIZE
|
||||
.. _PQftable: https://www.postgresql.org/docs/current/static/libpq-exec.html#LIBPQ-PQFTABLE
|
||||
.. _PQftablecol: https://www.postgresql.org/docs/current/static/libpq-exec.html#LIBPQ-PQFTABLECOL
|
||||
.. _NUMERIC: https://www.postgresql.org/docs/current/static/datatype-numeric.html#DATATYPE-NUMERIC-DECIMAL
|
||||
.. |NUMERIC| replace:: :sql:`NUMERIC`
|
||||
|
||||
.. autoclass:: Notify(pid, channel, payload='')
|
||||
:members: pid, channel, payload
|
||||
|
||||
|
|
48
psycopg/column.h
Normal file
48
psycopg/column.h
Normal file
|
@ -0,0 +1,48 @@
|
|||
/* 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;
|
||||
|
||||
/* Extensions to the DBAPI */
|
||||
PyObject *table_oid;
|
||||
PyObject *table_column;
|
||||
|
||||
} columnObject;
|
||||
|
||||
#endif /* PSYCOPG_COLUMN_H */
|
375
psycopg/column_type.c
Normal file
375
psycopg/column_type.c
Normal file
|
@ -0,0 +1,375 @@
|
|||
/* 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.";
|
||||
|
||||
static const char table_oid_doc[] =
|
||||
"The OID of the table from which the column was fetched.\n\n"
|
||||
"None if not available";
|
||||
|
||||
static const char table_column_doc[] =
|
||||
"The number (within its table) of the column making up the result\n\n"
|
||||
"None if not available. Note that PostgreSQL column numbers start at 1";
|
||||
|
||||
|
||||
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 },
|
||||
{ "table_oid", T_OBJECT, offsetof(columnObject, table_oid), READONLY, (char *)table_oid_doc },
|
||||
{ "table_column", T_OBJECT, offsetof(columnObject, table_column), READONLY, (char *)table_column_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", "table_oid", "table_column", NULL};
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|OOOOOOOOO", kwlist,
|
||||
&self->name, &self->type_code, &self->display_size,
|
||||
&self->internal_size, &self->precision, &self->scale,
|
||||
&self->null_ok, &self->table_oid, &self->table_column)) {
|
||||
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_CLEAR(self->table_oid);
|
||||
Py_CLEAR(self->table_column);
|
||||
|
||||
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);
|
||||
}
|
||||
if (size > 7) {
|
||||
Py_CLEAR(self->table_oid);
|
||||
self->table_oid = PyTuple_GET_ITEM(state, 7);
|
||||
Py_INCREF(self->table_oid);
|
||||
}
|
||||
if (size > 8) {
|
||||
Py_CLEAR(self->table_column);
|
||||
self->table_column = PyTuple_GET_ITEM(state, 8);
|
||||
Py_INCREF(self->table_column);
|
||||
}
|
||||
|
||||
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*/
|
||||
};
|
|
@ -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"
|
||||
|
@ -1206,12 +1207,17 @@ _pq_fetch_tuples(cursorObject *curs)
|
|||
Oid ftype = PQftype(curs->pgres, i);
|
||||
int fsize = PQfsize(curs->pgres, i);
|
||||
int fmod = PQfmod(curs->pgres, i);
|
||||
Oid ftable = PQftable(curs->pgres, i);
|
||||
int ftablecol = PQftablecol(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 +1254,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 +1272,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 +1293,35 @@ _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; }
|
||||
/* table_oid, table_column */
|
||||
if (ftable != InvalidOid) {
|
||||
PyObject *tmp;
|
||||
if (!(tmp = PyInt_FromLong((long)ftable))) { goto err_for; }
|
||||
column->table_oid = tmp;
|
||||
}
|
||||
|
||||
PyTuple_SET_ITEM(description, i, dtitem);
|
||||
dtitem = NULL;
|
||||
if (ftablecol > 0) {
|
||||
PyObject *tmp;
|
||||
if (!(tmp = PyInt_FromLong((long)ftablecol))) { goto err_for; }
|
||||
column->table_column = tmp;
|
||||
}
|
||||
|
||||
PyTuple_SET_ITEM(description, i, (PyObject *)column);
|
||||
column = NULL;
|
||||
|
||||
continue;
|
||||
|
||||
err_for:
|
||||
Py_XDECREF(type);
|
||||
Py_XDECREF(dtitem);
|
||||
Py_XDECREF(column);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
4
setup.py
4
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',
|
||||
|
|
|
@ -377,7 +377,7 @@ class CursorTests(ConnectingTestCase):
|
|||
for i, rec in enumerate(curs):
|
||||
self.assertEqual(i + 1, curs.rownumber)
|
||||
|
||||
def test_namedtuple_description(self):
|
||||
def test_description_attribs(self):
|
||||
curs = self.conn.cursor()
|
||||
curs.execute("""select
|
||||
3.14::decimal(10,2) as pi,
|
||||
|
@ -412,6 +412,27 @@ class CursorTests(ConnectingTestCase):
|
|||
self.assertEqual(c.precision, None)
|
||||
self.assertEqual(c.scale, None)
|
||||
|
||||
def test_description_extra_attribs(self):
|
||||
curs = self.conn.cursor()
|
||||
curs.execute("""
|
||||
create table testcol (
|
||||
pi decimal(10,2),
|
||||
hi text)
|
||||
""")
|
||||
curs.execute("select oid from pg_class where relname = %s", ('testcol',))
|
||||
oid = curs.fetchone()[0]
|
||||
|
||||
curs.execute("insert into testcol values (3.14, 'hello')")
|
||||
curs.execute("select hi, pi, 42 from testcol")
|
||||
self.assertEqual(curs.description[0].table_oid, oid)
|
||||
self.assertEqual(curs.description[0].table_column, 2)
|
||||
|
||||
self.assertEqual(curs.description[1].table_oid, oid)
|
||||
self.assertEqual(curs.description[1].table_column, 1)
|
||||
|
||||
self.assertEqual(curs.description[2].table_oid, None)
|
||||
self.assertEqual(curs.description[2].table_column, None)
|
||||
|
||||
def test_pickle_description(self):
|
||||
curs = self.conn.cursor()
|
||||
curs.execute('SELECT 1 AS foo')
|
||||
|
|
Loading…
Reference in New Issue
Block a user