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 */
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 @@ Datumfmgr_sql_validator(PG_FUNCTION_ARGS);
4547static Datum create_parameternames_array (int parameterCount ,
4648const char * parameterNames []);
4749static 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)
763769prosrc = 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 */
770774sqlerrcontext .callback = sql_function_parse_error_callback ;
771- sqlerrcontext .arg = proc ;
775+ sqlerrcontext .arg = tuple ;
772776sqlerrcontext .previous = error_context_stack ;
773777error_context_stack = & sqlerrcontext ;
774778
@@ -800,22 +804,203 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
800804}
801805
802806/*
803- *error context callbackto let us supply a context marker
807+ *Error context callbackfor handling errors in SQL function definitions
804808 */
805809static void
806810sql_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 themeantime, put in a CONTEXT entry that can cue clients
817- *not to trust the syntax error position completely .
970+ *We do thecomparison 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}