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.
This commit is contained in:
parent
bee3b2a0a0
commit
f938c2b91b
|
@ -1,5 +1,5 @@
|
|||
<!--
|
||||
$PostgreSQL: pgsql/doc/src/sgml/libpq.sgml,v 1.147 2004/03/11 02:39:10 momjian Exp $
|
||||
$PostgreSQL: pgsql/doc/src/sgml/libpq.sgml,v 1.148 2004/03/21 22:29:10 tgl Exp $
|
||||
-->
|
||||
|
||||
<chapter id="libpq">
|
||||
|
@ -1390,13 +1390,37 @@ bytes.
|
|||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><symbol>PG_DIAG_INTERNAL_POSITION</></term>
|
||||
<listitem>
|
||||
<para>
|
||||
This is defined the same as the <symbol>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 <symbol>PG_DIAG_INTERNAL_QUERY</> field will always appear when this field
|
||||
appears.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><symbol>PG_DIAG_INTERNAL_QUERY</></term>
|
||||
<listitem>
|
||||
<para>
|
||||
The text of a failed internally-generated command.
|
||||
This could be, for example, a SQL query issued by a PL/pgSQL function.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><symbol>PG_DIAG_CONTEXT</></term>
|
||||
<listitem>
|
||||
<para>
|
||||
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.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<!-- $PostgreSQL: pgsql/doc/src/sgml/protocol.sgml,v 1.50 2004/03/09 16:57:46 neilc Exp $ -->
|
||||
<!-- $PostgreSQL: pgsql/doc/src/sgml/protocol.sgml,v 1.51 2004/03/21 22:29:10 tgl Exp $ -->
|
||||
|
||||
<chapter id="protocol">
|
||||
<title>Frontend/Backend Protocol</title>
|
||||
|
@ -3902,6 +3902,32 @@ message.
|
|||
</ListItem>
|
||||
</VarListEntry>
|
||||
|
||||
<VarListEntry>
|
||||
<Term>
|
||||
<literal>p</>
|
||||
</Term>
|
||||
<ListItem>
|
||||
<Para>
|
||||
Internal position: this is defined the same as the <literal>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 <literal>q</> field will always appear when this field appears.
|
||||
</Para>
|
||||
</ListItem>
|
||||
</VarListEntry>
|
||||
|
||||
<VarListEntry>
|
||||
<Term>
|
||||
<literal>q</>
|
||||
</Term>
|
||||
<ListItem>
|
||||
<Para>
|
||||
Internal query: the text of a failed internally-generated command.
|
||||
This could be, for example, a SQL query issued by a PL/pgSQL function.
|
||||
</Para>
|
||||
</ListItem>
|
||||
</VarListEntry>
|
||||
|
||||
<VarListEntry>
|
||||
<Term>
|
||||
<literal>W</>
|
||||
|
@ -3909,9 +3935,9 @@ message.
|
|||
<ListItem>
|
||||
<Para>
|
||||
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.
|
||||
</Para>
|
||||
</ListItem>
|
||||
</VarListEntry>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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');
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)));
|
||||
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue