Skip to content

Commit f938c2b

Browse files
committed
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.
1 parent bee3b2a commit f938c2b

File tree

25 files changed

+672
-125
lines changed

25 files changed

+672
-125
lines changed

doc/src/sgml/libpq.sgml

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<!--
2-
$PostgreSQL: pgsql/doc/src/sgml/libpq.sgml,v 1.147 2004/03/11 02:39:10 momjian Exp $
2+
$PostgreSQL: pgsql/doc/src/sgml/libpq.sgml,v 1.148 2004/03/21 22:29:10 tgl Exp $
33
-->
44

55
<chapter id="libpq">
@@ -1390,13 +1390,37 @@ bytes.
13901390
</listitem>
13911391
</varlistentry>
13921392

1393+
<varlistentry>
1394+
<term><symbol>PG_DIAG_INTERNAL_POSITION</></term>
1395+
<listitem>
1396+
<para>
1397+
This is defined the same as the <symbol>PG_DIAG_STATEMENT_POSITION</>
1398+
field, but it is used when the cursor position refers to an internally
1399+
generated command rather than the one submitted by the client.
1400+
The <symbol>PG_DIAG_INTERNAL_QUERY</> field will always appear when this field
1401+
appears.
1402+
</para>
1403+
</listitem>
1404+
</varlistentry>
1405+
1406+
<varlistentry>
1407+
<term><symbol>PG_DIAG_INTERNAL_QUERY</></term>
1408+
<listitem>
1409+
<para>
1410+
The text of a failed internally-generated command.
1411+
This could be, for example, a SQL query issued by a PL/pgSQL function.
1412+
</para>
1413+
</listitem>
1414+
</varlistentry>
1415+
13931416
<varlistentry>
13941417
<term><symbol>PG_DIAG_CONTEXT</></term>
13951418
<listitem>
13961419
<para>
1397-
An indication of the context in which the error occurred. Presently
1398-
this includes a call stack traceback of active PL functions. The
1399-
trace is one entry per line, most recent first.
1420+
An indication of the context in which the error occurred.
1421+
Presently this includes a call stack traceback of active
1422+
procedural language functions and internally-generated queries.
1423+
The trace is one entry per line, most recent first.
14001424
</para>
14011425
</listitem>
14021426
</varlistentry>

doc/src/sgml/protocol.sgml

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<!-- $PostgreSQL: pgsql/doc/src/sgml/protocol.sgml,v 1.50 2004/03/09 16:57:46 neilc Exp $ -->
1+
<!-- $PostgreSQL: pgsql/doc/src/sgml/protocol.sgml,v 1.51 2004/03/21 22:29:10 tgl Exp $ -->
22

33
<chapter id="protocol">
44
<title>Frontend/Backend Protocol</title>
@@ -3902,16 +3902,42 @@ message.
39023902
</ListItem>
39033903
</VarListEntry>
39043904

3905+
<VarListEntry>
3906+
<Term>
3907+
<literal>p</>
3908+
</Term>
3909+
<ListItem>
3910+
<Para>
3911+
Internal position: this is defined the same as the <literal>P</>
3912+
field, but it is used when the cursor position refers to an internally
3913+
generated command rather than the one submitted by the client.
3914+
The <literal>q</> field will always appear when this field appears.
3915+
</Para>
3916+
</ListItem>
3917+
</VarListEntry>
3918+
3919+
<VarListEntry>
3920+
<Term>
3921+
<literal>q</>
3922+
</Term>
3923+
<ListItem>
3924+
<Para>
3925+
Internal query: the text of a failed internally-generated command.
3926+
This could be, for example, a SQL query issued by a PL/pgSQL function.
3927+
</Para>
3928+
</ListItem>
3929+
</VarListEntry>
3930+
39053931
<VarListEntry>
39063932
<Term>
39073933
<literal>W</>
39083934
</Term>
39093935
<ListItem>
39103936
<Para>
39113937
Where: an indication of the context in which the error occurred.
3912-
Presently this includes a call stack traceback of active
3913-
procedural language functions. The trace is one entry per line,
3914-
most recent first.
3938+
Presently this includes a call stack traceback of active
3939+
procedural language functions and internally-generated queries.
3940+
The trace is one entry per line, most recent first.
39153941
</Para>
39163942
</ListItem>
39173943
</VarListEntry>

src/backend/catalog/pg_proc.c

Lines changed: 200 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*
99
*
1010
* IDENTIFICATION
11-
* $PostgreSQL: pgsql/src/backend/catalog/pg_proc.c,v 1.112 2004/03/14 01:58:41 tgl Exp $
11+
* $PostgreSQL: pgsql/src/backend/catalog/pg_proc.c,v 1.113 2004/03/21 22:29:10 tgl Exp $
1212
*
1313
*-------------------------------------------------------------------------
1414
*/
@@ -23,9 +23,11 @@
2323
#include "executor/executor.h"
2424
#include "fmgr.h"
2525
#include "miscadmin.h"
26+
#include "mb/pg_wchar.h"
2627
#include "parser/parse_coerce.h"
2728
#include "parser/parse_expr.h"
2829
#include "parser/parse_type.h"
30+
#include "tcop/pquery.h"
2931
#include "tcop/tcopprot.h"
3032
#include "utils/acl.h"
3133
#include "utils/builtins.h"
@@ -45,6 +47,10 @@ Datum fmgr_sql_validator(PG_FUNCTION_ARGS);
4547
static Datum create_parameternames_array(int parameterCount,
4648
const char *parameterNames[]);
4749
static void sql_function_parse_error_callback(void *arg);
50+
static int match_prosrc_to_query(const char *prosrc, const char *queryText,
51+
int cursorpos);
52+
static bool match_prosrc_to_literal(const char *prosrc, const char *literal,
53+
int cursorpos, int *newcursorpos);
4854

4955

5056
/* ----------------------------------------------------------------
@@ -763,12 +769,10 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
763769
prosrc = DatumGetCString(DirectFunctionCall1(textout, tmp));
764770

765771
/*
766-
* Setup error traceback support for ereport(). This is mostly
767-
* so we can add context info that shows that a syntax-error
768-
* location is inside the function body, not out in CREATE FUNCTION.
772+
* Setup error traceback support for ereport().
769773
*/
770774
sqlerrcontext.callback = sql_function_parse_error_callback;
771-
sqlerrcontext.arg = proc;
775+
sqlerrcontext.arg = tuple;
772776
sqlerrcontext.previous = error_context_stack;
773777
error_context_stack = &sqlerrcontext;
774778

@@ -800,22 +804,203 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
800804
}
801805

802806
/*
803-
* error context callback to let us supply a context marker
807+
* Error context callback for handling errors in SQL function definitions
804808
*/
805809
static void
806810
sql_function_parse_error_callback(void *arg)
807811
{
808-
Form_pg_proc proc = (Form_pg_proc) arg;
812+
HeapTuple tuple = (HeapTuple) arg;
813+
Form_pg_proc proc = (Form_pg_proc) GETSTRUCT(tuple);
814+
bool isnull;
815+
Datum tmp;
816+
char *prosrc;
817+
818+
/* See if it's a syntax error; if so, transpose to CREATE FUNCTION */
819+
tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosrc, &isnull);
820+
if (isnull)
821+
elog(ERROR, "null prosrc");
822+
prosrc = DatumGetCString(DirectFunctionCall1(textout, tmp));
823+
824+
if (!function_parse_error_transpose(prosrc))
825+
{
826+
/* If it's not a syntax error, push info onto context stack */
827+
errcontext("SQL function \"%s\"", NameStr(proc->proname));
828+
}
829+
830+
pfree(prosrc);
831+
}
832+
833+
/*
834+
* Adjust a syntax error occurring inside the function body of a CREATE
835+
* FUNCTION command. This can be used by any function validator, not only
836+
* for SQL-language functions. It is assumed that the syntax error position
837+
* is initially relative to the function body string (as passed in). If
838+
* possible, we adjust the position to reference the original CREATE command;
839+
* if we can't manage that, we set up an "internal query" syntax error instead.
840+
*
841+
* Returns true if a syntax error was processed, false if not.
842+
*/
843+
bool
844+
function_parse_error_transpose(const char *prosrc)
845+
{
846+
int origerrposition;
847+
int newerrposition;
848+
const char *queryText;
849+
850+
/*
851+
* Nothing to do unless we are dealing with a syntax error that has
852+
* a cursor position.
853+
*
854+
* Some PLs may prefer to report the error position as an internal
855+
* error to begin with, so check that too.
856+
*/
857+
origerrposition = geterrposition();
858+
if (origerrposition <= 0)
859+
{
860+
origerrposition = getinternalerrposition();
861+
if (origerrposition <= 0)
862+
return false;
863+
}
864+
865+
/* We can get the original query text from the active portal (hack...) */
866+
Assert(ActivePortal && ActivePortal->portalActive);
867+
queryText = ActivePortal->sourceText;
868+
869+
/* Try to locate the prosrc in the original text */
870+
newerrposition = match_prosrc_to_query(prosrc, queryText, origerrposition);
871+
872+
if (newerrposition > 0)
873+
{
874+
/* Successful, so fix error position to reference original query */
875+
errposition(newerrposition);
876+
/* Get rid of any report of the error as an "internal query" */
877+
internalerrposition(0);
878+
internalerrquery(NULL);
879+
}
880+
else
881+
{
882+
/*
883+
* If unsuccessful, convert the position to an internal position
884+
* marker and give the function text as the internal query.
885+
*/
886+
errposition(0);
887+
internalerrposition(origerrposition);
888+
internalerrquery(prosrc);
889+
}
890+
891+
return true;
892+
}
809893

894+
/*
895+
* Try to locate the string literal containing the function body in the
896+
* given text of the CREATE FUNCTION command. If successful, return the
897+
* character (not byte) index within the command corresponding to the
898+
* given character index within the literal. If not successful, return 0.
899+
*/
900+
static int
901+
match_prosrc_to_query(const char *prosrc, const char *queryText,
902+
int cursorpos)
903+
{
810904
/*
811-
* XXX it'd be really nice to adjust the syntax error position to
812-
* account for the offset from the start of the statement to the
813-
* function body string, not to mention any quoting characters in
814-
* the string, but I can't see any decent way to do that...
905+
* Rather than fully parsing the CREATE FUNCTION command, we just scan
906+
* the command looking for $prosrc$ or 'prosrc'. This could be fooled
907+
* (though not in any very probable scenarios), so fail if we find
908+
* more than one match.
909+
*/
910+
int prosrclen = strlen(prosrc);
911+
int querylen = strlen(queryText);
912+
int matchpos = 0;
913+
int curpos;
914+
int newcursorpos;
915+
916+
for (curpos = 0; curpos < querylen-prosrclen; curpos++)
917+
{
918+
if (queryText[curpos] == '$' &&
919+
strncmp(prosrc, &queryText[curpos+1], prosrclen) == 0 &&
920+
queryText[curpos+1+prosrclen] == '$')
921+
{
922+
/*
923+
* Found a $foo$ match. Since there are no embedded quoting
924+
* characters in a dollar-quoted literal, we don't have to do
925+
* any fancy arithmetic; just offset by the starting position.
926+
*/
927+
if (matchpos)
928+
return 0; /* multiple matches, fail */
929+
matchpos = pg_mbstrlen_with_len(queryText, curpos+1)
930+
+ cursorpos;
931+
}
932+
else if (queryText[curpos] == '\'' &&
933+
match_prosrc_to_literal(prosrc, &queryText[curpos+1],
934+
cursorpos, &newcursorpos))
935+
{
936+
/*
937+
* Found a 'foo' match. match_prosrc_to_literal() has adjusted
938+
* for any quotes or backslashes embedded in the literal.
939+
*/
940+
if (matchpos)
941+
return 0; /* multiple matches, fail */
942+
matchpos = pg_mbstrlen_with_len(queryText, curpos+1)
943+
+ newcursorpos;
944+
}
945+
}
946+
947+
return matchpos;
948+
}
949+
950+
/*
951+
* Try to match the given source text to a single-quoted literal.
952+
* If successful, adjust newcursorpos to correspond to the character
953+
* (not byte) index corresponding to cursorpos in the source text.
954+
*
955+
* At entry, literal points just past a ' character. We must check for the
956+
* trailing quote.
957+
*/
958+
static bool
959+
match_prosrc_to_literal(const char *prosrc, const char *literal,
960+
int cursorpos, int *newcursorpos)
961+
{
962+
int newcp = cursorpos;
963+
int chlen;
964+
965+
/*
966+
* This implementation handles backslashes and doubled quotes in the
967+
* string literal. It does not handle the SQL syntax for literals
968+
* continued across line boundaries.
815969
*
816-
* In the meantime, put in a CONTEXT entry that can cue clients
817-
* not to trust the syntax error position completely.
970+
* We do the comparison a character at a time, not a byte at a time,
971+
* so that we can do the correct cursorpos math.
818972
*/
819-
errcontext("SQL function \"%s\"",
820-
NameStr(proc->proname));
973+
while (*prosrc)
974+
{
975+
cursorpos--; /* characters left before cursor */
976+
/*
977+
* Check for backslashes and doubled quotes in the literal; adjust
978+
* newcp when one is found before the cursor.
979+
*/
980+
if (*literal == '\\')
981+
{
982+
literal++;
983+
if (cursorpos > 0)
984+
newcp++;
985+
}
986+
else if (*literal == '\'')
987+
{
988+
if (literal[1] != '\'')
989+
return false;
990+
literal++;
991+
if (cursorpos > 0)
992+
newcp++;
993+
}
994+
chlen = pg_mblen(prosrc);
995+
if (strncmp(prosrc, literal, chlen) != 0)
996+
return false;
997+
prosrc += chlen;
998+
literal += chlen;
999+
}
1000+
1001+
*newcursorpos = newcp;
1002+
1003+
if (*literal == '\'' && literal[1] != '\'')
1004+
return true;
1005+
return false;
8211006
}

src/backend/commands/portalcmds.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
*
1515
*
1616
* IDENTIFICATION
17-
* $PostgreSQL: pgsql/src/backend/commands/portalcmds.c,v 1.25 2003/11/29 19:51:47 pgsql Exp $
17+
* $PostgreSQL: pgsql/src/backend/commands/portalcmds.c,v 1.26 2004/03/21 22:29:10 tgl Exp $
1818
*
1919
*-------------------------------------------------------------------------
2020
*/
@@ -270,6 +270,7 @@ void
270270
PersistHoldablePortal(Portal portal)
271271
{
272272
QueryDesc *queryDesc = PortalGetQueryDesc(portal);
273+
Portal saveActivePortal;
273274
MemoryContext savePortalContext;
274275
MemoryContext saveQueryContext;
275276
MemoryContext oldcxt;
@@ -311,6 +312,8 @@ PersistHoldablePortal(Portal portal)
311312
/*
312313
* Set global portal context pointers.
313314
*/
315+
saveActivePortal = ActivePortal;
316+
ActivePortal = portal;
314317
savePortalContext = PortalContext;
315318
PortalContext = PortalGetHeapMemory(portal);
316319
saveQueryContext = QueryContext;
@@ -342,6 +345,7 @@ PersistHoldablePortal(Portal portal)
342345
/* Mark portal not active */
343346
portal->portalActive = false;
344347

348+
ActivePortal = saveActivePortal;
345349
PortalContext = savePortalContext;
346350
QueryContext = saveQueryContext;
347351

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy