Skip to content

Commit

Permalink
Add new query method fieldinfo(field) (#29)
Browse files Browse the repository at this point in the history
  • Loading branch information
Cito committed Jun 20, 2020
1 parent 584ca74 commit d06b02b
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 8 deletions.
4 changes: 2 additions & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,8 @@
'Notice', 'DATETIME'),
'data': ('defbase', 'defhost', 'defopt', 'defpasswd', 'defport',
'defuser'),
'exc': ('Exception', 'IOError', 'KeyError', 'MemoryError',
'SyntaxError', 'TypeError', 'ValueError',
'exc': ('Exception', 'IndexError', 'IOError', 'KeyError',
'MemoryError', 'SyntaxError', 'TypeError', 'ValueError',
'pg.InternalError', 'pg.InvalidResultError',
'pg.MultipleResultsError', 'pg.NoResultError',
'pg.OperationalError', 'pg.ProgrammingError'),
Expand Down
4 changes: 3 additions & 1 deletion docs/contents/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Version 5.2 (to be released)
of the pqlib used by PyGreSQL (needs PostgreSQL >= 9.1 on the client).
- New query method `memsize()` that gets the memory size allocated by
the query (needs PostgreSQL >= 12 on the client).
- New query method `fieldinfo()` that gets name and type information for
one or all field(s) of the query. Contributed by Justin Pryzby (#39).
- Experimental support for asynchronous command processing.
Additional connection parameter ``nowait``, and connection methods
`send_query()`, `poll()`, `set_non_blocking()`, `is_non_blocking()`.
Expand All @@ -20,7 +22,7 @@ Version 5.2 (to be released)
instead of type names. Suggested by Justin Pryzby (#38).
- The `inserttable()` method now accepts an optional column list that will
be passed on to the COPY command. Contributed by Justin Pryzby (#24).
- The `DBTyptes` class now also includes the `typlen` attribute with
- The `DBTypes` class now also includes the `typlen` attribute with
information about the size of the type (contributed by Justin Pryzby).

- Changes to the DB-API 2 module (pgdb):
Expand Down
32 changes: 29 additions & 3 deletions docs/contents/pg/query.rst
Original file line number Diff line number Diff line change
Expand Up @@ -328,12 +328,12 @@ is empty and of type :exc:`pg.MultipleResultsError` if it has multiple rows.

.. versionadded:: 5.1

listfields -- list fields names of previous query result
--------------------------------------------------------
listfields -- list field names of previous query result
-------------------------------------------------------

.. method:: Query.listfields()

List fields names of previous query result
List field names of previous query result

:returns: field names
:rtype: list
Expand Down Expand Up @@ -374,6 +374,32 @@ build a function that converts result list strings to their correct
type, using a hardcoded table definition. The number returned is the
field rank in the query result.

fieldinfo -- detailed info about fields of previous query result
----------------------------------------------------------------

.. method:: Query.fieldinfo([field])

Get information on one or all fields of the last query

:param field: a column number or name (optional)
:type field: int or str
:returns: field info tuple(s) for all fields or given field
:rtype: tuple
:raises IndexError: field does not exist
:raises TypeError: too many parameters

If the ``field`` is specified by passing either a column number or a field
name, a four-tuple with information for the specified field of the previous
query result will be returned. If no ``field`` is specified, a tuple of
four-tuples for every field of the previous query result will be returned,
in the order as they appear in the query result.

The four-tuples contain the following information: The field name, the
internal OID number of the field type, the size in bytes of the column or a
negative value if it is of variable size, and a type-specific modifier value.

.. versionadded:: 5.2

ntuples -- return number of tuples in query object
--------------------------------------------------

Expand Down
2 changes: 1 addition & 1 deletion pg.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ def wrapper(arg):
return decorator


# Auxiliary classes and functions that are independent from a DB connection:
# Auxiliary classes and functions that are independent of a DB connection:

try: # noinspection PyUnresolvedReferences
from inspect import signature
Expand Down
73 changes: 73 additions & 0 deletions pgquery.c
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,77 @@ query_fieldnum(queryObject *self, PyObject *args)
return PyInt_FromLong(num);
}

/* Build a tuple with info for query field with given number. */
static PyObject *
_query_build_field_info(PGresult *res, int col_num) {
PyObject *info;

info = PyTuple_New(4);
if (info) {
PyTuple_SET_ITEM(info, 0, PyStr_FromString(PQfname(res, col_num)));
PyTuple_SET_ITEM(info, 1, PyInt_FromLong(PQftype(res, col_num)));
PyTuple_SET_ITEM(info, 2, PyInt_FromLong(PQfsize(res, col_num)));
PyTuple_SET_ITEM(info, 3, PyInt_FromLong(PQfmod(res, col_num)));
}
return info;
}

/* Get information on one or all fields in last result. */
static char query_fieldinfo__doc__[] =
"fieldinfo() -- return info on field(s) in query";

static PyObject *
query_fieldinfo(queryObject *self, PyObject *args)
{
PyObject *result, *field = NULL;
int num;

/* gets args */
if (!PyArg_ParseTuple(args, "|O", &field)) {
PyErr_SetString(PyExc_TypeError,
"Method fieldinfo() takes one optional argument only");
return NULL;
}

/* check optional field arg */
if (field) {
/* gets field number */
if (PyBytes_Check(field)) {
num = PQfnumber(self->result, PyBytes_AsString(field));
} else if (PyStr_Check(field)) {
PyObject *tmp = get_encoded_string(field, self->encoding);
if (!tmp) return NULL;
num = PQfnumber(self->result, PyBytes_AsString(tmp));
Py_DECREF(tmp);
} else if (PyInt_Check(field)) {
num = (int) PyInt_AsLong(field);
} else {
PyErr_SetString(PyExc_TypeError,
"Field should be given as column number or name");
return NULL;
}
if (num < 0 || num >= self->num_fields) {
PyErr_SetString(PyExc_IndexError, "Unknown field");
return NULL;
}
return _query_build_field_info(self->result, num);
}

if (!(result = PyTuple_New(self->num_fields))) {
return NULL;
}
for (num = 0; num < self->num_fields; ++num) {
PyObject *info = _query_build_field_info(self->result, num);
if (!info) {
Py_DECREF(result);
return NULL;
}
PyTuple_SET_ITEM(result, num, info);
}
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"
Expand Down Expand Up @@ -869,6 +940,8 @@ static struct PyMethodDef query_methods[] = {
METH_VARARGS, query_fieldnum__doc__},
{"listfields", (PyCFunction) query_listfields,
METH_NOARGS, query_listfields__doc__},
{"fieldinfo", (PyCFunction) query_fieldinfo,
METH_VARARGS, query_fieldinfo__doc__},
{"ntuples", (PyCFunction) query_ntuples,
METH_NOARGS, query_ntuples__doc__},
#ifdef MEMORY_SIZE
Expand Down
30 changes: 29 additions & 1 deletion tests/test_classic_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ def testMethodSendQueryEmpty(self):
def testAllQueryMembers(self):
query = self.connection.query("select true where false")
members = '''
dictiter dictresult fieldname fieldnum getresult
dictiter dictresult fieldinfo fieldname fieldnum getresult
listfields memsize namediter namedresult ntuples
one onedict onenamed onescalar scalariter scalarresult
single singledict singlenamed singlescalar
Expand Down Expand Up @@ -694,6 +694,34 @@ def testFieldnum(self):
self.assertIsInstance(r, int)
self.assertEqual(r, 3)

def testFieldInfoName(self):
q = ('select true as FooBar, 42::smallint as "FooBar",'
' 4.2::numeric(4,2) as foo_bar, \'baz\'::char(3) as "Foo Bar"')
f = self.c.query(q).fieldinfo
result = (('foobar', 16, 1, -1), ('FooBar', 21, 2, -1),
('foo_bar', 1700, -1, ((4 << 16) | 2) + 4),
('Foo Bar', 1042, -1, 3 + 4))
r = f()
self.assertIsInstance(r, tuple)
self.assertEqual(len(r), 4)
self.assertEqual(r, result)
for field_num, info in enumerate(result):
field_name = info[0]
if field_num > 0:
field_name = '"%s"' % field_name
r = f(field_name)
self.assertIsInstance(r, tuple)
self.assertEqual(len(r), 4)
self.assertEqual(r, info)
r = f(field_num)
self.assertIsInstance(r, tuple)
self.assertEqual(len(r), 4)
self.assertEqual(r, info)
self.assertRaises(IndexError, f, 'foobaz')
self.assertRaises(IndexError, f, '"Foobar"')
self.assertRaises(IndexError, f, -1)
self.assertRaises(IndexError, f, 4)

def testNtuples(self): # deprecated
q = "select 1 where false"
r = self.c.query(q).ntuples()
Expand Down

0 comments on commit d06b02b

Please sign in to comment.