Fix aboriginal mistake in plpython's set-returning-function support.
authorTom Lane <[email protected]>
Mon, 15 Nov 2010 19:27:04 +0000 (14:27 -0500)
committerTom Lane <[email protected]>
Mon, 15 Nov 2010 19:27:04 +0000 (14:27 -0500)
We must stay in the function's SPI context until done calling the iterator
that returns the set result.  Otherwise, any attempt to invoke SPI features
in the python code called by the iterator will malfunction.  Diagnosis and
patch by Jan Urbanski, per bug report from Jean-Baptiste Quenot.

Back-patch to 8.2; there was no support for SRFs in previous versions of
plpython.

src/pl/plpython/expected/plpython_function.out
src/pl/plpython/expected/plpython_test.out
src/pl/plpython/plpython.c
src/pl/plpython/sql/plpython_function.sql
src/pl/plpython/sql/plpython_test.sql

index 79e225398e80d3be8102cef184a47e6c27fc163d..13360fb51637754eaf8753f1944464e62b168245 100644 (file)
@@ -406,6 +406,14 @@ class producer:
        return self.icontent
 return producer(count, content)
 $$ LANGUAGE plpythonu;
+CREATE FUNCTION test_setof_spi_in_iterator() RETURNS SETOF text AS
+$$
+    for s in ('Hello', 'Brave', 'New', 'World'):
+        plpy.execute('select 1')
+        yield s
+        plpy.execute('select 2')
+$$
+LANGUAGE plpythonu;
 --
 -- Test returning tuples
 --
index b84660da437ecec8ee42195059dc2782499ca668..661f28c24e18533534f9e8ee33f0fbfcd14ec781 100644 (file)
@@ -303,6 +303,15 @@ SELECT test_setof_as_iterator(2, null);
  
 (2 rows)
 
+SELECT test_setof_spi_in_iterator();
+ test_setof_spi_in_iterator 
+----------------------------
+ Hello
+ Brave
+ New
+ World
+(4 rows)
+
 -- Test tuple returning functions
 SELECT * FROM test_table_record_as('dict', null, null, false);
  first | second 
index 64640b625d505fb66882c356bd5bb1e42218e1a5..79203f1c0e0f798fb8108bbfb67a7808d5f3c5ab 100644 (file)
@@ -799,7 +799,10 @@ PLy_function_handler(FunctionCallInfo fcinfo, PLyProcedure *proc)
    {
        if (!proc->is_setof || proc->setof == NULL)
        {
-           /* Simple type returning function or first time for SETOF function */
+           /*
+            * Simple type returning function or first time for SETOF function:
+            * actually execute the function.
+            */
            plargs = PLy_function_build_args(fcinfo, proc);
            plrv = PLy_procedure_call(proc, "args", plargs);
            if (!proc->is_setof)
@@ -814,14 +817,10 @@ PLy_function_handler(FunctionCallInfo fcinfo, PLyProcedure *proc)
        }
 
        /*
-        * Disconnect from SPI manager and then create the return values datum
-        * (if the input function does a palloc for it this must not be
-        * allocated in the SPI memory context because SPI_finish would free
-        * it).
+        * If it returns a set, call the iterator to get the next return item.
+        * We stay in the SPI context while doing this, because PyIter_Next()
+        * calls back into Python code which might contain SPI calls.
         */
-       if (SPI_finish() != SPI_OK_FINISH)
-           elog(ERROR, "SPI_finish failed");
-
        if (proc->is_setof)
        {
            bool        has_error = false;
@@ -879,11 +878,24 @@ PLy_function_handler(FunctionCallInfo fcinfo, PLyProcedure *proc)
                            (errcode(ERRCODE_DATA_EXCEPTION),
                          errmsg("error fetching next item from iterator")));
 
+               /* Disconnect from the SPI manager before returning */
+               if (SPI_finish() != SPI_OK_FINISH)
+                   elog(ERROR, "SPI_finish failed");
+
                fcinfo->isnull = true;
                return (Datum) NULL;
            }
        }
 
+       /*
+        * Disconnect from SPI manager and then create the return values datum
+        * (if the input function does a palloc for it this must not be
+        * allocated in the SPI memory context because SPI_finish would free
+        * it).
+        */
+       if (SPI_finish() != SPI_OK_FINISH)
+           elog(ERROR, "SPI_finish failed");
+
        /*
         * If the function is declared to return void, the Python return value
         * must be None. For void-returning functions, we also treat a None
index a1544f3c422dbb0396f6b8e8e0b9adca3ebbe66f..0872f1a3091ca92809d3ca610523bb86ad53cf42 100644 (file)
@@ -448,6 +448,15 @@ class producer:
 return producer(count, content)
 $$ LANGUAGE plpythonu;
 
+CREATE FUNCTION test_setof_spi_in_iterator() RETURNS SETOF text AS
+$$
+    for s in ('Hello', 'Brave', 'New', 'World'):
+        plpy.execute('select 1')
+        yield s
+        plpy.execute('select 2')
+$$
+LANGUAGE plpythonu;
+
 
 --
 -- Test returning tuples
index 633d940e5d40b16de7ee481f340001b3e92bb4eb..acf39fd8f1ecd37dc4926da3761eb6f9dc3ec0f1 100644 (file)
@@ -96,6 +96,8 @@ SELECT test_setof_as_iterator(1, 'list');
 SELECT test_setof_as_iterator(2, 'list');
 SELECT test_setof_as_iterator(2, null);
 
+SELECT test_setof_spi_in_iterator();
+
 -- Test tuple returning functions
 SELECT * FROM test_table_record_as('dict', null, null, false);
 SELECT * FROM test_table_record_as('dict', 'one', null, false);