From beb8d3a94a567aecb21e3ffaf7ae3a051063c2c7 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <bossartn@amazon.com>
Date: Tue, 24 Nov 2020 22:14:59 +0000
Subject: [PATCH v1 1/1] Add FORCE, IMMEDIATE, and WAIT options to CHECKPOINT.

---
 doc/src/sgml/ref/checkpoint.sgml         | 93 ++++++++++++++++++++++++++++----
 src/backend/parser/gram.y                | 36 ++++++++++++-
 src/backend/postmaster/checkpointer.c    | 47 ++++++++++++++++
 src/backend/tcop/utility.c               |  8 +--
 src/include/nodes/parsenodes.h           |  1 +
 src/include/postmaster/bgwriter.h        |  3 ++
 src/test/regress/expected/checkpoint.out | 22 ++++++++
 src/test/regress/parallel_schedule       |  9 ++--
 src/test/regress/serial_schedule         |  1 +
 src/test/regress/sql/checkpoint.sql      | 16 ++++++
 10 files changed, 213 insertions(+), 23 deletions(-)
 create mode 100644 src/test/regress/expected/checkpoint.out
 create mode 100644 src/test/regress/sql/checkpoint.sql

diff --git a/doc/src/sgml/ref/checkpoint.sgml b/doc/src/sgml/ref/checkpoint.sgml
index 2afee6d7b5..ee5b674104 100644
--- a/doc/src/sgml/ref/checkpoint.sgml
+++ b/doc/src/sgml/ref/checkpoint.sgml
@@ -21,7 +21,13 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-CHECKPOINT
+CHECKPOINT [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
+
+<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
+
+    FORCE [ <replaceable class="parameter">boolean</replaceable> ]
+    IMMEDIATE [ <replaceable class="parameter">boolean</replaceable> ]
+    WAIT [ <replaceable class="parameter">boolean</replaceable> ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -33,22 +39,24 @@ CHECKPOINT
    all data files have been updated to reflect the information in the
    log.  All data files will be flushed to disk.  Refer to
    <xref linkend="wal-configuration"/> for more details about what happens
-   during a checkpoint.
+   during a checkpoint.  Since automatic checkpoints are regularly scheduled by
+   the system (controlled by the settings in
+   <xref linkend="runtime-config-wal-checkpoints"/>), it is not necessary to use
+   the <command>CHECKPOINT</command> command during normal operation.
   </para>
 
   <para>
-   The <command>CHECKPOINT</command> command forces an immediate
-   checkpoint when the command is issued, without waiting for a
-   regular checkpoint scheduled by the system (controlled by the settings in
-   <xref linkend="runtime-config-wal-checkpoints"/>).
-   <command>CHECKPOINT</command> is not intended for use during normal
-   operation.
+   If executed during recovery, the <command>CHECKPOINT</command> command
+   will create a restartpoint (see <xref linkend="wal-configuration"/>)
+   rather than writing a new checkpoint.
   </para>
 
   <para>
-   If executed during recovery, the <command>CHECKPOINT</command> command
-   will force a restartpoint (see <xref linkend="wal-configuration"/>)
-   rather than writing a new checkpoint.
+   Note that the server may consolidate concurrently requested checkpoints or
+   restartpoints.  Such consolidated requests will contain a combined set of
+   options.  For example, if one session requested an immediate checkpoint and
+   another session requested a non-immediate checkpoint, the server may combine
+   these requests and perform one immediate checkpoint.
   </para>
 
   <para>
@@ -56,6 +64,69 @@ CHECKPOINT
   </para>
  </refsect1>
 
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>FORCE</literal></term>
+    <listitem>
+     <para>
+      Specifies that <command>CHECKPOINT</command> should create a checkpoint
+      even if no transaction log activity has occurred since the last one.  If
+      this option is disabled and no transaction log activity has occurred since
+      the last checkpoint, <command>CHECKPOINT</command> will not initiate a new
+      checkpoint.  By default, <literal>FORCE</literal> is enabled.  However,
+      this option is ignored if <command>CHECKPOINT</command> is executed during
+      recovery, as restartpoints cannot be initiated when no transaction log
+      activity has occurred.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>IMMEDIATE</literal></term>
+    <listitem>
+     <para>
+      Specifies that <command>CHECKPOINT</command> should create a checkpoint or
+      restartpoint as fast as possible.  If this option is disabled, the
+      checkpoint activity is throttled based on the setting for the
+      <xref linkend="guc-checkpoint-completion-target"/> parameter.  By default,
+      <literal>IMMEDIATE</literal> is enabled, and the checkpoint activity is
+      not throttled.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>WAIT</literal></term>
+    <listitem>
+     <para>
+      Specifies that <command>CHECKPOINT</command> should wait for the
+      checkpoint or restartpoint to complete before returning.  If this option
+      is disabled, <command>CHECKPOINT</command> requests a checkpoint or
+      restartpoint but does not wait for it to complete before returning.  By
+      default, <literal>WAIT</literal> is enabled.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">boolean</replaceable></term>
+    <listitem>
+     <para>
+      Specifies whether the selected option should be turned on or off.
+      You can write <literal>TRUE</literal>, <literal>ON</literal>, or
+      <literal>1</literal> to enable the option, and <literal>FALSE</literal>,
+      <literal>OFF</literal>, or <literal>0</literal> to disable it.  The
+      <replaceable class="parameter">boolean</replaceable> value can also
+      be omitted, in which case <literal>TRUE</literal> is assumed.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
  <refsect1>
   <title>Compatibility</title>
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index efc9c99754..b36f4a1d30 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -314,6 +314,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				transaction_mode_item
 				create_extension_opt_item alter_extension_opt_item
 
+%type <list>	opt_chkpt_option_list chkpt_option_list
+%type <defelt>	chkpt_option_elem
+%type <node>	chkpt_option_arg
+
 %type <ival>	opt_lock lock_type cast_context
 %type <str>		vac_analyze_option_name
 %type <defelt>	vac_analyze_option_elem
@@ -1777,13 +1781,43 @@ constraints_set_mode:
  * Checkpoint statement
  */
 CheckPointStmt:
-			CHECKPOINT
+			CHECKPOINT opt_chkpt_option_list
 				{
 					CheckPointStmt *n = makeNode(CheckPointStmt);
+					n->options = $2;
 					$$ = (Node *)n;
 				}
 		;
 
+opt_chkpt_option_list:
+			'(' chkpt_option_list ')'				{ $$ = $2; }
+			| /* EMPTY */							{ $$ = NIL; }
+		;
+
+chkpt_option_list:
+			chkpt_option_elem
+				{
+					$$ = list_make1($1);
+				}
+			| chkpt_option_list ',' chkpt_option_elem
+				{
+					$$ = lappend($1, $3);
+				}
+		;
+
+chkpt_option_elem:
+			NonReservedWord chkpt_option_arg
+				{
+					$$ = makeDefElem($1, $2, @1);
+				}
+		;
+
+chkpt_option_arg:
+			opt_boolean_or_string					{ $$ = (Node *) makeString($1); }
+			| NumericOnly							{ $$ = (Node *) $1; }
+			| /* EMPTY */							{ $$ = NULL; }
+		;
+
 
 /*****************************************************************************
  *
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index 429c8010ef..2cb2a73d54 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -41,6 +41,7 @@
 
 #include "access/xlog.h"
 #include "access/xlog_internal.h"
+#include "commands/defrem.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -895,6 +896,52 @@ CheckpointerShmemInit(void)
 	}
 }
 
+/*
+ * ExecCheckPointStmt
+ *		Primary entry point for CHECKPOINT commands
+ */
+void
+ExecCheckPointStmt(ParseState *pstate, CheckPointStmt *stmt)
+{
+	ListCell *lc;
+	bool immediate = true;
+	bool wait = true;
+	bool force = true;
+	int flags;
+
+	if (!superuser())
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("must be superuser to do CHECKPOINT")));
+
+	foreach(lc, stmt->options)
+	{
+		DefElem *opt = (DefElem *) lfirst(lc);
+
+		if (strcmp(opt->defname, "immediate") == 0)
+			immediate = defGetBoolean(opt);
+		else if (strcmp(opt->defname, "wait") == 0)
+			wait = defGetBoolean(opt);
+		else if (strcmp(opt->defname, "force") == 0)
+			force = defGetBoolean(opt);
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("unrecognized CHECKPOINT option \"%s\"", opt->defname),
+					 parser_errposition(pstate, opt->location)));
+	}
+
+	/* not possible to force a restartpoint */
+	if (RecoveryInProgress())
+		force = false;
+
+	flags = (immediate ? CHECKPOINT_IMMEDIATE : 0) |
+			(wait ? CHECKPOINT_WAIT : 0) |
+			(force ? CHECKPOINT_FORCE : 0);
+
+	RequestCheckpoint(flags);
+}
+
 /*
  * RequestCheckpoint
  *		Called in backend processes to request a checkpoint
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 81ac9b1cb2..4af8de0a26 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -906,13 +906,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 			break;
 
 		case T_CheckPointStmt:
-			if (!superuser())
-				ereport(ERROR,
-						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-						 errmsg("must be superuser to do CHECKPOINT")));
-
-			RequestCheckpoint(CHECKPOINT_IMMEDIATE | CHECKPOINT_WAIT |
-							  (RecoveryInProgress() ? 0 : CHECKPOINT_FORCE));
+			ExecCheckPointStmt(pstate, (CheckPointStmt *) parsetree);
 			break;
 
 		case T_ReindexStmt:
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d1f9ef29ca..f1d9d46f69 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3297,6 +3297,7 @@ typedef struct RefreshMatViewStmt
 typedef struct CheckPointStmt
 {
 	NodeTag		type;
+	List	   *options;		/* list of DefElem nodes */
 } CheckPointStmt;
 
 /* ----------------------
diff --git a/src/include/postmaster/bgwriter.h b/src/include/postmaster/bgwriter.h
index 0a5708b32e..d945009707 100644
--- a/src/include/postmaster/bgwriter.h
+++ b/src/include/postmaster/bgwriter.h
@@ -15,6 +15,8 @@
 #ifndef _BGWRITER_H
 #define _BGWRITER_H
 
+#include "nodes/parsenodes.h"
+#include "parser/parse_node.h"
 #include "storage/block.h"
 #include "storage/relfilenode.h"
 #include "storage/smgr.h"
@@ -30,6 +32,7 @@ extern double CheckPointCompletionTarget;
 extern void BackgroundWriterMain(void) pg_attribute_noreturn();
 extern void CheckpointerMain(void) pg_attribute_noreturn();
 
+extern void ExecCheckPointStmt(ParseState *pstate, CheckPointStmt *stmt);
 extern void RequestCheckpoint(int flags);
 extern void CheckpointWriteDelay(int flags, double progress);
 
diff --git a/src/test/regress/expected/checkpoint.out b/src/test/regress/expected/checkpoint.out
new file mode 100644
index 0000000000..7a92e92c7a
--- /dev/null
+++ b/src/test/regress/expected/checkpoint.out
@@ -0,0 +1,22 @@
+-- CHECKPOINT
+CHECKPOINT;
+CHECKPOINT (FORCE);
+CHECKPOINT (IMMEDIATE);
+CHECKPOINT (WAIT);
+CHECKPOINT (FORCE true, IMMEDIATE 0);
+CHECKPOINT (IMMEDIATE, WAIT off);
+CHECKPOINT (FORCE, IMMEDIATE, WAIT);
+CHECKPOINT (FORCE 1, IMMEDIATE true, WAIT on);
+CHECKPOINT (FORCE false, IMMEDIATE off, WAIT 0);
+CHECKPOINT (FORCE = false);
+ERROR:  syntax error at or near "="
+LINE 1: CHECKPOINT (FORCE = false);
+                          ^
+CHECKPOINT (IMMEDIATE 2, WAIT);
+ERROR:  immediate requires a Boolean value
+CHECKPOINT (WAIT no, FORCE no);
+ERROR:  wait requires a Boolean value
+CHECKPOINT (NONEXISTENT);
+ERROR:  unrecognized CHECKPOINT option "nonexistent"
+LINE 1: CHECKPOINT (NONEXISTENT);
+                    ^
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index ae89ed7f0b..354dda263f 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -5,10 +5,11 @@
 # this limits the number of connections needed to run the tests.
 # ----------
 
-# run tablespace by itself, and first, because it forces a checkpoint;
-# we'd prefer not to have checkpoints later in the tests because that
-# interferes with crash-recovery testing.
-test: tablespace
+# ----------
+# Run tests that force checkpoints first.  We'd prefer to not have checkpoints
+# later in the tests because that interferes with crash-recovery testing.
+# ----------
+test: checkpoint tablespace
 
 # ----------
 # The first group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 525bdc804f..49901521f4 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -1,5 +1,6 @@
 # src/test/regress/serial_schedule
 # This should probably be in an order similar to parallel_schedule.
+test: checkpoint
 test: tablespace
 test: boolean
 test: char
diff --git a/src/test/regress/sql/checkpoint.sql b/src/test/regress/sql/checkpoint.sql
new file mode 100644
index 0000000000..bc3ae1aa03
--- /dev/null
+++ b/src/test/regress/sql/checkpoint.sql
@@ -0,0 +1,16 @@
+-- CHECKPOINT
+
+CHECKPOINT;
+CHECKPOINT (FORCE);
+CHECKPOINT (IMMEDIATE);
+CHECKPOINT (WAIT);
+CHECKPOINT (FORCE true, IMMEDIATE 0);
+CHECKPOINT (IMMEDIATE, WAIT off);
+CHECKPOINT (FORCE, IMMEDIATE, WAIT);
+CHECKPOINT (FORCE 1, IMMEDIATE true, WAIT on);
+CHECKPOINT (FORCE false, IMMEDIATE off, WAIT 0);
+
+CHECKPOINT (FORCE = false);
+CHECKPOINT (IMMEDIATE 2, WAIT);
+CHECKPOINT (WAIT no, FORCE no);
+CHECKPOINT (NONEXISTENT);
-- 
2.16.6

