Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
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

+55-20
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

+25-1
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

+36-15
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)