PL/Python: Fix slicing support for result objects for Python 3

The old way of implementing slicing support by implementing
PySequenceMethods.sq_slice no longer works in Python 3.  You now have
to implement PyMappingMethods.mp_subscript.  Do this by simply
proxying the call to the wrapped list of result dictionaries.
Consolidate some of the subscripting regression tests.

Jan Urbański
This commit is contained in:
Peter Eisentraut 2012-05-10 20:38:17 +03:00
parent 1540d3bf4d
commit a97207b690
3 changed files with 116 additions and 36 deletions

View File

@ -1,23 +1,3 @@
--
-- result objects
--
CREATE FUNCTION test_resultobject_access() RETURNS void
AS $$
rv = plpy.execute("SELECT fname, lname, username FROM users ORDER BY username")
plpy.info([row for row in rv])
rv[1] = dict([(k, v*2) for (k, v) in rv[1].items()])
plpy.info([row for row in rv])
$$ LANGUAGE plpythonu;
SELECT test_resultobject_access();
INFO: [{'lname': 'doe', 'username': 'j_doe', 'fname': 'jane'}, {'lname': 'doe', 'username': 'johnd', 'fname': 'john'}, {'lname': 'smith', 'username': 'slash', 'fname': 'rick'}, {'lname': 'doe', 'username': 'w_doe', 'fname': 'willem'}]
CONTEXT: PL/Python function "test_resultobject_access"
INFO: [{'lname': 'doe', 'username': 'j_doe', 'fname': 'jane'}, {'lname': 'doedoe', 'username': 'johndjohnd', 'fname': 'johnjohn'}, {'lname': 'smith', 'username': 'slash', 'fname': 'rick'}, {'lname': 'doe', 'username': 'w_doe', 'fname': 'willem'}]
CONTEXT: PL/Python function "test_resultobject_access"
test_resultobject_access
--------------------------
(1 row)
-- --
-- nested calls -- nested calls
-- --
@ -228,6 +208,61 @@ SELECT result_len_test($$UPDATE foo3 SET b= '' WHERE a = 2$$);
0 0
(1 row) (1 row)
CREATE FUNCTION result_subscript_test() RETURNS void
AS $$
result = plpy.execute("SELECT 1 AS c UNION SELECT 2 "
"UNION SELECT 3 UNION SELECT 4")
plpy.info(result[1]['c'])
plpy.info(result[-1]['c'])
plpy.info([item['c'] for item in result[1:3]])
plpy.info([item['c'] for item in result[::2]])
result[-1] = {'c': 1000}
result[:2] = [{'c': 10}, {'c': 100}]
plpy.info([item['c'] for item in result[:]])
# raises TypeError, but the message differs on Python 2.6, so silence it
try:
plpy.info(result['foo'])
except TypeError:
pass
else:
assert False, "TypeError not raised"
$$ LANGUAGE plpythonu;
SELECT result_subscript_test();
INFO: 2
CONTEXT: PL/Python function "result_subscript_test"
INFO: 4
CONTEXT: PL/Python function "result_subscript_test"
INFO: [2, 3]
CONTEXT: PL/Python function "result_subscript_test"
INFO: [1, 3]
CONTEXT: PL/Python function "result_subscript_test"
INFO: [10, 100, 3, 1000]
CONTEXT: PL/Python function "result_subscript_test"
result_subscript_test
-----------------------
(1 row)
CREATE FUNCTION result_empty_test() RETURNS void
AS $$
result = plpy.execute("select 1 where false")
plpy.info(result[:])
$$ LANGUAGE plpythonu;
SELECT result_empty_test();
INFO: []
CONTEXT: PL/Python function "result_empty_test"
result_empty_test
-------------------
(1 row)
-- cursor objects -- cursor objects
CREATE FUNCTION simple_cursor_test() RETURNS int AS $$ CREATE FUNCTION simple_cursor_test() RETURNS int AS $$
res = plpy.cursor("select fname, lname from users") res = plpy.cursor("select fname, lname from users")

View File

@ -23,6 +23,8 @@ static PyObject *PLy_result_item(PyObject *arg, Py_ssize_t idx);
static PyObject *PLy_result_slice(PyObject *arg, Py_ssize_t lidx, Py_ssize_t hidx); static PyObject *PLy_result_slice(PyObject *arg, Py_ssize_t lidx, Py_ssize_t hidx);
static int PLy_result_ass_item(PyObject *arg, Py_ssize_t idx, PyObject *item); static int PLy_result_ass_item(PyObject *arg, Py_ssize_t idx, PyObject *item);
static int PLy_result_ass_slice(PyObject *rg, Py_ssize_t lidx, Py_ssize_t hidx, PyObject *slice); static int PLy_result_ass_slice(PyObject *rg, Py_ssize_t lidx, Py_ssize_t hidx, PyObject *slice);
static PyObject *PLy_result_subscript(PyObject *arg, PyObject *item);
static int PLy_result_ass_subscript(PyObject* self, PyObject* item, PyObject* value);
static char PLy_result_doc[] = { static char PLy_result_doc[] = {
"Results of a PostgreSQL query" "Results of a PostgreSQL query"
@ -38,6 +40,12 @@ static PySequenceMethods PLy_result_as_sequence = {
PLy_result_ass_slice, /* sq_ass_slice */ PLy_result_ass_slice, /* sq_ass_slice */
}; };
static PyMappingMethods PLy_result_as_mapping = {
PLy_result_length, /* mp_length */
PLy_result_subscript, /* mp_subscript */
PLy_result_ass_subscript, /* mp_ass_subscript */
};
static PyMethodDef PLy_result_methods[] = { static PyMethodDef PLy_result_methods[] = {
{"colnames", PLy_result_colnames, METH_NOARGS, NULL}, {"colnames", PLy_result_colnames, METH_NOARGS, NULL},
{"coltypes", PLy_result_coltypes, METH_NOARGS, NULL}, {"coltypes", PLy_result_coltypes, METH_NOARGS, NULL},
@ -64,7 +72,7 @@ static PyTypeObject PLy_ResultType = {
0, /* tp_repr */ 0, /* tp_repr */
0, /* tp_as_number */ 0, /* tp_as_number */
&PLy_result_as_sequence, /* tp_as_sequence */ &PLy_result_as_sequence, /* tp_as_sequence */
0, /* tp_as_mapping */ &PLy_result_as_mapping, /* tp_as_mapping */
0, /* tp_hash */ 0, /* tp_hash */
0, /* tp_call */ 0, /* tp_call */
0, /* tp_str */ 0, /* tp_str */
@ -251,3 +259,19 @@ PLy_result_ass_slice(PyObject *arg, Py_ssize_t lidx, Py_ssize_t hidx, PyObject *
rv = PyList_SetSlice(ob->rows, lidx, hidx, slice); rv = PyList_SetSlice(ob->rows, lidx, hidx, slice);
return rv; return rv;
} }
static PyObject *
PLy_result_subscript(PyObject *arg, PyObject *item)
{
PLyResultObject *ob = (PLyResultObject *) arg;
return PyObject_GetItem(ob->rows, item);
}
static int
PLy_result_ass_subscript(PyObject *arg, PyObject *item, PyObject *value)
{
PLyResultObject *ob = (PLyResultObject *) arg;
return PyObject_SetItem(ob->rows, item, value);
}

View File

@ -1,18 +1,3 @@
--
-- result objects
--
CREATE FUNCTION test_resultobject_access() RETURNS void
AS $$
rv = plpy.execute("SELECT fname, lname, username FROM users ORDER BY username")
plpy.info([row for row in rv])
rv[1] = dict([(k, v*2) for (k, v) in rv[1].items()])
plpy.info([row for row in rv])
$$ LANGUAGE plpythonu;
SELECT test_resultobject_access();
-- --
-- nested calls -- nested calls
-- --
@ -147,6 +132,42 @@ SELECT result_len_test($$CREATE TEMPORARY TABLE foo3 (a int, b text)$$);
SELECT result_len_test($$INSERT INTO foo3 VALUES (1, 'one'), (2, 'two')$$); SELECT result_len_test($$INSERT INTO foo3 VALUES (1, 'one'), (2, 'two')$$);
SELECT result_len_test($$UPDATE foo3 SET b= '' WHERE a = 2$$); SELECT result_len_test($$UPDATE foo3 SET b= '' WHERE a = 2$$);
CREATE FUNCTION result_subscript_test() RETURNS void
AS $$
result = plpy.execute("SELECT 1 AS c UNION SELECT 2 "
"UNION SELECT 3 UNION SELECT 4")
plpy.info(result[1]['c'])
plpy.info(result[-1]['c'])
plpy.info([item['c'] for item in result[1:3]])
plpy.info([item['c'] for item in result[::2]])
result[-1] = {'c': 1000}
result[:2] = [{'c': 10}, {'c': 100}]
plpy.info([item['c'] for item in result[:]])
# raises TypeError, but the message differs on Python 2.6, so silence it
try:
plpy.info(result['foo'])
except TypeError:
pass
else:
assert False, "TypeError not raised"
$$ LANGUAGE plpythonu;
SELECT result_subscript_test();
CREATE FUNCTION result_empty_test() RETURNS void
AS $$
result = plpy.execute("select 1 where false")
plpy.info(result[:])
$$ LANGUAGE plpythonu;
SELECT result_empty_test();
-- cursor objects -- cursor objects