Fix aboriginal mistake in plpython's set-returning-function support.
authorTom Lane <[email protected]>
Mon, 15 Nov 2010 19:27:09 +0000 (14:27 -0500)
committerTom Lane <[email protected]>
Mon, 15 Nov 2010 19:27:09 +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 e1ffa7302dbd3f5abc7efd66800a8a1d6d2f34b9..b2bba9470406f49f0bfb69d67cde07ad58575315 100644 (file)
@@ -403,6 +403,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 170abe7ab6e68250df134238e1a61ea85af5752b..1deab6c0d34d634d51a35fad91d025aa5de59bee 100644 (file)
@@ -298,6 +298,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 b4d37f508ad3276830d959f9564deb4fd3a8229c..ebb8023fc0d1e992431e03b161a1a93f1ccd3729 100644 (file)
@@ -779,7 +779,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)
@@ -794,14 +797,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;
@@ -858,11 +857,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 224d5196a3c236782f695d6bc061c503516da511..133704dbbe742868035c0d484da3299e78bc04c4 100644 (file)
@@ -444,6 +444,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 dafcae089e92c5b36eb5aaba15e9dd25b45fc422..75d2f06ffc56b52e667bbb758e923ae798119cf7 100644 (file)
@@ -95,6 +95,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);