From 6a25c6e1d1036db1162f3137bfc8213ecd7446a4 Mon Sep 17 00:00:00 2001
From: Bruce Momjian <bruce@momjian.us>
Date: Wed, 7 Apr 2004 05:05:50 +0000
Subject: [PATCH] > >>1. change the type of "log_statement" option from boolean
 to string, > >>with allowed values of "all, mod, ddl, none" with default
 "none".

OK, here is a patch that implements #1.  Here is sample output:

        test=> set client_min_messages = 'log';
        SET
        test=> set log_statement = 'mod';
        SET
        test=> select 1;
         ?column?
        ----------
                1
        (1 row)

        test=> update test set x=1;
        LOG:  statement: update test set x=1;
        ERROR:  relation "test" does not exist
        test=> update test set x=1;
        LOG:  statement: update test set x=1;
        ERROR:  relation "test" does not exist
        test=> copy test from '/tmp/x';
        LOG:  statement: copy test from '/tmp/x';
        ERROR:  relation "test" does not exist
        test=> copy test to  '/tmp/x';
        ERROR:  relation "test" does not exist
        test=> prepare xx as select 1;
        PREPARE
        test=> prepare xx as update x set y=1;
        LOG:  statement: prepare xx as update x set y=1;
        ERROR:  relation "x" does not exist
        test=> explain analyze select 1;;
                                             QUERY PLAN
        ------------------------------------------------------------------------------------
         Result  (cost=0.00..0.01 rows=1 width=0) (actual time=0.006..0.007 rows=1 loops=1)
         Total runtime: 0.046 ms
        (2 rows)

        test=> explain analyze update test set x=1;
        LOG:  statement: explain analyze update test set x=1;
        ERROR:  relation "test" does not exist
        test=> explain update test set x=1;
        ERROR:  relation "test" does not exist

It checks PREPARE and EXECUTE ANALYZE too.  The log_statement values are
'none', 'mod', 'ddl', and 'all'.  For 'all', it prints before the query
is parsed, and for ddl/mod, it does it right after parsing using the
node tag (or command tag for CREATE/ALTER/DROP), so any non-parse errors
will print after the log line.
---
 doc/src/sgml/runtime.sgml                     |  23 ++--
 src/backend/tcop/postgres.c                   |  56 ++++++++-
 src/backend/utils/misc/guc.c                  | 112 ++++++++++++------
 src/backend/utils/misc/postgresql.conf.sample |   2 +-
 src/include/tcop/tcopprot.h                   |  15 ++-
 src/include/utils/guc.h                       |   3 +-
 6 files changed, 161 insertions(+), 50 deletions(-)

diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index f23772b8da7..bf9caac2aee 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/runtime.sgml,v 1.257 2004/04/05 03:02:03 momjian Exp $
+$PostgreSQL: pgsql/doc/src/sgml/runtime.sgml,v 1.258 2004/04/07 05:05:49 momjian Exp $
 -->
 
 <Chapter Id="runtime">
@@ -2121,12 +2121,21 @@ SET ENABLE_SEQSCAN TO OFF;
      </varlistentry>
 
      <varlistentry id="guc-log-statement" xreflabel="log_statement">
-      <term><varname>log_statement</varname> (<type>boolean</type>)</term>
-      <listitem>
-       <para>
-        Causes each SQL statement to be logged. The default is
-        off. Only superusers can disable this option if it has been
-        enabled by an administrator.
+      <term><varname>log_statement</varname> (<type>string</type>)</term>
+      <listitem>
+       <para>
+        Controls which SQL statement are logged. Valid values are
+        <literal>all</>, <literal>ddl</>, <literal>mod</>, and
+        <literal>none</>. <literal>ddl</> logs all data definition
+        commands like <literal>CREATE</>, <literal>ALTER</>, and
+        <literal>DROP</> commands. <literal>mod</> logs all
+        <literal>ddl</> statements, plus <literal>INSERT</>,
+        <literal>UPDATE</>, <literal>DELETE</>, <literal>TRUNCATE</>,
+        and <literal>COPY FROM</>. <literal>PREPARE</> and
+        <literal>EXPLAIN ANALYZE</> statements are also considered for
+        appropriate commands. The default is <literal>none</>. Only
+        superusers can reduce the detail of this option if it has been
+        set by an administrator.
        </para>
 
        <note>
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index ff0ac6aa64a..6cece54b30b 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.397 2004/03/24 22:40:29 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.398 2004/04/07 05:05:49 momjian Exp $
  *
  * NOTES
  *	  this is the "main" module of the postgres backend and
@@ -87,6 +87,8 @@ bool		InError = false;
 /* flag for logging end of session */
 bool        Log_disconnections = false;
 
+LogStmtLevel log_statement = LOGSTMT_NONE;
+
 /*
  * Flags for expensive function optimization -- JMH 3/9/92
  */
@@ -471,9 +473,10 @@ pg_parse_and_rewrite(const char *query_string,	/* string to execute */
 List *
 pg_parse_query(const char *query_string)
 {
-	List	   *raw_parsetree_list;
+	List	   *raw_parsetree_list,
+			   *parsetree_item;
 
-	if (log_statement)
+	if (log_statement == LOGSTMT_ALL)
 		ereport(LOG,
 				(errmsg("statement: %s", query_string)));
 
@@ -482,6 +485,51 @@ pg_parse_query(const char *query_string)
 
 	raw_parsetree_list = raw_parser(query_string);
 
+	/* do log_statement tests for mod and ddl */
+	if (log_statement == LOGSTMT_MOD ||
+		log_statement == LOGSTMT_DDL)
+	{
+		foreach(parsetree_item, raw_parsetree_list)
+		{
+			Node	   *parsetree = (Node *) lfirst(parsetree_item);
+			const char *commandTag;
+	
+			if (IsA(parsetree, ExplainStmt) &&
+				((ExplainStmt *)parsetree)->analyze)
+				parsetree = (Node *)(((ExplainStmt *)parsetree)->query);
+			
+			if (IsA(parsetree, PrepareStmt))
+				parsetree = (Node *)(((PrepareStmt *)parsetree)->query);
+			
+			if (IsA(parsetree, SelectStmt))
+				continue;	/* optimization for frequent command */
+				
+			if (log_statement == LOGSTMT_MOD &&
+				(IsA(parsetree, InsertStmt) ||
+				 IsA(parsetree, UpdateStmt) ||
+				 IsA(parsetree, DeleteStmt) ||
+				 IsA(parsetree, TruncateStmt) ||
+				 (IsA(parsetree, CopyStmt) &&
+				  ((CopyStmt *)parsetree)->is_from)))	/* COPY FROM */
+			{
+				ereport(LOG,
+						(errmsg("statement: %s", query_string)));
+				break;
+			}
+			commandTag = CreateCommandTag(parsetree);
+			if (strncmp(commandTag, "CREATE ", strlen("CREATE ")) == 0 ||
+				strncmp(commandTag, "ALTER ", strlen("ALTER ")) == 0 ||
+				strncmp(commandTag, "DROP ", strlen("DROP ")) == 0 ||
+				IsA(parsetree, GrantStmt) ||	/* GRANT or REVOKE */
+				IsA(parsetree, CommentStmt))
+			{
+				ereport(LOG,
+						(errmsg("statement: %s", query_string)));
+				break;
+			}
+		}
+	}
+
 	if (log_parser_stats)
 		ShowUsage("PARSER STATISTICS");
 
@@ -2488,7 +2536,7 @@ PostgresMain(int argc, char *argv[], const char *username)
 		SetConfigOption("log_disconnections", "true", debug_context, gucsource);
 	}
 	if (debug_flag >= 2)
-		SetConfigOption("log_statement", "true", debug_context, gucsource);
+		SetConfigOption("log_statement", "all", debug_context, gucsource);
 	if (debug_flag >= 3)
 		SetConfigOption("debug_print_parse", "true", debug_context, gucsource);
 	if (debug_flag >= 4)
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 215378749b6..adbbda25d48 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -10,7 +10,7 @@
  * Written by Peter Eisentraut <peter_e@gmx.net>.
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.197 2004/04/05 03:02:07 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.198 2004/04/07 05:05:50 momjian Exp $
  *
  *--------------------------------------------------------------------
  */
@@ -86,18 +86,22 @@ static const char *assign_facility(const char *facility,
 				bool doit, GucSource source);
 #endif
 
-static const char *assign_defaultxactisolevel(const char *newval,
-						   bool doit, GucSource source);
-static const char *assign_log_min_messages(const char *newval,
-						bool doit, GucSource source);
+static const char *assign_defaultxactisolevel(const char *newval, bool doit,
+						   GucSource source);
+static const char *assign_log_min_messages(const char *newval, bool doit,
+						   GucSource source);
 static const char *assign_client_min_messages(const char *newval,
 						   bool doit, GucSource source);
 static const char *assign_min_error_statement(const char *newval, bool doit,
 						   GucSource source);
-static const char *assign_msglvl(int *var, const char *newval,
-			  bool doit, GucSource source);
+static const char *assign_msglvl(int *var, const char *newval, bool doit,
+						   GucSource source);
 static const char *assign_log_error_verbosity(const char *newval, bool doit,
 						   GucSource source);
+static const char *assign_log_statement(const char *newval, bool doit,
+						   GucSource source);
+static const char *assign_log_stmtlvl(int *var, const char *newval,
+						   bool doit, GucSource source);
 static bool assign_phony_autocommit(bool newval, bool doit, GucSource source);
 
 
@@ -107,7 +111,6 @@ static bool assign_phony_autocommit(bool newval, bool doit, GucSource source);
 #ifdef USE_ASSERT_CHECKING
 bool		assert_enabled = true;
 #endif
-bool		log_statement = false;
 bool		log_duration = false;
 bool		Debug_print_plan = false;
 bool		Debug_print_parse = false;
@@ -145,6 +148,7 @@ int			log_min_duration_statement = -1;
 static char *client_min_messages_str;
 static char *log_min_messages_str;
 static char *log_error_verbosity_str;
+static char *log_statement_str;
 static char *log_min_error_statement_str;
 static char *log_destination_string;
 static bool phony_autocommit;
@@ -527,14 +531,6 @@ static struct config_bool ConfigureNamesBool[] =
 		&ExitOnAnyError,
 		false, NULL, NULL
 	},
-	{
-		{"log_statement", PGC_USERLIMIT, LOGGING_WHAT,
-			gettext_noop("Logs each SQL statement."),
-			NULL
-		},
-		&log_statement,
-		false, NULL, NULL
-	},
 	{
 		{"log_duration", PGC_USERLIMIT, LOGGING_WHAT,
 			gettext_noop("Logs the duration each completed SQL statement."),
@@ -1442,6 +1438,14 @@ static struct config_string ConfigureNamesString[] =
 		&log_error_verbosity_str,
 		"default", assign_log_error_verbosity, NULL
 	},
+	{
+		{"log_statement", PGC_USERLIMIT, LOGGING_WHAT,
+			gettext_noop("Sets the type of statements logged."),
+			gettext_noop("Valid values are \"none\", \"mod\", \"ddl\", and \"all\".")
+		},
+		&log_statement_str,
+		"none", assign_log_statement, NULL
+	},
 
 	{
 		{"log_min_error_statement", PGC_USERLIMIT, LOGGING_WHEN,
@@ -2007,14 +2011,11 @@ InitializeGUCOptions(void)
 					struct config_string *conf = (struct config_string *) gconf;
 					char	   *str;
 
-					/*
-					 * Check to make sure we only have valid
-					 * PGC_USERLIMITs
-					 */
+					/* Check to make sure we only have valid PGC_USERLIMITs */
 					Assert(conf->gen.context != PGC_USERLIMIT ||
 						   conf->assign_hook == assign_log_min_messages ||
-					   conf->assign_hook == assign_client_min_messages ||
-						conf->assign_hook == assign_min_error_statement);
+						   conf->assign_hook == assign_min_error_statement ||
+						   conf->assign_hook == assign_log_statement);
 					*conf->variable = NULL;
 					conf->reset_val = NULL;
 					conf->session_val = NULL;
@@ -3025,15 +3026,23 @@ set_config_option(const char *name, const char *value,
 					if (record->context == PGC_USERLIMIT &&
 						IsUnderPostmaster && !superuser())
 					{
-						int			old_int_value,
-									new_int_value;
-
-						/* all USERLIMIT strings are message levels */
-						assign_msglvl(&new_int_value, newval,
-									  true, source);
-						assign_msglvl(&old_int_value, conf->reset_val,
-									  true, source);
-						if (new_int_value > old_int_value)
+						int		var_value, reset_value, new_value;
+						const char * (*var_hook) (int *var, const char *newval,
+									bool doit, GucSource source);
+    
+						if (conf->assign_hook == assign_log_statement)
+							var_hook = assign_log_stmtlvl;
+						else
+							var_hook = assign_msglvl;
+
+						(*var_hook) (&new_value, newval, true, source);
+						(*var_hook) (&reset_value, conf->reset_val,	true,
+									 source);
+						(*var_hook) (&var_value, *conf->variable, true,
+									 source);
+
+						/* Limit non-superuser changes */
+						if (new_value > reset_value)
 						{
 							/* Limit non-superuser changes */
 							if (source > PGC_S_UNPRIVILEGED)
@@ -3046,10 +3055,9 @@ set_config_option(const char *name, const char *value,
 								return false;
 							}
 						}
-							/* Allow change if admin should override */
-						assign_msglvl(&old_int_value, *conf->variable,
-									  true, source);
-						if (new_int_value < old_int_value)
+
+						/* Allow change if admin should override */
+						if (new_value < var_value)
 						{
 							if (source < PGC_S_UNPRIVILEGED &&
 								record->source > PGC_S_UNPRIVILEGED)
@@ -4652,6 +4660,40 @@ assign_log_error_verbosity(const char *newval, bool doit, GucSource source)
 	return newval;				/* OK */
 }
 
+static const char *
+assign_log_statement(const char *newval, bool doit, GucSource source)
+{
+	return (assign_log_stmtlvl((int *)&log_statement, newval, doit, source));
+}
+
+static const char *
+assign_log_stmtlvl(int *var, const char *newval, bool doit, GucSource source)
+{
+	if (strcasecmp(newval, "none") == 0)
+	{
+		if (doit)
+			(*var) = LOGSTMT_NONE;
+	}
+	else if (strcasecmp(newval, "mod") == 0)
+	{
+		if (doit)
+			(*var) = LOGSTMT_MOD;
+	}
+	else if (strcasecmp(newval, "ddl") == 0)
+	{
+		if (doit)
+			(*var) = LOGSTMT_DDL;
+	}
+	else if (strcasecmp(newval, "all") == 0)
+	{
+		if (doit)
+			(*var) = LOGSTMT_ALL;
+	}
+	else
+		return NULL;			/* fail */
+	return newval;				/* OK */
+}
+
 static bool
 assign_phony_autocommit(bool newval, bool doit, GucSource source)
 {
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 536e3de0c06..7983abbf7f0 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -191,7 +191,7 @@
 				# %s=session start timestamp
 				# %x=stop here in non-session processes
 				# %%='%'
-#log_statement = false
+#log_statement = 'none'		# none, mod, ddl, all
 #log_hostname = false
 
 
diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h
index b2520b210e6..046fe3e810e 100644
--- a/src/include/tcop/tcopprot.h
+++ b/src/include/tcop/tcopprot.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/tcop/tcopprot.h,v 1.63 2004/03/24 22:40:29 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/tcop/tcopprot.h,v 1.64 2004/04/07 05:05:50 momjian Exp $
  *
  * OLD COMMENTS
  *	  This file was created so that other c files could get the two
@@ -35,6 +35,19 @@ extern DLLIMPORT const char *debug_query_string;
 extern char *rendezvous_name;
 extern int	max_stack_depth;
 
+/* GUC-configurable parameters */
+
+typedef enum
+{
+	/* Reverse order so GUC USERLIMIT is easier */
+	LOGSTMT_ALL,				/* log all statements */
+	LOGSTMT_DDL,				/* log data definition statements */
+	LOGSTMT_MOD,				/* log modification statements, plus DDL */
+	LOGSTMT_NONE				/* log no statements */
+} LogStmtLevel;
+
+extern LogStmtLevel log_statement;
+
 #ifndef BOOTSTRAP_INCLUDE
 
 extern List *pg_parse_and_rewrite(const char *query_string,
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 597f10069fd..ca1f87b1fe1 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -7,7 +7,7 @@
  * Copyright (c) 2000-2003, PostgreSQL Global Development Group
  * Written by Peter Eisentraut <peter_e@gmx.net>.
  *
- * $PostgreSQL: pgsql/src/include/utils/guc.h,v 1.44 2004/01/19 19:04:40 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/utils/guc.h,v 1.45 2004/04/07 05:05:50 momjian Exp $
  *--------------------------------------------------------------------
  */
 #ifndef GUC_H
@@ -103,7 +103,6 @@ typedef enum
 } GucSource;
 
 /* GUC vars that are actually declared in guc.c, rather than elsewhere */
-extern bool log_statement;
 extern bool log_duration;
 extern bool Debug_print_plan;
 extern bool Debug_print_parse;
-- 
GitLab