>From 000a1285d66c65c36ae6fa064266f00def5ee9d7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Urba=C5=84ski?= <wulczer@wulczer.org>
Date: Sat, 5 May 2012 22:39:26 +0200
Subject: [PATCH] Fix slicing support for result objects for Python 3.

The old way of implementing slicing support by implementing
PySequenceMethods.sq_slice has been deprecated in Python 3, you should
not implement PyMappingMethods.mp_subscript. Do this by simply
proxying the call to the wrapped list of result dictionaries.

While at it, fix an incorrect comment about PLyResultObject->rows being
None if the result set is empty (it actually is an empty list in that
case).
---
 src/pl/plpython/expected/plpython_spi.out |   55 +++++++++++++++++++++++++++++
 src/pl/plpython/plpy_resultobject.c       |   27 +++++++++++++-
 src/pl/plpython/plpy_resultobject.h       |    2 +-
 src/pl/plpython/sql/plpython_spi.sql      |   36 +++++++++++++++++++
 4 files changed, 118 insertions(+), 2 deletions(-)

diff --git a/src/pl/plpython/expected/plpython_spi.out b/src/pl/plpython/expected/plpython_spi.out
index 671c24e..54ef512 100644
--- a/src/pl/plpython/expected/plpython_spi.out
+++ b/src/pl/plpython/expected/plpython_spi.out
@@ -228,6 +228,61 @@ SELECT result_len_test($$UPDATE foo3 SET b= '' WHERE a = 2$$);
                0
 (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
 CREATE FUNCTION simple_cursor_test() RETURNS int AS $$
 res = plpy.cursor("select fname, lname from users")
diff --git a/src/pl/plpython/plpy_resultobject.c b/src/pl/plpython/plpy_resultobject.c
index fcf8074..06ba2ee 100644
--- a/src/pl/plpython/plpy_resultobject.c
+++ b/src/pl/plpython/plpy_resultobject.c
@@ -23,6 +23,9 @@ 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 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 PyObject *PLy_result_subscript(PyObject *arg, PyObject *item);
+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[] = {
 	"Results of a PostgreSQL query"
@@ -38,6 +41,12 @@ static PySequenceMethods PLy_result_as_sequence = {
 	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[] = {
 	{"colnames", PLy_result_colnames, METH_NOARGS, NULL},
 	{"coltypes", PLy_result_coltypes, METH_NOARGS, NULL},
@@ -64,7 +73,7 @@ static PyTypeObject PLy_ResultType = {
 	0,							/* tp_repr */
 	0,							/* tp_as_number */
 	&PLy_result_as_sequence,	/* tp_as_sequence */
-	0,							/* tp_as_mapping */
+	&PLy_result_as_mapping,		/* tp_as_mapping */
 	0,							/* tp_hash */
 	0,							/* tp_call */
 	0,							/* tp_str */
@@ -251,3 +260,19 @@ PLy_result_ass_slice(PyObject *arg, Py_ssize_t lidx, Py_ssize_t hidx, PyObject *
 	rv = PyList_SetSlice(ob->rows, lidx, hidx, slice);
 	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);
+}
diff --git a/src/pl/plpython/plpy_resultobject.h b/src/pl/plpython/plpy_resultobject.h
index 1b37d1d..c5ba999 100644
--- a/src/pl/plpython/plpy_resultobject.h
+++ b/src/pl/plpython/plpy_resultobject.h
@@ -13,7 +13,7 @@ typedef struct PLyResultObject
 	PyObject_HEAD
 	/* HeapTuple *tuples; */
 	PyObject   *nrows;			/* number of rows returned by query */
-	PyObject   *rows;			/* data rows, or None if no data returned */
+	PyObject   *rows;			/* data rows, or empty list if no data returned */
 	PyObject   *status;			/* query status, SPI_OK_*, or SPI_ERR_* */
 	TupleDesc	tupdesc;
 } PLyResultObject;
diff --git a/src/pl/plpython/sql/plpython_spi.sql b/src/pl/plpython/sql/plpython_spi.sql
index 7be2fbf..cf0128c 100644
--- a/src/pl/plpython/sql/plpython_spi.sql
+++ b/src/pl/plpython/sql/plpython_spi.sql
@@ -147,6 +147,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($$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
 
-- 
1.7.10

