From f938c2b91bebb7f436a3615cf86347d7261f71e8 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Sun, 21 Mar 2004 22:29:11 +0000 Subject: [PATCH] Revise syntax-error reporting behavior to give pleasant results for errors in internally-generated queries, such as those submitted by plpgsql functions. Per recent discussions with Fabien Coelho. --- doc/src/sgml/libpq.sgml | 32 ++- doc/src/sgml/protocol.sgml | 34 ++- src/backend/catalog/pg_proc.c | 215 ++++++++++++++++-- src/backend/commands/portalcmds.c | 6 +- src/backend/executor/functions.c | 41 +++- src/backend/executor/spi.c | 165 ++++++++++---- src/backend/optimizer/util/clauses.c | 26 ++- src/backend/parser/parse_type.c | 8 +- src/backend/tcop/postgres.c | 3 +- src/backend/tcop/pquery.c | 17 +- src/backend/utils/error/elog.c | 112 ++++++++- src/bin/psql/command.c | 12 +- src/bin/psql/common.c | 23 +- src/bin/psql/settings.h | 6 +- src/bin/psql/startup.c | 3 +- src/include/catalog/pg_proc.h | 4 +- src/include/executor/spi_priv.h | 9 +- src/include/postgres_ext.h | 4 +- src/include/tcop/pquery.h | 5 +- src/include/utils/elog.h | 8 +- src/interfaces/libpq/fe-protocol3.c | 15 +- src/pl/plpgsql/src/pl_comp.c | 22 +- src/pl/plpgsql/src/scan.l | 18 +- src/test/regress/expected/triggers.out | 2 + .../regress/output/create_function_1.source | 5 +- 25 files changed, 671 insertions(+), 124 deletions(-) diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index 82b8099a4a..ee30b0d7e5 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -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. diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml index a4baff82a4..de3b72738d 100644 --- a/doc/src/sgml/protocol.sgml +++ b/doc/src/sgml/protocol.sgml @@ -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. diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c index 3ea2af44d3..6fe64eadd0 100644 --- a/src/backend/catalog/pg_proc.c +++ b/src/backend/catalog/pg_proc.c @@ -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 $ * *------------------------------------------------------------------------- */ @@ -23,9 +23,11 @@ #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; /* - * 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... + * Nothing to do unless we are dealing with a syntax error that has + * a cursor position. * - * In the meantime, put in a CONTEXT entry that can cue clients - * not to trust the syntax error position completely. + * Some PLs may prefer to report the error position as an internal + * error to begin with, so check that too. */ - errcontext("SQL function \"%s\"", - NameStr(proc->proname)); + 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) +{ + /* + * 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. + * + * We do the comparison a character at a time, not a byte at a time, + * so that we can do the correct cursorpos math. + */ + 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; } diff --git a/src/backend/commands/portalcmds.c b/src/backend/commands/portalcmds.c index 34748fa77e..855c9391c1 100644 --- a/src/backend/commands/portalcmds.c +++ b/src/backend/commands/portalcmds.c @@ -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; diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c index 6ffc8875f1..8dec6131fb 100644 --- a/src/backend/executor/functions.c +++ b/src/backend/executor/functions.c @@ -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); } diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c index fb44d9d56f..1fb60fff02 100644 --- a/src/backend/executor/spi.c +++ b/src/backend/executor/spi.c @@ -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; - } + _SPI_plan *spiplan = (_SPI_plan *) plan; + List *qtlist; - 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; + 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; diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 1487aec453..c006cd49a1 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -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)); diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c index 7695bc49e5..e6e8b00cda 100644 --- a/src/backend/parser/parse_type.c +++ b/src/backend/parser/parse_type.c @@ -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); } /* diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 659eda80c8..91442d49e6 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -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; diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c index 132afd3c6e..c213182ad1 100644 --- a/src/backend/tcop/pquery.c +++ b/src/backend/tcop/pquery.c @@ -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 $ * *------------------------------------------------------------------------- */ @@ -23,6 +23,13 @@ #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; diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c index e826e6d99e..8569b6ce5c 100644 --- a/src/backend/utils/error/elog.c +++ b/src/backend/utils/error/elog.c @@ -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'); diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index 6b03e35c6f..97b338efea 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -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); } diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c index 2a6be545ca..3b1a1228c1 100644 --- a/src/bin/psql/common.c +++ b/src/bin/psql/common.c @@ -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) { diff --git a/src/bin/psql/settings.h b/src/bin/psql/settings.h index 9c16402ac4..e28383b16b 100644 --- a/src/bin/psql/settings.h +++ b/src/bin/psql/settings.h @@ -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; diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c index 400f7d4578..03f4e97d91 100644 --- a/src/bin/psql/startup.c +++ b/src/bin/psql/startup.c @@ -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))); diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index f3b70f31ec..9112d9df9d 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -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 */ diff --git a/src/include/executor/spi_priv.h b/src/include/executor/spi_priv.h index 6c5c19994d..dcafa1ccb9 100644 --- a/src/include/executor/spi_priv.h +++ b/src/include/executor/spi_priv.h @@ -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 */ diff --git a/src/include/postgres_ext.h b/src/include/postgres_ext.h index 235895c61f..8f235f698d 100644 --- a/src/include/postgres_ext.h +++ b/src/include/postgres_ext.h @@ -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' diff --git a/src/include/tcop/pquery.h b/src/include/tcop/pquery.h index baba06438f..e2efd3d5e3 100644 --- a/src/include/tcop/pquery.h +++ b/src/include/tcop/pquery.h @@ -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, diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h index f71881547e..24db6696e6 100644 --- a/src/include/utils/elog.h +++ b/src/include/utils/elog.h @@ -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: diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c index cfdd97cf73..1f9621a9b1 100644 --- a/src/interfaces/libpq/fe-protocol3.c +++ b/src/interfaces/libpq/fe-protocol3.c @@ -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); diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c index 46e9ca925b..8c930de9c2 100644 --- a/src/pl/plpgsql/src/pl_comp.c +++ b/src/pl/plpgsql/src/pl_comp.c @@ -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); diff --git a/src/pl/plpgsql/src/scan.l b/src/pl/plpgsql/src/scan.l index 3e6ee8fd18..077efe6671 100644 --- a/src/pl/plpgsql/src/scan.l +++ b/src/pl/plpgsql/src/scan.l @@ -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; diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out index c44a81a75f..870af40c06 100644 --- a/src/test/regress/expected/triggers.out +++ b/src/test/regress/expected/triggers.out @@ -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 diff --git a/src/test/regress/output/create_function_1.source b/src/test/regress/output/create_function_1.source index e3886febaa..62fcb01d09 100644 --- a/src/test/regress/output/create_function_1.source +++ b/src/test/regress/output/create_function_1.source @@ -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