Skip to content

Commit a97207b

Browse files
committed
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
1 parent 1540d3b commit a97207b

File tree

3 files changed

+116
-36
lines changed

3 files changed

+116
-36
lines changed

src/pl/plpython/expected/plpython_spi.out

Lines changed: 55 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,3 @@
1-
--
2-
-- result objects
3-
--
4-
CREATE FUNCTION test_resultobject_access() RETURNS void
5-
AS $$
6-
rv = plpy.execute("SELECT fname, lname, username FROM users ORDER BY username")
7-
plpy.info([row for row in rv])
8-
rv[1] = dict([(k, v*2) for (k, v) in rv[1].items()])
9-
plpy.info([row for row in rv])
10-
$$ LANGUAGE plpythonu;
11-
SELECT test_resultobject_access();
12-
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'}]
13-
CONTEXT: PL/Python function "test_resultobject_access"
14-
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'}]
15-
CONTEXT: PL/Python function "test_resultobject_access"
16-
test_resultobject_access
17-
--------------------------
18-
19-
(1 row)
20-
211
--
222
-- nested calls
233
--
@@ -228,6 +208,61 @@ SELECT result_len_test($$UPDATE foo3 SET b= '' WHERE a = 2$$);
228208
0
229209
(1 row)
230210

211+
CREATE FUNCTION result_subscript_test() RETURNS void
212+
AS $$
213+
result = plpy.execute("SELECT 1 AS c UNION SELECT 2 "
214+
"UNION SELECT 3 UNION SELECT 4")
215+
216+
plpy.info(result[1]['c'])
217+
plpy.info(result[-1]['c'])
218+
219+
plpy.info([item['c'] for item in result[1:3]])
220+
plpy.info([item['c'] for item in result[::2]])
221+
222+
result[-1] = {'c': 1000}
223+
result[:2] = [{'c': 10}, {'c': 100}]
224+
plpy.info([item['c'] for item in result[:]])
225+
226+
# raises TypeError, but the message differs on Python 2.6, so silence it
227+
try:
228+
plpy.info(result['foo'])
229+
except TypeError:
230+
pass
231+
else:
232+
assert False, "TypeError not raised"
233+
234+
$$ LANGUAGE plpythonu;
235+
SELECT result_subscript_test();
236+
INFO: 2
237+
CONTEXT: PL/Python function "result_subscript_test"
238+
INFO: 4
239+
CONTEXT: PL/Python function "result_subscript_test"
240+
INFO: [2, 3]
241+
CONTEXT: PL/Python function "result_subscript_test"
242+
INFO: [1, 3]
243+
CONTEXT: PL/Python function "result_subscript_test"
244+
INFO: [10, 100, 3, 1000]
245+
CONTEXT: PL/Python function "result_subscript_test"
246+
result_subscript_test
247+
-----------------------
248+
249+
(1 row)
250+
251+
CREATE FUNCTION result_empty_test() RETURNS void
252+
AS $$
253+
result = plpy.execute("select 1 where false")
254+
255+
plpy.info(result[:])
256+
257+
$$ LANGUAGE plpythonu;
258+
SELECT result_empty_test();
259+
INFO: []
260+
CONTEXT: PL/Python function "result_empty_test"
261+
result_empty_test
262+
-------------------
263+
264+
(1 row)
265+
231266
-- cursor objects
232267
CREATE FUNCTION simple_cursor_test() RETURNS int AS $$
233268
res = plpy.cursor("select fname, lname from users")

src/pl/plpython/plpy_resultobject.c

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ static PyObject *PLy_result_item(PyObject *arg, Py_ssize_t idx);
2323
static PyObject *PLy_result_slice(PyObject *arg, Py_ssize_t lidx, Py_ssize_t hidx);
2424
static int PLy_result_ass_item(PyObject *arg, Py_ssize_t idx, PyObject *item);
2525
static int PLy_result_ass_slice(PyObject *rg, Py_ssize_t lidx, Py_ssize_t hidx, PyObject *slice);
26+
static PyObject *PLy_result_subscript(PyObject *arg, PyObject *item);
27+
static int PLy_result_ass_subscript(PyObject* self, PyObject* item, PyObject* value);
2628

2729
static char PLy_result_doc[] = {
2830
"Results of a PostgreSQL query"
@@ -38,6 +40,12 @@ static PySequenceMethods PLy_result_as_sequence = {
3840
PLy_result_ass_slice, /* sq_ass_slice */
3941
};
4042

43+
static PyMappingMethods PLy_result_as_mapping = {
44+
PLy_result_length, /* mp_length */
45+
PLy_result_subscript, /* mp_subscript */
46+
PLy_result_ass_subscript, /* mp_ass_subscript */
47+
};
48+
4149
static PyMethodDef PLy_result_methods[] = {
4250
{"colnames", PLy_result_colnames, METH_NOARGS, NULL},
4351
{"coltypes", PLy_result_coltypes, METH_NOARGS, NULL},
@@ -64,7 +72,7 @@ static PyTypeObject PLy_ResultType = {
6472
0, /* tp_repr */
6573
0, /* tp_as_number */
6674
&PLy_result_as_sequence, /* tp_as_sequence */
67-
0, /* tp_as_mapping */
75+
&PLy_result_as_mapping, /* tp_as_mapping */
6876
0, /* tp_hash */
6977
0, /* tp_call */
7078
0, /* tp_str */
@@ -251,3 +259,19 @@ PLy_result_ass_slice(PyObject *arg, Py_ssize_t lidx, Py_ssize_t hidx, PyObject *
251259
rv = PyList_SetSlice(ob->rows, lidx, hidx, slice);
252260
return rv;
253261
}
262+
263+
static PyObject *
264+
PLy_result_subscript(PyObject *arg, PyObject *item)
265+
{
266+
PLyResultObject *ob = (PLyResultObject *) arg;
267+
268+
return PyObject_GetItem(ob->rows, item);
269+
}
270+
271+
static int
272+
PLy_result_ass_subscript(PyObject *arg, PyObject *item, PyObject *value)
273+
{
274+
PLyResultObject *ob = (PLyResultObject *) arg;
275+
276+
return PyObject_SetItem(ob->rows, item, value);
277+
}

src/pl/plpython/sql/plpython_spi.sql

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,3 @@
1-
--
2-
-- result objects
3-
--
4-
5-
CREATE FUNCTION test_resultobject_access() RETURNS void
6-
AS $$
7-
rv = plpy.execute("SELECT fname, lname, username FROM users ORDER BY username")
8-
plpy.info([row for row in rv])
9-
rv[1] = dict([(k, v*2) for (k, v) in rv[1].items()])
10-
plpy.info([row for row in rv])
11-
$$ LANGUAGE plpythonu;
12-
13-
SELECT test_resultobject_access();
14-
15-
161
--
172
-- nested calls
183
--
@@ -147,6 +132,42 @@ SELECT result_len_test($$CREATE TEMPORARY TABLE foo3 (a int, b text)$$);
147132
SELECT result_len_test($$INSERT INTO foo3 VALUES (1, 'one'), (2, 'two')$$);
148133
SELECT result_len_test($$UPDATE foo3 SET b= '' WHERE a = 2$$);
149134

135+
CREATE FUNCTION result_subscript_test() RETURNS void
136+
AS $$
137+
result = plpy.execute("SELECT 1 AS c UNION SELECT 2 "
138+
"UNION SELECT 3 UNION SELECT 4")
139+
140+
plpy.info(result[1]['c'])
141+
plpy.info(result[-1]['c'])
142+
143+
plpy.info([item['c'] for item in result[1:3]])
144+
plpy.info([item['c'] for item in result[::2]])
145+
146+
result[-1] = {'c': 1000}
147+
result[:2] = [{'c': 10}, {'c': 100}]
148+
plpy.info([item['c'] for item in result[:]])
149+
150+
# raises TypeError, but the message differs on Python 2.6, so silence it
151+
try:
152+
plpy.info(result['foo'])
153+
except TypeError:
154+
pass
155+
else:
156+
assert False, "TypeError not raised"
157+
158+
$$ LANGUAGE plpythonu;
159+
160+
SELECT result_subscript_test();
161+
162+
CREATE FUNCTION result_empty_test() RETURNS void
163+
AS $$
164+
result = plpy.execute("select 1 where false")
165+
166+
plpy.info(result[:])
167+
168+
$$ LANGUAGE plpythonu;
169+
170+
SELECT result_empty_test();
150171

151172
-- cursor objects
152173

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy