From 3be9e8866fb7a6634f27b85eb0bcbcbf8357fbe4 Mon Sep 17 00:00:00 2001
From: Abhijit Menon-Sen <ams@2ndQuadrant.com>
Date: Sun, 27 Sep 2020 17:00:53 +0530
Subject: Support SET +=/-= syntax for numeric configuration settings

This allows SET to modify the value of PGC_INT and PGC_REAL settings
with the += and -= operations introduced earlier, by adding/subtracting
the argument (which may be an integer, a real, or a string representing
one of those two with some appropriate unit).

Examples:

    SET cpu_tuple_cost += 0.01;
    SET effective_cache_size += '2GB';
    ALTER SYSTEM SET max_worker_processes += 4;
---
 doc/src/sgml/ref/set.sgml    |   6 ++
 src/backend/utils/misc/guc.c | 114 ++++++++++++++++++++++++++++++++++-
 2 files changed, 118 insertions(+), 2 deletions(-)

diff --git a/doc/src/sgml/ref/set.sgml b/doc/src/sgml/ref/set.sgml
index e30e9b42f0..4da3e05396 100644
--- a/doc/src/sgml/ref/set.sgml
+++ b/doc/src/sgml/ref/set.sgml
@@ -49,6 +49,12 @@ SET [ SESSION | LOCAL ] TIME ZONE { <replaceable class="parameter">timezone</rep
    already present, while the latter will remove an existing value.
   </para>
 
+  <para>
+   You can also use <literal>+=</literal> and <literal>-=</literal> to change
+   numeric configuration parameters, such as <varname>cpu_tuple_cost</varname>
+   or <varname>effective_cache_size</varname>.
+  </para>
+
   <para>
    If <command>SET</command> (or equivalently <command>SET SESSION</command>)
    is issued within a transaction that is later aborted, the effects of the
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 9e56f64fec..f8e74d8991 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -7986,6 +7986,108 @@ flatten_set_variable_args(const char *name, List *args)
 	return buf.data;
 }
 
+/*
+ * alter_set_number_args
+ *		Given a parsenode List as emitted by the grammar for SET,
+ *		convert to the flat string representation used by GUC, with the
+ *		args added to or subtracted from the current numeric (integer or
+ *		real) value of the setting, depending on the desired operation
+ *
+ * The result is a palloc'd string.
+ */
+static char *
+alter_set_number_args(struct config_generic *record, VariableSetKind operation,
+					  List *args)
+{
+	StringInfoData value;
+	Node	   *arg = (Node *) linitial(args);
+	NodeTag		tag;
+	A_Const    *con;
+
+	if (!IsA(arg, A_Const))
+		elog(ERROR, "unrecognized node type: %d", (int) nodeTag(arg));
+
+	con = (A_Const *) arg;
+	tag = nodeTag(&con->val);
+
+	/*
+	 * The single constant argument may be an integer, a floating point
+	 * value, or a string representing one of those two things. Once we
+	 * have the desired change (positive or negative), we can just add
+	 * it to the current value.
+	 */
+	if (record->vartype == PGC_INT)
+	{
+		struct config_int *conf = (struct config_int *) record;
+		int64		current = *conf->variable;
+		int			delta = 0;
+
+		if (tag == T_Integer)
+			delta = intVal(&con->val);
+		else if (tag == T_String)
+		{
+			const char *value = strVal(&con->val);
+			const char *hintmsg;
+
+			if (!parse_int(value, &delta, conf->gen.flags, &hintmsg))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("invalid value for parameter \"%s\": \"%s\"",
+								record->name, value),
+						 hintmsg ? errhint("%s", _(hintmsg)) : 0));
+		}
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("invalid value for parameter \"%s\"",
+							record->name)));
+
+		delta = operation == VAR_ADD_VALUE ? delta : -delta;
+		if ((delta > 0 && current < PG_INT64_MAX - delta)
+			|| (delta < 0 && current > PG_INT64_MIN - delta))
+			current = current + delta;
+
+		initStringInfo(&value);
+		appendStringInfo(&value, INT64_FORMAT, current);
+	}
+	else if (record->vartype == PGC_REAL)
+	{
+		struct config_real *conf = (struct config_real *) record;
+		double		current = *conf->variable;
+		double		delta = 0;
+
+		if (tag == T_Float)
+			delta = floatVal(&con->val);
+		else if (tag == T_Integer)
+			delta = intVal(&con->val);
+		else if (tag == T_String)
+		{
+			const char *value = strVal(&con->val);
+			const char *hintmsg;
+
+			if (!parse_real(value, &delta, conf->gen.flags, &hintmsg))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("invalid value for parameter \"%s\": \"%s\"",
+								record->name, value),
+						 hintmsg ? errhint("%s", _(hintmsg)) : 0));
+		}
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("invalid value for parameter \"%s\"",
+							record->name)));
+
+		delta = operation == VAR_ADD_VALUE ? delta : -delta;
+		current = current + delta;
+
+		initStringInfo(&value);
+		appendStringInfo(&value, "%g", current);
+	}
+
+	return value.data;
+}
+
 /*
  * alter_set_variable_args
  *		Given a parsenode List as emitted by the grammar for SET,
@@ -8016,8 +8118,16 @@ alter_set_variable_args(const char *name, VariableSetKind operation, List *args)
 				 errmsg("unrecognized configuration parameter \"%s\"", name)));
 
 	/*
-	 * At present, this function can operate only on a list represented
-	 * as a comma-separated string.
+	 * If the setting is a number and there's only one argument, we deal
+	 * with it separately.
+	 */
+	if ((record->vartype == PGC_INT || record->vartype == PGC_REAL)
+		&& list_length(args) == 1)
+		return alter_set_number_args(record, operation, args);
+
+	/*
+	 * This function can operate only on a list represented as a
+	 * comma-separated string.
 	 */
 	if (record->vartype != PGC_STRING || (record->flags & GUC_LIST_INPUT) == 0)
 		ereport(ERROR,
-- 
2.27.0

