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

Reply via email to