From c03523ed3fc65e219068aff536330ce451f63ca7 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Sun, 15 Apr 2012 20:23:08 +0300
Subject: [PATCH] PL/Python: Fix crash when colnames() etc. called without
 result set

The result object methods colnames() etc. would crash when called
after a command that did not produce a result set.  Now they throw an
exception.

discovery and initial patch by Jean-Baptiste Quenot
---
 doc/src/sgml/plpython.sgml                |  8 +++++++
 src/pl/plpython/expected/plpython_spi.out | 28 +++++++++++++++--------
 src/pl/plpython/plpy_resultobject.c       | 19 +++++++++++++++
 src/pl/plpython/sql/plpython_spi.sql      |  7 +++---
 4 files changed, 49 insertions(+), 13 deletions(-)

diff --git a/doc/src/sgml/plpython.sgml b/doc/src/sgml/plpython.sgml
index 75a939406c6..a39438836fd 100644
--- a/doc/src/sgml/plpython.sgml
+++ b/doc/src/sgml/plpython.sgml
@@ -935,6 +935,14 @@ foo = rv[i]["my_column"]
           Return a list of column names, list of column type OIDs, and list of
           type-specific type modifiers for the columns, respectively.
          </para>
+
+         <para>
+          These methods raise an exception when called on a result object from
+          a command that did not produce a result set, e.g.,
+          <command>UPDATE</command> without <literal>RETURNING</literal>, or
+          <command>DROP TABLE</command>.  But it is OK to use these methods on
+          a result set containing zero rows.
+         </para>
         </listitem>
        </varlistentry>
       </variablelist>
diff --git a/src/pl/plpython/expected/plpython_spi.out b/src/pl/plpython/expected/plpython_spi.out
index 9ed081b184b..f3709aeb882 100644
--- a/src/pl/plpython/expected/plpython_spi.out
+++ b/src/pl/plpython/expected/plpython_spi.out
@@ -115,9 +115,9 @@ SELECT join_sequences(sequences) FROM sequences
 --
 -- plan and result objects
 --
-CREATE FUNCTION result_nrows_test() RETURNS int
+CREATE FUNCTION result_metadata_test(cmd text) RETURNS int
 AS $$
-plan = plpy.prepare("SELECT 1 AS foo, '11'::text AS bar UNION SELECT 2, '22'")
+plan = plpy.prepare(cmd)
 plpy.info(plan.status()) # not really documented or useful
 result = plpy.execute(plan)
 if result.status() > 0:
@@ -128,20 +128,28 @@ if result.status() > 0:
 else:
    return None
 $$ LANGUAGE plpythonu;
-SELECT result_nrows_test();
+SELECT result_metadata_test($$SELECT 1 AS foo, '11'::text AS bar UNION SELECT 2, '22'$$);
 INFO:  True
-CONTEXT:  PL/Python function "result_nrows_test"
+CONTEXT:  PL/Python function "result_metadata_test"
 INFO:  ['foo', 'bar']
-CONTEXT:  PL/Python function "result_nrows_test"
+CONTEXT:  PL/Python function "result_metadata_test"
 INFO:  [23, 25]
-CONTEXT:  PL/Python function "result_nrows_test"
+CONTEXT:  PL/Python function "result_metadata_test"
 INFO:  [-1, -1]
-CONTEXT:  PL/Python function "result_nrows_test"
- result_nrows_test 
--------------------
-                 2
+CONTEXT:  PL/Python function "result_metadata_test"
+ result_metadata_test 
+----------------------
+                    2
 (1 row)
 
+SELECT result_metadata_test($$CREATE TEMPORARY TABLE foo1 (a int, b text)$$);
+INFO:  True
+CONTEXT:  PL/Python function "result_metadata_test"
+ERROR:  plpy.Error: command did not produce a result set
+CONTEXT:  Traceback (most recent call last):
+  PL/Python function "result_metadata_test", line 6, in <module>
+    plpy.info(result.colnames())
+PL/Python function "result_metadata_test"
 -- 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 b25e8083b9e..fcf8074228d 100644
--- a/src/pl/plpython/plpy_resultobject.c
+++ b/src/pl/plpython/plpy_resultobject.c
@@ -9,6 +9,7 @@
 #include "plpython.h"
 
 #include "plpy_resultobject.h"
+#include "plpy_elog.h"
 
 
 static void PLy_result_dealloc(PyObject *arg);
@@ -131,6 +132,12 @@ PLy_result_colnames(PyObject *self, PyObject *unused)
 	PyObject   *list;
 	int			i;
 
+	if (!ob->tupdesc)
+	{
+		PLy_exception_set(PLy_exc_error, "command did not produce a result set");
+		return NULL;
+	}
+
 	list = PyList_New(ob->tupdesc->natts);
 	for (i = 0; i < ob->tupdesc->natts; i++)
 		PyList_SET_ITEM(list, i, PyString_FromString(NameStr(ob->tupdesc->attrs[i]->attname)));
@@ -145,6 +152,12 @@ PLy_result_coltypes(PyObject *self, PyObject *unused)
 	PyObject   *list;
 	int			i;
 
+	if (!ob->tupdesc)
+	{
+		PLy_exception_set(PLy_exc_error, "command did not produce a result set");
+		return NULL;
+	}
+
 	list = PyList_New(ob->tupdesc->natts);
 	for (i = 0; i < ob->tupdesc->natts; i++)
 		PyList_SET_ITEM(list, i, PyInt_FromLong(ob->tupdesc->attrs[i]->atttypid));
@@ -159,6 +172,12 @@ PLy_result_coltypmods(PyObject *self, PyObject *unused)
 	PyObject   *list;
 	int			i;
 
+	if (!ob->tupdesc)
+	{
+		PLy_exception_set(PLy_exc_error, "command did not produce a result set");
+		return NULL;
+	}
+
 	list = PyList_New(ob->tupdesc->natts);
 	for (i = 0; i < ob->tupdesc->natts; i++)
 		PyList_SET_ITEM(list, i, PyInt_FromLong(ob->tupdesc->attrs[i]->atttypmod));
diff --git a/src/pl/plpython/sql/plpython_spi.sql b/src/pl/plpython/sql/plpython_spi.sql
index b828744d1f8..d8457ce98c5 100644
--- a/src/pl/plpython/sql/plpython_spi.sql
+++ b/src/pl/plpython/sql/plpython_spi.sql
@@ -93,9 +93,9 @@ SELECT join_sequences(sequences) FROM sequences
 -- plan and result objects
 --
 
-CREATE FUNCTION result_nrows_test() RETURNS int
+CREATE FUNCTION result_metadata_test(cmd text) RETURNS int
 AS $$
-plan = plpy.prepare("SELECT 1 AS foo, '11'::text AS bar UNION SELECT 2, '22'")
+plan = plpy.prepare(cmd)
 plpy.info(plan.status()) # not really documented or useful
 result = plpy.execute(plan)
 if result.status() > 0:
@@ -107,7 +107,8 @@ else:
    return None
 $$ LANGUAGE plpythonu;
 
-SELECT result_nrows_test();
+SELECT result_metadata_test($$SELECT 1 AS foo, '11'::text AS bar UNION SELECT 2, '22'$$);
+SELECT result_metadata_test($$CREATE TEMPORARY TABLE foo1 (a int, b text)$$);
 
 
 -- cursor objects
-- 
GitLab