Implement two new special variables in PL/PgSQL: SQLSTATE and SQLERRM.
authorNeil Conway
Fri, 10 Jun 2005 16:23:11 +0000 (16:23 +0000)
committerNeil Conway
Fri, 10 Jun 2005 16:23:11 +0000 (16:23 +0000)
These contain the SQLSTATE and error message of the current exception,
respectively. They are scope-local variables that are only defined
in exception handlers (so attempting to reference them outside an
exception handler is an error). Update the regression tests and the
documentation.

Also, do some minor related cleanup: export an unpack_sql_state()
function from the backend and use it to unpack a SQLSTATE into a
string, and add a free_var() function to pl_exec.c

Original patch from Pavel Stehule, review by Neil Conway.

doc/src/sgml/plpgsql.sgml
src/backend/utils/error/elog.c
src/include/utils/elog.h
src/pl/plpgsql/src/gram.y
src/pl/plpgsql/src/pl_comp.c
src/pl/plpgsql/src/pl_exec.c
src/pl/plpgsql/src/pl_funcs.c
src/pl/plpgsql/src/plpgsql.h
src/test/regress/expected/plpgsql.out
src/test/regress/sql/plpgsql.sql

index d96c123e21c0ec87c0de013c7e3b254f61539a00..c043c78fea3efb3ddf8ded119967a3be8b28067f 100644 (file)
@@ -1,5 +1,5 @@
 
 
  
@@ -2110,6 +2110,17 @@ END;
       don't use EXCEPTION without need.
      
     
+
+    
+     Within an exception handler, the SQLSTATE
+     variable contains the error code that corresponds to the
+     exception that was raised (refer to 
+     linkend="errcodes-table"> for a list of possible error
+     codes). The SQLERRM variable contains the
+     error message associated with the exception. These variables are
+     undefined outside exception handlers.
+    
+
     
     Exceptions with UPDATE/INSERT
     
index 122a0a92a55df74d5ea12d4c7a60ce24e4496991..3e883887ee811795002b9d50cd9d908e9cf45b30 100644 (file)
@@ -42,7 +42,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/utils/error/elog.c,v 1.159 2005/06/09 22:29:52 momjian Exp $
+ *   $PostgreSQL: pgsql/src/backend/utils/error/elog.c,v 1.160 2005/06/10 16:23:10 neilc Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1482,6 +1482,26 @@ log_line_prefix(StringInfo buf)
    }
 }
 
+/*
+ * Unpack MAKE_SQLSTATE code. Note that this returns a pointer to a
+ * static buffer.
+ */
+char *
+unpack_sql_state(int sql_state)
+{
+   static char buf[12];
+   int         i;
+
+   for (i = 0; i < 5; i++)
+   {
+       buf[i] = PGUNSIXBIT(sql_state);
+       sql_state >>= 6;
+   }
+
+   buf[i] = '\0';
+   return buf;
+}
+
 
 /*
  * Write error report to server's log
@@ -1497,21 +1517,7 @@ send_message_to_server_log(ErrorData *edata)
    appendStringInfo(&buf, "%s:  ", error_severity(edata->elevel));
 
    if (Log_error_verbosity >= PGERROR_VERBOSE)
-   {
-       /* unpack MAKE_SQLSTATE code */
-       char        tbuf[12];
-       int         ssval;
-       int         i;
-
-       ssval = edata->sqlerrcode;
-       for (i = 0; i < 5; i++)
-       {
-           tbuf[i] = PGUNSIXBIT(ssval);
-           ssval >>= 6;
-       }
-       tbuf[i] = '\0';
-       appendStringInfo(&buf, "%s: ", tbuf);
-   }
+       appendStringInfo(&buf, "%s: ", unpack_sql_state(edata->sqlerrcode));
 
    if (edata->message)
        append_with_tabs(&buf, edata->message);
index 0b7d3f17ebb40d73e9b344a5e562858ee637f066..f226f772911a04b024b82de54742ee381c19b2e7 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/utils/elog.h,v 1.78 2004/12/31 22:03:46 pgsql Exp $
+ * $PostgreSQL: pgsql/src/include/utils/elog.h,v 1.79 2005/06/10 16:23:10 neilc Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -282,6 +282,7 @@ extern int  Log_destination;
 
 /* Other exported functions */
 extern void DebugFileOpen(void);
+extern char *unpack_sql_state(int sql_state);
 
 /*
  * Write errors to stderr (or by equal means when stderr is
index e1ba564ca900b42cd542e0743bc9b5067bcc4092..48164d983b5586059cae992763e27c3d2e28638a 100644 (file)
@@ -4,7 +4,7 @@
  *                       procedural language
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.74 2005/06/08 00:49:36 neilc Exp $
+ *   $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.75 2005/06/10 16:23:11 neilc Exp $
  *
  *   This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -92,6 +92,7 @@ static    void             plpgsql_sql_error_callback(void *arg);
        PLpgSQL_stmt_block      *program;
        PLpgSQL_condition       *condition;
        PLpgSQL_exception       *exception;
+       PLpgSQL_exception_block *exception_block;
        PLpgSQL_nsitem          *nsitem;
        PLpgSQL_diag_item       *diagitem;
 }
@@ -129,7 +130,8 @@ static  void             plpgsql_sql_error_callback(void *arg);
 %type    stmt_dynexecute stmt_getdiag
 %type    stmt_open stmt_fetch stmt_close stmt_null
 
-%type    exception_sect proc_exceptions
+%type    proc_exceptions
+%type  exception_sect
 %type   proc_exception
 %type   proc_conditions
 
@@ -1495,9 +1497,38 @@ execsql_start    : T_WORD
                ;
 
 exception_sect :
-                   { $$ = NIL; }
-               | K_EXCEPTION proc_exceptions
-                   { $$ = $2; }
+                   { $$ = NULL; }
+               | K_EXCEPTION lno
+                   {
+                       /*
+                        * We use a mid-rule action to add these
+                        * special variables to the namespace before
+                        * parsing the WHEN clauses themselves.
+                        */
+                       PLpgSQL_exception_block *new = palloc(sizeof(PLpgSQL_exception_block));
+                       PLpgSQL_variable *var;
+
+                       var = plpgsql_build_variable("sqlstate", $2,
+                                                    plpgsql_build_datatype(TEXTOID, -1),
+                                                    true);
+                       ((PLpgSQL_var *) var)->isconst = true;
+                       new->sqlstate_varno = var->dno;
+
+                       var = plpgsql_build_variable("sqlerrm", $2,
+                                                    plpgsql_build_datatype(TEXTOID, -1),
+                                                    true);
+                       ((PLpgSQL_var *) var)->isconst = true;
+                       new->sqlerrm_varno = var->dno;
+
+                       $$ = new;
+                   }
+                   proc_exceptions
+                   {
+                       PLpgSQL_exception_block *new = $3;
+                       new->exc_list = $4;
+
+                       $$ = new;
+                   }
                ;
 
 proc_exceptions    : proc_exceptions proc_exception
index b746a8374bda03603ff92feebc6de69fb7fef602..38b3d077de21221011559bd737572695be84c060 100644 (file)
@@ -3,7 +3,7 @@
  *           procedural language
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.90 2005/05/29 04:23:06 tgl Exp $
+ *   $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.91 2005/06/10 16:23:11 neilc Exp $
  *
  *   This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -656,7 +656,7 @@ do_compile(FunctionCallInfo fcinfo,
    if (num_out_args > 0 || function->fn_rettype == VOIDOID ||
        function->fn_retset)
    {
-       if (function->action->exceptions != NIL)
+       if (function->action->exceptions != NULL)
        {
            PLpgSQL_stmt_block *new;
 
@@ -882,7 +882,7 @@ plpgsql_parse_word(char *word)
    }
 
    /*
-    * Do a lookup on the compilers namestack
+    * Do a lookup on the compiler's namestack
     */
    nse = plpgsql_ns_lookup(cp[0], NULL);
    if (nse != NULL)
@@ -1935,7 +1935,7 @@ plpgsql_parse_err_condition(char *condname)
 
 /* ----------
  * plpgsql_adddatum            Add a variable, record or row
- *                 to the compilers datum list.
+ *                 to the compiler's datum list.
  * ----------
  */
 void
index 1fe1ed4cbedf38612d3b40b39a1a0b5339849b50..ae5f7473146c1bfefcb5663a6289bc268d4a845d 100644 (file)
@@ -3,7 +3,7 @@
  *           procedural language
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.142 2005/06/07 02:47:17 neilc Exp $
+ *   $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.143 2005/06/10 16:23:11 neilc Exp $
  *
  *   This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -180,7 +180,7 @@ static Datum exec_simple_cast_value(Datum value, Oid valtype,
 static void exec_init_tuple_store(PLpgSQL_execstate *estate);
 static bool compatible_tupdesc(TupleDesc td1, TupleDesc td2);
 static void exec_set_found(PLpgSQL_execstate *estate, bool state);
-
+static void free_var(PLpgSQL_var *var);
 
 /* ----------
  * plpgsql_exec_function   Called by the call handler for
@@ -760,12 +760,7 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
                {
                    PLpgSQL_var *var = (PLpgSQL_var *) (estate->datums[n]);
 
-                   if (var->freeval)
-                   {
-                       pfree((void *) (var->value));
-                       var->freeval = false;
-                   }
-
+                   free_var(var);
                    if (!var->isconst || var->isnull)
                    {
                        if (var->default_val == NULL)
@@ -864,13 +859,37 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
            SPI_restore_connection();
 
            /* Look for a matching exception handler */
-           foreach (e, block->exceptions)
+           foreach (e, block->exceptions->exc_list)
            {
                PLpgSQL_exception *exception = (PLpgSQL_exception *) lfirst(e);
 
                if (exception_matches_conditions(edata, exception->conditions))
                {
+                   /*
+                    * Initialize the magic SQLSTATE and SQLERRM
+                    * variables for the exception block. We needn't
+                    * do this until we have found a matching
+                    * exception.
+                    */
+                   PLpgSQL_var *state_var;
+                   PLpgSQL_var *errm_var;
+
+                   state_var = (PLpgSQL_var *) (estate->datums[block->exceptions->sqlstate_varno]);
+                   state_var->value = DirectFunctionCall1(textin,
+                                                          CStringGetDatum(unpack_sql_state(edata->sqlerrcode)));
+                   state_var->freeval = true;
+                   state_var->isnull = false;
+
+                   errm_var = (PLpgSQL_var *) (estate->datums[block->exceptions->sqlerrm_varno]);
+                   errm_var->value = DirectFunctionCall1(textin,
+                                                         CStringGetDatum(edata->message));
+                   errm_var->freeval = true;
+                   errm_var->isnull = false;
+
                    rc = exec_stmts(estate, exception->action);
+
+                   free_var(state_var);
+                   free_var(errm_var);
                    break;
                }
            }
@@ -2586,9 +2605,7 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
         * Store the eventually assigned cursor name in the cursor variable
         * ----------
         */
-       if (curvar->freeval)
-           pfree((void *) (curvar->value));
-
+       free_var(curvar);
        curvar->value = DirectFunctionCall1(textin, CStringGetDatum(portal->name));
        curvar->isnull = false;
        curvar->freeval = true;
@@ -2684,9 +2701,7 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
     * Store the eventually assigned portal name in the cursor variable
     * ----------
     */
-   if (curvar->freeval)
-       pfree((void *) (curvar->value));
-
+   free_var(curvar);
    curvar->value = DirectFunctionCall1(textin, CStringGetDatum(portal->name));
    curvar->isnull = false;
    curvar->freeval = true;
@@ -2857,11 +2872,7 @@ exec_assign_value(PLpgSQL_execstate *estate,
                             errmsg("NULL cannot be assigned to variable \"%s\" declared NOT NULL",
                                    var->refname)));
 
-               if (var->freeval)
-               {
-                   pfree(DatumGetPointer(var->value));
-                   var->freeval = false;
-               }
+               free_var(var);
 
                /*
                 * If type is by-reference, make sure we have a freshly
@@ -4343,3 +4354,13 @@ plpgsql_xact_cb(XactEvent event, void *arg)
        FreeExecutorState(simple_eval_estate);
    simple_eval_estate = NULL;
 }
+
+static void
+free_var(PLpgSQL_var *var)
+{
+   if (var->freeval)
+   {
+       pfree(DatumGetPointer(var->value));
+       var->freeval = false;
+   }
+}
index 475d2224bffcfc6d13360540b96fb7d3c692744f..9e2ef4902c2b246d99077390da7cbf6ba4a4897e 100644 (file)
@@ -3,7 +3,7 @@
  *           procedural language
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.40 2005/04/05 06:22:16 tgl Exp $
+ *   $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.41 2005/06/10 16:23:11 neilc Exp $
  *
  *   This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -634,7 +634,7 @@ dump_block(PLpgSQL_stmt_block *block)
    {
        ListCell *e;
 
-       foreach (e, block->exceptions)
+       foreach (e, block->exceptions->exc_list)
        {
            PLpgSQL_exception *exc = (PLpgSQL_exception *) lfirst(e);
            PLpgSQL_condition *cond;
index a70553ac2c8a2e62f1de10e33405af990cbf457d..580439c88c5682720e2c364ee022c478f93d997d 100644 (file)
@@ -3,7 +3,7 @@
  *           procedural language
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.61 2005/06/07 02:47:18 neilc Exp $
+ *   $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.62 2005/06/10 16:23:11 neilc Exp $
  *
  *   This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -322,6 +322,13 @@ typedef struct PLpgSQL_condition
    struct PLpgSQL_condition *next;
 } PLpgSQL_condition;
 
+typedef struct
+{
+   int         sqlstate_varno;
+   int         sqlerrm_varno;
+   List       *exc_list;       /* List of WHEN clauses */
+} PLpgSQL_exception_block;
+
 typedef struct
 {                              /* One EXCEPTION ... WHEN clause */
    int         lineno;
@@ -336,9 +343,9 @@ typedef struct
    int         lineno;
    char       *label;
    List       *body;           /* List of statements */
-   List       *exceptions;     /* List of WHEN clauses */
    int         n_initvars;
    int        *initvarnos;
+   PLpgSQL_exception_block *exceptions;
 } PLpgSQL_stmt_block;
 
 
index 39e61e09cf26d94a5e92a85f0c48dcb4b2658c68..2650cbb908a55dbd22d13c03b65b21ec70a89742 100644 (file)
@@ -2415,3 +2415,57 @@ NOTICE:  10 15 20
 
 drop table eifoo cascade;
 drop type eitype cascade;
+--
+-- SQLSTATE and SQLERRM test
+--
+-- should fail: SQLSTATE and SQLERRM are only in defined EXCEPTION
+-- blocks
+create function excpt_test() returns void as $$
+begin
+    raise notice '% %', sqlstate, sqlerrm;
+end; $$ language plpgsql;
+ERROR:  syntax error at or near "sqlstate" at character 79
+LINE 3:     raise notice '% %', sqlstate, sqlerrm;
+                                ^
+-- should fail
+create function excpt_test() returns void as $$
+begin
+    begin
+        begin
+           raise notice '% %', sqlstate, sqlerrm;
+        end;
+    end;
+end; $$ language plpgsql;
+ERROR:  syntax error at or near "sqlstate" at character 108
+LINE 5:          raise notice '% %', sqlstate, sqlerrm;
+                                     ^
+create function excpt_test() returns void as $$
+begin
+    begin
+       raise exception 'user exception';
+    exception when others then
+       raise notice 'caught exception % %', sqlstate, sqlerrm;
+       begin
+           raise notice '% %', sqlstate, sqlerrm;
+           perform 10/0;
+        exception
+            when substring_error then
+                -- this exception handler shouldn't be invoked
+                raise notice 'unexpected exception: % %', sqlstate, sqlerrm;
+           when division_by_zero then
+               raise notice 'caught exception % %', sqlstate, sqlerrm;
+       end;
+       raise notice '% %', sqlstate, sqlerrm;
+    end;
+end; $$ language plpgsql;
+select excpt_test();
+NOTICE:  caught exception P0001 user exception
+NOTICE:  P0001 user exception
+NOTICE:  caught exception 22012 division by zero
+NOTICE:  P0001 user exception
+ excpt_test 
+------------
+(1 row)
+
+drop function excpt_test();
index 314f69915fc61dfecc5963666cf7c7c7083773d5..9dc00f2f1e5b7b15103d0d4b4bb3a69d62b0b39b 100644 (file)
@@ -2050,3 +2050,47 @@ select execute_into_test('eifoo');
 
 drop table eifoo cascade;
 drop type eitype cascade;
+
+--
+-- SQLSTATE and SQLERRM test
+--
+
+-- should fail: SQLSTATE and SQLERRM are only in defined EXCEPTION
+-- blocks
+create function excpt_test() returns void as $$
+begin
+    raise notice '% %', sqlstate, sqlerrm;
+end; $$ language plpgsql;
+
+-- should fail
+create function excpt_test() returns void as $$
+begin
+    begin
+        begin
+           raise notice '% %', sqlstate, sqlerrm;
+        end;
+    end;
+end; $$ language plpgsql;
+
+create function excpt_test() returns void as $$
+begin
+    begin
+       raise exception 'user exception';
+    exception when others then
+       raise notice 'caught exception % %', sqlstate, sqlerrm;
+       begin
+           raise notice '% %', sqlstate, sqlerrm;
+           perform 10/0;
+        exception
+            when substring_error then
+                -- this exception handler shouldn't be invoked
+                raise notice 'unexpected exception: % %', sqlstate, sqlerrm;
+           when division_by_zero then
+               raise notice 'caught exception % %', sqlstate, sqlerrm;
+       end;
+       raise notice '% %', sqlstate, sqlerrm;
+    end;
+end; $$ language plpgsql;
+
+select excpt_test();
+drop function excpt_test();