Attached is a patch from Joachim Wieland that changes the parser to
optionally maintain a list of the source texts of the statements in the
input query string. Currently debug_query_string is used for that
purpose -- this is incorrect when multiple comma-separated statements
are concatenated together in a single query string.
The patch maintains the list by having the parser record when each
statement ends; the lexer's current position is used to determine the
source text that corresponds to the current statement. A new global
variable "current_statement" has been introduced to replace
"debug_query_string" (which has been removed).
Two issues/questions:
(1) Joachim's patch removes leading and trailing whitespace, and any
trailing semi-colon from the statement's text. Is this the right
behavior? I'm not sure, I can see an argument for both sides.
(2) Some of the logging code (e.g. log_after_parse(), the code for
log_min_duration_statement, etc.) needs to be updated to use
current_statement rather than the user-submitted query string. I'll do
that shortly.
Patch from Joachim Wieland, reviewed by Neil Conway.
-Neil
============================================================
*** contrib/dblink/dblink.c 039b7c90248c9087a49f8fe3c21e214b6287389b
--- contrib/dblink/dblink.c c48debddd198b88ad89208dcc9c6292645a56a94
***************
*** 1411,1417 ****
Datum
dblink_current_query(PG_FUNCTION_ARGS)
{
! PG_RETURN_TEXT_P(GET_TEXT(debug_query_string));
}
--- 1411,1417 ----
Datum
dblink_current_query(PG_FUNCTION_ARGS)
{
! PG_RETURN_TEXT_P(GET_TEXT(current_statement));
}
============================================================
*** src/backend/commands/portalcmds.c aeae492a1365b3212189d13fc9e5b149b4de72fb
--- src/backend/commands/portalcmds.c f3855d47c30802c98308269d9cc53d20ce9144e5
***************
*** 107,117 ****
plan = copyObject(plan);
/*
! * XXX: debug_query_string is wrong here: the user might have
! * submitted more than one semicolon delimited queries.
*/
PortalDefineQuery(portal,
! pstrdup(debug_query_string),
"SELECT", /* cursor's query is always a SELECT */
list_make1(query),
list_make1(plan),
--- 107,117 ----
plan = copyObject(plan);
/*
! * We can't get the source text of the cursor's query, so use the
! * source text of the DECLARE CURSOR statement itself.
*/
PortalDefineQuery(portal,
! pstrdup(current_statement),
"SELECT", /* cursor's query is always a SELECT */
list_make1(query),
list_make1(plan),
============================================================
*** src/backend/commands/prepare.c 034ca223d78bf6cd2fbc9181c85b05cf2f035ea3
--- src/backend/commands/prepare.c ff29651f010251425ecc2de732bc2c3f57b360ec
***************
*** 112,122 ****
plan_list = pg_plan_queries(query_list, NULL, false);
/*
! * Save the results. We don't have the query string for this PREPARE, but
! * we do have the string we got from the client, so use that.
*/
StorePreparedStatement(stmt->name,
! debug_query_string,
commandTag,
query_list,
plan_list,
--- 112,123 ----
plan_list = pg_plan_queries(query_list, NULL, false);
/*
! * Save the results. We can't easily get a textual representation
! * of the query that was specified for PREPARE, but we have the
! * source text of the PREPARE statement itself, so use that.
*/
StorePreparedStatement(stmt->name,
! current_statement,
commandTag,
query_list,
plan_list,
============================================================
*** src/backend/parser/gram.y 6c5505404f13582983817cc202b855744735f613
--- src/backend/parser/gram.y 1d20c04e255c93db596167ca67b955222955d6a6
***************
*** 472,493 ****
* psql already handles such cases, but other interfaces don't.
* bjm 1999/10/05
*/
! stmtblock: stmtmulti { parsetree = $1; }
;
/* the thrashing around here is to discard "empty" statements... */
stmtmulti: stmtmulti ';' stmt
! { if ($3 != NULL)
! $$ = lappend($1, $3);
! else
! $$ = $1;
}
| stmt
! { if ($1 != NULL)
$$ = list_make1($1);
! else
$$ = NIL;
}
;
stmt :
--- 472,511 ----
* psql already handles such cases, but other interfaces don't.
* bjm 1999/10/05
*/
! stmtblock: stmtmulti
! {
! parsetree = $1;
! record_stmt_reset();
! }
;
/* the thrashing around here is to discard "empty" statements... */
stmtmulti: stmtmulti ';' stmt
! {
! if ($3 != NULL)
! {
! record_stmt_boundary();
! $$ = lappend($1, $3);
! }
! else
! {
! record_stmt_empty();
! $$ = $1;
! }
}
| stmt
! {
! if ($1 != NULL)
! {
! record_stmt_boundary();
$$ = list_make1($1);
! }
! else
! {
! record_stmt_empty();
$$ = NIL;
}
+ }
;
stmt :
============================================================
*** src/backend/parser/scan.l 8c85b3b9c0e4031a884ba386d4f1f87b98be7b4c
--- src/backend/parser/scan.l 38f6968ae7a765cb5b83e951283a7f7cfebe3e03
***************
*** 95,100 ****
--- 95,114 ----
unsigned char unescape_single_char(unsigned char c);
+ /*
+ * Should we record the source text corresponding to the individual
+ * statements in the input string? This should be enabled when parsing
+ * the query submitted by the user, and left disabled for internally
+ * generated queries.
+ */
+ bool record_stmt_boundaries = false;
+
+ /* Used to record boundaries between statements */
+ static int last_stmt_finish;
+
+ /* List of SQL statements */
+ List *current_statements;
+
%}
%option 8bit
***************
*** 886,888 ****
--- 900,967 ----
errposition(pg_err_position())));
warn_on_first_escape = false; /* warn only once per string */
}
+
+ void
+ record_stmt_reset(void)
+ {
+ if (record_stmt_boundaries)
+ last_stmt_finish = 0;
+ }
+
+ void
+ record_stmt_empty(void)
+ {
+ if (record_stmt_boundaries)
+ last_stmt_finish = strlen(scanbufhandle->yy_buf_pos);
+ }
+
+ void
+ record_stmt_boundary(void)
+ {
+ char *stmt;
+ char *start, *p;
+ int stmt_length;
+ int buffer_length;
+
+ if (!record_stmt_boundaries)
+ return;
+
+ Assert(scanbufhandle != NULL);
+ Assert(scanbufhandle->yy_buf_pos != NULL);
+
+ buffer_length = strlen(scanbufhandle->yy_buf_pos);
+
+ if (last_stmt_finish == 0)
+ {
+ start = scanbufhandle->yy_buf_pos;
+ stmt_length = buffer_length;
+ }
+ else
+ {
+ start = scanbufhandle->yy_buf_pos + last_stmt_finish;
+ stmt_length = buffer_length - last_stmt_finish;
+ }
+
+ /* skip over ";" and whitespace at the beginning of the statement */
+ while ((*start == ';' || isspace(*start)) && stmt_length > 0)
+ {
+ start++;
+ stmt_length--;
+ }
+
+ /* strip ";" and whitespace at the end of the statement */
+ p = start + stmt_length - 1;
+ while ((*p == ';' || isspace(*p)) && stmt_length > 0)
+ {
+ stmt_length--;
+ p--;
+ }
+
+ stmt = (char *) palloc(stmt_length + 1);
+ memcpy(stmt, start, stmt_length);
+ stmt[stmt_length] = '\0';
+ current_statements = lappend(current_statements, stmt);
+
+ last_stmt_finish = buffer_length;
+ }
+
============================================================
*** src/backend/tcop/postgres.c fb01fe600f5b7785c5d1f334d34d7e7bc4082161
--- src/backend/tcop/postgres.c 2173f8c6ea2c1f94c124c1342f3194d3bdc54a73
***************
*** 44,49 ****
--- 44,50 ----
#include "optimizer/cost.h"
#include "optimizer/planner.h"
#include "parser/analyze.h"
+ #include "parser/gramparse.h"
#include "parser/parser.h"
#include "rewrite/rewriteHandler.h"
#include "storage/freespace.h"
***************
*** 72,79 ****
* global variables
* ----------------
*/
- const char *debug_query_string; /* for pgmonitor and log_min_error_statement */
/* Note: whereToSendOutput is initialized for the bootstrap/standalone case */
CommandDest whereToSendOutput = DestDebug;
--- 73,85 ----
* global variables
* ----------------
*/
+ /*
+ * This is the source text of the SQL statement we are currently
+ * processing.
+ */
+ const char *current_statement;
+
/* Note: whereToSendOutput is initialized for the bootstrap/standalone case */
CommandDest whereToSendOutput = DestDebug;
***************
*** 88,93 ****
--- 94,104 ----
/* wait N seconds to allow attach from a debugger */
int PostAuthDelay = 0;
+ /*
+ * Stack base pointer -- initialized by PostgresMain. This is not static
+ * so that PL/Java can modify it.
+ */
+ char *stack_base_ptr = NULL;
/* ----------------
***************
*** 95,111 ****
* ----------------
*/
/* max_stack_depth converted to bytes for speed of checking */
static int max_stack_depth_bytes = 2048 * 1024;
/*
- * Stack base pointer -- initialized by PostgresMain. This is not static
- * so that PL/Java can modify it.
- */
- char *stack_base_ptr = NULL;
-
-
- /*
* Flag to mark SIGHUP. Whenever the main loop comes around it
* will reread the configuration file. (Better than doing the
* reading in the signal handler, ey?)
--- 106,116 ----
* ----------------
*/
+
/* max_stack_depth converted to bytes for speed of checking */
static int max_stack_depth_bytes = 2048 * 1024;
/*
* Flag to mark SIGHUP. Whenever the main loop comes around it
* will reread the configuration file. (Better than doing the
* reading in the signal handler, ey?)
***************
*** 826,837 ****
bool save_log_statement_stats = log_statement_stats;
char *prepare_string = NULL;
bool was_logged = false;
/*
* Report query to various monitoring facilities.
*/
- debug_query_string = query_string;
-
pgstat_report_activity(query_string);
/*
--- 831,841 ----
bool save_log_statement_stats = log_statement_stats;
char *prepare_string = NULL;
bool was_logged = false;
+ ListCell *current_statement_cell;
/*
* Report query to various monitoring facilities.
*/
pgstat_report_activity(query_string);
/*
***************
*** 880,886 ****
--- 884,893 ----
* Do basic parsing of the query or queries (this should be safe even if
* we are in aborted transaction state!)
*/
+ record_stmt_boundaries = true;
parsetree_list = pg_parse_query(query_string);
+ record_stmt_boundaries = false;
+ current_statement_cell = list_head(current_statements);
if (log_statement != LOGSTMT_NONE || save_log_min_duration_statement != -1)
was_logged = log_after_parse(parsetree_list, query_string,
***************
*** 905,910 ****
--- 912,920 ----
DestReceiver *receiver;
int16 format;
+ current_statement = lfirst(current_statement_cell);
+ current_statement_cell = lnext(current_statement_cell);
+
/*
* Get the command name for use in status display (it also becomes the
* default completion tag, down inside PortalRun). Set ps_status and
***************
*** 962,968 ****
portal->visible = false;
PortalDefineQuery(portal,
! query_string,
commandTag,
querytree_list,
plantree_list,
--- 972,978 ----
portal->visible = false;
PortalDefineQuery(portal,
! current_statement,
commandTag,
querytree_list,
plantree_list,
***************
*** 1118,1124 ****
if (prepare_string != NULL)
pfree(prepare_string);
! debug_query_string = NULL;
}
/*
--- 1128,1139 ----
if (prepare_string != NULL)
pfree(prepare_string);
! /*
! * The strings these variables point to are freed when
! * MessageContext is reset
! */
! current_statements = NIL;
! current_statement = NULL;
}
/*
***************
*** 1144,1150 ****
/*
* Report query to various monitoring facilities.
*/
! debug_query_string = query_string;
pgstat_report_activity(query_string);
--- 1159,1165 ----
/*
* Report query to various monitoring facilities.
*/
! current_statement = query_string;
pgstat_report_activity(query_string);
***************
*** 1354,1360 ****
if (save_log_statement_stats)
ShowUsage("PARSE MESSAGE STATISTICS");
! debug_query_string = NULL;
}
/*
--- 1369,1375 ----
if (save_log_statement_stats)
ShowUsage("PARSE MESSAGE STATISTICS");
! current_statement = NULL;
}
/*
***************
*** 1692,1708 ****
/* Should we display the portal names here? */
if (execute_is_fetch)
{
! debug_query_string = "fetch message";
pgstat_report_activity("<FETCH>");
}
else if (portal->sourceText)
{
! debug_query_string = portal->sourceText;
pgstat_report_activity(portal->sourceText);
}
else
{
! debug_query_string = "execute message";
pgstat_report_activity("<EXECUTE>");
}
--- 1707,1723 ----
/* Should we display the portal names here? */
if (execute_is_fetch)
{
! current_statement = "fetch message";
pgstat_report_activity("<FETCH>");
}
else if (portal->sourceText)
{
! current_statement = portal->sourceText;
pgstat_report_activity(portal->sourceText);
}
else
{
! current_statement = "execute message";
pgstat_report_activity("<EXECUTE>");
}
***************
*** 1844,1850 ****
if (save_log_statement_stats)
ShowUsage("QUERY STATISTICS");
! debug_query_string = NULL;
}
/*
--- 1859,1865 ----
if (save_log_statement_stats)
ShowUsage("QUERY STATISTICS");
! current_statement = NULL;
}
/*
***************
*** 1925,1931 ****
NULL);
else
pq_putemptymessage('n'); /* NoData */
-
}
/*
--- 1940,1945 ----
***************
*** 3090,3099 ****
EmitErrorReport();
/*
! * Make sure debug_query_string gets reset before we possibly clobber
! * the storage it points at.
*/
! debug_query_string = NULL;
/*
* Abort the current transaction in order to recover.
--- 3104,3114 ----
EmitErrorReport();
/*
! * Make sure current_statement and current_statements get
! * reset before we possibly clobber the storage they point at.
*/
! current_statements = NIL;
! current_statement = NULL;
/*
* Abort the current transaction in order to recover.
============================================================
*** src/backend/utils/error/elog.c 71ce44e3f14eea07333140ba06079c731822d1bd
--- src/backend/utils/error/elog.c d84556d001157aa8b39b15c435f37f583f03c72b
***************
*** 1635,1645 ****
/*
* If the user wants the query that generated this error logged, do it.
*/
! if (edata->elevel >= log_min_error_statement && debug_query_string != NULL)
{
log_line_prefix(&buf);
appendStringInfoString(&buf, _("STATEMENT: "));
! append_with_tabs(&buf, debug_query_string);
appendStringInfoChar(&buf, '\n');
}
--- 1635,1645 ----
/*
* If the user wants the query that generated this error logged, do it.
*/
! if (edata->elevel >= log_min_error_statement && current_statement != NULL)
{
log_line_prefix(&buf);
appendStringInfoString(&buf, _("STATEMENT: "));
! append_with_tabs(&buf, current_statement);
appendStringInfoChar(&buf, '\n');
}
============================================================
*** src/include/parser/gramparse.h 6f8c37f49dc4a151a8b3bff22b19b3c8734f538d
--- src/include/parser/gramparse.h 7d73e56c32445e95612d041fbd8772eee497ea36
***************
*** 26,31 ****
--- 26,36 ----
extern void scanner_finish(void);
extern int base_yylex(void);
extern void yyerror(const char *message);
+ extern bool record_stmt_boundaries;
+ extern List *current_statements;
+ extern void record_stmt_boundary(void);
+ extern void record_stmt_empty(void);
+ extern void record_stmt_reset(void);
/* from gram.y */
extern void parser_init(void);
============================================================
*** src/include/tcop/tcopprot.h e91b269466d4798972844442b999acd47f538d99
--- src/include/tcop/tcopprot.h 89dd402d1d971479bad788d155ec36a287b9d541
***************
*** 26,32 ****
extern CommandDest whereToSendOutput;
! extern DLLIMPORT const char *debug_query_string;
extern int max_stack_depth;
extern int PostAuthDelay;
--- 26,32 ----
extern CommandDest whereToSendOutput;
! extern DLLIMPORT const char *current_statement;
extern int max_stack_depth;
extern int PostAuthDelay;
---------------------------(end of broadcast)---------------------------
TIP 1: if posting/reading through Usenet, please send an appropriate
subscribe-nomail command to [EMAIL PROTECTED] so that your
message can get through to the mailing list cleanly