From 29d4b8b47ee521b573cf091b3c5794616e5a6963 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 11 May 2023 14:34:17 +0900
Subject: [PATCH v1 1/2] Revert "Replace SQLValueFunction by COERCE_SQL_SYNTAX"

This reverts commit f193883fc9cebe8fa20359b0797832837a788112.
---
 src/include/catalog/catversion.h          |  2 +-
 src/include/catalog/pg_proc.dat           | 17 -----
 src/include/executor/execExpr.h           |  8 ++
 src/include/nodes/primnodes.h             | 37 +++++++++
 src/include/utils/date.h                  |  4 +
 src/include/utils/timestamp.h             |  4 +
 src/backend/catalog/system_functions.sql  | 26 -------
 src/backend/executor/execExpr.c           | 11 +++
 src/backend/executor/execExprInterp.c     | 46 ++++++++++++
 src/backend/jit/llvm/llvmjit_expr.c       |  6 ++
 src/backend/jit/llvm/llvmjit_types.c      |  1 +
 src/backend/nodes/nodeFuncs.c             | 27 ++++++-
 src/backend/optimizer/path/costsize.c     |  1 +
 src/backend/optimizer/util/clauses.c      | 39 ++++++++--
 src/backend/parser/gram.y                 | 59 ++++++---------
 src/backend/parser/parse_expr.c           | 52 +++++++++++++
 src/backend/parser/parse_target.c         | 25 ++++++
 src/backend/utils/adt/date.c              | 75 ++++++++----------
 src/backend/utils/adt/ruleutils.c         | 92 +++++++++++------------
 src/backend/utils/adt/timestamp.c         | 66 +++++++---------
 src/test/regress/expected/expressions.out |  2 +-
 src/test/regress/sql/expressions.sql      |  2 +-
 src/tools/pgindent/typedefs.list          |  2 +
 23 files changed, 385 insertions(+), 219 deletions(-)

diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index d10cc28b0c..cdd51094cd 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -57,6 +57,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	202305121
+#define CATALOG_VERSION_NO	202305141
 
 #endif
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index b2bc81b15f..0a0f749385 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -1520,23 +1520,6 @@
 { oid => '9977', descr => 'system user name',
   proname => 'system_user', provolatile => 's', prorettype => 'text',
   proargtypes => '', prosrc => 'system_user' },
-{ oid => '9978', descr => 'current date',
-  proname => 'current_date', provolatile => 's', prorettype => 'date',
-  proargtypes => '', prosrc => 'current_date' },
-{ oid => '9979', descr => 'current time',
-  proname => 'current_time', proisstrict => 'f', provolatile => 's',
-  prorettype => 'timetz', proargtypes => 'int4', prosrc => 'current_time' },
-{ oid => '9980', descr => 'current timestamp',
-  proname => 'current_timestamp', proisstrict => 'f', provolatile => 's',
-  prorettype => 'timestamptz', proargtypes => 'int4',
-  prosrc => 'current_timestamp' },
-{ oid => '9981', descr => 'local time',
-  proname => 'localtime', proisstrict => 'f', provolatile => 's',
-  prorettype => 'time', proargtypes => 'int4', prosrc => 'sql_localtime' },
-{ oid => '9982', descr => 'local timestamp',
-  proname => 'localtimestamp', proisstrict => 'f', provolatile => 's',
-  prorettype => 'timestamp', proargtypes => 'int4',
-  prosrc => 'sql_localtimestamp' },
 
 { oid => '744',
   proname => 'array_eq', prorettype => 'bool',
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 157b0d85f2..048573c2bc 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -171,6 +171,7 @@ typedef enum ExprEvalOp
 	EEOP_DISTINCT,
 	EEOP_NOT_DISTINCT,
 	EEOP_NULLIF,
+	EEOP_SQLVALUEFUNCTION,
 	EEOP_CURRENTOFEXPR,
 	EEOP_NEXTVALUEEXPR,
 	EEOP_ARRAYEXPR,
@@ -418,6 +419,12 @@ typedef struct ExprEvalStep
 			FunctionCallInfo fcinfo_data_in;
 		}			iocoerce;
 
+		/* for EEOP_SQLVALUEFUNCTION */
+		struct
+		{
+			SQLValueFunction *svf;
+		}			sqlvaluefunction;
+
 		/* for EEOP_NEXTVALUEEXPR */
 		struct
 		{
@@ -769,6 +776,7 @@ extern void ExecEvalParamExec(ExprState *state, ExprEvalStep *op,
 							  ExprContext *econtext);
 extern void ExecEvalParamExtern(ExprState *state, ExprEvalStep *op,
 								ExprContext *econtext);
+extern void ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalCurrentOfExpr(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalNextValueExpr(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalRowNull(ExprState *state, ExprEvalStep *op,
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index be9c29f0bf..7ade0d83f7 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1445,6 +1445,43 @@ typedef struct MinMaxExpr
 	int			location;
 } MinMaxExpr;
 
+/*
+ * SQLValueFunction - parameterless functions with special grammar productions
+ *
+ * The SQL standard categorizes some of these as <datetime value function>
+ * and others as <general value specification>.  We call 'em SQLValueFunctions
+ * for lack of a better term.  We store type and typmod of the result so that
+ * some code doesn't need to know each function individually, and because
+ * we would need to store typmod anyway for some of the datetime functions.
+ * Note that currently, all variants return non-collating datatypes, so we do
+ * not need a collation field; also, all these functions are stable.
+ */
+typedef enum SQLValueFunctionOp
+{
+	SVFOP_CURRENT_DATE,
+	SVFOP_CURRENT_TIME,
+	SVFOP_CURRENT_TIME_N,
+	SVFOP_CURRENT_TIMESTAMP,
+	SVFOP_CURRENT_TIMESTAMP_N,
+	SVFOP_LOCALTIME,
+	SVFOP_LOCALTIME_N,
+	SVFOP_LOCALTIMESTAMP,
+	SVFOP_LOCALTIMESTAMP_N
+} SQLValueFunctionOp;
+
+typedef struct SQLValueFunction
+{
+	Expr		xpr;
+	SQLValueFunctionOp op;		/* which function this is */
+	/*
+	 * Result type/typmod.  Type is fully determined by "op", so no need to
+	 * include this Oid in the query jumbling.
+	 */
+	Oid			type pg_node_attr(query_jumble_ignore);
+	int32		typmod;
+	int			location;		/* token location, or -1 if unknown */
+} SQLValueFunction;
+
 /*
  * XmlExpr - various SQL/XML functions requiring special grammar productions
  *
diff --git a/src/include/utils/date.h b/src/include/utils/date.h
index 5fd886b3db..97e1a02121 100644
--- a/src/include/utils/date.h
+++ b/src/include/utils/date.h
@@ -96,6 +96,7 @@ TimeTzADTPGetDatum(const TimeTzADT *X)
 
 
 /* date.c */
+extern int32 anytime_typmod_check(bool istz, int32 typmod);
 extern double date2timestamp_no_overflow(DateADT dateVal);
 extern Timestamp date2timestamp_opt_overflow(DateADT dateVal, int *overflow);
 extern TimestampTz date2timestamptz_opt_overflow(DateADT dateVal, int *overflow);
@@ -103,6 +104,9 @@ extern int32 date_cmp_timestamp_internal(DateADT dateVal, Timestamp dt2);
 extern int32 date_cmp_timestamptz_internal(DateADT dateVal, TimestampTz dt2);
 
 extern void EncodeSpecialDate(DateADT dt, char *str);
+extern DateADT GetSQLCurrentDate(void);
+extern TimeTzADT *GetSQLCurrentTime(int32 typmod);
+extern TimeADT GetSQLLocalTime(int32 typmod);
 extern int	time2tm(TimeADT time, struct pg_tm *tm, fsec_t *fsec);
 extern int	timetz2tm(TimeTzADT *time, struct pg_tm *tm, fsec_t *fsec, int *tzp);
 extern int	tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result);
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index edd59dc432..c4dd96c8c9 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -95,7 +95,11 @@ extern PGDLLIMPORT TimestampTz PgReloadTime;
 
 /* Internal routines (not fmgr-callable) */
 
+extern int32 anytimestamp_typmod_check(bool istz, int32 typmod);
+
 extern TimestampTz GetCurrentTimestamp(void);
+extern TimestampTz GetSQLCurrentTimestamp(int32 typmod);
+extern Timestamp GetSQLLocalTimestamp(int32 typmod);
 extern void TimestampDifference(TimestampTz start_time, TimestampTz stop_time,
 								long *secs, int *microsecs);
 extern long TimestampDifferenceMilliseconds(TimestampTz start_time,
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index b7c65ea37d..07c0d89c4f 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -601,32 +601,6 @@ LANGUAGE internal
 STRICT IMMUTABLE PARALLEL SAFE
 AS 'unicode_is_normalized';
 
--- Functions with SQL-mandated special syntax and some defaults.
-CREATE OR REPLACE FUNCTION
-  "current_time"(int4 DEFAULT NULL)
- RETURNS timetz
- LANGUAGE internal
- STABLE PARALLEL SAFE
-AS 'current_time';
-CREATE OR REPLACE FUNCTION
-  "current_timestamp"(int4 DEFAULT NULL)
- RETURNS timestamptz
- LANGUAGE internal
- STABLE PARALLEL SAFE
- AS 'current_timestamp';
-CREATE OR REPLACE FUNCTION
-  "localtime"(int4 DEFAULT NULL)
- RETURNS time
- LANGUAGE internal
- STABLE PARALLEL SAFE
- AS 'sql_localtime';
-CREATE OR REPLACE FUNCTION
-  "localtimestamp"(int4 DEFAULT NULL)
- RETURNS timestamp
- LANGUAGE internal
- STABLE PARALLEL SAFE
- AS 'sql_localtimestamp';
-
 --
 -- The default permissions for functions mean that anyone can execute them.
 -- A number of functions shouldn't be executable by just anyone, but rather
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index dcf56446c7..bf257a41c8 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -2213,6 +2213,17 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				break;
 			}
 
+		case T_SQLValueFunction:
+			{
+				SQLValueFunction *svf = (SQLValueFunction *) node;
+
+				scratch.opcode = EEOP_SQLVALUEFUNCTION;
+				scratch.d.sqlvaluefunction.svf = svf;
+
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
 		case T_XmlExpr:
 			{
 				XmlExpr    *xexpr = (XmlExpr *) node;
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index ca44d39100..d98710bfb3 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -455,6 +455,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 		&&CASE_EEOP_DISTINCT,
 		&&CASE_EEOP_NOT_DISTINCT,
 		&&CASE_EEOP_NULLIF,
+		&&CASE_EEOP_SQLVALUEFUNCTION,
 		&&CASE_EEOP_CURRENTOFEXPR,
 		&&CASE_EEOP_NEXTVALUEEXPR,
 		&&CASE_EEOP_ARRAYEXPR,
@@ -1305,6 +1306,17 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
+		EEO_CASE(EEOP_SQLVALUEFUNCTION)
+		{
+			/*
+			 * Doesn't seem worthwhile to have an inline implementation
+			 * efficiency-wise.
+			 */
+			ExecEvalSQLValueFunction(state, op);
+
+			EEO_NEXT();
+		}
+
 		EEO_CASE(EEOP_CURRENTOFEXPR)
 		{
 			/* error invocation uses space, and shouldn't ever occur */
@@ -2497,6 +2509,40 @@ ExecEvalParamExtern(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
 			 errmsg("no value found for parameter %d", paramId)));
 }
 
+/*
+ * Evaluate a SQLValueFunction expression.
+ */
+void
+ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op)
+{
+	SQLValueFunction *svf = op->d.sqlvaluefunction.svf;
+
+	*op->resnull = false;
+
+	switch (svf->op)
+	{
+		case SVFOP_CURRENT_DATE:
+			*op->resvalue = DateADTGetDatum(GetSQLCurrentDate());
+			break;
+		case SVFOP_CURRENT_TIME:
+		case SVFOP_CURRENT_TIME_N:
+			*op->resvalue = TimeTzADTPGetDatum(GetSQLCurrentTime(svf->typmod));
+			break;
+		case SVFOP_CURRENT_TIMESTAMP:
+		case SVFOP_CURRENT_TIMESTAMP_N:
+			*op->resvalue = TimestampTzGetDatum(GetSQLCurrentTimestamp(svf->typmod));
+			break;
+		case SVFOP_LOCALTIME:
+		case SVFOP_LOCALTIME_N:
+			*op->resvalue = TimeADTGetDatum(GetSQLLocalTime(svf->typmod));
+			break;
+		case SVFOP_LOCALTIMESTAMP:
+		case SVFOP_LOCALTIMESTAMP_N:
+			*op->resvalue = TimestampGetDatum(GetSQLLocalTimestamp(svf->typmod));
+			break;
+	}
+}
+
 /*
  * Raise error if a CURRENT OF expression is evaluated.
  *
diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index 2c3d64ea6e..774db57ae2 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -1549,6 +1549,12 @@ llvm_compile_expr(ExprState *state)
 					break;
 				}
 
+			case EEOP_SQLVALUEFUNCTION:
+				build_EvalXFunc(b, mod, "ExecEvalSQLValueFunction",
+								v_state, op);
+				LLVMBuildBr(b, opblocks[opno + 1]);
+				break;
+
 			case EEOP_CURRENTOFEXPR:
 				build_EvalXFunc(b, mod, "ExecEvalCurrentOfExpr",
 								v_state, op);
diff --git a/src/backend/jit/llvm/llvmjit_types.c b/src/backend/jit/llvm/llvmjit_types.c
index feb8208b79..41ac4c6f45 100644
--- a/src/backend/jit/llvm/llvmjit_types.c
+++ b/src/backend/jit/llvm/llvmjit_types.c
@@ -126,6 +126,7 @@ void	   *referenced_functions[] =
 	ExecEvalRow,
 	ExecEvalRowNotNull,
 	ExecEvalRowNull,
+	ExecEvalSQLValueFunction,
 	ExecEvalScalarArrayOp,
 	ExecEvalHashedScalarArrayOp,
 	ExecEvalSubPlan,
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index fe3a113c8f..5252efdf21 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -210,6 +210,9 @@ exprType(const Node *expr)
 		case T_MinMaxExpr:
 			type = ((const MinMaxExpr *) expr)->minmaxtype;
 			break;
+		case T_SQLValueFunction:
+			type = ((const SQLValueFunction *) expr)->type;
+			break;
 		case T_XmlExpr:
 			if (((const XmlExpr *) expr)->op == IS_DOCUMENT)
 				type = BOOLOID;
@@ -486,6 +489,8 @@ exprTypmod(const Node *expr)
 				return typmod;
 			}
 			break;
+		case T_SQLValueFunction:
+			return ((const SQLValueFunction *) expr)->typmod;
 		case T_JsonValueExpr:
 			return exprTypmod((Node *) ((const JsonValueExpr *) expr)->formatted_expr);
 		case T_JsonConstructorExpr:
@@ -930,6 +935,10 @@ exprCollation(const Node *expr)
 		case T_MinMaxExpr:
 			coll = ((const MinMaxExpr *) expr)->minmaxcollid;
 			break;
+		case T_SQLValueFunction:
+			/* Returns a non-collatable type */
+			coll = InvalidOid;
+			break;
 		case T_XmlExpr:
 
 			/*
@@ -1167,6 +1176,9 @@ exprSetCollation(Node *expr, Oid collation)
 		case T_MinMaxExpr:
 			((MinMaxExpr *) expr)->minmaxcollid = collation;
 			break;
+		case T_SQLValueFunction:
+			Assert(collation == InvalidOid);
+			break;
 		case T_XmlExpr:
 			Assert((((XmlExpr *) expr)->op == IS_XMLSERIALIZE) ?
 				   (collation == DEFAULT_COLLATION_OID) :
@@ -1468,6 +1480,10 @@ exprLocation(const Node *expr)
 			/* GREATEST/LEAST keyword should always be the first thing */
 			loc = ((const MinMaxExpr *) expr)->location;
 			break;
+		case T_SQLValueFunction:
+			/* function keyword should always be the first thing */
+			loc = ((const SQLValueFunction *) expr)->location;
+			break;
 		case T_XmlExpr:
 			{
 				const XmlExpr *xexpr = (const XmlExpr *) expr;
@@ -1789,10 +1805,10 @@ set_sa_opfuncid(ScalarArrayOpExpr *opexpr)
  * for themselves, in case additional checks should be made, or because they
  * have special rules about which parts of the tree need to be visited.
  *
- * Note: we ignore MinMaxExpr, XmlExpr, CoerceToDomain, and NextValueExpr
- * nodes, because they do not contain SQL function OIDs.  However, they can
- * invoke SQL-visible functions, so callers should take thought about how
- * to treat them.
+ * Note: we ignore MinMaxExpr, SQLValueFunction, XmlExpr, CoerceToDomain,
+ * and NextValueExpr nodes, because they do not contain SQL function OIDs.
+ * However, they can invoke SQL-visible functions, so callers should take
+ * thought about how to treat them.
  */
 bool
 check_functions_in_node(Node *node, check_function_callback checker,
@@ -2008,6 +2024,7 @@ expression_tree_walker_impl(Node *node,
 		case T_Const:
 		case T_Param:
 		case T_CaseTestExpr:
+		case T_SQLValueFunction:
 		case T_CoerceToDomainValue:
 		case T_SetToDefault:
 		case T_CurrentOfExpr:
@@ -2836,6 +2853,7 @@ expression_tree_mutator_impl(Node *node,
 			break;
 		case T_Param:
 		case T_CaseTestExpr:
+		case T_SQLValueFunction:
 		case T_JsonFormat:
 		case T_CoerceToDomainValue:
 		case T_SetToDefault:
@@ -3797,6 +3815,7 @@ raw_expression_tree_walker_impl(Node *node,
 		case T_JsonFormat:
 		case T_SetToDefault:
 		case T_CurrentOfExpr:
+		case T_SQLValueFunction:
 		case T_Integer:
 		case T_Float:
 		case T_Boolean:
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 0a2562c149..e60603df81 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -4606,6 +4606,7 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
 		}
 	}
 	else if (IsA(node, MinMaxExpr) ||
+			 IsA(node, SQLValueFunction) ||
 			 IsA(node, XmlExpr) ||
 			 IsA(node, CoerceToDomain) ||
 			 IsA(node, NextValueExpr))
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 11269fee3e..7f453b04f8 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -412,6 +412,12 @@ contain_mutable_functions_walker(Node *node, void *context)
 		/* Check all subnodes */
 	}
 
+	if (IsA(node, SQLValueFunction))
+	{
+		/* all variants of SQLValueFunction are stable */
+		return true;
+	}
+
 	if (IsA(node, NextValueExpr))
 	{
 		/* NextValueExpr is volatile */
@@ -560,8 +566,8 @@ contain_volatile_functions_walker(Node *node, void *context)
 
 	/*
 	 * See notes in contain_mutable_functions_walker about why we treat
-	 * MinMaxExpr, XmlExpr, and CoerceToDomain as immutable.  Hence, none of
-	 * them are of interest here.
+	 * MinMaxExpr, XmlExpr, and CoerceToDomain as immutable, while
+	 * SQLValueFunction is stable.  Hence, none of them are of interest here.
 	 */
 
 	/* Recurse to check arguments */
@@ -606,9 +612,10 @@ contain_volatile_functions_not_nextval_walker(Node *node, void *context)
 
 	/*
 	 * See notes in contain_mutable_functions_walker about why we treat
-	 * MinMaxExpr, XmlExpr, and CoerceToDomain as immutable.  Hence, none of
-	 * them are of interest here.  Also, since we're intentionally ignoring
-	 * nextval(), presumably we should ignore NextValueExpr.
+	 * MinMaxExpr, XmlExpr, and CoerceToDomain as immutable, while
+	 * SQLValueFunction is stable.  Hence, none of them are of interest here.
+	 * Also, since we're intentionally ignoring nextval(), presumably we
+	 * should ignore NextValueExpr.
 	 */
 
 	/* Recurse to check arguments */
@@ -754,8 +761,8 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
 	 * (Note: in principle that's wrong because a domain constraint could
 	 * contain a parallel-unsafe function; but useful constraints probably
 	 * never would have such, and assuming they do would cripple use of
-	 * parallel query in the presence of domain types.)  NextValueExpr is
-	 * parallel-unsafe.
+	 * parallel query in the presence of domain types.)  SQLValueFunction
+	 * should be safe in all cases.  NextValueExpr is parallel-unsafe.
 	 */
 	if (IsA(node, CoerceToDomain))
 	{
@@ -1202,6 +1209,7 @@ contain_leaked_vars_walker(Node *node, void *context)
 		case T_CaseExpr:
 		case T_CaseTestExpr:
 		case T_RowExpr:
+		case T_SQLValueFunction:
 		case T_NullTest:
 		case T_BooleanTest:
 		case T_NextValueExpr:
@@ -3243,6 +3251,23 @@ eval_const_expressions_mutator(Node *node,
 				newcoalesce->location = coalesceexpr->location;
 				return (Node *) newcoalesce;
 			}
+		case T_SQLValueFunction:
+			{
+				/*
+				 * All variants of SQLValueFunction are stable, so if we are
+				 * estimating the expression's value, we should evaluate the
+				 * current function value.  Otherwise just copy.
+				 */
+				SQLValueFunction *svf = (SQLValueFunction *) node;
+
+				if (context->estimate)
+					return (Node *) evaluate_expr((Expr *) svf,
+												  svf->type,
+												  svf->typmod,
+												  InvalidOid);
+				else
+					return copyObject((Node *) svf);
+			}
 		case T_FieldSelect:
 			{
 				/*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index d6426f3b8e..62f521cf10 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -198,6 +198,8 @@ static Node *makeAndExpr(Node *lexpr, Node *rexpr, int location);
 static Node *makeOrExpr(Node *lexpr, Node *rexpr, int location);
 static Node *makeNotExpr(Node *expr, int location);
 static Node *makeAArrayExpr(List *elements, int location);
+static Node *makeSQLValueFunction(SQLValueFunctionOp op, int32 typmod,
+								  int location);
 static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args,
 						 List *args, int location);
 static List *mergeTableFuncParameters(List *func_args, List *columns);
@@ -15288,66 +15290,39 @@ func_expr_common_subexpr:
 				}
 			| CURRENT_DATE
 				{
-					$$ = (Node *) makeFuncCall(SystemFuncName("current_date"),
-											   NIL,
-											   COERCE_SQL_SYNTAX,
-											   @1);
+					$$ = makeSQLValueFunction(SVFOP_CURRENT_DATE, -1, @1);
 				}
 			| CURRENT_TIME
 				{
-					$$ = (Node *) makeFuncCall(SystemFuncName("current_time"),
-											   NIL,
-											   COERCE_SQL_SYNTAX,
-											   @1);
+					$$ = makeSQLValueFunction(SVFOP_CURRENT_TIME, -1, @1);
 				}
 			| CURRENT_TIME '(' Iconst ')'
 				{
-					$$ = (Node *) makeFuncCall(SystemFuncName("current_time"),
-											   list_make1(makeIntConst($3, @3)),
-											   COERCE_SQL_SYNTAX,
-											   @1);
+					$$ = makeSQLValueFunction(SVFOP_CURRENT_TIME_N, $3, @1);
 				}
 			| CURRENT_TIMESTAMP
 				{
-					$$ = (Node *) makeFuncCall(SystemFuncName("current_timestamp"),
-											   NIL,
-											   COERCE_SQL_SYNTAX,
-											   @1);
+					$$ = makeSQLValueFunction(SVFOP_CURRENT_TIMESTAMP, -1, @1);
 				}
 			| CURRENT_TIMESTAMP '(' Iconst ')'
 				{
-					$$ = (Node *) makeFuncCall(SystemFuncName("current_timestamp"),
-											   list_make1(makeIntConst($3, @3)),
-											   COERCE_SQL_SYNTAX,
-											   @1);
+					$$ = makeSQLValueFunction(SVFOP_CURRENT_TIMESTAMP_N, $3, @1);
 				}
 			| LOCALTIME
 				{
-					$$ = (Node *) makeFuncCall(SystemFuncName("localtime"),
-											   NIL,
-											   COERCE_SQL_SYNTAX,
-											   @1);
+					$$ = makeSQLValueFunction(SVFOP_LOCALTIME, -1, @1);
 				}
 			| LOCALTIME '(' Iconst ')'
 				{
-					$$ = (Node *) makeFuncCall(SystemFuncName("localtime"),
-											   list_make1(makeIntConst($3, @3)),
-											   COERCE_SQL_SYNTAX,
-											   @1);
+					$$ = makeSQLValueFunction(SVFOP_LOCALTIME_N, $3, @1);
 				}
 			| LOCALTIMESTAMP
 				{
-					$$ = (Node *) makeFuncCall(SystemFuncName("localtimestamp"),
-											   NIL,
-											   COERCE_SQL_SYNTAX,
-											   @1);
+					$$ = makeSQLValueFunction(SVFOP_LOCALTIMESTAMP, -1, @1);
 				}
 			| LOCALTIMESTAMP '(' Iconst ')'
 				{
-					$$ = (Node *) makeFuncCall(SystemFuncName("localtimestamp"),
-											   list_make1(makeIntConst($3, @3)),
-											   COERCE_SQL_SYNTAX,
-											   @1);
+					$$ = makeSQLValueFunction(SVFOP_LOCALTIMESTAMP_N, $3, @1);
 				}
 			| CURRENT_ROLE
 				{
@@ -18519,6 +18494,18 @@ makeAArrayExpr(List *elements, int location)
 	return (Node *) n;
 }
 
+static Node *
+makeSQLValueFunction(SQLValueFunctionOp op, int32 typmod, int location)
+{
+	SQLValueFunction *svf = makeNode(SQLValueFunction);
+
+	svf->op = op;
+	/* svf->type will be filled during parse analysis */
+	svf->typmod = typmod;
+	svf->location = location;
+	return (Node *) svf;
+}
+
 static Node *
 makeXmlExpr(XmlExprOp op, char *name, List *named_args, List *args,
 			int location)
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 64356436ef..f0369eee41 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -64,6 +64,8 @@ static Node *transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 static Node *transformRowExpr(ParseState *pstate, RowExpr *r, bool allowDefault);
 static Node *transformCoalesceExpr(ParseState *pstate, CoalesceExpr *c);
 static Node *transformMinMaxExpr(ParseState *pstate, MinMaxExpr *m);
+static Node *transformSQLValueFunction(ParseState *pstate,
+									   SQLValueFunction *svf);
 static Node *transformXmlExpr(ParseState *pstate, XmlExpr *x);
 static Node *transformXmlSerialize(ParseState *pstate, XmlSerialize *xs);
 static Node *transformBooleanTest(ParseState *pstate, BooleanTest *b);
@@ -250,6 +252,11 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 			result = transformMinMaxExpr(pstate, (MinMaxExpr *) expr);
 			break;
 
+		case T_SQLValueFunction:
+			result = transformSQLValueFunction(pstate,
+											   (SQLValueFunction *) expr);
+			break;
+
 		case T_XmlExpr:
 			result = transformXmlExpr(pstate, (XmlExpr *) expr);
 			break;
@@ -2220,6 +2227,51 @@ transformMinMaxExpr(ParseState *pstate, MinMaxExpr *m)
 	return (Node *) newm;
 }
 
+static Node *
+transformSQLValueFunction(ParseState *pstate, SQLValueFunction *svf)
+{
+	/*
+	 * All we need to do is insert the correct result type and (where needed)
+	 * validate the typmod, so we just modify the node in-place.
+	 */
+	switch (svf->op)
+	{
+		case SVFOP_CURRENT_DATE:
+			svf->type = DATEOID;
+			break;
+		case SVFOP_CURRENT_TIME:
+			svf->type = TIMETZOID;
+			break;
+		case SVFOP_CURRENT_TIME_N:
+			svf->type = TIMETZOID;
+			svf->typmod = anytime_typmod_check(true, svf->typmod);
+			break;
+		case SVFOP_CURRENT_TIMESTAMP:
+			svf->type = TIMESTAMPTZOID;
+			break;
+		case SVFOP_CURRENT_TIMESTAMP_N:
+			svf->type = TIMESTAMPTZOID;
+			svf->typmod = anytimestamp_typmod_check(true, svf->typmod);
+			break;
+		case SVFOP_LOCALTIME:
+			svf->type = TIMEOID;
+			break;
+		case SVFOP_LOCALTIME_N:
+			svf->type = TIMEOID;
+			svf->typmod = anytime_typmod_check(false, svf->typmod);
+			break;
+		case SVFOP_LOCALTIMESTAMP:
+			svf->type = TIMESTAMPOID;
+			break;
+		case SVFOP_LOCALTIMESTAMP_N:
+			svf->type = TIMESTAMPOID;
+			svf->typmod = anytimestamp_typmod_check(false, svf->typmod);
+			break;
+	}
+
+	return (Node *) svf;
+}
+
 static Node *
 transformXmlExpr(ParseState *pstate, XmlExpr *x)
 {
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index e77b542fd7..4702f5f79e 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1876,6 +1876,31 @@ FigureColnameInternal(Node *node, char **name)
 					return 2;
 			}
 			break;
+		case T_SQLValueFunction:
+			/* make these act like a function or variable */
+			switch (((SQLValueFunction *) node)->op)
+			{
+				case SVFOP_CURRENT_DATE:
+					*name = "current_date";
+					return 2;
+				case SVFOP_CURRENT_TIME:
+				case SVFOP_CURRENT_TIME_N:
+					*name = "current_time";
+					return 2;
+				case SVFOP_CURRENT_TIMESTAMP:
+				case SVFOP_CURRENT_TIMESTAMP_N:
+					*name = "current_timestamp";
+					return 2;
+				case SVFOP_LOCALTIME:
+				case SVFOP_LOCALTIME_N:
+					*name = "localtime";
+					return 2;
+				case SVFOP_LOCALTIMESTAMP:
+				case SVFOP_LOCALTIMESTAMP_N:
+					*name = "localtimestamp";
+					return 2;
+			}
+			break;
 		case T_XmlExpr:
 			/* make SQL/XML functions act like a regular function */
 			switch (((XmlExpr *) node)->op)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index a163fbb4ab..ae0f24de2c 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -46,6 +46,27 @@
 
 /* common code for timetypmodin and timetztypmodin */
 static int32
+anytime_typmodin(bool istz, ArrayType *ta)
+{
+	int32	   *tl;
+	int			n;
+
+	tl = ArrayGetIntegerTypmods(ta, &n);
+
+	/*
+	 * we're not too tense about good error message here because grammar
+	 * shouldn't allow wrong number of modifiers for TIME
+	 */
+	if (n != 1)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("invalid type modifier")));
+
+	return anytime_typmod_check(istz, tl[0]);
+}
+
+/* exported so parse_expr.c can use it */
+int32
 anytime_typmod_check(bool istz, int32 typmod)
 {
 	if (typmod < 0)
@@ -66,26 +87,6 @@ anytime_typmod_check(bool istz, int32 typmod)
 	return typmod;
 }
 
-static int32
-anytime_typmodin(bool istz, ArrayType *ta)
-{
-	int32	   *tl;
-	int			n;
-
-	tl = ArrayGetIntegerTypmods(ta, &n);
-
-	/*
-	 * we're not too tense about good error message here because grammar
-	 * shouldn't allow wrong number of modifiers for TIME
-	 */
-	if (n != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-				 errmsg("invalid type modifier")));
-
-	return anytime_typmod_check(istz, tl[0]);
-}
-
 /* common code for timetypmodout and timetztypmodout */
 static char *
 anytime_typmodout(bool istz, int32 typmod)
@@ -301,10 +302,10 @@ EncodeSpecialDate(DateADT dt, char *str)
 
 
 /*
- * current_date -- implements CURRENT_DATE
+ * GetSQLCurrentDate -- implements CURRENT_DATE
  */
-Datum
-current_date(PG_FUNCTION_ARGS)
+DateADT
+GetSQLCurrentDate(void)
 {
 	struct pg_tm tm;
 
@@ -330,56 +331,46 @@ current_date(PG_FUNCTION_ARGS)
 		cache_mday = tm.tm_mday;
 	}
 
-	return DateADTGetDatum(cache_date);
+	return cache_date;
 }
 
 /*
- * current_time -- implements CURRENT_TIME, CURRENT_TIME(n)
+ * GetSQLCurrentTime -- implements CURRENT_TIME, CURRENT_TIME(n)
  */
-Datum
-current_time(PG_FUNCTION_ARGS)
+TimeTzADT *
+GetSQLCurrentTime(int32 typmod)
 {
 	TimeTzADT  *result;
 	struct pg_tm tt,
 			   *tm = &tt;
 	fsec_t		fsec;
 	int			tz;
-	int32		typmod = -1;
-
-	if (!PG_ARGISNULL(0))
-		typmod = anytime_typmod_check(true, PG_GETARG_INT32(0));
 
 	GetCurrentTimeUsec(tm, &fsec, &tz);
 
 	result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
 	tm2timetz(tm, fsec, tz, result);
 	AdjustTimeForTypmod(&(result->time), typmod);
-
-	return TimeTzADTPGetDatum(result);
+	return result;
 }
 
 /*
- * sql_localtime -- implements LOCALTIME, LOCALTIME(n)
+ * GetSQLLocalTime -- implements LOCALTIME, LOCALTIME(n)
  */
-Datum
-sql_localtime(PG_FUNCTION_ARGS)
+TimeADT
+GetSQLLocalTime(int32 typmod)
 {
 	TimeADT		result;
 	struct pg_tm tt,
 			   *tm = &tt;
 	fsec_t		fsec;
 	int			tz;
-	int32		typmod = -1;
-
-	if (!PG_ARGISNULL(0))
-		typmod = anytime_typmod_check(false, PG_GETARG_INT32(0));
 
 	GetCurrentTimeUsec(tm, &fsec, &tz);
 
 	tm2time(tm, fsec, &result);
 	AdjustTimeForTypmod(&result, typmod);
-
-	return TimeADTGetDatum(result);
+	return result;
 }
 
 
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 60f9d08d5d..b815a9441d 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8254,6 +8254,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
 		case T_RowExpr:
 		case T_CoalesceExpr:
 		case T_MinMaxExpr:
+		case T_SQLValueFunction:
 		case T_XmlExpr:
 		case T_NextValueExpr:
 		case T_NullIfExpr:
@@ -9242,6 +9243,49 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_SQLValueFunction:
+			{
+				SQLValueFunction *svf = (SQLValueFunction *) node;
+
+				/*
+				 * Note: this code knows that typmod for time, timestamp, and
+				 * timestamptz just prints as integer.
+				 */
+				switch (svf->op)
+				{
+					case SVFOP_CURRENT_DATE:
+						appendStringInfoString(buf, "CURRENT_DATE");
+						break;
+					case SVFOP_CURRENT_TIME:
+						appendStringInfoString(buf, "CURRENT_TIME");
+						break;
+					case SVFOP_CURRENT_TIME_N:
+						appendStringInfo(buf, "CURRENT_TIME(%d)", svf->typmod);
+						break;
+					case SVFOP_CURRENT_TIMESTAMP:
+						appendStringInfoString(buf, "CURRENT_TIMESTAMP");
+						break;
+					case SVFOP_CURRENT_TIMESTAMP_N:
+						appendStringInfo(buf, "CURRENT_TIMESTAMP(%d)",
+										 svf->typmod);
+						break;
+					case SVFOP_LOCALTIME:
+						appendStringInfoString(buf, "LOCALTIME");
+						break;
+					case SVFOP_LOCALTIME_N:
+						appendStringInfo(buf, "LOCALTIME(%d)", svf->typmod);
+						break;
+					case SVFOP_LOCALTIMESTAMP:
+						appendStringInfoString(buf, "LOCALTIMESTAMP");
+						break;
+					case SVFOP_LOCALTIMESTAMP_N:
+						appendStringInfo(buf, "LOCALTIMESTAMP(%d)",
+										 svf->typmod);
+						break;
+				}
+			}
+			break;
+
 		case T_XmlExpr:
 			{
 				XmlExpr    *xexpr = (XmlExpr *) node;
@@ -9816,6 +9860,7 @@ looks_like_function(Node *node)
 		case T_NullIfExpr:
 		case T_CoalesceExpr:
 		case T_MinMaxExpr:
+		case T_SQLValueFunction:
 		case T_XmlExpr:
 			/* these are all accepted by func_expr_common_subexpr */
 			return true;
@@ -10217,33 +10262,6 @@ get_windowfunc_expr_helper(WindowFunc *wfunc, deparse_context *context,
 	}
 }
 
-/*
- * get_func_sql_syntax_time
- *
- * Parse back argument of SQL-syntax function call related to a time or a
- * timestamp.  These require a specific handling when their typmod is given
- * by the function caller through their SQL keyword.
- */
-static void
-get_func_sql_syntax_time(List *args, deparse_context *context)
-{
-	StringInfo	buf = context->buf;
-	Const	   *cons;
-
-	if (list_length(args) != 1)
-		return;
-
-	cons = (Const *) linitial(args);
-	Assert(IsA(cons, Const));
-
-	if (!cons->constisnull)
-	{
-		appendStringInfoString(buf, "(");
-		get_rule_expr((Node *) cons, context, false);
-		appendStringInfoString(buf, ")");
-	}
-}
-
 /*
  * get_func_sql_syntax		- Parse back a SQL-syntax function call
  *
@@ -10492,26 +10510,6 @@ get_func_sql_syntax(FuncExpr *expr, deparse_context *context)
 			appendStringInfoString(buf, "SYSTEM_USER");
 			return true;
 
-		case F_CURRENT_DATE:
-			appendStringInfoString(buf, "CURRENT_DATE");
-			return true;
-		case F_CURRENT_TIME:
-			appendStringInfoString(buf, "CURRENT_TIME");
-			get_func_sql_syntax_time(expr->args, context);
-			return true;
-		case F_CURRENT_TIMESTAMP:
-			appendStringInfoString(buf, "CURRENT_TIMESTAMP");
-			get_func_sql_syntax_time(expr->args, context);
-			return true;
-		case F_LOCALTIME:
-			appendStringInfoString(buf, "LOCALTIME");
-			get_func_sql_syntax_time(expr->args, context);
-			return true;
-		case F_LOCALTIMESTAMP:
-			appendStringInfoString(buf, "LOCALTIMESTAMP");
-			get_func_sql_syntax_time(expr->args, context);
-			return true;
-
 		case F_XMLEXISTS:
 			/* XMLEXISTS ... extra parens because args are c_expr */
 			appendStringInfoString(buf, "XMLEXISTS((");
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index aaadc68ae6..0e50aaec5a 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -83,6 +83,27 @@ static Timestamp timestamptz2timestamp(TimestampTz timestamp);
 
 /* common code for timestamptypmodin and timestamptztypmodin */
 static int32
+anytimestamp_typmodin(bool istz, ArrayType *ta)
+{
+	int32	   *tl;
+	int			n;
+
+	tl = ArrayGetIntegerTypmods(ta, &n);
+
+	/*
+	 * we're not too tense about good error message here because grammar
+	 * shouldn't allow wrong number of modifiers for TIMESTAMP
+	 */
+	if (n != 1)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("invalid type modifier")));
+
+	return anytimestamp_typmod_check(istz, tl[0]);
+}
+
+/* exported so parse_expr.c can use it */
+int32
 anytimestamp_typmod_check(bool istz, int32 typmod)
 {
 	if (typmod < 0)
@@ -103,26 +124,6 @@ anytimestamp_typmod_check(bool istz, int32 typmod)
 	return typmod;
 }
 
-static int32
-anytimestamp_typmodin(bool istz, ArrayType *ta)
-{
-	int32	   *tl;
-	int			n;
-
-	tl = ArrayGetIntegerTypmods(ta, &n);
-
-	/*
-	 * we're not too tense about good error message here because grammar
-	 * shouldn't allow wrong number of modifiers for TIMESTAMP
-	 */
-	if (n != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-				 errmsg("invalid type modifier")));
-
-	return anytimestamp_typmod_check(istz, tl[0]);
-}
-
 /* common code for timestamptypmodout and timestamptztypmodout */
 static char *
 anytimestamp_typmodout(bool istz, int32 typmod)
@@ -1594,42 +1595,33 @@ GetCurrentTimestamp(void)
 }
 
 /*
- * current_timestamp -- implements CURRENT_TIMESTAMP, CURRENT_TIMESTAMP(n)
+ * GetSQLCurrentTimestamp -- implements CURRENT_TIMESTAMP, CURRENT_TIMESTAMP(n)
  */
-Datum
-current_timestamp(PG_FUNCTION_ARGS)
+TimestampTz
+GetSQLCurrentTimestamp(int32 typmod)
 {
 	TimestampTz ts;
-	int32		typmod = -1;
-
-	if (!PG_ARGISNULL(0))
-		typmod = anytimestamp_typmod_check(true, PG_GETARG_INT32(0));
 
 	ts = GetCurrentTransactionStartTimestamp();
 	if (typmod >= 0)
 		AdjustTimestampForTypmod(&ts, typmod, NULL);
-	return TimestampTzGetDatum(ts);
+	return ts;
 }
 
 /*
- * sql_localtimestamp -- implements LOCALTIMESTAMP, LOCALTIMESTAMP(n)
+ * GetSQLLocalTimestamp -- implements LOCALTIMESTAMP, LOCALTIMESTAMP(n)
  */
-Datum
-sql_localtimestamp(PG_FUNCTION_ARGS)
+Timestamp
+GetSQLLocalTimestamp(int32 typmod)
 {
 	Timestamp	ts;
-	int32		typmod = -1;
-
-	if (!PG_ARGISNULL(0))
-		typmod = anytimestamp_typmod_check(false, PG_GETARG_INT32(0));
 
 	ts = timestamptz2timestamp(GetCurrentTransactionStartTimestamp());
 	if (typmod >= 0)
 		AdjustTimestampForTypmod(&ts, typmod, NULL);
-	return TimestampGetDatum(ts);
+	return ts;
 }
 
-
 /*
  * timeofday(*) -- returns the current time as a text.
  */
diff --git a/src/test/regress/expected/expressions.out b/src/test/regress/expected/expressions.out
index d2c6db1bd5..caeeb19674 100644
--- a/src/test/regress/expected/expressions.out
+++ b/src/test/regress/expected/expressions.out
@@ -2,7 +2,7 @@
 -- expression evaluation tests that don't fit into a more specific file
 --
 --
--- Tests for various FuncCalls with COERCE_SQL_SYNTAX.
+-- Tests for SQLValueFunction
 --
 -- current_date  (always matches because of transactional behaviour)
 SELECT date(now())::text = current_date::text;
diff --git a/src/test/regress/sql/expressions.sql b/src/test/regress/sql/expressions.sql
index d315ef5af5..e02c21f336 100644
--- a/src/test/regress/sql/expressions.sql
+++ b/src/test/regress/sql/expressions.sql
@@ -3,7 +3,7 @@
 --
 
 --
--- Tests for various FuncCalls with COERCE_SQL_SYNTAX.
+-- Tests for SQLValueFunction
 --
 
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4058b88c3..06da7cd428 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2425,6 +2425,8 @@ SQLFunctionCache
 SQLFunctionCachePtr
 SQLFunctionParseInfo
 SQLFunctionParseInfoPtr
+SQLValueFunction
+SQLValueFunctionOp
 SSL
 SSLExtensionInfoContext
 SSL_CTX
-- 
2.40.1

