@ -54,6 +54,7 @@
# include "funcapi.h"
# include "mb/pg_wchar.h"
# include "miscadmin.h"
# include "nodes/queryjumble.h"
# include "storage/fd.h"
# include "tcop/utility.h"
# include "utils/acl.h"
@ -107,6 +108,17 @@ typedef struct ExtensionVersionInfo
struct ExtensionVersionInfo * previous ; /* current best predecessor */
} ExtensionVersionInfo ;
/*
* Information for script_error_callback ( )
*/
typedef struct
{
const char * sql ; /* entire script file contents */
const char * filename ; /* script file pathname */
ParseLoc stmt_location ; /* current stmt start loc, or -1 if unknown */
ParseLoc stmt_len ; /* length in bytes; 0 means "rest of string" */
} script_error_callback_arg ;
/* Local functions */
static List * find_update_path ( List * evi_list ,
ExtensionVersionInfo * evi_start ,
@ -670,9 +682,139 @@ read_extension_script_file(const ExtensionControlFile *control,
return dest_str ;
}
/*
* error context callback for failures in script - file execution
*/
static void
script_error_callback ( void * arg )
{
script_error_callback_arg * callback_arg = ( script_error_callback_arg * ) arg ;
const char * query = callback_arg - > sql ;
int location = callback_arg - > stmt_location ;
int len = callback_arg - > stmt_len ;
int syntaxerrposition ;
const char * lastslash ;
/*
* If there is a syntax error position , convert to internal syntax error ;
* otherwise report the current query as an item of context stack .
*
* Note : we ' ll provide no context except the filename if there ' s neither
* an error position nor any known current query . That shouldn ' t happen
* though : all errors reported during raw parsing should come with an
* error position .
*/
syntaxerrposition = geterrposition ( ) ;
if ( syntaxerrposition > 0 )
{
/*
* If we do not know the bounds of the current statement ( as would
* happen for an error occurring during initial raw parsing ) , we have
* to use a heuristic to decide how much of the script to show . We ' ll
* also use the heuristic in the unlikely case that syntaxerrposition
* is outside what we think the statement bounds are .
*/
if ( location < 0 | | syntaxerrposition < location | |
( len > 0 & & syntaxerrposition > location + len ) )
{
/*
* Our heuristic is pretty simple : look for semicolon - newline
* sequences , and break at the last one strictly before
* syntaxerrposition and the first one strictly after . It ' s
* certainly possible to fool this with semicolon - newline embedded
* in a string literal , but it seems better to do this than to
* show the entire extension script .
*/
int slen = strlen ( query ) ;
location = len = 0 ;
for ( int loc = 0 ; loc < slen ; loc + + )
{
if ( query [ loc ] ! = ' ; ' )
continue ;
if ( query [ loc + 1 ] = = ' \r ' )
loc + + ;
if ( query [ loc + 1 ] = = ' \n ' )
{
int bkpt = loc + 2 ;
if ( bkpt < syntaxerrposition )
location = bkpt ;
else if ( bkpt > syntaxerrposition )
{
len = bkpt - location ;
break ; /* no need to keep searching */
}
}
}
}
/* Trim leading/trailing whitespace, for consistency */
query = CleanQuerytext ( query , & location , & len ) ;
/*
* Adjust syntaxerrposition . It shouldn ' t be pointing into the
* whitespace we just trimmed , but cope if it is .
*/
syntaxerrposition - = location ;
if ( syntaxerrposition < 0 )
syntaxerrposition = 0 ;
else if ( syntaxerrposition > len )
syntaxerrposition = len ;
/* And report. */
errposition ( 0 ) ;
internalerrposition ( syntaxerrposition ) ;
internalerrquery ( pnstrdup ( query , len ) ) ;
}
else if ( location > = 0 )
{
/*
* Since no syntax cursor will be shown , it ' s okay and helpful to trim
* the reported query string to just the current statement .
*/
query = CleanQuerytext ( query , & location , & len ) ;
errcontext ( " SQL statement \" %.*s \" " , len , query ) ;
}
/*
* Trim the reported file name to remove the path . We know that
* get_extension_script_filename ( ) inserted a ' / ' , regardless of whether
* we ' re on Windows .
*/
lastslash = strrchr ( callback_arg - > filename , ' / ' ) ;
if ( lastslash )
lastslash + + ;
else
lastslash = callback_arg - > filename ; /* shouldn't happen, but cope */
/*
* If we have a location ( which , as said above , we really always should )
* then report a line number to aid in localizing problems in big scripts .
*/
if ( location > = 0 )
{
int linenumber = 1 ;
for ( query = callback_arg - > sql ; * query ; query + + )
{
if ( - - location < 0 )
break ;
if ( * query = = ' \n ' )
linenumber + + ;
}
errcontext ( " extension script file \" %s \" , near line %d " ,
lastslash , linenumber ) ;
}
else
errcontext ( " extension script file \" %s \" " , lastslash ) ;
}
/*
* Execute given SQL string .
*
* The filename the string came from is also provided , for error reporting .
*
* Note : it ' s tempting to just use SPI to execute the string , but that does
* not work very well . The really serious problem is that SPI will parse ,
* analyze , and plan the whole string before executing any of it ; of course
@ -682,12 +824,27 @@ read_extension_script_file(const ExtensionControlFile *control,
* could be very long .
*/
static void
execute_sql_string ( const char * sql )
execute_sql_string ( const char * sql , const char * filename )
{
script_error_callback_arg callback_arg ;
ErrorContextCallback scripterrcontext ;
List * raw_parsetree_list ;
DestReceiver * dest ;
ListCell * lc1 ;
/*
* Setup error traceback support for ereport ( ) .
*/
callback_arg . sql = sql ;
callback_arg . filename = filename ;
callback_arg . stmt_location = - 1 ;
callback_arg . stmt_len = - 1 ;
scripterrcontext . callback = script_error_callback ;
scripterrcontext . arg = ( void * ) & callback_arg ;
scripterrcontext . previous = error_context_stack ;
error_context_stack = & scripterrcontext ;
/*
* Parse the SQL string into a list of raw parse trees .
*/
@ -709,6 +866,10 @@ execute_sql_string(const char *sql)
List * stmt_list ;
ListCell * lc2 ;
/* Report location of this query for error context callback */
callback_arg . stmt_location = parsetree - > stmt_location ;
callback_arg . stmt_len = parsetree - > stmt_len ;
/*
* We do the work for each parsetree in a short - lived context , to
* limit the memory used when there are many commands in the string .
@ -778,6 +939,8 @@ execute_sql_string(const char *sql)
MemoryContextDelete ( per_parsetree_context ) ;
}
error_context_stack = scripterrcontext . previous ;
/* Be sure to advance the command counter after the last script command */
CommandCounterIncrement ( ) ;
}
@ -1054,7 +1217,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
/* And now back to C string */
c_sql = text_to_cstring ( DatumGetTextPP ( t_sql ) ) ;
execute_sql_string ( c_sql ) ;
execute_sql_string ( c_sql , filename ) ;
}
PG_FINALLY ( ) ;
{