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