Skip to content

Commit f99a8de

Browse files
committed
Added table_oid, table_column on cursor.description items
Close psycopg#661
1 parent b3b225a commit f99a8de

File tree

4 files changed

+66
-6
lines changed

4 files changed

+66
-6
lines changed

psycopg/column.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ typedef struct {
3939
PyObject *scale;
4040
PyObject *null_ok;
4141

42+
/* Extensions to the DBAPI */
43+
PyObject *table_oid;
44+
PyObject *table_column;
45+
4246
} columnObject;
4347

4448
#endif /* PSYCOPG_COLUMN_H */

psycopg/column_type.c

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,15 @@ static const char scale_doc[] =
6262
"None for other types.";
6363

6464
static const char null_ok_doc[] =
65-
"Always none.\n\n";
65+
"Always none.";
66+
67+
static const char table_oid_doc[] =
68+
"The OID of the table from which the column was fetched.\n\n"
69+
"None if not available";
70+
71+
static const char table_column_doc[] =
72+
"The number (within its table) of the column making up the result\n\n"
73+
"None if not available. Note that PostgreSQL column numbers start at 1";
6674

6775

6876
static PyMemberDef column_members[] = {
@@ -73,6 +81,8 @@ static PyMemberDef column_members[] = {
7381
{ "precision", T_OBJECT, offsetof(columnObject, precision), READONLY, (char *)precision_doc },
7482
{ "scale", T_OBJECT, offsetof(columnObject, scale), READONLY, (char *)scale_doc },
7583
{ "null_ok", T_OBJECT, offsetof(columnObject, null_ok), READONLY, (char *)null_ok_doc },
84+
{ "table_oid", T_OBJECT, offsetof(columnObject, table_oid), READONLY, (char *)table_oid_doc },
85+
{ "table_column", T_OBJECT, offsetof(columnObject, table_column), READONLY, (char *)table_column_doc },
7686
{ NULL }
7787
};
7888

@@ -89,12 +99,12 @@ column_init(columnObject *self, PyObject *args, PyObject *kwargs)
8999
{
90100
static char *kwlist[] = {
91101
"name", "type_code", "display_size", "internal_size",
92-
"precision", "scale", "null_ok", NULL};
102+
"precision", "scale", "null_ok", "table_oid", "table_column", NULL};
93103

94-
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|OOOOOOO", kwlist,
104+
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|OOOOOOOOO", kwlist,
95105
&self->name, &self->type_code, &self->display_size,
96106
&self->internal_size, &self->precision, &self->scale,
97-
&self->null_ok)) {
107+
&self->null_ok, &self->table_oid, &self->table_column)) {
98108
return -1;
99109
}
100110

@@ -112,6 +122,8 @@ column_dealloc(columnObject *self)
112122
Py_CLEAR(self->precision);
113123
Py_CLEAR(self->scale);
114124
Py_CLEAR(self->null_ok);
125+
Py_CLEAR(self->table_oid);
126+
Py_CLEAR(self->table_column);
115127

116128
Py_TYPE(self)->tp_free((PyObject *)self);
117129
}
@@ -294,6 +306,16 @@ column_setstate(columnObject *self, PyObject *state)
294306
self->null_ok = PyTuple_GET_ITEM(state, 6);
295307
Py_INCREF(self->null_ok);
296308
}
309+
if (size > 7) {
310+
Py_CLEAR(self->table_oid);
311+
self->table_oid = PyTuple_GET_ITEM(state, 7);
312+
Py_INCREF(self->table_oid);
313+
}
314+
if (size > 8) {
315+
Py_CLEAR(self->table_column);
316+
self->table_column = PyTuple_GET_ITEM(state, 8);
317+
Py_INCREF(self->table_column);
318+
}
297319

298320
exit:
299321
rv = Py_None;

psycopg/pqpath.c

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1207,6 +1207,8 @@ _pq_fetch_tuples(cursorObject *curs)
12071207
Oid ftype = PQftype(curs->pgres, i);
12081208
int fsize = PQfsize(curs->pgres, i);
12091209
int fmod = PQfmod(curs->pgres, i);
1210+
Oid ftable = PQftable(curs->pgres, i);
1211+
int ftablecol = PQftablecol(curs->pgres, i);
12101212

12111213
columnObject *column = NULL;
12121214
PyObject *type = NULL;
@@ -1299,7 +1301,18 @@ _pq_fetch_tuples(cursorObject *curs)
12991301
column->scale = tmp;
13001302
}
13011303

1302-
/* 6/ FIXME: null_ok??? */
1304+
/* table_oid, table_column */
1305+
if (ftable != InvalidOid) {
1306+
PyObject *tmp;
1307+
if (!(tmp = PyInt_FromLong((long)ftable))) { goto err_for; }
1308+
column->table_oid = tmp;
1309+
}
1310+
1311+
if (ftablecol > 0) {
1312+
PyObject *tmp;
1313+
if (!(tmp = PyInt_FromLong((long)ftablecol))) { goto err_for; }
1314+
column->table_column = tmp;
1315+
}
13031316

13041317
PyTuple_SET_ITEM(description, i, (PyObject *)column);
13051318
column = NULL;

tests/test_cursor.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ def test_iter_named_cursor_rownumber(self):
377377
for i, rec in enumerate(curs):
378378
self.assertEqual(i + 1, curs.rownumber)
379379

380-
def test_namedtuple_description(self):
380+
def test_description_attribs(self):
381381
curs = self.conn.cursor()
382382
curs.execute("""select
383383
3.14::decimal(10,2) as pi,
@@ -412,6 +412,27 @@ def test_namedtuple_description(self):
412412
self.assertEqual(c.precision, None)
413413
self.assertEqual(c.scale, None)
414414

415+
def test_description_extra_attribs(self):
416+
curs = self.conn.cursor()
417+
curs.execute("""
418+
create table testcol (
419+
pi decimal(10,2),
420+
hi text)
421+
""")
422+
curs.execute("select oid from pg_class where relname = %s", ('testcol',))
423+
oid = curs.fetchone()[0]
424+
425+
curs.execute("insert into testcol values (3.14, 'hello')")
426+
curs.execute("select hi, pi, 42 from testcol")
427+
self.assertEqual(curs.description[0].table_oid, oid)
428+
self.assertEqual(curs.description[0].table_column, 2)
429+
430+
self.assertEqual(curs.description[1].table_oid, oid)
431+
self.assertEqual(curs.description[1].table_column, 1)
432+
433+
self.assertEqual(curs.description[2].table_oid, None)
434+
self.assertEqual(curs.description[2].table_column, None)
435+
415436
def test_pickle_description(self):
416437
curs = self.conn.cursor()
417438
curs.execute('SELECT 1 AS foo')

0 commit comments

Comments
 (0)