54
54
#include "funcapi.h"
55
55
#include "mb/pg_wchar.h"
56
56
#include "miscadmin.h"
57
+ #include "nodes/queryjumble.h"
57
58
#include "storage/fd.h"
58
59
#include "tcop/utility.h"
59
60
#include "utils/acl.h"
@@ -107,6 +108,17 @@ typedef struct ExtensionVersionInfo
107
108
struct ExtensionVersionInfo * previous ; /* current best predecessor */
108
109
} ExtensionVersionInfo ;
109
110
111
+ /*
112
+ * Information for script_error_callback()
113
+ */
114
+ typedef struct
115
+ {
116
+ const char * sql ; /* entire script file contents */
117
+ const char * filename ; /* script file pathname */
118
+ ParseLoc stmt_location ; /* current stmt start loc, or -1 if unknown */
119
+ ParseLoc stmt_len ; /* length in bytes; 0 means "rest of string" */
120
+ } script_error_callback_arg ;
121
+
110
122
/* Local functions */
111
123
static List * find_update_path (List * evi_list ,
112
124
ExtensionVersionInfo * evi_start ,
@@ -670,9 +682,139 @@ read_extension_script_file(const ExtensionControlFile *control,
670
682
return dest_str ;
671
683
}
672
684
685
+ /*
686
+ * error context callback for failures in script-file execution
687
+ */
688
+ static void
689
+ script_error_callback (void * arg )
690
+ {
691
+ script_error_callback_arg * callback_arg = (script_error_callback_arg * ) arg ;
692
+ const char * query = callback_arg -> sql ;
693
+ int location = callback_arg -> stmt_location ;
694
+ int len = callback_arg -> stmt_len ;
695
+ int syntaxerrposition ;
696
+ const char * lastslash ;
697
+
698
+ /*
699
+ * If there is a syntax error position, convert to internal syntax error;
700
+ * otherwise report the current query as an item of context stack.
701
+ *
702
+ * Note: we'll provide no context except the filename if there's neither
703
+ * an error position nor any known current query. That shouldn't happen
704
+ * though: all errors reported during raw parsing should come with an
705
+ * error position.
706
+ */
707
+ syntaxerrposition = geterrposition ();
708
+ if (syntaxerrposition > 0 )
709
+ {
710
+ /*
711
+ * If we do not know the bounds of the current statement (as would
712
+ * happen for an error occurring during initial raw parsing), we have
713
+ * to use a heuristic to decide how much of the script to show. We'll
714
+ * also use the heuristic in the unlikely case that syntaxerrposition
715
+ * is outside what we think the statement bounds are.
716
+ */
717
+ if (location < 0 || syntaxerrposition < location ||
718
+ (len > 0 && syntaxerrposition > location + len ))
719
+ {
720
+ /*
721
+ * Our heuristic is pretty simple: look for semicolon-newline
722
+ * sequences, and break at the last one strictly before
723
+ * syntaxerrposition and the first one strictly after. It's
724
+ * certainly possible to fool this with semicolon-newline embedded
725
+ * in a string literal, but it seems better to do this than to
726
+ * show the entire extension script.
727
+ */
728
+ int slen = strlen (query );
729
+
730
+ location = len = 0 ;
731
+ for (int loc = 0 ; loc < slen ; loc ++ )
732
+ {
733
+ if (query [loc ] != ';' )
734
+ continue ;
735
+ if (query [loc + 1 ] == '\r' )
736
+ loc ++ ;
737
+ if (query [loc + 1 ] == '\n' )
738
+ {
739
+ int bkpt = loc + 2 ;
740
+
741
+ if (bkpt < syntaxerrposition )
742
+ location = bkpt ;
743
+ else if (bkpt > syntaxerrposition )
744
+ {
745
+ len = bkpt - location ;
746
+ break ; /* no need to keep searching */
747
+ }
748
+ }
749
+ }
750
+ }
751
+
752
+ /* Trim leading/trailing whitespace, for consistency */
753
+ query = CleanQuerytext (query , & location , & len );
754
+
755
+ /*
756
+ * Adjust syntaxerrposition. It shouldn't be pointing into the
757
+ * whitespace we just trimmed, but cope if it is.
758
+ */
759
+ syntaxerrposition -= location ;
760
+ if (syntaxerrposition < 0 )
761
+ syntaxerrposition = 0 ;
762
+ else if (syntaxerrposition > len )
763
+ syntaxerrposition = len ;
764
+
765
+ /* And report. */
766
+ errposition (0 );
767
+ internalerrposition (syntaxerrposition );
768
+ internalerrquery (pnstrdup (query , len ));
769
+ }
770
+ else if (location >= 0 )
771
+ {
772
+ /*
773
+ * Since no syntax cursor will be shown, it's okay and helpful to trim
774
+ * the reported query string to just the current statement.
775
+ */
776
+ query = CleanQuerytext (query , & location , & len );
777
+ errcontext ("SQL statement \"%.*s\"" , len , query );
778
+ }
779
+
780
+ /*
781
+ * Trim the reported file name to remove the path. We know that
782
+ * get_extension_script_filename() inserted a '/', regardless of whether
783
+ * we're on Windows.
784
+ */
785
+ lastslash = strrchr (callback_arg -> filename , '/' );
786
+ if (lastslash )
787
+ lastslash ++ ;
788
+ else
789
+ lastslash = callback_arg -> filename ; /* shouldn't happen, but cope */
790
+
791
+ /*
792
+ * If we have a location (which, as said above, we really always should)
793
+ * then report a line number to aid in localizing problems in big scripts.
794
+ */
795
+ if (location >= 0 )
796
+ {
797
+ int linenumber = 1 ;
798
+
799
+ for (query = callback_arg -> sql ; * query ; query ++ )
800
+ {
801
+ if (-- location < 0 )
802
+ break ;
803
+ if (* query == '\n' )
804
+ linenumber ++ ;
805
+ }
806
+ errcontext ("extension script file \"%s\", near line %d" ,
807
+ lastslash , linenumber );
808
+ }
809
+ else
810
+ errcontext ("extension script file \"%s\"" , lastslash );
811
+ }
812
+
673
813
/*
674
814
* Execute given SQL string.
675
815
*
816
+ * The filename the string came from is also provided, for error reporting.
817
+ *
676
818
* Note: it's tempting to just use SPI to execute the string, but that does
677
819
* not work very well. The really serious problem is that SPI will parse,
678
820
* analyze, and plan the whole string before executing any of it; of course
@@ -682,12 +824,27 @@ read_extension_script_file(const ExtensionControlFile *control,
682
824
* could be very long.
683
825
*/
684
826
static void
685
- execute_sql_string (const char * sql )
827
+ execute_sql_string (const char * sql , const char * filename )
686
828
{
829
+ script_error_callback_arg callback_arg ;
830
+ ErrorContextCallback scripterrcontext ;
687
831
List * raw_parsetree_list ;
688
832
DestReceiver * dest ;
689
833
ListCell * lc1 ;
690
834
835
+ /*
836
+ * Setup error traceback support for ereport().
837
+ */
838
+ callback_arg .sql = sql ;
839
+ callback_arg .filename = filename ;
840
+ callback_arg .stmt_location = -1 ;
841
+ callback_arg .stmt_len = -1 ;
842
+
843
+ scripterrcontext .callback = script_error_callback ;
844
+ scripterrcontext .arg = (void * ) & callback_arg ;
845
+ scripterrcontext .previous = error_context_stack ;
846
+ error_context_stack = & scripterrcontext ;
847
+
691
848
/*
692
849
* Parse the SQL string into a list of raw parse trees.
693
850
*/
@@ -709,6 +866,10 @@ execute_sql_string(const char *sql)
709
866
List * stmt_list ;
710
867
ListCell * lc2 ;
711
868
869
+ /* Report location of this query for error context callback */
870
+ callback_arg .stmt_location = parsetree -> stmt_location ;
871
+ callback_arg .stmt_len = parsetree -> stmt_len ;
872
+
712
873
/*
713
874
* We do the work for each parsetree in a short-lived context, to
714
875
* limit the memory used when there are many commands in the string.
@@ -778,6 +939,8 @@ execute_sql_string(const char *sql)
778
939
MemoryContextDelete (per_parsetree_context );
779
940
}
780
941
942
+ error_context_stack = scripterrcontext .previous ;
943
+
781
944
/* Be sure to advance the command counter after the last script command */
782
945
CommandCounterIncrement ();
783
946
}
@@ -1054,7 +1217,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
1054
1217
/* And now back to C string */
1055
1218
c_sql = text_to_cstring (DatumGetTextPP (t_sql ));
1056
1219
1057
- execute_sql_string (c_sql );
1220
+ execute_sql_string (c_sql , filename );
1058
1221
}
1059
1222
PG_FINALLY ();
1060
1223
{
0 commit comments