diff --git a/psycopg/column.h b/psycopg/column.h index b551345d..59e9d9cb 100644 --- a/psycopg/column.h +++ b/psycopg/column.h @@ -39,6 +39,10 @@ typedef struct { PyObject *scale; PyObject *null_ok; + /* Extensions to the DBAPI */ + PyObject *table_oid; + PyObject *table_column; + } columnObject; #endif /* PSYCOPG_COLUMN_H */ diff --git a/psycopg/column_type.c b/psycopg/column_type.c index f8476a4d..8ee7d4c4 100644 --- a/psycopg/column_type.c +++ b/psycopg/column_type.c @@ -62,7 +62,15 @@ static const char scale_doc[] = "None for other types."; static const char null_ok_doc[] = - "Always none.\n\n"; + "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[] = { @@ -73,6 +81,8 @@ static PyMemberDef column_members[] = { { "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 } }; @@ -89,12 +99,12 @@ column_init(columnObject *self, PyObject *args, PyObject *kwargs) { static char *kwlist[] = { "name", "type_code", "display_size", "internal_size", - "precision", "scale", "null_ok", NULL}; + "precision", "scale", "null_ok", "table_oid", "table_column", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|OOOOOOO", kwlist, + 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->null_ok, &self->table_oid, &self->table_column)) { return -1; } @@ -112,6 +122,8 @@ column_dealloc(columnObject *self) 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); } @@ -294,6 +306,16 @@ column_setstate(columnObject *self, PyObject *state) 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; diff --git a/psycopg/pqpath.c b/psycopg/pqpath.c index e318802b..2893739a 100644 --- a/psycopg/pqpath.c +++ b/psycopg/pqpath.c @@ -1207,6 +1207,8 @@ _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); columnObject *column = NULL; PyObject *type = NULL; @@ -1299,7 +1301,18 @@ _pq_fetch_tuples(cursorObject *curs) column->scale = tmp; } - /* 6/ FIXME: null_ok??? */ + /* table_oid, table_column */ + if (ftable != InvalidOid) { + PyObject *tmp; + if (!(tmp = PyInt_FromLong((long)ftable))) { goto err_for; } + column->table_oid = tmp; + } + + 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; diff --git a/tests/test_cursor.py b/tests/test_cursor.py index 37110db3..d048f3ec 100755 --- a/tests/test_cursor.py +++ b/tests/test_cursor.py @@ -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')