Revise syntax-error reporting behavior to give pleasant results for
authorTom Lane
Sun, 21 Mar 2004 22:29:11 +0000 (22:29 +0000)
committerTom Lane
Sun, 21 Mar 2004 22:29:11 +0000 (22:29 +0000)
errors in internally-generated queries, such as those submitted by
plpgsql functions.  Per recent discussions with Fabien Coelho.

25 files changed:
doc/src/sgml/libpq.sgml
doc/src/sgml/protocol.sgml
src/backend/catalog/pg_proc.c
src/backend/commands/portalcmds.c
src/backend/executor/functions.c
src/backend/executor/spi.c
src/backend/optimizer/util/clauses.c
src/backend/parser/parse_type.c
src/backend/tcop/postgres.c
src/backend/tcop/pquery.c
src/backend/utils/error/elog.c
src/bin/psql/command.c
src/bin/psql/common.c
src/bin/psql/settings.h
src/bin/psql/startup.c
src/include/catalog/pg_proc.h
src/include/executor/spi_priv.h
src/include/postgres_ext.h
src/include/tcop/pquery.h
src/include/utils/elog.h
src/interfaces/libpq/fe-protocol3.c
src/pl/plpgsql/src/pl_comp.c
src/pl/plpgsql/src/scan.l
src/test/regress/expected/triggers.out
src/test/regress/output/create_function_1.source

index 82b8099a4a0b2fb72a3bae66b3d4be920361990a..ee30b0d7e5725816104ae4accc6b2226b9eba654 100644 (file)
@@ -1,5 +1,5 @@
 
 
  
@@ -1390,13 +1390,37 @@ bytes.
 
 
 
+
+PG_DIAG_INTERNAL_POSITION
+
+
+This is defined the same as the PG_DIAG_STATEMENT_POSITION
+field, but it is used when the cursor position refers to an internally
+generated command rather than the one submitted by the client.
+The PG_DIAG_INTERNAL_QUERY field will always appear when this field
+appears.
+
+
+
+
+
+PG_DIAG_INTERNAL_QUERY
+
+
+The text of a failed internally-generated command.
+This could be, for example, a SQL query issued by a PL/pgSQL function.
+
+
+
+
 
 PG_DIAG_CONTEXT
 
 
-An indication of the context in which the error occurred.  Presently
-this includes a call stack traceback of active PL functions.  The
-trace is one entry per line, most recent first.
+An indication of the context in which the error occurred.
+Presently this includes a call stack traceback of active
+procedural language functions and internally-generated queries.
+The trace is one entry per line, most recent first.
 
 
 
index a4baff82a4b867bfbb62e7153690cce184384a40..de3b72738d752140aa54d9031490cb0aeb48d3e8 100644 (file)
@@ -1,4 +1,4 @@
-
+
 
 
  Frontend/Backend Protocol
@@ -3902,6 +3902,32 @@ message.
 
 
 
+
+
+p
+
+
+
+        Internal position: this is defined the same as the P
+        field, but it is used when the cursor position refers to an internally
+        generated command rather than the one submitted by the client.
+        The q field will always appear when this field appears.
+
+
+
+
+
+
+q
+
+
+
+        Internal query: the text of a failed internally-generated command.
+        This could be, for example, a SQL query issued by a PL/pgSQL function.
+
+
+
+
 
 
 W
@@ -3909,9 +3935,9 @@ message.
 
 
         Where: an indication of the context in which the error occurred.
-   Presently this includes a call stack traceback of active
-   procedural language functions.  The trace is one entry per line,
-   most recent first.
+        Presently this includes a call stack traceback of active
+        procedural language functions and internally-generated queries.
+        The trace is one entry per line, most recent first.
 
 
 
index 3ea2af44d3e11c950b20c41babb4b8f6de6f4d32..6fe64eadd0d9ba459dadb40723a778009d9466db 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/catalog/pg_proc.c,v 1.112 2004/03/14 01:58:41 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/catalog/pg_proc.c,v 1.113 2004/03/21 22:29:10 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "executor/executor.h"
 #include "fmgr.h"
 #include "miscadmin.h"
+#include "mb/pg_wchar.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_expr.h"
 #include "parser/parse_type.h"
+#include "tcop/pquery.h"
 #include "tcop/tcopprot.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -45,6 +47,10 @@ Datum        fmgr_sql_validator(PG_FUNCTION_ARGS);
 static Datum create_parameternames_array(int parameterCount,
                                         const char *parameterNames[]);
 static void sql_function_parse_error_callback(void *arg);
+static int match_prosrc_to_query(const char *prosrc, const char *queryText,
+                                 int cursorpos);
+static bool match_prosrc_to_literal(const char *prosrc, const char *literal,
+                                   int cursorpos, int *newcursorpos);
 
 
 /* ----------------------------------------------------------------
@@ -763,12 +769,10 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
        prosrc = DatumGetCString(DirectFunctionCall1(textout, tmp));
 
        /*
-        * Setup error traceback support for ereport().  This is mostly
-        * so we can add context info that shows that a syntax-error
-        * location is inside the function body, not out in CREATE FUNCTION.
+        * Setup error traceback support for ereport().
         */
        sqlerrcontext.callback = sql_function_parse_error_callback;
-       sqlerrcontext.arg = proc;
+       sqlerrcontext.arg = tuple;
        sqlerrcontext.previous = error_context_stack;
        error_context_stack = &sqlerrcontext;
 
@@ -800,22 +804,203 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
 }
 
 /*
- * error context callback to let us supply a context marker
+ * Error context callback for handling errors in SQL function definitions
  */
 static void
 sql_function_parse_error_callback(void *arg)
 {
-   Form_pg_proc proc = (Form_pg_proc) arg;
+   HeapTuple   tuple = (HeapTuple) arg;
+   Form_pg_proc proc = (Form_pg_proc) GETSTRUCT(tuple);
+   bool        isnull;
+   Datum       tmp;
+   char       *prosrc;
+
+   /* See if it's a syntax error; if so, transpose to CREATE FUNCTION */
+   tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosrc, &isnull);
+   if (isnull)
+       elog(ERROR, "null prosrc");
+   prosrc = DatumGetCString(DirectFunctionCall1(textout, tmp));
+
+   if (!function_parse_error_transpose(prosrc))
+   {
+       /* If it's not a syntax error, push info onto context stack */
+       errcontext("SQL function \"%s\"", NameStr(proc->proname));
+   }
+
+   pfree(prosrc);
+}
+
+/*
+ * Adjust a syntax error occurring inside the function body of a CREATE
+ * FUNCTION command.  This can be used by any function validator, not only
+ * for SQL-language functions.  It is assumed that the syntax error position
+ * is initially relative to the function body string (as passed in).  If
+ * possible, we adjust the position to reference the original CREATE command;
+ * if we can't manage that, we set up an "internal query" syntax error instead.
+ *
+ * Returns true if a syntax error was processed, false if not.
+ */
+bool
+function_parse_error_transpose(const char *prosrc)
+{
+   int         origerrposition;
+   int         newerrposition;
+   const char *queryText;
+
+   /*
+    * Nothing to do unless we are dealing with a syntax error that has
+    * a cursor position.
+    *
+    * Some PLs may prefer to report the error position as an internal
+    * error to begin with, so check that too.
+    */
+   origerrposition = geterrposition();
+   if (origerrposition <= 0)
+   {
+       origerrposition = getinternalerrposition();
+       if (origerrposition <= 0)
+           return false;
+   }
+
+   /* We can get the original query text from the active portal (hack...) */
+   Assert(ActivePortal && ActivePortal->portalActive);
+   queryText = ActivePortal->sourceText;
+
+   /* Try to locate the prosrc in the original text */
+   newerrposition = match_prosrc_to_query(prosrc, queryText, origerrposition);
+
+   if (newerrposition > 0)
+   {
+       /* Successful, so fix error position to reference original query */
+       errposition(newerrposition);
+       /* Get rid of any report of the error as an "internal query" */
+       internalerrposition(0);
+       internalerrquery(NULL);
+   }
+   else
+   {
+       /*
+        * If unsuccessful, convert the position to an internal position
+        * marker and give the function text as the internal query.
+        */
+       errposition(0);
+       internalerrposition(origerrposition);
+       internalerrquery(prosrc);
+   }
+
+   return true;
+}
 
+/*
+ * Try to locate the string literal containing the function body in the
+ * given text of the CREATE FUNCTION command.  If successful, return the
+ * character (not byte) index within the command corresponding to the
+ * given character index within the literal.  If not successful, return 0.
+ */
+static int
+match_prosrc_to_query(const char *prosrc, const char *queryText,
+                     int cursorpos)
+{
    /*
-    * XXX it'd be really nice to adjust the syntax error position to
-    * account for the offset from the start of the statement to the
-    * function body string, not to mention any quoting characters in
-    * the string, but I can't see any decent way to do that...
+    * Rather than fully parsing the CREATE FUNCTION command, we just scan
+    * the command looking for $prosrc$ or 'prosrc'.  This could be fooled
+    * (though not in any very probable scenarios), so fail if we find
+    * more than one match.
+    */
+   int     prosrclen = strlen(prosrc);
+   int     querylen = strlen(queryText);
+   int     matchpos = 0;
+   int     curpos;
+   int     newcursorpos;
+
+   for (curpos = 0; curpos < querylen-prosrclen; curpos++)
+   {
+       if (queryText[curpos] == '$' &&
+           strncmp(prosrc, &queryText[curpos+1], prosrclen) == 0 &&
+           queryText[curpos+1+prosrclen] == '$')
+       {
+           /*
+            * Found a $foo$ match.  Since there are no embedded quoting
+            * characters in a dollar-quoted literal, we don't have to do
+            * any fancy arithmetic; just offset by the starting position.
+            */
+           if (matchpos)
+               return 0;       /* multiple matches, fail */
+           matchpos = pg_mbstrlen_with_len(queryText, curpos+1)
+               + cursorpos;
+       }
+       else if (queryText[curpos] == '\'' &&
+                match_prosrc_to_literal(prosrc, &queryText[curpos+1],
+                                        cursorpos, &newcursorpos))
+       {
+           /*
+            * Found a 'foo' match.  match_prosrc_to_literal() has adjusted
+            * for any quotes or backslashes embedded in the literal.
+            */
+           if (matchpos)
+               return 0;       /* multiple matches, fail */
+           matchpos = pg_mbstrlen_with_len(queryText, curpos+1)
+               + newcursorpos;
+       }
+   }
+
+   return matchpos;
+}
+
+/*
+ * Try to match the given source text to a single-quoted literal.
+ * If successful, adjust newcursorpos to correspond to the character
+ * (not byte) index corresponding to cursorpos in the source text.
+ *
+ * At entry, literal points just past a ' character.  We must check for the
+ * trailing quote.
+ */
+static bool
+match_prosrc_to_literal(const char *prosrc, const char *literal,
+                       int cursorpos, int *newcursorpos)
+{
+   int         newcp = cursorpos;
+   int         chlen;
+
+   /*
+    * This implementation handles backslashes and doubled quotes in the
+    * string literal.  It does not handle the SQL syntax for literals
+    * continued across line boundaries.
     *
-    * In the meantime, put in a CONTEXT entry that can cue clients
-    * not to trust the syntax error position completely.
+    * We do the comparison a character at a time, not a byte at a time,
+    * so that we can do the correct cursorpos math.
     */
-   errcontext("SQL function \"%s\"",
-              NameStr(proc->proname));
+   while (*prosrc)
+   {
+       cursorpos--;            /* characters left before cursor */
+       /*
+        * Check for backslashes and doubled quotes in the literal; adjust
+        * newcp when one is found before the cursor.
+        */
+       if (*literal == '\\')
+       {
+           literal++;
+           if (cursorpos > 0)
+               newcp++;
+       }
+       else if (*literal == '\'')
+       {
+           if (literal[1] != '\'')
+               return false;
+           literal++;
+           if (cursorpos > 0)
+               newcp++;
+       }
+       chlen = pg_mblen(prosrc);
+       if (strncmp(prosrc, literal, chlen) != 0)
+           return false;
+       prosrc += chlen;
+       literal += chlen;
+   }
+
+   *newcursorpos = newcp;
+
+   if (*literal == '\'' && literal[1] != '\'')
+       return true;
+   return false;
 }
index 34748fa77e1e8d95a58c4022e51a7620ad34bf5a..855c9391c18e4405b07d43060ad8f5b3a74bcb31 100644 (file)
@@ -14,7 +14,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/commands/portalcmds.c,v 1.25 2003/11/29 19:51:47 pgsql Exp $
+ *   $PostgreSQL: pgsql/src/backend/commands/portalcmds.c,v 1.26 2004/03/21 22:29:10 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -270,6 +270,7 @@ void
 PersistHoldablePortal(Portal portal)
 {
    QueryDesc  *queryDesc = PortalGetQueryDesc(portal);
+   Portal      saveActivePortal;
    MemoryContext savePortalContext;
    MemoryContext saveQueryContext;
    MemoryContext oldcxt;
@@ -311,6 +312,8 @@ PersistHoldablePortal(Portal portal)
    /*
     * Set global portal context pointers.
     */
+   saveActivePortal = ActivePortal;
+   ActivePortal = portal;
    savePortalContext = PortalContext;
    PortalContext = PortalGetHeapMemory(portal);
    saveQueryContext = QueryContext;
@@ -342,6 +345,7 @@ PersistHoldablePortal(Portal portal)
    /* Mark portal not active */
    portal->portalActive = false;
 
+   ActivePortal = saveActivePortal;
    PortalContext = savePortalContext;
    QueryContext = saveQueryContext;
 
index 6ffc8875f1950d16c14b7a21af7151ff61257ef7..8dec6131fb4ea7326eabdbccb666814c764c9e54 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.77 2004/01/07 18:56:26 neilc Exp $
+ *   $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.78 2004/03/21 22:29:11 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -645,12 +645,40 @@ sql_exec_error_callback(void *arg)
 {
    FmgrInfo   *flinfo = (FmgrInfo *) arg;
    SQLFunctionCachePtr fcache = (SQLFunctionCachePtr) flinfo->fn_extra;
+   HeapTuple   func_tuple;
+   Form_pg_proc functup;
    char       *fn_name;
+   int         syntaxerrposition;
 
-   fn_name = get_func_name(flinfo->fn_oid);
-   /* safety check, shouldn't happen */
-   if (fn_name == NULL)
-       return;
+   /* Need access to function's pg_proc tuple */
+   func_tuple = SearchSysCache(PROCOID,
+                               ObjectIdGetDatum(flinfo->fn_oid),
+                               0, 0, 0);
+   if (!HeapTupleIsValid(func_tuple))
+       return;                 /* shouldn't happen */
+   functup = (Form_pg_proc) GETSTRUCT(func_tuple);
+   fn_name = NameStr(functup->proname);
+
+   /*
+    * If there is a syntax error position, convert to internal syntax error
+    */
+   syntaxerrposition = geterrposition();
+   if (syntaxerrposition > 0)
+   {
+       bool        isnull;
+       Datum       tmp;
+       char       *prosrc;
+
+       tmp = SysCacheGetAttr(PROCOID, func_tuple, Anum_pg_proc_prosrc,
+                             &isnull);
+       if (isnull)
+           elog(ERROR, "null prosrc");
+       prosrc = DatumGetCString(DirectFunctionCall1(textout, tmp));
+       errposition(0);
+       internalerrposition(syntaxerrposition);
+       internalerrquery(prosrc);
+       pfree(prosrc);
+   }
 
    /*
     * Try to determine where in the function we failed.  If there is a
@@ -692,8 +720,7 @@ sql_exec_error_callback(void *arg)
        errcontext("SQL function \"%s\" during startup", fn_name);
    }
 
-   /* free result of get_func_name (in case this is only a notice) */
-   pfree(fn_name);
+   ReleaseSysCache(func_tuple);
 }
 
 
index fb44d9d56fd02922ded6acbce74025a1736cf10d..1fb60fff02d60f16f4c5f2d98f9202a4546c17ba 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.111 2004/03/17 01:05:10 momjian Exp $
+ *   $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.112 2004/03/21 22:29:11 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -39,6 +39,8 @@ static int _SPI_execute_plan(_SPI_plan *plan,
                             Datum *Values, const char *Nulls,
                             bool useCurrentSnapshot, int tcount);
 
+static void _SPI_error_callback(void *arg);
+
 static void _SPI_cursor_operation(Portal portal, bool forward, int count,
                      DestReceiver *dest);
 
@@ -286,7 +288,8 @@ SPI_execp_current(void *plan, Datum *Values, const char *Nulls,
 void *
 SPI_prepare(const char *src, int nargs, Oid *argtypes)
 {
-   _SPI_plan  *plan;
+   _SPI_plan   plan;
+   _SPI_plan  *result;
 
    if (src == NULL || nargs < 0 || (nargs > 0 && argtypes == NULL))
    {
@@ -298,20 +301,21 @@ SPI_prepare(const char *src, int nargs, Oid *argtypes)
    if (SPI_result < 0)
        return NULL;
 
-   plan = (_SPI_plan *) palloc(sizeof(_SPI_plan));     /* Executor context */
-   plan->argtypes = argtypes;
-   plan->nargs = nargs;
+   plan.plancxt = NULL;        /* doesn't have own context */
+   plan.query = src;
+   plan.nargs = nargs;
+   plan.argtypes = argtypes;
 
-   SPI_result = _SPI_execute(src, 0, plan);
+   SPI_result = _SPI_execute(src, 0, &plan);
 
    if (SPI_result >= 0)        /* copy plan to procedure context */
-       plan = _SPI_copy_plan(plan, _SPI_CPLAN_PROCXT);
+       result = _SPI_copy_plan(&plan, _SPI_CPLAN_PROCXT);
    else
-       plan = NULL;
+       result = NULL;
 
    _SPI_end_call(true);
 
-   return (void *) plan;
+   return (void *) result;
 }
 
 void *
@@ -335,7 +339,6 @@ SPI_saveplan(void *plan)
    SPI_result = 0;
 
    return (void *) newplan;
-
 }
 
 int
@@ -927,12 +930,12 @@ SPI_cursor_close(Portal portal)
 Oid
 SPI_getargtypeid(void *plan, int argIndex)
 {
- if (plan == NULL || argIndex < 0 || argIndex >= ((_SPI_plan*)plan)->nargs)
- {
-  SPI_result = SPI_ERROR_ARGUMENT;
-  return InvalidOid;
- }
- return ((_SPI_plan *) plan)->argtypes[argIndex];
  if (plan == NULL || argIndex < 0 || argIndex >= ((_SPI_plan*)plan)->nargs)
  {
+       SPI_result = SPI_ERROR_ARGUMENT;
+       return InvalidOid;
  }
  return ((_SPI_plan *) plan)->argtypes[argIndex];
 }
 
 /*
@@ -941,12 +944,12 @@ SPI_getargtypeid(void *plan, int argIndex)
 int
 SPI_getargcount(void *plan)
 {
- if (plan == NULL)
- {
-  SPI_result = SPI_ERROR_ARGUMENT;
-  return -1;
- }
- return ((_SPI_plan *) plan)->nargs;
  if (plan == NULL)
  {
+       SPI_result = SPI_ERROR_ARGUMENT;
+       return -1;
  }
  return ((_SPI_plan *) plan)->nargs;
 }
 
 /*
@@ -961,22 +964,24 @@ SPI_getargcount(void *plan)
 bool
 SPI_is_cursor_plan(void *plan)
 {
- List *qtlist;
- _SPI_plan *spiplan = (_SPI_plan *) plan;
- if (spiplan == NULL)
- {
-  SPI_result = SPI_ERROR_ARGUMENT;
-  return false;
- }
-
- qtlist = spiplan->qtlist;
- if(length(spiplan->ptlist) == 1 && length(qtlist) == 1)
- {
-  Query *queryTree = (Query *) lfirst((List *) lfirst(qtlist));
-  if(queryTree->commandType == CMD_SELECT && queryTree->into == NULL)
-   return true;
- }
- return false;
+   _SPI_plan *spiplan = (_SPI_plan *) plan;
+   List *qtlist;
+
+   if (spiplan == NULL)
+   {
+       SPI_result = SPI_ERROR_ARGUMENT;
+       return false;
+   }
+
+   qtlist = spiplan->qtlist;
+   if (length(spiplan->ptlist) == 1 && length(qtlist) == 1)
+   {
+       Query *queryTree = (Query *) lfirst((List *) lfirst(qtlist));
+
+       if (queryTree->commandType == CMD_SELECT && queryTree->into == NULL)
+           return true;
+   }
+   return false;
 }
 
 /* =================== private functions =================== */
@@ -1071,7 +1076,8 @@ spi_printtup(HeapTuple tuple, TupleDesc tupdesc, DestReceiver *self)
 /*
  * Plan and optionally execute a querystring.
  *
- * If plan != NULL, just prepare plan tree, else execute immediately.
+ * If plan != NULL, just prepare plan trees and save them in *plan;
+ * else execute immediately.
  */
 static int
 _SPI_execute(const char *src, int tcount, _SPI_plan *plan)
@@ -1080,6 +1086,7 @@ _SPI_execute(const char *src, int tcount, _SPI_plan *plan)
    List       *query_list_list;
    List       *plan_list;
    List       *list_item;
+   ErrorContextCallback spierrcontext;
    int         nargs = 0;
    Oid        *argtypes = NULL;
    int         res = 0;
@@ -1099,6 +1106,14 @@ _SPI_execute(const char *src, int tcount, _SPI_plan *plan)
    SPI_tuptable = NULL;
    _SPI_current->tuptable = NULL;
 
+   /*
+    * Setup error traceback support for ereport()
+    */
+   spierrcontext.callback = _SPI_error_callback;
+   spierrcontext.arg = (void *) src;
+   spierrcontext.previous = error_context_stack;
+   error_context_stack = &spierrcontext;
+
    /*
     * Parse the request string into a list of raw parse trees.
     */
@@ -1149,14 +1164,23 @@ _SPI_execute(const char *src, int tcount, _SPI_plan *plan)
                    CopyStmt   *stmt = (CopyStmt *) queryTree->utilityStmt;
 
                    if (stmt->filename == NULL)
-                       return SPI_ERROR_COPY;
+                   {
+                       res = SPI_ERROR_COPY;
+                       goto fail;
+                   }
                }
                else if (IsA(queryTree->utilityStmt, DeclareCursorStmt) ||
                         IsA(queryTree->utilityStmt, ClosePortalStmt) ||
                         IsA(queryTree->utilityStmt, FetchStmt))
-                   return SPI_ERROR_CURSOR;
+               {
+                   res = SPI_ERROR_CURSOR;
+                   goto fail;
+               }
                else if (IsA(queryTree->utilityStmt, TransactionStmt))
-                   return SPI_ERROR_TRANSACTION;
+               {
+                   res = SPI_ERROR_TRANSACTION;
+                   goto fail;
+               }
                res = SPI_OK_UTILITY;
                if (plan == NULL)
                {
@@ -1171,7 +1195,7 @@ _SPI_execute(const char *src, int tcount, _SPI_plan *plan)
                res = _SPI_pquery(qdesc, true, false,
                                  queryTree->canSetTag ? tcount : 0);
                if (res < 0)
-                   return res;
+                   goto fail;
                CommandCounterIncrement();
            }
            else
@@ -1180,7 +1204,7 @@ _SPI_execute(const char *src, int tcount, _SPI_plan *plan)
                                        NULL, false);
                res = _SPI_pquery(qdesc, false, false, 0);
                if (res < 0)
-                   return res;
+                   goto fail;
            }
        }
    }
@@ -1191,6 +1215,13 @@ _SPI_execute(const char *src, int tcount, _SPI_plan *plan)
        plan->ptlist = plan_list;
    }
 
+fail:
+
+   /*
+    * Pop the error context stack
+    */
+   error_context_stack = spierrcontext.previous;
+
    return res;
 }
 
@@ -1201,6 +1232,7 @@ _SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
    List       *query_list_list = plan->qtlist;
    List       *plan_list = plan->ptlist;
    List       *query_list_list_item;
+   ErrorContextCallback spierrcontext;
    int         nargs = plan->nargs;
    int         res = 0;
    ParamListInfo paramLI;
@@ -1234,6 +1266,14 @@ _SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
    SPI_tuptable = NULL;
    _SPI_current->tuptable = NULL;
 
+   /*
+    * Setup error traceback support for ereport()
+    */
+   spierrcontext.callback = _SPI_error_callback;
+   spierrcontext.arg = (void *) plan->query;
+   spierrcontext.previous = error_context_stack;
+   error_context_stack = &spierrcontext;
+
    foreach(query_list_list_item, query_list_list)
    {
        List       *query_list = lfirst(query_list_list_item);
@@ -1270,12 +1310,19 @@ _SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
                res = _SPI_pquery(qdesc, true, useCurrentSnapshot,
                                  queryTree->canSetTag ? tcount : 0);
                if (res < 0)
-                   return res;
+                   goto fail;
                CommandCounterIncrement();
            }
        }
    }
 
+fail:
+
+   /*
+    * Pop the error context stack
+    */
+   error_context_stack = spierrcontext.previous;
+
    return res;
 }
 
@@ -1355,6 +1402,32 @@ _SPI_pquery(QueryDesc *queryDesc, bool runit,
    return res;
 }
 
+/*
+ * _SPI_error_callback
+ *
+ * Add context information when a query invoked via SPI fails
+ */
+static void
+_SPI_error_callback(void *arg)
+{
+   const char *query = (const char *) arg;
+   int         syntaxerrposition;
+
+   /*
+    * If there is a syntax error position, convert to internal syntax error;
+    * otherwise treat the query as an item of context stack
+    */
+   syntaxerrposition = geterrposition();
+   if (syntaxerrposition > 0)
+   {
+       errposition(0);
+       internalerrposition(syntaxerrposition);
+       internalerrquery(query);
+   }
+   else
+       errcontext("SQL query \"%s\"", query);
+}
+
 /*
  * _SPI_cursor_operation()
  *
@@ -1490,8 +1563,7 @@ _SPI_copy_plan(_SPI_plan *plan, int location)
        parentcxt = _SPI_current->procCxt;
    else if (location == _SPI_CPLAN_TOPCXT)
        parentcxt = TopMemoryContext;
-   else
-/* (this case not currently used) */
+   else                /* (this case not currently used) */
        parentcxt = CurrentMemoryContext;
 
    /*
@@ -1508,6 +1580,7 @@ _SPI_copy_plan(_SPI_plan *plan, int location)
    /* Copy the SPI plan into its own context */
    newplan = (_SPI_plan *) palloc(sizeof(_SPI_plan));
    newplan->plancxt = plancxt;
+   newplan->query = pstrdup(plan->query);
    newplan->qtlist = (List *) copyObject(plan->qtlist);
    newplan->ptlist = (List *) copyObject(plan->ptlist);
    newplan->nargs = plan->nargs;
index 1487aec453d76deca5227bcc8210904396b4a93b..c006cd49a1fe65f8643a0aa518dc6afda7eedb9b 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.165 2004/03/17 20:48:42 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.166 2004/03/21 22:29:11 tgl Exp $
  *
  * HISTORY
  *   AUTHOR            DATE            MAJOR EVENT
@@ -1917,7 +1917,7 @@ inline_function(Oid funcid, Oid result_type, List *args,
     * can finger the function that bad information came from.
     */
    sqlerrcontext.callback = sql_inline_error_callback;
-   sqlerrcontext.arg = funcform;
+   sqlerrcontext.arg = func_tuple;
    sqlerrcontext.previous = error_context_stack;
    error_context_stack = &sqlerrcontext;
 
@@ -2146,7 +2146,27 @@ substitute_actual_parameters_mutator(Node *node,
 static void
 sql_inline_error_callback(void *arg)
 {
-   Form_pg_proc funcform = (Form_pg_proc) arg;
+   HeapTuple func_tuple = (HeapTuple) arg;
+   Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
+   int         syntaxerrposition;
+
+   /* If it's a syntax error, convert to internal syntax error report */
+   syntaxerrposition = geterrposition();
+   if (syntaxerrposition > 0)
+   {
+       bool        isnull;
+       Datum       tmp;
+       char       *prosrc;
+
+       tmp = SysCacheGetAttr(PROCOID, func_tuple, Anum_pg_proc_prosrc,
+                             &isnull);
+       if (isnull)
+           elog(ERROR, "null prosrc");
+       prosrc = DatumGetCString(DirectFunctionCall1(textout, tmp));
+       errposition(0);
+       internalerrposition(syntaxerrposition);
+       internalerrquery(prosrc);
+   }
 
    errcontext("SQL function \"%s\" during inlining",
               NameStr(funcform->proname));
index 7695bc49e51389d4cbbc8ba8ab434892caf2992c..e6e8b00cda492a5601c8d0ef1865d3c70484db92 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/parser/parse_type.c,v 1.64 2003/11/29 19:51:52 pgsql Exp $
+ *   $PostgreSQL: pgsql/src/backend/parser/parse_type.c,v 1.65 2004/03/21 22:29:11 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -439,6 +439,12 @@ pts_error_callback(void *arg)
    const char *str = (const char *) arg;
 
    errcontext("invalid type name \"%s\"", str);
+   /*
+    * Currently we just suppress any syntax error position report,
+    * rather than transforming to an "internal query" error.  It's
+    * unlikely that a type name is complex enough to need positioning.
+    */
+   errposition(0);
 }
 
 /*
index 659eda80c85040f621b5793ad6ed70921c365bca..91442d49e6e096028511e19a90edcbf10c6d465e 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.395 2004/03/15 15:56:22 momjian Exp $
+ *   $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.396 2004/03/21 22:29:11 tgl Exp $
  *
  * NOTES
  *   this is the "main" module of the postgres backend and
@@ -2710,6 +2710,7 @@ PostgresMain(int argc, char *argv[], const char *username)
         */
        MemoryContextSwitchTo(TopMemoryContext);
        MemoryContextResetAndDeleteChildren(ErrorContext);
+       ActivePortal = NULL;
        PortalContext = NULL;
        QueryContext = NULL;
 
index 132afd3c6ebb869fc3262f81bdafdc675a01cdeb..c213182ad1f34891e29afcd33655bf8fd180988b 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/tcop/pquery.c,v 1.76 2004/03/18 23:26:17 momjian Exp $
+ *   $PostgreSQL: pgsql/src/backend/tcop/pquery.c,v 1.77 2004/03/21 22:29:11 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "utils/memutils.h"
 
 
+/*
+ * ActivePortal is the currently executing Portal (the most closely nested,
+ * if there are several).
+ */
+Portal ActivePortal = NULL;
+
+
 static uint32 RunFromStore(Portal portal, ScanDirection direction, long count,
             DestReceiver *dest);
 static long PortalRunSelect(Portal portal, bool forward, long count,
@@ -395,6 +402,7 @@ PortalRun(Portal portal, long count,
          char *completionTag)
 {
    bool        result;
+   Portal      saveActivePortal;
    MemoryContext savePortalContext;
    MemoryContext saveQueryContext;
    MemoryContext oldContext;
@@ -430,6 +438,8 @@ PortalRun(Portal portal, long count,
    /*
     * Set global portal context pointers.
     */
+   saveActivePortal = ActivePortal;
+   ActivePortal = portal;
    savePortalContext = PortalContext;
    PortalContext = PortalGetHeapMemory(portal);
    saveQueryContext = QueryContext;
@@ -505,6 +515,7 @@ PortalRun(Portal portal, long count,
    /* Mark portal not active */
    portal->portalActive = false;
 
+   ActivePortal = saveActivePortal;
    PortalContext = savePortalContext;
    QueryContext = saveQueryContext;
 
@@ -922,6 +933,7 @@ PortalRunFetch(Portal portal,
               DestReceiver *dest)
 {
    long        result;
+   Portal      saveActivePortal;
    MemoryContext savePortalContext;
    MemoryContext saveQueryContext;
    MemoryContext oldContext;
@@ -945,6 +957,8 @@ PortalRunFetch(Portal portal,
    /*
     * Set global portal context pointers.
     */
+   saveActivePortal = ActivePortal;
+   ActivePortal = portal;
    savePortalContext = PortalContext;
    PortalContext = PortalGetHeapMemory(portal);
    saveQueryContext = QueryContext;
@@ -969,6 +983,7 @@ PortalRunFetch(Portal portal,
    /* Mark portal not active */
    portal->portalActive = false;
 
+   ActivePortal = saveActivePortal;
    PortalContext = savePortalContext;
    QueryContext = saveQueryContext;
 
index e826e6d99eee7b341ef88942221ed5568e15c128..8569b6ce5cffb7385702725b46d3aa4ef7a11956 100644 (file)
@@ -37,7 +37,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/utils/error/elog.c,v 1.129 2004/03/19 02:23:59 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/utils/error/elog.c,v 1.130 2004/03/21 22:29:11 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -112,6 +112,8 @@ typedef struct ErrorData
    char       *hint;           /* hint message */
    char       *context;        /* context message */
    int         cursorpos;      /* cursor index into query string */
+   int         internalpos;    /* cursor index into internalquery */
+   char       *internalquery;  /* text of internally-generated query */
    int         saved_errno;    /* errno at entry */
 } ErrorData;
 
@@ -364,6 +366,8 @@ errfinish(int dummy,...)
        pfree(edata->hint);
    if (edata->context)
        pfree(edata->context);
+   if (edata->internalquery)
+       pfree(edata->internalquery);
 
    MemoryContextSwitchTo(oldcontext);
 
@@ -809,6 +813,83 @@ errposition(int cursorpos)
    return 0;                   /* return value does not matter */
 }
 
+/*
+ * internalerrposition --- add internal cursor position to the current error
+ */
+int
+internalerrposition(int cursorpos)
+{
+   ErrorData  *edata = &errordata[errordata_stack_depth];
+
+   /* we don't bother incrementing recursion_depth */
+   CHECK_STACK_DEPTH();
+
+   edata->internalpos = cursorpos;
+
+   return 0;                   /* return value does not matter */
+}
+
+/*
+ * internalerrquery --- add internal query text to the current error
+ *
+ * Can also pass NULL to drop the internal query text entry.  This case
+ * is intended for use in error callback subroutines that are editorializing
+ * on the layout of the error report.
+ */
+int
+internalerrquery(const char *query)
+{
+   ErrorData  *edata = &errordata[errordata_stack_depth];
+
+   /* we don't bother incrementing recursion_depth */
+   CHECK_STACK_DEPTH();
+
+   if (edata->internalquery)
+   {
+       pfree(edata->internalquery);
+       edata->internalquery = NULL;
+   }
+
+   if (query)
+       edata->internalquery = MemoryContextStrdup(ErrorContext, query);
+
+   return 0;                   /* return value does not matter */
+}
+
+/*
+ * geterrposition --- return the currently set error position (0 if none)
+ *
+ * This is only intended for use in error callback subroutines, since there
+ * is no other place outside elog.c where the concept is meaningful.
+ */
+int
+geterrposition(void)
+{
+   ErrorData  *edata = &errordata[errordata_stack_depth];
+
+   /* we don't bother incrementing recursion_depth */
+   CHECK_STACK_DEPTH();
+
+   return edata->cursorpos;
+}
+
+/*
+ * getinternalerrposition --- same for internal error position
+ *
+ * This is only intended for use in error callback subroutines, since there
+ * is no other place outside elog.c where the concept is meaningful.
+ */
+int
+getinternalerrposition(void)
+{
+   ErrorData  *edata = &errordata[errordata_stack_depth];
+
+   /* we don't bother incrementing recursion_depth */
+   CHECK_STACK_DEPTH();
+
+   return edata->internalpos;
+}
+
 
 /*
  * elog_finish --- finish up for old-style API
@@ -1192,7 +1273,11 @@ send_message_to_server_log(ErrorData *edata)
        append_with_tabs(&buf, gettext("missing error text"));
 
    if (edata->cursorpos > 0)
-       appendStringInfo(&buf, gettext(" at character %d"), edata->cursorpos);
+       appendStringInfo(&buf, gettext(" at character %d"),
+                        edata->cursorpos);
+   else if (edata->internalpos > 0)
+       appendStringInfo(&buf, gettext(" at character %d"),
+                        edata->internalpos);
 
    appendStringInfoChar(&buf, '\n');
 
@@ -1212,6 +1297,13 @@ send_message_to_server_log(ErrorData *edata)
            append_with_tabs(&buf, edata->hint);
            appendStringInfoChar(&buf, '\n');
        }
+       if (edata->internalquery)
+       {
+           log_line_prefix(&buf);
+           appendStringInfoString(&buf, gettext("QUERY:  "));
+           append_with_tabs(&buf, edata->internalquery);
+           appendStringInfoChar(&buf, '\n');
+       }
        if (edata->context)
        {
            log_line_prefix(&buf);
@@ -1365,6 +1457,19 @@ send_message_to_frontend(ErrorData *edata)
            pq_sendstring(&msgbuf, tbuf);
        }
 
+       if (edata->internalpos > 0)
+       {
+           snprintf(tbuf, sizeof(tbuf), "%d", edata->internalpos);
+           pq_sendbyte(&msgbuf, PG_DIAG_INTERNAL_POSITION);
+           pq_sendstring(&msgbuf, tbuf);
+       }
+
+       if (edata->internalquery)
+       {
+           pq_sendbyte(&msgbuf, PG_DIAG_INTERNAL_QUERY);
+           pq_sendstring(&msgbuf, edata->internalquery);
+       }
+
        if (edata->filename)
        {
            pq_sendbyte(&msgbuf, PG_DIAG_SOURCE_FILE);
@@ -1406,6 +1511,9 @@ send_message_to_frontend(ErrorData *edata)
        if (edata->cursorpos > 0)
            appendStringInfo(&buf, gettext(" at character %d"),
                             edata->cursorpos);
+       else if (edata->internalpos > 0)
+           appendStringInfo(&buf, gettext(" at character %d"),
+                            edata->internalpos);
 
        appendStringInfoChar(&buf, '\n');
 
index 6b03e35c6f6e22b6d24a29c6eeff27dd3b8b48d5..97b338efea71d4503e3b4c872e8e47cd85730441 100644 (file)
@@ -3,7 +3,7 @@
  *
  * Copyright (c) 2000-2003, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/bin/psql/command.c,v 1.113 2004/02/19 19:40:08 tgl Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/command.c,v 1.114 2004/03/21 22:29:11 tgl Exp $
  */
 #include "postgres_fe.h"
 #include "command.h"
@@ -1042,18 +1042,20 @@ SyncVerbosityVariable(void)
                           "default", "terse", "verbose", NULL))
    {
        case 1:         /* default */
-           PQsetErrorVerbosity(pset.db, PQERRORS_DEFAULT);
+           pset.verbosity = PQERRORS_DEFAULT;
            break;
        case 2:         /* terse */
-           PQsetErrorVerbosity(pset.db, PQERRORS_TERSE);
+           pset.verbosity = PQERRORS_TERSE;
            break;
        case 3:         /* verbose */
-           PQsetErrorVerbosity(pset.db, PQERRORS_VERBOSE);
+           pset.verbosity = PQERRORS_VERBOSE;
            break;
        default:                /* not set or unrecognized value */
-           PQsetErrorVerbosity(pset.db, PQERRORS_DEFAULT);
+           pset.verbosity = PQERRORS_DEFAULT;
            break;
    }
+
+   PQsetErrorVerbosity(pset.db, pset.verbosity);
 }
 
 
index 2a6be545caf325c89d29481fa533bbcf74638027..3b1a1228c1e804752c72b99a00f419dd61586838 100644 (file)
@@ -3,7 +3,7 @@
  *
  * Copyright (c) 2000-2003, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/bin/psql/common.c,v 1.84 2004/03/15 10:41:26 ishii Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/common.c,v 1.85 2004/03/21 22:29:11 tgl Exp $
  */
 #include "postgres_fe.h"
 #include "common.h"
@@ -364,18 +364,19 @@ ReportSyntaxErrorPosition(const PGresult *result, const char *query)
    bool beg_trunc, end_trunc;
    PQExpBufferData msg;
 
-   if (query == NULL)
-       return;                 /* nothing to do */
+   if (pset.verbosity == PQERRORS_TERSE)
+       return;
+
    sp = PQresultErrorField(result, PG_DIAG_STATEMENT_POSITION);
    if (sp == NULL)
-       return;                 /* no syntax error location */
-   /*
-    * We punt if the report contains any CONTEXT.  This typically means that
-    * the syntax error is from inside a function, and the cursor position
-    * is not relevant to the original query string.
-    */
-   if (PQresultErrorField(result, PG_DIAG_CONTEXT) != NULL)
-       return;
+   {
+       sp = PQresultErrorField(result, PG_DIAG_INTERNAL_POSITION);
+       if (sp == NULL)
+           return;             /* no syntax error */
+       query = PQresultErrorField(result, PG_DIAG_INTERNAL_QUERY);
+   }
+   if (query == NULL)
+       return;                 /* nothing to reference location to */
 
    if (sscanf(sp, "%d", &loc) != 1)
    {
index 9c16402ac4c402c55b80ca23598c473b3a731762..e28383b16be7a57e14f52c8f59bd5efd258ad517 100644 (file)
@@ -3,7 +3,7 @@
  *
  * Copyright (c) 2000-2003, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/bin/psql/settings.h,v 1.16 2003/11/29 19:52:07 pgsql Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/settings.h,v 1.17 2004/03/21 22:29:11 tgl Exp $
  */
 #ifndef SETTINGS_H
 #define SETTINGS_H
@@ -36,8 +36,6 @@ typedef struct _psqlSettings
 
    bool        notty;          /* stdin or stdout is not a tty (as
                                 * determined on startup) */
-   bool        useReadline;    /* use libreadline routines */
-   bool        useHistory;
    bool        getPassword;    /* prompt the user for a username and
                                 * password */
    FILE       *cur_cmd_source; /* describe the status of the current main
@@ -49,6 +47,8 @@ typedef struct _psqlSettings
    unsigned    lineno;         /* also for error reporting */
 
    bool        timing;         /* enable timing of all queries */
+
+   PGVerbosity verbosity;      /* current error verbosity level */
 } PsqlSettings;
 
 extern PsqlSettings pset;
index 400f7d45786c615860a5e4e4e75979d530c6cdf8..03f4e97d918ae931eb9c7f452a083c3e1014bfa4 100644 (file)
@@ -3,7 +3,7 @@
  *
  * Copyright (c) 2000-2003, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/bin/psql/startup.c,v 1.85 2004/02/19 19:40:09 tgl Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/startup.c,v 1.86 2004/03/21 22:29:11 tgl Exp $
  */
 #include "postgres_fe.h"
 
@@ -143,6 +143,7 @@ main(int argc, char *argv[])
    /* Default values for variables that are used in noninteractive cases */
    SetVariableBool(pset.vars, "AUTOCOMMIT");
    SetVariable(pset.vars, "VERBOSITY", "default");
+   pset.verbosity = PQERRORS_DEFAULT;
 
    pset.notty = (!isatty(fileno(stdin)) || !isatty(fileno(stdout)));
 
index f3b70f31ec754d5bb3dfbffdca64363b5f663f1b..9112d9df9da13fb0f30780790fb01260ef9ad8a3 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.320 2004/02/14 20:16:17 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.321 2004/03/21 22:29:11 tgl Exp $
  *
  * NOTES
  *   The script catalog/genbki.sh reads this file and generates .bki
@@ -3533,4 +3533,6 @@ extern Oid ProcedureCreate(const char *procedureName,
 extern void check_sql_fn_retval(Oid rettype, char fn_typtype,
                    List *queryTreeList);
 
+extern bool function_parse_error_transpose(const char *prosrc);
+
 #endif   /* PG_PROC_H */
index 6c5c19994d5e4c4ab492ff3474464acbd3f5c578..dcafa1ccb9a060074db8f11a6f28913688e540c6 100644 (file)
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/executor/spi_priv.h,v 1.17 2003/11/29 22:41:01 pgsql Exp $
+ * $PostgreSQL: pgsql/src/include/executor/spi_priv.h,v 1.18 2004/03/21 22:29:11 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -27,11 +27,10 @@ typedef struct
 
 typedef struct
 {
-   /*
-    * context containing _SPI_plan itself as well as subsidiary
-    * structures
-    */
+   /* Context containing _SPI_plan itself as well as subsidiary data */
    MemoryContext plancxt;
+   /* Original query string (used for error reporting) */
+   const char *query;
    /* List of List of querytrees; one sublist per original parsetree */
    List       *qtlist;
    /* List of plan trees --- length == # of querytrees, but flat list */
index 235895c61f10cd79093fad094c0fb639761e95bc..8f235f698d19ac5d701ac837bb4ea173ea79c4d1 100644 (file)
@@ -15,7 +15,7 @@
  * use header files that are otherwise internal to Postgres to interface
  * with the backend.
  *
- * $PostgreSQL: pgsql/src/include/postgres_ext.h,v 1.14 2003/11/29 22:40:53 pgsql Exp $
+ * $PostgreSQL: pgsql/src/include/postgres_ext.h,v 1.15 2004/03/21 22:29:11 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -59,6 +59,8 @@ typedef unsigned int Oid;
 #define PG_DIAG_MESSAGE_DETAIL 'D'
 #define PG_DIAG_MESSAGE_HINT   'H'
 #define PG_DIAG_STATEMENT_POSITION 'P'
+#define PG_DIAG_INTERNAL_POSITION 'p'
+#define PG_DIAG_INTERNAL_QUERY 'q'
 #define PG_DIAG_CONTEXT            'W'
 #define PG_DIAG_SOURCE_FILE        'F'
 #define PG_DIAG_SOURCE_LINE        'L'
index baba06438ff1536372992255c65845cf642eb10f..e2efd3d5e309197a649a8c35032b6d02843d3e64 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/tcop/pquery.h,v 1.30 2003/11/29 22:41:14 pgsql Exp $
+ * $PostgreSQL: pgsql/src/include/tcop/pquery.h,v 1.31 2004/03/21 22:29:11 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -17,6 +17,9 @@
 #include "utils/portal.h"
 
 
+extern DLLIMPORT Portal ActivePortal;
+
+
 extern void ProcessQuery(Query *parsetree,
             Plan *plan,
             ParamListInfo params,
index f71881547eb6d6762d7df4a9061aad5bce1371b2..24db6696e66daa7eb5ba34eec7614255b54189d1 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/utils/elog.h,v 1.66 2004/03/15 15:56:28 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/utils/elog.h,v 1.67 2004/03/21 22:29:11 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -132,6 +132,12 @@ __attribute__((format(printf, 1, 2)));
 extern int errfunction(const char *funcname);
 extern int errposition(int cursorpos);
 
+extern int internalerrposition(int cursorpos);
+extern int internalerrquery(const char *query);
+
+extern int geterrposition(void);
+extern int getinternalerrposition(void);
+
 
 /*----------
  * Old-style error reporting API: to be used in this way:
index cfdd97cf7378f9632c5918bde4570c686b42f1a3..1f9621a9b17dd61a018e01dc3ed780a46091caf6 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/interfaces/libpq/fe-protocol3.c,v 1.11 2003/12/28 17:43:57 tgl Exp $
+ *   $PostgreSQL: pgsql/src/interfaces/libpq/fe-protocol3.c,v 1.12 2004/03/21 22:29:11 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -640,6 +640,16 @@ pqGetErrorNotice3(PGconn *conn, bool isError)
        /* translator: %s represents a digit string */
        appendPQExpBuffer(&workBuf, libpq_gettext(" at character %s"), val);
    }
+   else
+   {
+       val = PQresultErrorField(res, PG_DIAG_INTERNAL_POSITION);
+       if (val)
+       {
+           /* translator: %s represents a digit string */
+           appendPQExpBuffer(&workBuf, libpq_gettext(" at character %s"),
+                             val);
+       }
+   }
    appendPQExpBufferChar(&workBuf, '\n');
    if (conn->verbosity != PQERRORS_TERSE)
    {
@@ -649,6 +659,9 @@ pqGetErrorNotice3(PGconn *conn, bool isError)
        val = PQresultErrorField(res, PG_DIAG_MESSAGE_HINT);
        if (val)
            appendPQExpBuffer(&workBuf, libpq_gettext("HINT:  %s\n"), val);
+       val = PQresultErrorField(res, PG_DIAG_INTERNAL_QUERY);
+       if (val)
+           appendPQExpBuffer(&workBuf, libpq_gettext("QUERY:  %s\n"), val);
        val = PQresultErrorField(res, PG_DIAG_CONTEXT);
        if (val)
            appendPQExpBuffer(&workBuf, libpq_gettext("CONTEXT:  %s\n"), val);
index 46e9ca925b72eaaba30fa85b5c37c475e8e2cac9..8c930de9c237ec01b3f4a015b455cc11a857e902 100644 (file)
@@ -3,7 +3,7 @@
  *           procedural language
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.74 2004/03/19 18:58:07 tgl Exp $
+ *   $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.75 2004/03/21 22:29:11 tgl Exp $
  *
  *   This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -270,7 +270,6 @@ do_compile(FunctionCallInfo fcinfo,
        elog(ERROR, "null prosrc");
    proc_source = DatumGetCString(DirectFunctionCall1(textout, prosrcdatum));
    plpgsql_scanner_init(proc_source, functype);
-   pfree(proc_source);
 
    plpgsql_error_funcname = pstrdup(NameStr(procStruct->proname));
    plpgsql_error_lineno = 0;
@@ -279,7 +278,7 @@ do_compile(FunctionCallInfo fcinfo,
     * Setup error traceback support for ereport()
     */
    plerrcontext.callback = plpgsql_compile_error_callback;
-   plerrcontext.arg = NULL;
+   plerrcontext.arg = forValidator ? proc_source : (char *) NULL;
    plerrcontext.previous = error_context_stack;
    error_context_stack = &plerrcontext;
 
@@ -714,6 +713,7 @@ do_compile(FunctionCallInfo fcinfo,
        elog(ERROR, "plpgsql parser returned %d", parse_rc);
 
    plpgsql_scanner_finish();
+   pfree(proc_source);
 
    /*
     * If that was successful, complete the functions info.
@@ -749,10 +749,26 @@ do_compile(FunctionCallInfo fcinfo,
 
 /*
  * error context callback to let us supply a call-stack traceback
+ *
+ * If we are validating, the function source is passed as argument.
  */
 static void
 plpgsql_compile_error_callback(void *arg)
 {
+   if (arg)
+   {
+       /*
+        * Try to convert syntax error position to reference text of
+        * original CREATE FUNCTION command.
+        */
+       if (function_parse_error_transpose((const char *) arg))
+           return;
+       /*
+        * Done if a syntax error position was reported; otherwise we
+        * have to fall back to a "near line N" report.
+        */
+   }
+
    if (plpgsql_error_funcname)
        errcontext("compile of PL/pgSQL function \"%s\" near line %d",
                   plpgsql_error_funcname, plpgsql_error_lineno);
index 3e6ee8fd187be273d1b497f7f130645cf617b3d5..077efe6671e440dad4a4b2fadc07e65c75b27099 100644 (file)
@@ -4,7 +4,7 @@
  *           procedural language
  *
  * IDENTIFICATION
- *    $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.33 2004/03/19 18:58:07 tgl Exp $
+ *    $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.34 2004/03/21 22:29:11 tgl Exp $
  *
  *    This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -51,6 +51,8 @@
 static YY_BUFFER_STATE scanbufhandle;
 static char *scanbuf;
 
+static const char *scanstr;        /* original input string */
+
 static int scanner_functype;
 static int scanner_typereported;
 static int pushback_token;
@@ -431,7 +433,8 @@ plpgsql_yyerror(const char *message)
                (errcode(ERRCODE_SYNTAX_ERROR),
                 /* translator: %s is typically "syntax error" */
                 errmsg("%s at end of input", message),
-                errposition(cursorpos)));
+                internalerrposition(cursorpos),
+                internalerrquery(scanstr)));
    }
    else
    {
@@ -439,7 +442,8 @@ plpgsql_yyerror(const char *message)
                (errcode(ERRCODE_SYNTAX_ERROR),
                 /* translator: first %s is typically "syntax error" */
                 errmsg("%s at or near \"%s\"", message, loc),
-                errposition(cursorpos)));
+                internalerrposition(cursorpos),
+                internalerrquery(scanstr)));
    }
 }
 
@@ -467,6 +471,10 @@ plpgsql_scanner_lineno(void)
 
 /*
  * Called before any actual parsing is done
+ *
+ * Note: the passed "str" must remain valid until plpgsql_scanner_finish().
+ * Although it is not fed directly to flex, we need the original string
+ * to cite in error messages.
  */
 void
 plpgsql_scanner_init(const char *str, int functype)
@@ -490,7 +498,9 @@ plpgsql_scanner_init(const char *str, int functype)
    scanbufhandle = yy_scan_buffer(scanbuf, slen + 2);
 
    /* Other setup */
-    scanner_functype     = functype;
+   scanstr = str;
+
+    scanner_functype = functype;
     scanner_typereported = 0;
 
    have_pushback_token = false;
index c44a81a75f872cf67b7e6f3db38e0644a85c1abd..870af40c069a8079da6ef77de0dcf11deada9733 100644 (file)
@@ -85,12 +85,14 @@ DETAIL:  Trigger "check_fkeys_pkey2_exist" found tuple referencing non-existent
 delete from pkeys where pkey1 = 30 and pkey2 = '3';
 NOTICE:  check_pkeys_fkey_cascade: 1 tuple(s) of fkeys are deleted
 ERROR:  "check_fkeys2_fkey_restrict": tuple is referenced in "fkeys"
+CONTEXT:  SQL query "delete from fkeys2 where fkey21 = $1 and fkey22 = $2 "
 delete from pkeys where pkey1 = 40 and pkey2 = '4';
 NOTICE:  check_pkeys_fkey_cascade: 1 tuple(s) of fkeys are deleted
 NOTICE:  check_pkeys_fkey_cascade: 1 tuple(s) of fkeys2 are deleted
 update pkeys set pkey1 = 7, pkey2 = '70' where pkey1 = 50 and pkey2 = '5';
 NOTICE:  check_pkeys_fkey_cascade: 1 tuple(s) of fkeys are deleted
 ERROR:  "check_fkeys2_fkey_restrict": tuple is referenced in "fkeys"
+CONTEXT:  SQL query "delete from fkeys2 where fkey21 = $1 and fkey22 = $2 "
 update pkeys set pkey1 = 7, pkey2 = '70' where pkey1 = 10 and pkey2 = '1';
 NOTICE:  check_pkeys_fkey_cascade: 1 tuple(s) of fkeys are deleted
 NOTICE:  check_pkeys_fkey_cascade: 1 tuple(s) of fkeys2 are deleted
index e3886febaa9f0fd63cdf1b43b8b456387ba7f640..62fcb01d0938f7ea9865d6182f6a24bad3d7648f 100644 (file)
@@ -55,8 +55,9 @@ DETAIL:  Actual return type is "unknown".
 CONTEXT:  SQL function "test1"
 CREATE FUNCTION test1 (int) RETURNS int LANGUAGE sql
     AS 'not even SQL';
-ERROR:  syntax error at or near "not" at character 1
-CONTEXT:  SQL function "test1"
+ERROR:  syntax error at or near "not" at character 62
+LINE 2:     AS 'not even SQL';
+                ^
 CREATE FUNCTION test1 (int) RETURNS int LANGUAGE sql
     AS 'SELECT 1, 2, 3;';
 ERROR:  return type mismatch in function declared to return integer