Re: syntax error position "CREATE FUNCTION" bug fix - Mailing list pgsql-patches
From | Tom Lane |
---|---|
Subject | Re: syntax error position "CREATE FUNCTION" bug fix |
Date | |
Msg-id | 14229.1079652829@sss.pgh.pa.us Whole thread Raw |
In response to | Re: syntax error position "CREATE FUNCTION" bug fix (Fabien COELHO <coelho@cri.ensmp.fr>) |
Responses |
Re: syntax error position "CREATE FUNCTION" bug fix
|
List | pgsql-patches |
Attached is a proposed patch that fixes the cursor-position-in-CREATE-FUNCTION issue per my earlier suggestion. Since I complained that your proposal was too grotty, it's only fair to throw this out to let people take potshots at it ;-). The main grottiness here is providing access to the executing Portal so that the callback function can get at the original command string. I don't think this is unreasonably bad, since we already have a global PortalContext that points to the portal's memory context --- I just added parallel code that updates a new global ActivePortal. The re-parsing of the original command is simplistic but will handle all normal cases. regards, tom lane *** src/backend/catalog/pg_proc.c.orig Sat Mar 13 20:58:41 2004 --- src/backend/catalog/pg_proc.c Thu Mar 18 18:20:20 2004 *************** *** 23,31 **** --- 23,33 ---- #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,50 **** --- 47,56 ---- 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); /* ---------------------------------------------------------------- *************** *** 768,774 **** * location is inside the function body, not out in CREATE FUNCTION. */ sqlerrcontext.callback = sql_function_parse_error_callback; ! sqlerrcontext.arg = proc; sqlerrcontext.previous = error_context_stack; error_context_stack = &sqlerrcontext; --- 774,780 ---- * location is inside the function body, not out in CREATE FUNCTION. */ sqlerrcontext.callback = sql_function_parse_error_callback; ! sqlerrcontext.arg = tuple; sqlerrcontext.previous = error_context_stack; error_context_stack = &sqlerrcontext; *************** *** 800,821 **** } /* ! * error context callback to let us supply a context marker */ static void sql_function_parse_error_callback(void *arg) { ! Form_pg_proc proc = (Form_pg_proc) arg; /* ! * 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... * ! * In the meantime, put in a CONTEXT entry that can cue clients ! * not to trust the syntax error position completely. */ ! errcontext("SQL function \"%s\"", ! NameStr(proc->proname)); } --- 806,976 ---- } /* ! * error context callback to let us adjust syntax errors from SQL functions */ static void sql_function_parse_error_callback(void *arg) { ! HeapTuple tuple = (HeapTuple) arg; ! Form_pg_proc proc = (Form_pg_proc) GETSTRUCT(tuple); ! int origerrposition; ! const char *queryText; ! bool isnull; ! Datum tmp; ! char *prosrc; ! int newerrposition; ! ! /* ! * Nothing to do unless we are dealing with a syntax error that has ! * a cursor position. In that case, we need to try to adjust the ! * position to match the original query, not the text of the function. ! */ ! origerrposition = geterrposition(); ! if (origerrposition <= 0) ! return; ! ! /* ! * We can get the original query text from the active portal (hack...) ! */ ! Assert(ActivePortal && ActivePortal->portalActive); ! queryText = ActivePortal->sourceText; ! ! /* ! * Try to locate the function text in the original query. ! */ ! tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosrc, &isnull); ! if (isnull) ! elog(ERROR, "null prosrc"); ! prosrc = DatumGetCString(DirectFunctionCall1(textout, tmp)); ! ! newerrposition = match_prosrc_to_query(prosrc, queryText, origerrposition); ! ! if (newerrposition > 0) ! { ! /* Successful, so fix the error position */ ! errposition(newerrposition); ! } ! else ! { ! /* ! * If unsuccessful, put in a CONTEXT entry that will cue clients ! * not to treat the error position as relative to the original query. ! */ ! errcontext("SQL function \"%s\"", ! NameStr(proc->proname)); ! } ! } ! ! /* ! * 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; } *** src/backend/commands/portalcmds.c.orig Sat Nov 29 14:51:47 2003 --- src/backend/commands/portalcmds.c Thu Mar 18 16:57:10 2004 *************** *** 270,275 **** --- 270,276 ---- PersistHoldablePortal(Portal portal) { QueryDesc *queryDesc = PortalGetQueryDesc(portal); + Portal saveActivePortal; MemoryContext savePortalContext; MemoryContext saveQueryContext; MemoryContext oldcxt; *************** *** 311,316 **** --- 312,319 ---- /* * Set global portal context pointers. */ + saveActivePortal = ActivePortal; + ActivePortal = portal; savePortalContext = PortalContext; PortalContext = PortalGetHeapMemory(portal); saveQueryContext = QueryContext; *************** *** 342,347 **** --- 345,351 ---- /* Mark portal not active */ portal->portalActive = false; + ActivePortal = saveActivePortal; PortalContext = savePortalContext; QueryContext = saveQueryContext; *** src/backend/tcop/postgres.c.orig Mon Mar 15 15:01:58 2004 --- src/backend/tcop/postgres.c Thu Mar 18 16:56:55 2004 *************** *** 2710,2715 **** --- 2710,2716 ---- */ MemoryContextSwitchTo(TopMemoryContext); MemoryContextResetAndDeleteChildren(ErrorContext); + ActivePortal = NULL; PortalContext = NULL; QueryContext = NULL; *** src/backend/tcop/pquery.c.orig Fri Mar 5 15:57:31 2004 --- src/backend/tcop/pquery.c Thu Mar 18 16:56:56 2004 *************** *** 23,28 **** --- 23,35 ---- #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,400 **** --- 402,408 ---- char *completionTag) { bool result; + Portal saveActivePortal; MemoryContext savePortalContext; MemoryContext saveQueryContext; MemoryContext oldContext; *************** *** 433,438 **** --- 441,448 ---- /* * Set global portal context pointers. */ + saveActivePortal = ActivePortal; + ActivePortal = portal; savePortalContext = PortalContext; PortalContext = PortalGetHeapMemory(portal); saveQueryContext = QueryContext; *************** *** 508,513 **** --- 518,524 ---- /* Mark portal not active */ portal->portalActive = false; + ActivePortal = saveActivePortal; PortalContext = savePortalContext; QueryContext = saveQueryContext; *************** *** 925,930 **** --- 936,942 ---- DestReceiver *dest) { long result; + Portal saveActivePortal; MemoryContext savePortalContext; MemoryContext saveQueryContext; MemoryContext oldContext; *************** *** 948,953 **** --- 960,967 ---- /* * Set global portal context pointers. */ + saveActivePortal = ActivePortal; + ActivePortal = portal; savePortalContext = PortalContext; PortalContext = PortalGetHeapMemory(portal); saveQueryContext = QueryContext; *************** *** 972,977 **** --- 986,992 ---- /* Mark portal not active */ portal->portalActive = false; + ActivePortal = saveActivePortal; PortalContext = savePortalContext; QueryContext = saveQueryContext; *** src/backend/utils/error/elog.c.orig Mon Mar 15 15:01:58 2004 --- src/backend/utils/error/elog.c Thu Mar 18 17:36:58 2004 *************** *** 808,813 **** --- 808,829 ---- 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; + } /* * elog_finish --- finish up for old-style API *** src/include/tcop/pquery.h.orig Sat Nov 29 17:41:14 2003 --- src/include/tcop/pquery.h Thu Mar 18 16:56:49 2004 *************** *** 17,22 **** --- 17,25 ---- #include "utils/portal.h" + extern DLLIMPORT Portal ActivePortal; + + extern void ProcessQuery(Query *parsetree, Plan *plan, ParamListInfo params, *** src/include/utils/elog.h.orig Mon Mar 15 15:02:09 2004 --- src/include/utils/elog.h Thu Mar 18 17:36:51 2004 *************** *** 132,137 **** --- 132,139 ---- extern int errfunction(const char *funcname); extern int errposition(int cursorpos); + extern int geterrposition(void); + /*---------- * Old-style error reporting API: to be used in this way:
pgsql-patches by date: