Rethink plpgsql's way of handling SPI execution during an exception block.
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 16 Nov 2004 18:10:16 +0000 (18:10 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Tue, 16 Nov 2004 18:10:16 +0000 (18:10 +0000)
We don't really want to start a new SPI connection, just keep using the old
one; otherwise we have memory management problems as illustrated by
John Kennedy's bug report of today.  This requires a bit of a hack to
ensure the SPI stack state is properly restored, but then again what we
were doing before was a hack too, strictly speaking.  Add a regression
test to cover this case.

src/backend/executor/spi.c
src/include/executor/spi.h
src/pl/plpgsql/src/pl_exec.c
src/test/regress/expected/plpgsql.out
src/test/regress/sql/plpgsql.sql

index b533470e1d523b1a87c45720ba9e54a7d7b25b07..4549ba00080d3b95105357fe90760f7a8e242c91 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.131 2004/10/13 01:25:10 neilc Exp $
+ *   $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.132 2004/11/16 18:10:13 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -270,6 +270,14 @@ SPI_pop(void)
    _SPI_curid--;
 }
 
+/* Restore state of SPI stack after aborting a subtransaction */
+void
+SPI_restore_connection(void)
+{
+   Assert(_SPI_connected >= 0);
+   _SPI_curid = _SPI_connected - 1;
+}
+
 /* Parse, plan, and execute a querystring */
 int
 SPI_execute(const char *src, bool read_only, int tcount)
index 3014dbce84083040c2989f35ef61191ee2ae1214..d5ba89fa3eaacc0cd03a3ad0876409f0aebf47ff 100644 (file)
@@ -2,7 +2,7 @@
  *
  * spi.h
  *
- * $PostgreSQL: pgsql/src/include/executor/spi.h,v 1.49 2004/09/16 16:58:40 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/executor/spi.h,v 1.50 2004/11/16 18:10:13 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -81,6 +81,7 @@ extern int    SPI_connect(void);
 extern int SPI_finish(void);
 extern void SPI_push(void);
 extern void SPI_pop(void);
+extern void SPI_restore_connection(void);
 extern int SPI_execute(const char *src, bool read_only, int tcount);
 extern int SPI_execute_plan(void *plan, Datum *Values, const char *Nulls,
                             bool read_only, int tcount);
index 12ca6c0888ab8aa78629d0d22882e20f1d63f2a4..fddbfefac475b549b6b0ca922a83eccc975e527d 100644 (file)
@@ -3,7 +3,7 @@
  *           procedural language
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.120 2004/09/16 16:58:44 tgl Exp $
+ *   $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.121 2004/11/16 18:10:14 tgl Exp $
  *
  *   This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -899,20 +899,11 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
        MemoryContext oldcontext = CurrentMemoryContext;
        ResourceOwner oldowner = CurrentResourceOwner;
        volatile bool caught = false;
-       int         xrc;
 
-       /*
-        * Start a subtransaction, and re-connect to SPI within it
-        */
-       SPI_push();
        BeginInternalSubTransaction(NULL);
        /* Want to run statements inside function's memory context */
        MemoryContextSwitchTo(oldcontext);
 
-       if ((xrc = SPI_connect()) != SPI_OK_CONNECT)
-           elog(ERROR, "SPI_connect failed: %s",
-                SPI_result_code_string(xrc));
-
        PG_TRY();
        {
            rc = exec_stmts(estate, block->body);
@@ -928,12 +919,17 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
            edata = CopyErrorData();
            FlushErrorState();
 
-           /* Abort the inner transaction (and inner SPI connection) */
+           /* Abort the inner transaction */
            RollbackAndReleaseCurrentSubTransaction();
            MemoryContextSwitchTo(oldcontext);
            CurrentResourceOwner = oldowner;
 
-           SPI_pop();
+           /*
+            * If AtEOSubXact_SPI() popped any SPI context of the subxact,
+            * it will have left us in a disconnected state.  We need this
+            * hack to return to connected state.
+            */
+           SPI_restore_connection();
 
            /* Look for a matching exception handler */
            exceptions = block->exceptions;
@@ -960,15 +956,14 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
        /* Commit the inner transaction, return to outer xact context */
        if (!caught)
        {
-           if ((xrc = SPI_finish()) != SPI_OK_FINISH)
-               elog(ERROR, "SPI_finish failed: %s",
-                    SPI_result_code_string(xrc));
-
            ReleaseCurrentSubTransaction();
            MemoryContextSwitchTo(oldcontext);
            CurrentResourceOwner = oldowner;
-
-           SPI_pop();
+           /*
+            * AtEOSubXact_SPI() should not have popped any SPI context,
+            * but just in case it did, make sure we remain connected.
+            */
+           SPI_restore_connection();
        }
    }
    else
index e1b57f9716b4719554e29309508642846ff1b1d7..8674fa9531d4423e31d3d39dd8f0c12da8706650 100644 (file)
@@ -1931,6 +1931,36 @@ select * from foo;
  20
 (2 rows)
 
+-- Test for pass-by-ref values being stored in proper context
+create function test_variable_storage() returns text as $$
+declare x text;
+begin
+  x := '1234';
+  begin
+    x := x || '5678';
+    -- force error inside subtransaction SPI context
+    perform trap_zero_divide(-100);
+  exception
+    when others then
+      x := x || '9012';
+  end;
+  return x;
+end$$ language plpgsql;
+select test_variable_storage();
+NOTICE:  should see this
+CONTEXT:  SQL statement "SELECT  trap_zero_divide(-100)"
+PL/pgSQL function "test_variable_storage" line 7 at perform
+NOTICE:  should see this only if -100 <> 0
+CONTEXT:  SQL statement "SELECT  trap_zero_divide(-100)"
+PL/pgSQL function "test_variable_storage" line 7 at perform
+NOTICE:  should see this only if -100 fits in smallint
+CONTEXT:  SQL statement "SELECT  trap_zero_divide(-100)"
+PL/pgSQL function "test_variable_storage" line 7 at perform
+ test_variable_storage 
+-----------------------
+ 123456789012
+(1 row)
+
 --
 -- test foreign key error trapping
 --
index 367a73986e1e20251c1db72a6dc9c041fb756243..f9f307b1ac041b2a6101175c41373c0f76368fa3 100644 (file)
@@ -1696,6 +1696,24 @@ reset statement_timeout;
 
 select * from foo;
 
+-- Test for pass-by-ref values being stored in proper context
+create function test_variable_storage() returns text as $$
+declare x text;
+begin
+  x := '1234';
+  begin
+    x := x || '5678';
+    -- force error inside subtransaction SPI context
+    perform trap_zero_divide(-100);
+  exception
+    when others then
+      x := x || '9012';
+  end;
+  return x;
+end$$ language plpgsql;
+
+select test_variable_storage();
+
 --
 -- test foreign key error trapping
 --