On Fri, Jul 19, 2019 at 07:44:21PM -0500, Justin Pryzby wrote: > On Fri, Jun 07, 2019 at 09:06:26PM +0200, Christoph Zwerschke wrote: > > >pgdb has > > >|Cursor.coltypes > > >| The list of columns types of the current result set > > >|The values in this list are the same values as the type_code elements in > > >the Cursor.description attribute. Always use the latter if you want to > > >remain standard compliant. > > > > > >But I was surprised to find that pg doesn't expose anything similar. I > > >wondered whether we shouldn't add interfaces for PQftype and maybe PQfmod, > > >pehaps by exposing query_obj->col_types. > > > > Yes, that's really lacking. The interface has grown over time and I think > > originally, the focus was on the entity methods like get() and insert() and > > update(). > > I threw this together, I guess we should do a layer of processing, like what's > done by pgdb.cursor._make_description.
Should the .description property exactly match the pgdb one ? Or maybe it should (also?) return the raw, unmanipulated values from libpq ? I don't know if we can return a namedtuple or highlevel type easily, since it has to be a method of C query_obj, and not of DB class. Actually, query_obj.description is good enough for our purposes... I guess DbTypes should also include size. PYTHONPATH=./build/lib.linux-x86_64-2.7/ python2.7 -c "import pg; d=pg.DB('postgres'); q=d.query('SELECT 1 a'); print q.description; x={i[0]: d.dbtypes.get(i[1]) for i in q.description}; print [ (k,v.__dict__) for k,v in x.items()]" (('a', 23, 4, -1),) [('a', {'typtype': 'b', 'category': 'N', 'relid': 0, '_get_attnames': <bound method DbTypes.get_attnames of {'int4': 'int', 23: 'int'}>, 'simple': 'int', 'pgtype': 'int4', 'typlen': 4, 'oid': 23, 'delim': ',', 'regtype': 'integer'})] That doesn't resolve the question of how to expose typmod. Also, if it were me, I think I'd populate _dbtypes in bulk, using SELECT * FROM pg_types, and "refresh" it (possibly in bulk) in __missing__(). Please pardon my hand-edited diff. I have a month old copy of last year's attempt to use server-side cursors. I'll hope to write about this soon.. Justin
Index: docs/contents/pg/db_types.rst =================================================================== --- docs/contents/pg/db_types.rst (revision 1017) +++ docs/contents/pg/db_types.rst (working copy) @@ -13,9 +13,9 @@ These type names are strings which are equal to either the simple PyGreSQL names or to the more fine-grained registered PostgreSQL type names if these -have been enabled with :meth:`DB.use_regtypes`. Besides being strings, they -carry additional information about the associated PostgreSQL type in the -following attributes: +have been enabled with :meth:`DB.use_regtypes`. Type names extend the string +class with additional information about the associated PostgreSQL type in +the following attributes: - *oid* -- the PostgreSQL type OID - *pgtype* -- the internal PostgreSQL data type name @@ -26,6 +26,7 @@ - *delim* -- delimiter for array types - *relid* -- corresponding table for composite types - *attnames* -- attributes for composite types + - *typlen* -- a positive value for length in bytes, or -1 for a varlena value or -2 for a null-terminated C string (since 5.2) For details, see the PostgreSQL documentation on `pg_type <http://www.postgresql.org/docs/current/static/catalog-pg-type.html>`_. Index: pg.py =================================================================== --- pg.py (revision 1017) +++ pg.py (working copy) @@ -1160,6 +1160,7 @@ delim: delimiter for array types relid: corresponding table for composite types attnames: attributes for composite types + typlen: positive number of bytes or negative indicating varlena or null-terminated """ @property @@ -1190,16 +1191,16 @@ # older remote databases (not officially supported) self._query_pg_type = ( "SELECT oid, typname, typname::text::regtype," - " typtype, null as typcategory, typdelim, typrelid" + " typtype, null as typcategory, typdelim, typrelid, typlen" " FROM pg_type WHERE oid=%s::regtype") else: self._query_pg_type = ( "SELECT oid, typname, typname::regtype," - " typtype, typcategory, typdelim, typrelid" + " typtype, typcategory, typdelim, typrelid, typlen" " FROM pg_type WHERE oid=%s::regtype") def add(self, oid, pgtype, regtype, - typtype, category, delim, relid): + typtype, category, delim, relid, typlen): """Create a PostgreSQL type name with additional info.""" if oid in self: return self[oid] @@ -1213,15 +1214,20 @@ typ.category = category typ.delim = delim typ.relid = relid + typ.typlen = typlen typ._get_attnames = self.get_attnames return typ def __missing__(self, key): """Get the type info from the database if it is not cached.""" + print('looking up type',key) try: q = self._query_pg_type % (_quote_if_unqualified('$1', key),) res = self._db.query(q, (key,)).getresult() except ProgrammingError: + # Ideally we would not catch prg_error, and instead use + # to_regtype() rather than ::regtype, but it's only been around + # since PG9.4. res = None if not res: raise KeyError('Type %s could not be found' % key) @@ -1588,6 +1594,9 @@ attrs.update(dir(self.db)) return sorted(attrs) + def __str__(self): + return 'DB' + # Context manager methods def __enter__(self): Index: pgquery.c =================================================================== --- pgquery.c (revision 1017) +++ pgquery.c (working copy) @@ -217,6 +250,7 @@ static char query_fieldnum__doc__[] = "fieldnum(name) -- return position in query for field from its name"; +// Retire these or rewrite based on fieldinfo ? static PyObject * query_fieldnum(queryObject *self, PyObject *args) { @@ -239,25 +273,61 @@ return PyInt_FromLong(num); } +/* Get field info for last result. */ +static char query_fieldinfo__doc__[] = +"fieldinfo(name) -- return information about query result"; + +/* Return information on field types */ +/* XXX: this should either be raw values from libpq, or should match pgdb.description, but should not be ambiguous! */ +static PyObject *query_fieldinfo(PyObject *self, void *closure) +{ + queryObject *q=(queryObject *)self; + PyObject *result; + int i; + + if (!(result = PyTuple_New(q->num_fields))) + return NULL; + + for (i = 0; i < q->num_fields; ++i) { + PyObject *tup = PyTuple_New(4); + if (!tup) + return NULL; + +// XXX: type = self->col_types[column]; + PyTuple_SET_ITEM(result, i, tup); + // PyTuple_SET_ITEM(tup, 0, PyInt_FromLong(i)); + PyTuple_SET_ITEM(tup, 0, + PyStr_FromString(PQfname(q->result, i))); + PyTuple_SET_ITEM(tup, 1, + PyInt_FromLong(PQftype(q->result, i))); + PyTuple_SET_ITEM(tup, 2, + PyInt_FromLong(PQfsize(q->result, i))); + PyTuple_SET_ITEM(tup, 3, + PyInt_FromLong(PQfmod(q->result, i))); + } + + /* returns result */ + return result; +} + /* Retrieve one row from the result as a tuple. */ static char query_one__doc__[] = "one() -- Get one row from the result of a query\n\n" "Only one row from the result is returned as a tuple of fields.\n" "This method can be called multiple times to return more rows.\n" -"It returns None if the result does not contain one more row.\n"; +"It returns None if the result does not contain any more rows.\n"; static PyObject * query_one(queryObject *self, PyObject *noargs) { @@ -265,14 +335,13 @@ "single() -- Get the result of a query as single row\n\n" "The single row from the query result is returned as a tuple of fields.\n" "This method returns the same single row when called multiple times.\n" -"It raises an InvalidResultError if the result doesn't have exactly one row,\n" -"which will be of type NoResultError or MultipleResultsError specifically.\n"; +"It raises an Exception if the result doesn't have exactly one row.\n" +"The exception will be a subclass of InvalidResultError; specifically, either\n" +"NoResultError or MultipleResultsError.\n"; static PyObject * query_single(queryObject *self, PyObject *noargs) { @@ -707,6 +774,7 @@ METH_VARARGS, query_fieldnum__doc__}, {"listfields", (PyCFunction) query_listfields, METH_NOARGS, query_listfields__doc__}, + // {"fieldinfo", (PyCFunction) query_fieldinfo, METH_NOARGS, query_fieldinfo__doc__}, {"ntuples", (PyCFunction) query_ntuples, METH_NOARGS, query_ntuples__doc__}, {NULL, NULL} @@ -714,6 +782,11 @@ static char query__doc__[] = "PyGreSQL query object"; +static struct PyGetSetDef getset[]={ + {"description", query_fieldinfo, NULL, query_fieldinfo__doc__, NULL, }, + {NULL,} +}; + /* Query type definition */ static PyTypeObject queryType = { PyVarObject_HEAD_INIT(NULL, 0) @@ -746,4 +819,6 @@ (getiterfunc) query_iter, /* tp_iter */ (iternextfunc) query_next, /* tp_iternext */ query_methods, /* tp_methods */ + 0, /* tp_members */ + getset, /* tp_getset */ };
_______________________________________________ PyGreSQL mailing list PyGreSQL@Vex.Net https://mail.vex.net/mailman/listinfo/pygresql