From 3b316fe48bd8a5c1edeb7160f03a97b2bc8da11a Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Mon, 29 Jun 2026 15:35:56 +0800
Subject: [PATCH v9 3/3] CAST(expr AS type FORMAT 'template')

This enables the CAST(expression AS type FORMAT template) syntax.

For Binary-coercible casts: Specifying a format template for binary-coercible
casts (e.g., text to text) will now raise an error.

No pg_cast entries have been modified at this stage. Adding these
formatting functions to pg_cast has complex implications requiring further
discussion (see [1]) and is not strictly necessary to achieve the current
behavior.

Under the hood, CAST FORMAT is transformed into a FuncExpr node.  Since only
function to_char, to_date, to_number, and to_timestamp support formatting, this
feature is currently limited to the input and result types compatible with these
functions.

[1]: https://postgr.es/m/CACJufxF4OW=x2rCwa+ZmcgopDwGKDXha09qTfTpCj3QSTG6Y9Q@mail.gmail.com

context: https://wiki.postgresql.org/wiki/PostgreSQL_vs_SQL_Standard#Major_features_simply_not_implemented_yet
discussion: https://postgr.es/m/CACJufxGqm7cYQ5C65Eoh1z-f+aMdhv9_7V=NoLH_p6uuyesi6A@mail.gmail.com
commitfest: https://commitfest.postgresql.org/patch/5957
---
 src/backend/parser/gram.y                     |  19 +
 src/backend/parser/parse_agg.c                |  11 +
 src/backend/parser/parse_coerce.c             | 250 ++++++++++++-
 src/backend/parser/parse_expr.c               |  50 +++
 src/backend/parser/parse_func.c               |   3 +
 src/backend/parser/parse_type.c               |  33 ++
 src/backend/parser/parse_utilcmd.c            |   1 +
 src/backend/utils/adt/ruleutils.c             |  20 +
 src/backend/utils/fmgr/fmgr.c                 |  39 ++
 src/include/fmgr.h                            |   3 +
 src/include/nodes/parsenodes.h                |   1 +
 src/include/parser/parse_coerce.h             |   6 +
 src/include/parser/parse_node.h               |   1 +
 src/include/parser/parse_type.h               |   3 +
 src/include/utils/lsyscache.h                 |   4 +
 src/test/regress/expected/cast.out            | 345 ++++++++++++++++++
 .../regress/expected/collate.linux.utf8.out   |  39 ++
 src/test/regress/expected/create_type.out     |  78 ++++
 src/test/regress/expected/horology.out        | 133 +++++++
 src/test/regress/expected/interval.out        |  12 +
 src/test/regress/expected/numeric.out         | 147 +++++++-
 src/test/regress/parallel_schedule            |   2 +-
 src/test/regress/sql/cast.sql                 | 123 +++++++
 src/test/regress/sql/collate.linux.utf8.sql   |   8 +
 src/test/regress/sql/create_type.sql          |  58 +++
 src/test/regress/sql/horology.sql             |  42 +++
 src/test/regress/sql/interval.sql             |   2 +
 src/test/regress/sql/numeric.sql              |  26 +-
 28 files changed, 1444 insertions(+), 15 deletions(-)
 create mode 100644 src/test/regress/expected/cast.out
 create mode 100644 src/test/regress/sql/cast.sql

diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ff4e1388c55..ce1017bddfe 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -157,6 +157,9 @@ static RawStmt *makeRawStmt(Node *stmt, int stmt_location);
 static void updateRawStmtEnd(RawStmt *rs, int end_location);
 static Node *makeColumnRef(char *colname, List *indirection,
 						   int location, core_yyscan_t yyscanner);
+static Node *makeFormattedTypeCast(Node *arg, TypeName *typename,
+								   Node *format,
+								   int location);
 static Node *makeTypeCast(Node *arg, TypeName *typename, int location);
 static Node *makeStringConstCast(char *str, int location, TypeName *typename);
 static Node *makeIntConst(int val, int location);
@@ -16787,6 +16790,8 @@ func_expr_common_subexpr:
 				}
 			| CAST '(' a_expr AS Typename ')'
 				{ $$ = makeTypeCast($3, $5, @1); }
+			| CAST '(' a_expr AS Typename FORMAT a_expr ')'
+				{ $$ = makeFormattedTypeCast($3, $5, $7, @1); }
 			| EXTRACT '(' extract_list ')'
 				{
 					$$ = (Node *) makeFuncCall(SystemFuncName("extract"),
@@ -19943,12 +19948,26 @@ makeColumnRef(char *colname, List *indirection,
 	return (Node *) c;
 }
 
+static Node *
+makeFormattedTypeCast(Node *arg, TypeName *typename, Node *format, int location)
+{
+	TypeCast   *n = makeNode(TypeCast);
+
+	n->arg = arg;
+	n->typeName = typename;
+	n->format = format;
+	n->location = location;
+
+	return (Node *) n;
+}
+
 static Node *
 makeTypeCast(Node *arg, TypeName *typename, int location)
 {
 	TypeCast   *n = makeNode(TypeCast);
 
 	n->arg = arg;
+	n->format = NULL;
 	n->typeName = typename;
 	n->location = location;
 	return (Node *) n;
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index acb933392de..486dd573bbe 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -600,6 +600,14 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 
 			break;
 
+		case EXPR_KIND_TYPECAST_FORMAT:
+			if (isAgg)
+				err = _("aggregate functions are not allowed in CAST FORMAT expressions");
+			else
+				err = _("grouping operations are not allowed in CAST FORMAT expressions");
+
+			break;
+
 			/*
 			 * There is intentionally no default: case here, so that the
 			 * compiler will warn if we add a new ParseExprKind without
@@ -1045,6 +1053,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_FOR_PORTION:
 			err = _("window functions are not allowed in FOR PORTION OF expressions");
 			break;
+		case EXPR_KIND_TYPECAST_FORMAT:
+			err = _("window functions are not allowed in CAST FORMAT expressions");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index e2edde40fc9..c62317f4526 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -23,6 +23,7 @@
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parse_coerce.h"
+#include "parser/parse_func.h"
 #include "parser/parse_relation.h"
 #include "parser/parse_type.h"
 #include "utils/builtins.h"
@@ -56,7 +57,12 @@ static Node *coerceUnknownConst(ParseState *pstate, Node *node,
 								Oid inputTypeId,
 								Oid targetTypeId, int32 targetTypeMod,
 								CoercionContext ccontext, CoercionForm cformat,
-								int location);
+								Node *format, int location);
+static Node *coerce_type_with_format(ParseState *pstate, Node *node, Node *format,
+									 Oid inputTypeId, Oid targetTypeId, int32 targetTypeMod,
+									 CoercionContext ccontext, CoercionForm cformat,
+									 Oid formatfunc, int location);
+static Oid	get_coerce_type_formatfunc(Node *expr, Oid exprtype, Oid targettype);
 
 
 /*
@@ -136,6 +142,65 @@ coerce_to_target_type(ParseState *pstate, Node *expr, Oid exprtype,
 	return result;
 }
 
+/*
+ * format - cast format template expression, this typically is NULL, but it's
+ * for CAST(expr as type FORMAT 'format_expr') construct.
+*/
+Node *
+coerce_to_target_type_fmt(ParseState *pstate, Node *expr, Oid exprtype,
+						  Oid targettype, int32 targettypmod,
+						  CoercionContext ccontext,
+						  CoercionForm cformat,
+						  int location,
+						  Node *format)
+{
+	Oid			formatfunc = InvalidOid;
+	Node	   *result;
+
+	if (!can_coerce_type(1, &exprtype, &targettype, ccontext))
+		return NULL;
+
+	formatfunc = get_coerce_type_formatfunc(expr, exprtype,
+											targettype);
+
+	if (!OidIsValid(formatfunc))
+		return NULL;
+
+	if (exprType(expr) == UNKNOWNOID && IsA(expr, Const) &&
+		IsA(format, Const))
+		result = coerceUnknownConst(pstate,
+									expr,
+									exprtype,
+									targettype,
+									targettypmod,
+									ccontext,
+									cformat,
+									format,
+									location);
+	else
+		result = coerce_type_with_format(pstate, expr, format,
+										 exprtype,
+										 targettype,
+										 targettypmod,
+										 ccontext,
+										 cformat,
+										 formatfunc,
+										 location);
+
+	/*
+	 * If the target is a fixed-length type, it may need a length coercion as
+	 * well as a type coercion.  If we find ourselves adding both, force the
+	 * inner coercion node to implicit display form.
+	 */
+	result = coerce_type_typmod(result,
+								targettype, targettypmod,
+								ccontext, cformat, location,
+								(result != expr && !IsA(result, Const)));
+
+	return result;
+}
+
+
 
 /*
  * coerce_type()
@@ -243,6 +308,7 @@ coerce_type(ParseState *pstate, Node *node,
 								  targetTypeMod,
 								  ccontext,
 								  cformat,
+								  NULL,
 								  location);
 
 	if (IsA(node, Param) &&
@@ -425,7 +491,7 @@ static Node *
 coerceUnknownConst(ParseState *pstate, Node *node, Oid inputTypeId,
 				   Oid targetTypeId, int32 targetTypeMod,
 				   CoercionContext ccontext, CoercionForm cformat,
-				   int location)
+				   Node *format, int location)
 {
 	Node	   *result;
 
@@ -505,14 +571,38 @@ coerceUnknownConst(ParseState *pstate, Node *node, Oid inputTypeId,
 	 * We assume here that UNKNOWN's internal representation is the same as
 	 * CSTRING.
 	 */
-	if (!con->constisnull)
-		newcon->constvalue = stringTypeDatum(baseType,
-											 DatumGetCString(con->constvalue),
-											 inputTypeMod);
+	if (!format)
+	{
+		if (!con->constisnull)
+			newcon->constvalue = stringTypeDatum(baseType,
+												 DatumGetCString(con->constvalue),
+												 inputTypeMod);
+		else
+			newcon->constvalue = stringTypeDatum(baseType,
+												 NULL,
+												 inputTypeMod);
+	}
 	else
-		newcon->constvalue = stringTypeDatum(baseType,
-											 NULL,
-											 inputTypeMod);
+	{
+		Const	   *fmtcon;
+
+		Assert(IsA(format, Const));
+
+		fmtcon = (Const *) format;
+
+		if (!con->constisnull)
+			newcon->constvalue = stringTypeDatumWithFormat(baseType,
+														   DatumGetCString(con->constvalue),
+														   fmtcon->constvalue,
+														   fmtcon->constisnull,
+														   fmtcon->constcollid);
+		else
+			newcon->constvalue = stringTypeDatumWithFormat(baseType,
+														   NULL,
+														   fmtcon->constvalue,
+														   fmtcon->constisnull,
+														   fmtcon->constcollid);
+	}
 
 	/*
 	 * If it's a varlena value, force it to be in non-expanded (non-toasted)
@@ -567,6 +657,111 @@ coerceUnknownConst(ParseState *pstate, Node *node, Oid inputTypeId,
 	return result;
 }
 
+/*
+ * "node" may contain CollateExpr
+*/
+static Node *
+coerce_type_with_format(ParseState *pstate, Node *node, Node *format,
+						Oid inputTypeId, Oid targetTypeId, int32 targetTypeMod,
+						CoercionContext ccontext, CoercionForm cformat,
+						Oid formatfunc, int location)
+{
+	HeapTuple	tp;
+	Form_pg_proc procstruct;
+	FuncExpr   *fexpr;
+	List	   *args;
+	Oid			baseTypeId;
+	int32		baseTypeMod;
+	Node	   *result;
+	bool		format_for_input;
+	Node	   *expr = node;
+	Node	   *origexpr = node;
+
+	tp = SearchSysCache1(PROCOID, ObjectIdGetDatum(formatfunc));
+	if (!HeapTupleIsValid(tp))
+		elog(ERROR, "cache lookup failed for function %u", formatfunc);
+	procstruct = (Form_pg_proc) GETSTRUCT(tp);
+
+	/*
+	 * These Asserts essentially check that function is a legal FORMAT
+	 * coercion function.  We can't make the seemingly obvious tests on
+	 * prorettype and proargtypes[0], even in the COERCION_PATH_FUNC case,
+	 * because of various binary-compatibility cases. TODO: comments need to
+	 * change!
+	 *
+	 * XXX the above comment need change TODO
+	 *
+	 */
+	/* Assert(targetTypeId == procstruct->prorettype); */
+	Assert(!procstruct->proretset);
+	Assert(procstruct->prokind == PROKIND_FUNCTION);
+	Assert(procstruct->pronargs == 2);
+	/* Assert(procstruct->proargtypes.values[0] == exprType(node)); */
+	Assert(procstruct->prorettype == TEXTOID || procstruct->proargtypes.values[1] == TEXTOID);
+
+	if (procstruct->prorettype == TEXTOID)
+		format_for_input = false;
+	else
+		format_for_input = true;
+
+	ReleaseSysCache(tp);
+
+	while (expr && IsA(expr, CollateExpr))
+		expr = (Node *) ((CollateExpr *) expr)->arg;
+
+	/*
+	 * input type format function require the source expression be type TEXT
+	 */
+	if (format_for_input)
+		expr = coerce_type(pstate, expr,
+						   inputTypeId,
+						   TEXTOID, -1,
+						   COERCION_IMPLICIT,
+						   COERCE_IMPLICIT_CAST,
+						   -1);
+
+	if (origexpr && IsA(origexpr, CollateExpr))
+	{
+		CollateExpr *coll = castNode(CollateExpr, origexpr);
+
+		CollateExpr *newcoll = makeNode(CollateExpr);
+
+		newcoll->arg = (Expr *) expr;
+		newcoll->collOid = coll->collOid;
+		newcoll->location = coll->location;
+		expr = (Node *) newcoll;
+	}
+
+	args = list_make1(expr);
+	args = lappend(args, format);
+
+	baseTypeMod = targetTypeMod;
+	baseTypeId = getBaseTypeAndTypmod(targetTypeId, &baseTypeMod);
+
+	fexpr = makeFuncExpr(formatfunc,
+						 baseTypeId,
+						 args,
+						 InvalidOid,
+						 InvalidOid,
+						 COERCE_SQL_SYNTAX);
+	fexpr->location = location;
+
+	result = (Node *) fexpr;
+
+	if (baseTypeId != targetTypeId)
+		result = coerce_to_domain(result,
+								  baseTypeId,
+								  baseTypeMod,
+								  targetTypeId,
+								  ccontext,
+								  cformat,
+								  location,
+								  true);
+
+	return result;
+}
+
+
 
 /*
  * can_coerce_type()
@@ -3425,3 +3620,40 @@ typeIsOfTypedTable(Oid reltypeId, Oid reloftypeId)
 
 	return result;
 }
+
+static Oid
+get_coerce_type_formatfunc(Node *expr, Oid exprtype, Oid targettype)
+{
+	Oid			baseTypeId = getBaseType(exprtype);
+	Oid			targetbaseTypeId = getBaseType(targettype);
+	char		src_category;
+	bool		preferred1;
+	char		dst_category;
+	bool		preferred2;
+	Oid			formatfunc = InvalidOid;
+
+	while (expr && IsA(expr, CollateExpr))
+		expr = (Node *) ((CollateExpr *) expr)->arg;
+
+	if (IsA(expr, Const) && exprtype == UNKNOWNOID)
+		baseTypeId = TEXTOID;
+
+	if (baseTypeId == targetbaseTypeId)
+		return InvalidOid;
+
+	get_type_category_preferred(baseTypeId, &src_category, &preferred1);
+
+	get_type_category_preferred(targetbaseTypeId, &dst_category, &preferred2);
+
+	if (src_category == TYPCATEGORY_STRING && dst_category == TYPCATEGORY_STRING)
+		return InvalidOid;		/* TODO: maybe not return NULL */
+	else if (src_category != TYPCATEGORY_STRING && dst_category != TYPCATEGORY_STRING)
+		return InvalidOid;
+
+	if (dst_category == TYPCATEGORY_STRING)
+		getTypeOutputFormatInfo(baseTypeId, &formatfunc, true);
+	else
+		getTypeInputFormatInfo(targetbaseTypeId, &formatfunc, true);
+
+	return formatfunc;
+}
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 9adc9d4c0f6..f05fc2d0134 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -579,6 +579,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 		case EXPR_KIND_GENERATED_COLUMN:
 		case EXPR_KIND_CYCLE_MARK:
 		case EXPR_KIND_PROPGRAPH_PROPERTY:
+		case EXPR_KIND_TYPECAST_FORMAT:
 			/* okay */
 			break;
 
@@ -1892,6 +1893,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_FOR_PORTION:
 			err = _("cannot use subquery in FOR PORTION OF expression");
 			break;
+		case EXPR_KIND_TYPECAST_FORMAT:
+			err = _("cannot use subquery in CAST FORMAT expression");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -2795,6 +2799,50 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
 	if (location < 0)
 		location = tc->typeName->location;
 
+	if (tc->format)
+	{
+		Node	   *format = NULL;
+
+		int			fmtlocation = exprLocation(tc->format);
+
+		format = transformExpr(pstate, tc->format, EXPR_KIND_TYPECAST_FORMAT);
+
+		format = coerce_to_target_type(pstate, format,
+									   exprType(format), TEXTOID, -1,
+									   COERCION_IMPLICIT,
+									   COERCE_IMPLICIT_CAST,
+									   fmtlocation);
+		if (format == NULL)
+			ereport(ERROR,
+					errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					errmsg("cannot coerce FORMAT expression to type %s",
+						   format_type_be(TEXTOID)),
+					parser_errposition(pstate, fmtlocation));
+
+		assign_expr_collations(pstate, expr);
+
+		assign_expr_collations(pstate, format);
+
+		result = coerce_to_target_type_fmt(pstate, expr, inputType,
+										   targetType, targetTypmod,
+										   COERCION_EXPLICIT,
+										   COERCE_EXPLICIT_CAST,
+										   location,
+										   format);
+		if (result)
+			return result;
+
+		if (inputType == UNKNOWNOID && IsA(expr, Const))
+			inputType = TEXTOID;
+
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot cast type %s to %s using CAST( ... AS ... FORMAT ...)",
+					   format_type_be(inputType),
+					   format_type_be(targetType)),
+				parser_coercion_errposition(pstate, exprLocation(format), format));
+	}
+
 	result = coerce_to_target_type(pstate, expr, inputType,
 								   targetType, targetTypmod,
 								   COERCION_EXPLICIT,
@@ -3253,6 +3301,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "CYCLE";
 		case EXPR_KIND_PROPGRAPH_PROPERTY:
 			return "property definition expression";
+		case EXPR_KIND_TYPECAST_FORMAT:
+			return "CAST FORMAT expression";
 		case EXPR_KIND_FOR_PORTION:
 			return "FOR PORTION OF";
 
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index fb306c05112..918353c43ab 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2797,6 +2797,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
 		case EXPR_KIND_FOR_PORTION:
 			err = _("set-returning functions are not allowed in FOR PORTION OF expressions");
 			break;
+		case EXPR_KIND_TYPECAST_FORMAT:
+			err = _("set-returning functions are not allowed in CAST FORMAT expressions");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index bb7eccde9fd..1157b7f5aad 100644
--- a/src/backend/parser/parse_type.c
+++ b/src/backend/parser/parse_type.c
@@ -660,6 +660,39 @@ stringTypeDatum(Type tp, char *string, int32 atttypmod)
 	return OidInputFunctionCall(typinput, string, typioparam, atttypmod);
 }
 
+
+/*
+ * Given a type structure and a string, returns the internal representation
+ * of that string.  The "string" can be NULL to perform conversion of a NULL
+ * (which might result in failure, if the input function rejects NULLs).
+ *
+ * XXX this will use FORMAT expression's collation, because cstring don't have collation.
+ */
+Datum
+stringTypeDatumWithFormat(Type tp, char *string,
+						  Datum fmt, bool fmtisnull,
+						  Oid fmtcollation)
+{
+	FmgrInfo	flinfo;
+	Form_pg_type typform = (Form_pg_type) GETSTRUCT(tp);
+	Oid			typformtin = typform->typformatin;
+
+	if (!OidIsValid(typformtin))
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot cast type %s to %s using CAST( ... AS ... FORMAT ...)",
+					   format_type_be(TEXTOID),
+					   format_type_be(typform->oid)),
+				errhint("Ensure type %s have valid typformatin function",
+						format_type_be(typform->oid)));
+
+	fmgr_info(typformtin, &flinfo);
+
+	return InputFunctionCallWithFormat(&flinfo, string, fmt, fmtisnull,
+									   fmtcollation);
+}
+
+
 /*
  * Given a typeid, return the type's typrelid (associated relation), if any.
  * Returns InvalidOid if type is not a composite type.
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index a049cc67ed6..8e947b85277 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -680,6 +680,7 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 		castnode = makeNode(TypeCast);
 		castnode->typeName = SystemTypeName("regclass");
 		castnode->arg = (Node *) snamenode;
+		castnode->format = NULL;
 		castnode->location = -1;
 		funccallnode = makeFuncCall(SystemFuncName("nextval"),
 									list_make1(castnode),
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 88de5c0481c..5c3174bfe70 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -11980,6 +11980,26 @@ get_func_sql_syntax(FuncExpr *expr, deparse_context *context)
 			get_rule_expr((Node *) lsecond(expr->args), context, false);
 			appendStringInfoString(buf, "))");
 			return true;
+		case F_TO_CHAR_INT4_TEXT:
+		case F_TO_CHAR_INT8_TEXT:
+		case F_TO_CHAR_FLOAT4_TEXT:
+		case F_TO_CHAR_FLOAT8_TEXT:
+		case F_TO_CHAR_NUMERIC_TEXT:
+		case F_TO_CHAR_INTERVAL_TEXT:
+		case F_TO_CHAR_TIMESTAMP_TEXT:
+		case F_TO_CHAR_TIMESTAMPTZ_TEXT:
+		case F_TO_NUMBER:
+		case F_TO_TIMESTAMP_TEXT_TEXT:
+		case F_TO_DATE:
+			/* CAST FORMAT */
+			appendStringInfoString(buf, "CAST(");
+			get_rule_expr((Node *) linitial(expr->args), context, false);
+			appendStringInfoString(buf, " AS ");
+			appendStringInfoString(buf, format_type_be(expr->funcresulttype));
+			appendStringInfoString(buf, " FORMAT ");
+			get_rule_expr((Node *) lsecond(expr->args), context, false);
+			appendStringInfoChar(buf, ')');
+			return true;
 	}
 	return false;
 }
diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index bfeceb7a92f..3a275e714a2 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -1565,6 +1565,45 @@ InputFunctionCall(FmgrInfo *flinfo, char *str, Oid typioparam, int32 typmod)
 	return result;
 }
 
+Datum
+InputFunctionCallWithFormat(FmgrInfo *flinfo, char *str,
+							Datum format, bool fmtisnull,
+							Oid fmtcollation)
+{
+	Datum		result;
+
+	LOCAL_FCINFO(fcinfo, 2);
+
+	if ((str == NULL || fmtisnull) && flinfo->fn_strict)
+		return (Datum) 0;		/* just return null result */
+
+	InitFunctionCallInfoData(*fcinfo, flinfo, 2, fmtcollation, NULL, NULL);
+
+	fcinfo->args[0].value = CStringGetTextDatum(str);
+	fcinfo->args[0].isnull = false;
+	fcinfo->args[1].value = format;
+	fcinfo->args[1].isnull = false;
+
+	result = FunctionCallInvoke(fcinfo);
+
+	/* Should get null result if and only if str is NULL */
+	if (str == NULL)
+	{
+		if (!fcinfo->isnull)
+			elog(ERROR, "input function %u returned non-NULL",
+				 flinfo->fn_oid);
+	}
+	else
+	{
+		if (fcinfo->isnull)
+			elog(ERROR, "input function %u returned NULL",
+				 flinfo->fn_oid);
+	}
+
+	return result;
+}
+
+
 /*
  * Call a previously-looked-up datatype input function, with non-exception
  * handling of "soft" errors.
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index 38e143ac670..8e15cb4ed17 100644
--- a/src/include/fmgr.h
+++ b/src/include/fmgr.h
@@ -746,6 +746,9 @@ extern Datum OidFunctionCall9Coll(Oid functionId, Oid collation,
 /* Special cases for convenient invocation of datatype I/O functions. */
 extern Datum InputFunctionCall(FmgrInfo *flinfo, char *str,
 							   Oid typioparam, int32 typmod);
+extern Datum InputFunctionCallWithFormat(FmgrInfo *flinfo, char *str,
+										 Datum format, bool fmtisnull,
+										 Oid fmtcollation);
 extern bool InputFunctionCallSafe(FmgrInfo *flinfo, char *str,
 								  Oid typioparam, int32 typmod,
 								  Node *escontext,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 4133c404a6b..03ed3684e2e 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -402,6 +402,7 @@ typedef struct TypeCast
 	NodeTag		type;
 	Node	   *arg;			/* the expression being casted */
 	TypeName   *typeName;		/* the target type */
+	Node	   *format;			/* the cast format template expression */
 	ParseLoc	location;		/* token location, or -1 if unknown */
 } TypeCast;
 
diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h
index aabacd49b65..5a021fd6386 100644
--- a/src/include/parser/parse_coerce.h
+++ b/src/include/parser/parse_coerce.h
@@ -43,6 +43,12 @@ extern Node *coerce_to_target_type(ParseState *pstate,
 								   CoercionContext ccontext,
 								   CoercionForm cformat,
 								   int location);
+extern Node *coerce_to_target_type_fmt(ParseState *pstate, Node *expr, Oid exprtype,
+									   Oid targettype, int32 targettypmod,
+									   CoercionContext ccontext,
+									   CoercionForm cformat,
+									   int location,
+									   Node *format);
 extern bool can_coerce_type(int nargs, const Oid *input_typeids, const Oid *target_typeids,
 							CoercionContext ccontext);
 extern Node *coerce_type(ParseState *pstate, Node *node,
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index f7f4ba6c2a8..68aec0c689e 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -84,6 +84,7 @@ typedef enum ParseExprKind
 	EXPR_KIND_GENERATED_COLUMN, /* generation expression for a column */
 	EXPR_KIND_CYCLE_MARK,		/* cycle mark value */
 	EXPR_KIND_PROPGRAPH_PROPERTY,	/* derived property expression */
+	EXPR_KIND_TYPECAST_FORMAT,	/* CAST FORMAT */
 } ParseExprKind;
 
 
diff --git a/src/include/parser/parse_type.h b/src/include/parser/parse_type.h
index a335807b0b0..a6e4827951e 100644
--- a/src/include/parser/parse_type.h
+++ b/src/include/parser/parse_type.h
@@ -47,6 +47,9 @@ extern char *typeTypeName(Type t);
 extern Oid	typeTypeRelid(Type typ);
 extern Oid	typeTypeCollation(Type typ);
 extern Datum stringTypeDatum(Type tp, char *string, int32 atttypmod);
+extern Datum stringTypeDatumWithFormat(Type tp, char *string,
+									   Datum fmt, bool fmtisnull,
+									   Oid fmtcollation);
 
 extern Oid	typeidTypeRelid(Oid type_id);
 extern Oid	typeOrDomainTypeRelid(Oid type_id);
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 865980cb0f1..7884b54fa6d 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -190,6 +190,10 @@ extern void getTypeBinaryOutputInfo(Oid type, Oid *typSend, bool *typIsVarlena);
 extern Oid	get_typmodin(Oid typid);
 extern Oid	get_typcollation(Oid typid);
 extern bool type_is_collatable(Oid typid);
+extern void getTypeInputFormatInfo(Oid type, Oid *typInputFormat,
+								   bool missing_ok);
+extern void getTypeOutputFormatInfo(Oid type, Oid *typOutputFormat,
+									bool missing_ok);
 extern RegProcedure get_typsubscript(Oid typid, Oid *typelemp);
 extern const SubscriptRoutines *getSubscriptingRoutines(Oid typid,
 														Oid *typelemp);
diff --git a/src/test/regress/expected/cast.out b/src/test/regress/expected/cast.out
new file mode 100644
index 00000000000..8ee7978f870
--- /dev/null
+++ b/src/test/regress/expected/cast.out
@@ -0,0 +1,345 @@
+create function ret_settxt() returns setof text as
+$$
+begin
+    return query execute 'select 1 union all select 1';
+end;
+$$
+language plpgsql immutable;
+-- check CAST FORMAT expression, the following should all fail
+select cast(NULL as date format ret_settxt()); -- cannot return a set
+ERROR:  set-returning functions are not allowed in CAST FORMAT expressions
+LINE 1: select cast(NULL as date format ret_settxt());
+                                        ^
+select cast(NULL as date format (select 1::text where false));
+ERROR:  cannot use subquery in CAST FORMAT expression
+LINE 1: select cast(NULL as date format (select 1::text where false)...
+                                        ^
+select cast(NULL as date format (string_agg(NULL, ' ')));
+ERROR:  aggregate functions are not allowed in CAST FORMAT expressions
+LINE 1: select cast(NULL as date format (string_agg(NULL, ' ')));
+                                         ^
+select cast(NULL as date format (string_agg(NULL, ' ') over () ));
+ERROR:  window functions are not allowed in CAST FORMAT expressions
+LINE 1: select cast(NULL as date format (string_agg(NULL, ' ') over ...
+                                         ^
+select cast(NULL as date format NULL::int);
+ERROR:  cannot coerce FORMAT expression to type text
+LINE 1: select cast(NULL as date format NULL::int);
+                                        ^
+select cast('1' as date format B'01');
+ERROR:  cannot coerce FORMAT expression to type text
+LINE 1: select cast('1' as date format B'01');
+                                       ^
+-- CAST FORMAT is restricted to the source and target types used by to_char, to_date, to_timestamp, and to_number
+-- The following should all fail
+select cast('hello' as name format 'test');
+ERROR:  cannot cast type text to name using CAST( ... AS ... FORMAT ...)
+LINE 1: select cast('hello' as name format 'test');
+                                           ^
+select cast('hello' as bpchar format 'test');
+ERROR:  cannot cast type text to character using CAST( ... AS ... FORMAT ...)
+LINE 1: select cast('hello' as bpchar format 'test');
+                                             ^
+select cast('-34,338,492' as bigint format '99G999G999');
+ERROR:  cannot cast type text to bigint using CAST( ... AS ... FORMAT ...)
+LINE 1: select cast('-34,338,492' as bigint format '99G999G999');
+                                                   ^
+select cast(array[1] as text format 'YYYY');
+ERROR:  cannot cast type integer[] to text using CAST( ... AS ... FORMAT ...)
+LINE 1: select cast(array[1] as text format 'YYYY');
+                                            ^
+select cast('1' as timestamp[] format 'YYYY-MM-DD');
+ERROR:  cannot cast type text to timestamp without time zone[] using CAST( ... AS ... FORMAT ...)
+LINE 1: select cast('1' as timestamp[] format 'YYYY-MM-DD');
+                                              ^
+select cast('2012-13-12' as timestamp format 'YYYY-MM-DD');
+ERROR:  cannot cast type text to timestamp without time zone using CAST( ... AS ... FORMAT ...)
+LINE 1: select cast('2012-13-12' as timestamp format 'YYYY-MM-DD');
+                                                     ^
+select cast('2012-13-12' as time format 'YYYY-MM-DD');
+ERROR:  cannot cast type text to time without time zone using CAST( ... AS ... FORMAT ...)
+LINE 1: select cast('2012-13-12' as time format 'YYYY-MM-DD');
+                                                ^
+select cast('2012-13-12' as timetz format 'YYYY-MM-DD');
+ERROR:  cannot cast type text to time with time zone using CAST( ... AS ... FORMAT ...)
+LINE 1: select cast('2012-13-12' as timetz format 'YYYY-MM-DD');
+                                                  ^
+select cast('2012-13-12' as interval format 'YYYY-MM-DD');
+ERROR:  cannot cast type text to interval using CAST( ... AS ... FORMAT ...)
+LINE 1: select cast('2012-13-12' as interval format 'YYYY-MM-DD');
+                                                    ^
+select cast('1'::text as unknown format 'YYYY-MM-DD');
+ERROR:  cannot cast type text to unknown using CAST( ... AS ... FORMAT ...)
+LINE 1: select cast('1'::text as unknown format 'YYYY-MM-DD');
+                                                ^
+select cast('1' as bool format 'YYYY-MM-DD');
+ERROR:  cannot cast type text to boolean using CAST( ... AS ... FORMAT ...)
+LINE 1: select cast('1' as bool format 'YYYY-MM-DD');
+                                       ^
+select cast('1' as json format 'YYYY-MM-DD');
+ERROR:  cannot cast type text to json using CAST( ... AS ... FORMAT ...)
+LINE 1: select cast('1' as json format 'YYYY-MM-DD');
+                                       ^
+select cast('1'::json as text format 'YYYY-MM-DD');
+ERROR:  cannot cast type json to text using CAST( ... AS ... FORMAT ...)
+LINE 1: select cast('1'::json as text format 'YYYY-MM-DD');
+                                             ^
+select cast('1' as anyelement format 'YYYY-MM-DD');
+ERROR:  cannot cast type text to anyelement using CAST( ... AS ... FORMAT ...)
+LINE 1: select cast('1' as anyelement format 'YYYY-MM-DD');
+                                             ^
+select cast('1' as anyenum format 'YYYY-MM-DD');
+ERROR:  cannot cast type text to anyenum using CAST( ... AS ... FORMAT ...)
+LINE 1: select cast('1' as anyenum format 'YYYY-MM-DD');
+                                          ^
+select cast('1' as anyarray format 'YYYY-MM-DD');
+ERROR:  cannot cast type text to anyarray using CAST( ... AS ... FORMAT ...)
+LINE 1: select cast('1' as anyarray format 'YYYY-MM-DD');
+                                           ^
+select cast(null::anyelement as anyelement format 'YYYY-MM-DD');
+ERROR:  cannot cast type text to anyelement using CAST( ... AS ... FORMAT ...)
+LINE 1: select cast(null::anyelement as anyelement format 'YYYY-MM-D...
+                                                          ^
+select cast('2012-12-12 12:00'::timetz as text format 'YYYY-MM-DD HH:MI:SS TZ');
+ERROR:  cannot cast type time with time zone to text using CAST( ... AS ... FORMAT ...)
+LINE 1: ...ct cast('2012-12-12 12:00'::timetz as text format 'YYYY-MM-D...
+                                                             ^
+select cast(null::regclass as text format 'YYYY-MM-DD HH:MI:SS TZ');
+ERROR:  cannot cast type regclass to text using CAST( ... AS ... FORMAT ...)
+LINE 1: select cast(null::regclass as text format 'YYYY-MM-DD HH:MI:...
+                                                  ^
+select cast(null::int2 as numeric format null);
+ERROR:  cannot cast type smallint to numeric using CAST( ... AS ... FORMAT ...)
+LINE 1: select cast(null::int2 as numeric format null);
+                                                 ^
+select cast(null::date as timestamptz format null);
+ERROR:  cannot cast type date to timestamp with time zone using CAST( ... AS ... FORMAT ...)
+LINE 1: select cast(null::date as timestamptz format null);
+                                                     ^
+select cast(null::time as timestamptz format null);
+ERROR:  cannot cast type time without time zone to timestamp with time zone using CAST( ... AS ... FORMAT ...)
+LINE 1: select cast(null::time as timestamptz format null);
+                                                     ^
+select cast('hello'::text collate "C" as date format 'test' collate "POSIX"); -- error
+ERROR:  collation mismatch between explicit collations "C" and "POSIX"
+LINE 1: ...t('hello'::text collate "C" as date format 'test' collate "P...
+                                                             ^
+select cast('hello'::text collate "C" as date format 'test' collate "POSIX"); -- error
+ERROR:  collation mismatch between explicit collations "C" and "POSIX"
+LINE 1: ...t('hello'::text collate "C" as date format 'test' collate "P...
+                                                             ^
+-- CAST FORMAT is not supported for binary coercible type cast
+select cast('2022-01-01' as unknown format null);
+ERROR:  cannot cast type text to unknown using CAST( ... AS ... FORMAT ...)
+LINE 1: select cast('2022-01-01' as unknown format null);
+                                                   ^
+select cast('1' as text format '1'::text);
+ERROR:  cannot cast type text to text using CAST( ... AS ... FORMAT ...)
+LINE 1: select cast('1' as text format '1'::text);
+                                       ^
+select cast('1'::text as text format '1'::text);
+ERROR:  cannot cast type text to text using CAST( ... AS ... FORMAT ...)
+LINE 1: select cast('1'::text as text format '1'::text);
+                                             ^
+select cast('2012-12-12 12:00'::timetz as text format 'YYYY-MM-DD HH:MI:SS TZ'); -- error
+ERROR:  cannot cast type time with time zone to text using CAST( ... AS ... FORMAT ...)
+LINE 1: ...ct cast('2012-12-12 12:00'::timetz as text format 'YYYY-MM-D...
+                                                             ^
+select cast('2012-13-12' as date format 'YYYY-DD-MM'); -- ok
+    date    
+------------
+ 12-13-2012
+(1 row)
+
+select cast('2012-13-12' as date format 'YYYY-MM-DD'); -- error
+ERROR:  date/time field value out of range: "2012-13-12"
+LINE 1: select cast('2012-13-12' as date format 'YYYY-MM-DD');
+                    ^
+select cast('1' as date format 'YYYY-MM-DD');
+    date    
+------------
+ 01-01-0001
+(1 row)
+
+select cast('1' collate "C" as date format 'YYYY-MM-DD');
+    date    
+------------
+ 01-01-0001
+(1 row)
+
+select cast('2012-13-12' as date format 'YYYY-DD-MM') as date;
+    date    
+------------
+ 12-13-2012
+(1 row)
+
+select cast('2012-13-12' as timestamptz format 'YYYY-DD-MM') as date;
+             date             
+------------------------------
+ Thu Dec 13 00:00:00 2012 PST
+(1 row)
+
+select cast('2012-13-12'::text as timestamp format 'YYYY-DD-MM') as date; -- error
+ERROR:  cannot cast type text to timestamp without time zone using CAST( ... AS ... FORMAT ...)
+LINE 1: ...elect cast('2012-13-12'::text as timestamp format 'YYYY-DD-M...
+                                                             ^
+select cast('1' as timestamp format 'YYYY-MM-DD') = to_timestamp('1', 'YYYY-MM-DD');
+ERROR:  cannot cast type text to timestamp without time zone using CAST( ... AS ... FORMAT ...)
+LINE 1: select cast('1' as timestamp format 'YYYY-MM-DD') = to_times...
+                                            ^
+select cast('2026-01-28 13:29:12.324606+01'::text as timestamp format 'YYYY-MM-DD') =
+            to_timestamp('2026-01-28 13:29:12.324606+01'::text, 'YYYY-MM-DD');
+ERROR:  cannot cast type text to timestamp without time zone using CAST( ... AS ... FORMAT ...)
+LINE 1: ...-28 13:29:12.324606+01'::text as timestamp format 'YYYY-MM-D...
+                                                             ^
+select cast('1' as date format 'YYYY-MM-DD') = to_date('1', 'YYYY-MM-DD');
+ ?column? 
+----------
+ t
+(1 row)
+
+-- test with domain
+create domain d1 as date check (value <> '0001-01-01');
+create domain d2 as text check (value <> '125.80-');
+select cast(-125.8::numeric as text format '999D99S');
+  text   
+---------
+ 125.80-
+(1 row)
+
+select cast(-125.8::numeric as d2 format '999D99S');
+ERROR:  value for domain d2 violates check constraint "d2_check"
+select cast('1' as text format 'YYYY-MM-DD'); -- error
+ERROR:  cannot cast type text to text using CAST( ... AS ... FORMAT ...)
+LINE 1: select cast('1' as text format 'YYYY-MM-DD');
+                                       ^
+select cast('1' as d1 format 'YYYY-MM-DD'); -- error
+ERROR:  value for domain d1 violates check constraint "d1_check"
+select cast('1' as date format 'MM-DD'); -- ok
+     date      
+---------------
+ 01-01-0001 BC
+(1 row)
+
+select cast('1' as d1 format 'MM-DD'); -- ok
+      d1       
+---------------
+ 01-01-0001 BC
+(1 row)
+
+create temp table tcast0 as select '1'::text as a;
+select cast(a as d1 format 'YYYY-MM-DD') from tcast0;
+ERROR:  value for domain d1 violates check constraint "d1_check"
+select cast('1'::text collate "C" as date format 'YYYY-MM-DD');
+    date    
+------------
+ 01-01-0001
+(1 row)
+
+select cast('1' as date format 'YYYY-MM-DD') = to_date('1', 'YYYY-MM-DD') as expect_true;
+ expect_true 
+-------------
+ t
+(1 row)
+
+create table tcast(col1 text, col2 text, col3 date, col4 timestamptz, col5 int8);
+insert into tcast(col1, col2, col5)
+  values('2022-12-13', 'YYYY-MM-DD', 1234),
+        ('2022-12-01', 'YYYY-DD-MM', -1234);
+select cast(col1 as date format col2) from tcast;
+    col1    
+------------
+ 12-13-2022
+ 01-12-2022
+(2 rows)
+
+select cast(col1 as date format col3) from tcast; -- error
+ERROR:  cannot coerce FORMAT expression to type text
+LINE 1: select cast(col1 as date format col3) from tcast;
+                                        ^
+select cast(col1 as date format col3::text) from tcast; -- ok
+ col1 
+------
+ 
+ 
+(2 rows)
+
+create function imm_const() returns text as $$ begin return 'YYYY-MM-DD'; end; $$ language plpgsql immutable;
+select cast(col1 as date format imm_const()) from tcast;
+    col1    
+------------
+ 12-13-2022
+ 12-01-2022
+(2 rows)
+
+create index s1 on tcast(to_date(col1, 'YYYY-MM-DD')); -- error
+ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: create index s1 on tcast(to_date(col1, 'YYYY-MM-DD'));
+                                 ^
+create index s1 on tcast(cast(col1 as date format 'YYYY-MM-DD')); -- error
+ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: create index s1 on tcast(cast(col1 as date format 'YYYY-MM-D...
+                                 ^
+create view tcast_v1 as
+   select cast(col1 as date format 'YYYY-MM-DD') as to_date,
+         cast(col1 as timestamptz format 'YYYY-MM-DD') as to_timestamptz,
+         cast(NULL::interval as text format 'YYYY-MM-DD') as to_txt0,
+         cast(col1::timestamp as text format 'YYYY-MM-DD') as to_txt1,
+      --    cast(col3 as text format 'YYYY-MM-DD') as to_txt2,
+         cast(col4 as text format 'YYYY-MM-DD') as to_txt3,
+         cast(numeric 'inf' as text format 'YYYY-MM-DD') as to_txt4,
+         cast(bigint '12324' as text format 'YYYY-MM-DD') as to_txt5
+   from tcast;
+select pg_get_viewdef('tcast_v1', true);
+                                      pg_get_viewdef                                       
+-------------------------------------------------------------------------------------------
+  SELECT CAST(col1 AS date FORMAT 'YYYY-MM-DD'::text) AS to_date,                         +
+     CAST(col1 AS timestamp with time zone FORMAT 'YYYY-MM-DD'::text) AS to_timestamptz,  +
+     CAST(NULL::interval AS text FORMAT 'YYYY-MM-DD'::text) AS to_txt0,                   +
+     CAST(col1::timestamp without time zone AS text FORMAT 'YYYY-MM-DD'::text) AS to_txt1,+
+     CAST(col4 AS text FORMAT 'YYYY-MM-DD'::text) AS to_txt3,                             +
+     CAST('Infinity'::numeric AS text FORMAT 'YYYY-MM-DD'::text) AS to_txt4,              +
+     CAST('12324'::bigint AS text FORMAT 'YYYY-MM-DD'::text) AS to_txt5                   +
+    FROM tcast;
+(1 row)
+
+explain (verbose, costs off)
+select cast(col5::float8 as text format '9.99EEEE') as to_txt1,
+ cast(col5::numeric as text format '9.99EEEE') as to_txt2
+from tcast;
+                                                           QUERY PLAN                                                            
+---------------------------------------------------------------------------------------------------------------------------------
+ Seq Scan on public.tcast
+   Output: CAST((col5)::double precision AS text FORMAT '9.99EEEE'::text), CAST((col5)::numeric AS text FORMAT '9.99EEEE'::text)
+(2 rows)
+
+create view tcast_v2 as
+   select cast(col5 as text format '9.99EEEE') as to_txt0,
+         cast(col5::float8 as text format '9.99EEEE') as to_txt1,
+         cast(col5::float4 as text format '9.99EEEE') as to_txt2,
+         cast(col5::numeric as text format '9.99EEEE') as to_txt3,
+      --    cast(col5::int2 as text format '9.99EEEE') as to_txt4,
+         cast(col5::int4 as text format '9.99EEEE') as to_txt5
+   from tcast;
+select pg_get_viewdef('tcast_v2', true);
+                                pg_get_viewdef                                
+------------------------------------------------------------------------------
+  SELECT CAST(col5 AS text FORMAT '9.99EEEE'::text) AS to_txt0,              +
+     CAST(col5::double precision AS text FORMAT '9.99EEEE'::text) AS to_txt1,+
+     CAST(col5::real AS text FORMAT '9.99EEEE'::text) AS to_txt2,            +
+     CAST(col5::numeric AS text FORMAT '9.99EEEE'::text) AS to_txt3,         +
+     CAST(col5::integer AS text FORMAT '9.99EEEE'::text) AS to_txt5          +
+    FROM tcast;
+(1 row)
+
+select * from tcast_v2;
+  to_txt0  |  to_txt1  |  to_txt2  |  to_txt3  |  to_txt5  
+-----------+-----------+-----------+-----------+-----------
+  1.23e+03 |  1.23e+03 |  1.23e+03 |  1.23e+03 |  1.23e+03
+ -1.23e+03 | -1.23e+03 | -1.23e+03 | -1.23e+03 | -1.23e+03
+(2 rows)
+
+drop function ret_settxt;
+drop view tcast_v1, tcast_v2;
+drop table tcast;
+drop domain d1, d2;
diff --git a/src/test/regress/expected/collate.linux.utf8.out b/src/test/regress/expected/collate.linux.utf8.out
index c6e84c27b69..129130a4f1b 100644
--- a/src/test/regress/expected/collate.linux.utf8.out
+++ b/src/test/regress/expected/collate.linux.utf8.out
@@ -463,6 +463,30 @@ SELECT to_char(date '2010-04-01', 'DD TMMON YYYY' COLLATE "tr_TR");
  01 NİS 2010
 (1 row)
 
+SELECT CAST(date '2010-02-01' as text format 'DD TMMON YYYY');
+    text     
+-------------
+ 01 ŞUB 2010
+(1 row)
+
+SELECT CAST(date '2010-02-01' as text format 'DD TMMON YYYY' COLLATE "tr_TR");
+    text     
+-------------
+ 01 ŞUB 2010
+(1 row)
+
+SELECT CAST(date '2010-04-01' as text format 'DD TMMON YYYY');
+    text     
+-------------
+ 01 NIS 2010
+(1 row)
+
+SELECT CAST(date '2010-04-01' as text format  'DD TMMON YYYY' COLLATE "tr_TR");
+    text     
+-------------
+ 01 NİS 2010
+(1 row)
+
 -- to_date
 SELECT to_date('01 ŞUB 2010', 'DD TMMON YYYY');
   to_date   
@@ -479,6 +503,21 @@ SELECT to_date('01 Şub 2010', 'DD TMMON YYYY');
 SELECT to_date('1234567890ab 2010', 'TMMONTH YYYY'); -- fail
 ERROR:  invalid value "1234567890ab" for "MONTH"
 DETAIL:  The given value did not match any of the allowed values for this field.
+SELECT CAST('01 ŞUB 2010' as date format 'DD TMMON YYYY');
+    date    
+------------
+ 02-01-2010
+(1 row)
+
+SELECT CAST('01 ŞUB 2010' as date format 'DD TMMON YYYY'); -- ok
+    date    
+------------
+ 02-01-2010
+(1 row)
+
+SELECT CAST('1234567890ab 2010' as date format 'TMMONTH YYYY'); -- fail
+ERROR:  invalid value "1234567890ab" for "MONTH"
+DETAIL:  The given value did not match any of the allowed values for this field.
 -- backwards parsing
 CREATE VIEW collview1 AS SELECT * FROM collate_test1 WHERE b COLLATE "C" >= 'bbc';
 CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C";
diff --git a/src/test/regress/expected/create_type.out b/src/test/regress/expected/create_type.out
index 119e2916c91..e05fc12e548 100644
--- a/src/test/regress/expected/create_type.out
+++ b/src/test/regress/expected/create_type.out
@@ -124,6 +124,15 @@ CREATE TYPE int42 (
    default = 42,
    passedbyvalue
 );
+CREATE OR REPLACE FUNCTION int42_fmtout(int42, text)
+RETURNS TEXT AS
+$$
+   BEGIN
+      RETURN TO_CHAR($1::text::int, $2);
+   END
+$$
+LANGUAGE PLPGSQL STRICT IMMUTABLE;
+ALTER TYPE int42 SET (TYPFORMAT_OUT = int42_fmtout);
 CREATE TYPE text_w_default (
    internallength = variable,
    input = text_w_default_in,
@@ -139,6 +148,32 @@ SELECT * FROM default_test;
  zippo | 42
 (1 row)
 
+SELECT CAST('42'::text::int42 AS TEXT FORMAT 'EEEE9'); -- error
+ERROR:  "EEEE" must be the last pattern used
+CONTEXT:  PL/pgSQL function int42_fmtout(int42,text) line 3 at RETURN
+SELECT CAST('42'::text::int42 AS TEXT FORMAT 'S999S'); -- error
+ERROR:  cannot use "S" twice
+CONTEXT:  PL/pgSQL function int42_fmtout(int42,text) line 3 at RETURN
+SELECT CAST('42'::text::int42 AS TEXT FORMAT '9.9V9'); -- error
+ERROR:  cannot use "V" and decimal point together
+CONTEXT:  PL/pgSQL function int42_fmtout(int42,text) line 3 at RETURN
+SELECT CAST('42'::text::int42 AS TEXT FORMAT 'RNEEEE'); -- error
+ERROR:  "EEEE" is incompatible with other formats
+DETAIL:  "EEEE" may only be used together with digit and decimal point patterns.
+CONTEXT:  PL/pgSQL function int42_fmtout(int42,text) line 3 at RETURN
+SELECT CAST('42'::text::int42 AS TEXT FORMAT '9.9.9'); -- error
+ERROR:  multiple decimal points
+CONTEXT:  PL/pgSQL function int42_fmtout(int42,text) line 3 at RETURN
+SELECT CAST(g::text::int42 AS TEXT FORMAT 'FM000') FROM (VALUES (-1), (0), (5), (100), (99)) s(g);
+  g   
+------
+ -001
+ 000
+ 005
+ 100
+ 099
+(5 rows)
+
 -- We need a shell type to test some CREATE TYPE failure cases with
 CREATE TYPE bogus_type;
 -- invalid: non-lowercase quoted identifiers
@@ -209,6 +244,49 @@ ERROR:  type "text_w_default" already exists
 DROP TYPE default_test_row CASCADE;
 NOTICE:  drop cascades to function get_default_test()
 DROP TABLE default_test;
+-- CAST(expr as type FORMAT 'fmt') with user-defined types
+CREATE TYPE datealilas;
+CREATE FUNCTION datealilas_in(cstring)
+   RETURNS datealilas
+   AS 'date_in'
+   LANGUAGE internal STRICT IMMUTABLE;
+NOTICE:  return type datealilas is only a shell
+CREATE FUNCTION datealilas_out(datealilas)
+   RETURNS cstring
+   AS 'date_out'
+   LANGUAGE internal STRICT IMMUTABLE;
+NOTICE:  argument type datealilas is only a shell
+LINE 1: CREATE FUNCTION datealilas_out(datealilas)
+                                       ^
+CREATE TYPE datealilas (
+   internallength = 4,
+   input = datealilas_in,
+   output = datealilas_out,
+   alignment = int4,
+   passedbyvalue
+);
+CREATE OR REPLACE FUNCTION datealilasfmtin(TEXT, TEXT)
+RETURNS datealilas AS
+$$
+   BEGIN
+      RETURN TO_DATE($1, $2)::text::datealilas;
+   END
+$$
+LANGUAGE PLPGSQL STRICT IMMUTABLE;
+ALTER TYPE datealilas SET (TYPFORMAT_IN = datealilasfmtin);
+CREATE TEMP TABLE castfmt(a0 int2 default 2, a datealilas, b int default 3);
+INSERT INTO castfmt(a)
+   SELECT cast('2022-01-13' AS datealilas FORMAT 'YYYY-MM-DD')
+   UNION ALL
+   SELECT cast('2022-13-11' AS datealilas FORMAT 'YYYY-DD-MM');
+SELECT CAST('2022-01-13' AS datealilas FORMAT 'YYYY-DD-MM'); -- error
+ERROR:  date/time field value out of range: "2022-01-13"
+LINE 1: SELECT CAST('2022-01-13' AS datealilas FORMAT 'YYYY-DD-MM');
+                    ^
+CONTEXT:  PL/pgSQL function datealilasfmtin(text,text) line 3 at RETURN
+SELECT CAST('2022-01-13'::text AS datealilas FORMAT 'YYYY-DD-MM'); -- error
+ERROR:  date/time field value out of range: "2022-01-13"
+CONTEXT:  PL/pgSQL function datealilasfmtin(text,text) line 3 at RETURN
 -- Check dependencies are established when creating a new type
 CREATE TYPE base_type;
 CREATE FUNCTION base_fn_in(cstring) RETURNS base_type AS 'boolin'
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index 32cf62b6741..93765733788 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -3324,72 +3324,152 @@ SELECT to_timestamp('2011-12-18 11:38 EST', 'YYYY-MM-DD HH12:MI TZ');
  Sun Dec 18 08:38:00 2011 PST
 (1 row)
 
+SELECT cast('2011-12-18 11:38 EST' as timestamptz format 'YYYY-MM-DD HH12:MI TZ');
+         timestamptz          
+------------------------------
+ Sun Dec 18 08:38:00 2011 PST
+(1 row)
+
 SELECT to_timestamp('2011-12-18 11:38 -05', 'YYYY-MM-DD HH12:MI TZ');
          to_timestamp         
 ------------------------------
  Sun Dec 18 08:38:00 2011 PST
 (1 row)
 
+SELECT cast('2011-12-18 11:38 -05' as timestamptz format 'YYYY-MM-DD HH12:MI TZ');
+         timestamptz          
+------------------------------
+ Sun Dec 18 08:38:00 2011 PST
+(1 row)
+
 SELECT to_timestamp('2011-12-18 11:38 +01:30', 'YYYY-MM-DD HH12:MI TZ');
          to_timestamp         
 ------------------------------
  Sun Dec 18 02:08:00 2011 PST
 (1 row)
 
+SELECT cast('2011-12-18 11:38 +01:30' as timestamptz format 'YYYY-MM-DD HH12:MI TZ');
+         timestamptz          
+------------------------------
+ Sun Dec 18 02:08:00 2011 PST
+(1 row)
+
 SELECT to_timestamp('2011-12-18 11:38 MSK', 'YYYY-MM-DD HH12:MI TZ');  -- dyntz
          to_timestamp         
 ------------------------------
  Sat Dec 17 23:38:00 2011 PST
 (1 row)
 
+SELECT cast('2011-12-18 11:38 MSK' as timestamptz format 'YYYY-MM-DD HH12:MI TZ');  -- dyntz
+         timestamptz          
+------------------------------
+ Sat Dec 17 23:38:00 2011 PST
+(1 row)
+
 SELECT to_timestamp('2011-12-18 00:00 LMT', 'YYYY-MM-DD HH24:MI TZ');  -- dyntz
          to_timestamp         
 ------------------------------
  Sat Dec 17 23:52:58 2011 PST
 (1 row)
 
+SELECT cast('2011-12-18 00:00 LMT' as timestamptz format 'YYYY-MM-DD HH24:MI TZ');  -- dyntz
+         timestamptz          
+------------------------------
+ Sat Dec 17 23:52:58 2011 PST
+(1 row)
+
 SELECT to_timestamp('2011-12-18 11:38ESTFOO24', 'YYYY-MM-DD HH12:MITZFOOSS');
          to_timestamp         
 ------------------------------
  Sun Dec 18 08:38:24 2011 PST
 (1 row)
 
+SELECT cast('2011-12-18 11:38ESTFOO24' as timestamptz format 'YYYY-MM-DD HH12:MITZFOOSS');
+         timestamptz          
+------------------------------
+ Sun Dec 18 08:38:24 2011 PST
+(1 row)
+
 SELECT to_timestamp('2011-12-18 11:38-05FOO24', 'YYYY-MM-DD HH12:MITZFOOSS');
          to_timestamp         
 ------------------------------
  Sun Dec 18 08:38:24 2011 PST
 (1 row)
 
+SELECT cast('2011-12-18 11:38-05FOO24' as timestamptz format 'YYYY-MM-DD HH12:MITZFOOSS');
+         timestamptz          
+------------------------------
+ Sun Dec 18 08:38:24 2011 PST
+(1 row)
+
 SELECT to_timestamp('2011-12-18 11:38 JUNK', 'YYYY-MM-DD HH12:MI TZ');  -- error
 ERROR:  invalid value "JUNK" for "TZ"
 DETAIL:  Time zone abbreviation is not recognized.
+SELECT cast('2011-12-18 11:38 JUNK' as timestamptz format 'YYYY-MM-DD HH12:MI TZ');  -- error
+ERROR:  invalid value "JUNK" for "TZ"
+LINE 1: SELECT cast('2011-12-18 11:38 JUNK' as timestamptz format 'Y...
+                    ^
+DETAIL:  Time zone abbreviation is not recognized.
 SELECT to_timestamp('2011-12-18 11:38 ...', 'YYYY-MM-DD HH12:MI TZ');  -- error
 ERROR:  invalid value ".." for "TZ"
 DETAIL:  Value must be an integer.
+SELECT cast('2011-12-18 11:38 ...' as timestamptz format 'YYYY-MM-DD HH12:MI TZ');  -- error
+ERROR:  invalid value ".." for "TZ"
+LINE 1: SELECT cast('2011-12-18 11:38 ...' as timestamptz format 'YY...
+                    ^
+DETAIL:  Value must be an integer.
 SELECT to_timestamp('2011-12-18 11:38 -05', 'YYYY-MM-DD HH12:MI OF');
          to_timestamp         
 ------------------------------
  Sun Dec 18 08:38:00 2011 PST
 (1 row)
 
+SELECT cast ('2011-12-18 11:38 -05' as timestamptz format 'YYYY-MM-DD HH12:MI OF');
+         timestamptz          
+------------------------------
+ Sun Dec 18 08:38:00 2011 PST
+(1 row)
+
 SELECT to_timestamp('2011-12-18 11:38 +01:30', 'YYYY-MM-DD HH12:MI OF');
          to_timestamp         
 ------------------------------
  Sun Dec 18 02:08:00 2011 PST
 (1 row)
 
+SELECT cast('2011-12-18 11:38 +01:30' as timestamptz format 'YYYY-MM-DD HH12:MI OF');
+         timestamptz          
+------------------------------
+ Sun Dec 18 02:08:00 2011 PST
+(1 row)
+
 SELECT to_timestamp('2011-12-18 11:38 +xyz', 'YYYY-MM-DD HH12:MI OF');  -- error
 ERROR:  invalid value "xy" for "OF"
 DETAIL:  Value must be an integer.
+SELECT cast('2011-12-18 11:38 +xyz' as timestamptz format 'YYYY-MM-DD HH12:MI OF');  -- error
+ERROR:  invalid value "xy" for "OF"
+LINE 1: SELECT cast('2011-12-18 11:38 +xyz' as timestamptz format 'Y...
+                    ^
+DETAIL:  Value must be an integer.
 SELECT to_timestamp('2011-12-18 11:38 +01:xyz', 'YYYY-MM-DD HH12:MI OF');  -- error
 ERROR:  invalid value "xy" for "OF"
 DETAIL:  Value must be an integer.
+SELECT cast('2011-12-18 11:38 +01:xyz' as timestamptz format 'YYYY-MM-DD HH12:MI OF');  -- error
+ERROR:  invalid value "xy" for "OF"
+LINE 1: SELECT cast('2011-12-18 11:38 +01:xyz' as timestamptz format...
+                    ^
+DETAIL:  Value must be an integer.
 SELECT to_timestamp('2018-11-02 12:34:56.025', 'YYYY-MM-DD HH24:MI:SS.MS');
            to_timestamp           
 ----------------------------------
  Fri Nov 02 12:34:56.025 2018 PDT
 (1 row)
 
+SELECT cast('2018-11-02 12:34:56.025' as timestamptz format 'YYYY-MM-DD HH24:MI:SS.MS');
+           timestamptz            
+----------------------------------
+ Fri Nov 02 12:34:56.025 2018 PDT
+(1 row)
+
 SELECT i, to_timestamp('2018-11-02 12:34:56', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
  i |         to_timestamp         
 ---+------------------------------
@@ -3469,6 +3549,8 @@ SELECT i, to_timestamp('2018-11-02 12:34:56.123456', 'YYYY-MM-DD HH24:MI:SS.FF'
 
 SELECT i, to_timestamp('2018-11-02 12:34:56.123456789', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
 ERROR:  date/time field value out of range: "2018-11-02 12:34:56.123456789"
+SELECT i, cast('2018-11-02 12:34:56.123456789' as timestamptz format 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ERROR:  date/time field value out of range: "2018-11-02 12:34:56.123456789"
 SELECT i, to_timestamp('20181102123456123456', 'YYYYMMDDHH24MISSFF' || i) FROM generate_series(1, 6) i;
  i |            to_timestamp             
 ---+-------------------------------------
@@ -3486,18 +3568,36 @@ SELECT to_date('1 4 1902', 'Q MM YYYY');  -- Q is ignored
  04-01-1902
 (1 row)
 
+SELECT cast('1 4 1902' as date format 'Q MM YYYY');  -- Q is ignored
+    date    
+------------
+ 04-01-1902
+(1 row)
+
 SELECT to_date('3 4 21 01', 'W MM CC YY');
   to_date   
 ------------
  04-15-2001
 (1 row)
 
+SELECT cast('3 4 21 01' as date format 'W MM CC YY');
+    date    
+------------
+ 04-15-2001
+(1 row)
+
 SELECT to_date('2458872', 'J');
   to_date   
 ------------
  01-23-2020
 (1 row)
 
+SELECT cast('2458872' as date format 'J');
+    date    
+------------
+ 01-23-2020
+(1 row)
+
 --
 -- Check handling of BC dates
 --
@@ -3833,12 +3933,24 @@ SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD HH:MI:SS TZ');
  2012-12-12 12:00:00 PST
 (1 row)
 
+SELECT cast('2012-12-12 12:00'::timestamptz as text format 'YYYY-MM-DD HH:MI:SS TZ');
+          text           
+-------------------------
+ 2012-12-12 12:00:00 PST
+(1 row)
+
 SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD HH:MI:SS tz');
          to_char         
 -------------------------
  2012-12-12 12:00:00 pst
 (1 row)
 
+SELECT cast('2012-12-12 12:00'::timestamptz as text format 'YYYY-MM-DD HH:MI:SS tz');
+          text           
+-------------------------
+ 2012-12-12 12:00:00 pst
+(1 row)
+
 --
 -- Check behavior with SQL-style fixed-GMT-offset time zone (cf bug #8572)
 --
@@ -3868,6 +3980,27 @@ SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD HH:MI:SS TZ');
  2012-12-12 12:00:00 -01:30
 (1 row)
 
+SELECT cast('2012-12-12 12:00'::timestamptz as text format 'YYYY-MM-DD HH:MI:SS TZ'),
+        to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD HH:MI:SS TZ');
+            text            |          to_char           
+----------------------------+----------------------------
+ 2012-12-12 12:00:00 -01:30 | 2012-12-12 12:00:00 -01:30
+(1 row)
+
+SELECT cast('2012-12-12 12:00'::date as text format 'YYYY-MM-DD HH:MI:SS TZ'),
+        to_char('2012-12-12 12:00'::date, 'YYYY-MM-DD HH:MI:SS TZ');
+ERROR:  cannot cast type date to text using CAST( ... AS ... FORMAT ...)
+LINE 1: ...LECT cast('2012-12-12 12:00'::date as text format 'YYYY-MM-D...
+                                                             ^
+SELECT cast('12:00'::time as text format 'HH:MI:SS'),
+        to_char('12:00'::time, 'HH:MI:SS');
+ERROR:  cannot cast type time without time zone to text using CAST( ... AS ... FORMAT ...)
+LINE 1: SELECT cast('12:00'::time as text format 'HH:MI:SS'),
+                                                 ^
+SELECT cast('2012-12-12 12:00'::timetz as text format 'YYYY-MM-DD HH:MI:SS TZ');
+ERROR:  cannot cast type time with time zone to text using CAST( ... AS ... FORMAT ...)
+LINE 1: ...CT cast('2012-12-12 12:00'::timetz as text format 'YYYY-MM-D...
+                                                             ^
 SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD SSSS');
      to_char      
 ------------------
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index a16e3ccdb2e..4bcaaaf04a7 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -2263,12 +2263,24 @@ SELECT to_char('infinity'::interval, 'YYYY');
  
 (1 row)
 
+SELECT cast('infinity'::interval as text format 'YYYY');
+ text 
+------
+ 
+(1 row)
+
 SELECT to_char('-infinity'::interval, 'YYYY');
  to_char 
 ---------
  
 (1 row)
 
+SELECT cast('-infinity'::interval as text format 'YYYY');
+ text 
+------
+ 
+(1 row)
+
 -- "ago" can only appear once at the end of an interval.
 SELECT INTERVAL '42 days 2 seconds ago ago';
 ERROR:  invalid input syntax for type interval: "42 days 2 seconds ago ago"
diff --git a/src/test/regress/expected/numeric.out b/src/test/regress/expected/numeric.out
index c58e232a263..09264b98bfb 100644
--- a/src/test/regress/expected/numeric.out
+++ b/src/test/regress/expected/numeric.out
@@ -2264,147 +2264,286 @@ SELECT to_number('-34,338,492', '99G999G999');
  -34338492
 (1 row)
 
+SELECT CAST('-34,338,492' as numeric FORMAT  '99G999G999');
+  numeric  
+-----------
+ -34338492
+(1 row)
+
 SELECT to_number('-34,338,492.654,878', '99G999G999D999G999');
     to_number     
 ------------------
  -34338492.654878
 (1 row)
 
+SELECT CAST('-34,338,492.654,878' as numeric FORMAT '99G999G999D999G999');
+     numeric      
+------------------
+ -34338492.654878
+(1 row)
+
 SELECT to_number('<564646.654564>', '999999.999999PR');
    to_number    
 ----------------
  -564646.654564
 (1 row)
 
+SELECT CAST('<564646.654564>' as numeric FORMAT '999999.999999PR');
+    numeric     
+----------------
+ -564646.654564
+(1 row)
+
 SELECT to_number('0.00001-', '9.999999S');
  to_number 
 -----------
   -0.00001
 (1 row)
 
+SELECT CAST('0.00001-' as numeric FORMAT '9.999999S');
+ numeric  
+----------
+ -0.00001
+(1 row)
+
 SELECT to_number('5.01-', 'FM9.999999S');
  to_number 
 -----------
      -5.01
 (1 row)
 
+SELECT CAST('5.01-' as numeric FORMAT 'FM9.999999S');
+ numeric 
+---------
+   -5.01
+(1 row)
+
 SELECT to_number('5.01-', 'FM9.999999MI');
  to_number 
 -----------
      -5.01
 (1 row)
 
+SELECT CAST('5.01-' as numeric FORMAT 'FM9.999999MI');
+ numeric 
+---------
+   -5.01
+(1 row)
+
 SELECT to_number('5 4 4 4 4 8 . 7 8', '9 9 9 9 9 9 . 9 9');
  to_number 
 -----------
  544448.78
 (1 row)
 
+SELECT CAST('5 4 4 4 4 8 . 7 8' as numeric FORMAT '9 9 9 9 9 9 . 9 9');
+  numeric  
+-----------
+ 544448.78
+(1 row)
+
 SELECT to_number('.01', 'FM9.99');
  to_number 
 -----------
       0.01
 (1 row)
 
+SELECT CAST('.01' as numeric FORMAT 'FM9.99');
+ numeric 
+---------
+    0.01
+(1 row)
+
 SELECT to_number('.0', '99999999.99999999');
  to_number 
 -----------
        0.0
 (1 row)
 
+SELECT CAST('.0' as numeric FORMAT '99999999.99999999');
+ numeric 
+---------
+     0.0
+(1 row)
+
 SELECT to_number('0', '99.99');
  to_number 
 -----------
          0
 (1 row)
 
+SELECT CAST('0' as numeric FORMAT '99.99');
+ numeric 
+---------
+       0
+(1 row)
+
 SELECT to_number('.-01', 'S99.99');
  to_number 
 -----------
      -0.01
 (1 row)
 
+SELECT CAST('.-01' as numeric FORMAT 'S99.99');
+ numeric 
+---------
+   -0.01
+(1 row)
+
 SELECT to_number('.01-', '99.99S');
  to_number 
 -----------
      -0.01
 (1 row)
 
+SELECT CAST('.01-' as numeric FORMAT '99.99S');
+ numeric 
+---------
+   -0.01
+(1 row)
+
 SELECT to_number(' . 0 1-', ' 9 9 . 9 9 S');
  to_number 
 -----------
      -0.01
 (1 row)
 
+SELECT CAST(' . 0 1-' as numeric FORMAT ' 9 9 . 9 9 S');
+ numeric 
+---------
+   -0.01
+(1 row)
+
 SELECT to_number('34,50','999,99');
  to_number 
 -----------
       3450
 (1 row)
 
+SELECT CAST('34,50' as numeric FORMAT '999,99');
+ numeric 
+---------
+    3450
+(1 row)
+
 SELECT to_number('123,000','999G');
  to_number 
 -----------
        123
 (1 row)
 
+SELECT CAST('123,000' as numeric FORMAT '999G');
+ numeric 
+---------
+     123
+(1 row)
+
 SELECT to_number('123456','999G999');
  to_number 
 -----------
     123456
 (1 row)
 
+SELECT CAST('123456' as numeric FORMAT '999G999');
+ numeric 
+---------
+  123456
+(1 row)
+
 SELECT to_number('$1234.56','L9,999.99');
  to_number 
 -----------
    1234.56
 (1 row)
 
+SELECT CAST('$1234.56' as numeric FORMAT 'L9,999.99');
+ numeric 
+---------
+ 1234.56
+(1 row)
+
 SELECT to_number('$1234.56','L99,999.99');
  to_number 
 -----------
    1234.56
 (1 row)
 
+SELECT CAST('$1234.56' as numeric FORMAT 'L99,999.99');
+ numeric 
+---------
+ 1234.56
+(1 row)
+
 SELECT to_number('$1,234.56','L99,999.99');
  to_number 
 -----------
    1234.56
 (1 row)
 
+SELECT CAST('$1,234.56' as numeric FORMAT 'L99,999.99');
+ numeric 
+---------
+ 1234.56
+(1 row)
+
 SELECT to_number('1234.56','L99,999.99');
  to_number 
 -----------
    1234.56
 (1 row)
 
+SELECT CAST('1234.56' as numeric FORMAT 'L99,999.99');
+ numeric 
+---------
+ 1234.56
+(1 row)
+
 SELECT to_number('1,234.56','L99,999.99');
  to_number 
 -----------
    1234.56
 (1 row)
 
+SELECT CAST('1,234.56' as numeric FORMAT 'L99,999.99');
+ numeric 
+---------
+ 1234.56
+(1 row)
+
 SELECT to_number('42nd', '99th');
  to_number 
 -----------
         42
 (1 row)
 
+SELECT CAST('42nd' as numeric FORMAT '99th');
+ numeric 
+---------
+      42
+(1 row)
+
 SELECT to_number('123456', '99999V99');
         to_number        
 -------------------------
  1234.560000000000000000
 (1 row)
 
+SELECT CAST('123456' as numeric FORMAT '99999V99');
+         numeric         
+-------------------------
+ 1234.560000000000000000
+(1 row)
+
 -- Test for correct conversion between numbers and Roman numerals
 WITH rows AS
   (SELECT i, to_char(i, 'RN') AS roman FROM generate_series(1, 3999) AS i)
 SELECT
-  bool_and(to_number(roman, 'RN') = i) as valid
+  bool_and(to_number(roman, 'RN') = i) as valid,
+  bool_and(cast(roman as numeric format 'RN') = i) as valid
 FROM rows;
- valid 
--------
- t
+ valid | valid 
+-------+-------
+ t     | t
 (1 row)
 
 -- Some additional tests for RN input
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 8fa0a6c47fb..b6ef7ccecb2 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -115,7 +115,7 @@ test: json jsonb json_encoding jsonpath jsonpath_encoding jsonb_jsonpath sqljson
 # NB: temp.sql does reconnects which transiently uses 2 connections,
 # so keep this parallel group to at most 19 tests
 # ----------
-test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml
+test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml cast
 
 # ----------
 # Another group of parallel tests
diff --git a/src/test/regress/sql/cast.sql b/src/test/regress/sql/cast.sql
new file mode 100644
index 00000000000..e991016f828
--- /dev/null
+++ b/src/test/regress/sql/cast.sql
@@ -0,0 +1,123 @@
+create function ret_settxt() returns setof text as
+$$
+begin
+    return query execute 'select 1 union all select 1';
+end;
+$$
+language plpgsql immutable;
+
+-- check CAST FORMAT expression, the following should all fail
+select cast(NULL as date format ret_settxt()); -- cannot return a set
+select cast(NULL as date format (select 1::text where false));
+select cast(NULL as date format (string_agg(NULL, ' ')));
+select cast(NULL as date format (string_agg(NULL, ' ') over () ));
+select cast(NULL as date format NULL::int);
+select cast('1' as date format B'01');
+
+-- CAST FORMAT is restricted to the source and target types used by to_char, to_date, to_timestamp, and to_number
+-- The following should all fail
+select cast('hello' as name format 'test');
+select cast('hello' as bpchar format 'test');
+select cast('-34,338,492' as bigint format '99G999G999');
+select cast(array[1] as text format 'YYYY');
+select cast('1' as timestamp[] format 'YYYY-MM-DD');
+select cast('2012-13-12' as timestamp format 'YYYY-MM-DD');
+select cast('2012-13-12' as time format 'YYYY-MM-DD');
+select cast('2012-13-12' as timetz format 'YYYY-MM-DD');
+select cast('2012-13-12' as interval format 'YYYY-MM-DD');
+select cast('1'::text as unknown format 'YYYY-MM-DD');
+select cast('1' as bool format 'YYYY-MM-DD');
+select cast('1' as json format 'YYYY-MM-DD');
+select cast('1'::json as text format 'YYYY-MM-DD');
+select cast('1' as anyelement format 'YYYY-MM-DD');
+select cast('1' as anyenum format 'YYYY-MM-DD');
+select cast('1' as anyarray format 'YYYY-MM-DD');
+select cast(null::anyelement as anyelement format 'YYYY-MM-DD');
+select cast('2012-12-12 12:00'::timetz as text format 'YYYY-MM-DD HH:MI:SS TZ');
+select cast(null::regclass as text format 'YYYY-MM-DD HH:MI:SS TZ');
+select cast(null::int2 as numeric format null);
+select cast(null::date as timestamptz format null);
+select cast(null::time as timestamptz format null);
+
+select cast('hello'::text collate "C" as date format 'test' collate "POSIX"); -- error
+select cast('hello'::text collate "C" as date format 'test' collate "POSIX"); -- error
+
+-- CAST FORMAT is not supported for binary coercible type cast
+select cast('2022-01-01' as unknown format null);
+select cast('1' as text format '1'::text);
+select cast('1'::text as text format '1'::text);
+
+select cast('2012-12-12 12:00'::timetz as text format 'YYYY-MM-DD HH:MI:SS TZ'); -- error
+select cast('2012-13-12' as date format 'YYYY-DD-MM'); -- ok
+select cast('2012-13-12' as date format 'YYYY-MM-DD'); -- error
+select cast('1' as date format 'YYYY-MM-DD');
+select cast('1' collate "C" as date format 'YYYY-MM-DD');
+select cast('2012-13-12' as date format 'YYYY-DD-MM') as date;
+select cast('2012-13-12' as timestamptz format 'YYYY-DD-MM') as date;
+select cast('2012-13-12'::text as timestamp format 'YYYY-DD-MM') as date; -- error
+select cast('1' as timestamp format 'YYYY-MM-DD') = to_timestamp('1', 'YYYY-MM-DD');
+select cast('2026-01-28 13:29:12.324606+01'::text as timestamp format 'YYYY-MM-DD') =
+            to_timestamp('2026-01-28 13:29:12.324606+01'::text, 'YYYY-MM-DD');
+select cast('1' as date format 'YYYY-MM-DD') = to_date('1', 'YYYY-MM-DD');
+
+-- test with domain
+create domain d1 as date check (value <> '0001-01-01');
+create domain d2 as text check (value <> '125.80-');
+select cast(-125.8::numeric as text format '999D99S');
+select cast(-125.8::numeric as d2 format '999D99S');
+select cast('1' as text format 'YYYY-MM-DD'); -- error
+select cast('1' as d1 format 'YYYY-MM-DD'); -- error
+select cast('1' as date format 'MM-DD'); -- ok
+select cast('1' as d1 format 'MM-DD'); -- ok
+create temp table tcast0 as select '1'::text as a;
+select cast(a as d1 format 'YYYY-MM-DD') from tcast0;
+
+select cast('1'::text collate "C" as date format 'YYYY-MM-DD');
+select cast('1' as date format 'YYYY-MM-DD') = to_date('1', 'YYYY-MM-DD') as expect_true;
+
+create table tcast(col1 text, col2 text, col3 date, col4 timestamptz, col5 int8);
+insert into tcast(col1, col2, col5)
+  values('2022-12-13', 'YYYY-MM-DD', 1234),
+        ('2022-12-01', 'YYYY-DD-MM', -1234);
+
+select cast(col1 as date format col2) from tcast;
+select cast(col1 as date format col3) from tcast; -- error
+select cast(col1 as date format col3::text) from tcast; -- ok
+
+create function imm_const() returns text as $$ begin return 'YYYY-MM-DD'; end; $$ language plpgsql immutable;
+select cast(col1 as date format imm_const()) from tcast;
+create index s1 on tcast(to_date(col1, 'YYYY-MM-DD')); -- error
+create index s1 on tcast(cast(col1 as date format 'YYYY-MM-DD')); -- error
+
+create view tcast_v1 as
+   select cast(col1 as date format 'YYYY-MM-DD') as to_date,
+         cast(col1 as timestamptz format 'YYYY-MM-DD') as to_timestamptz,
+         cast(NULL::interval as text format 'YYYY-MM-DD') as to_txt0,
+         cast(col1::timestamp as text format 'YYYY-MM-DD') as to_txt1,
+      --    cast(col3 as text format 'YYYY-MM-DD') as to_txt2,
+         cast(col4 as text format 'YYYY-MM-DD') as to_txt3,
+         cast(numeric 'inf' as text format 'YYYY-MM-DD') as to_txt4,
+         cast(bigint '12324' as text format 'YYYY-MM-DD') as to_txt5
+   from tcast;
+
+select pg_get_viewdef('tcast_v1', true);
+
+explain (verbose, costs off)
+select cast(col5::float8 as text format '9.99EEEE') as to_txt1,
+ cast(col5::numeric as text format '9.99EEEE') as to_txt2
+from tcast;
+
+create view tcast_v2 as
+   select cast(col5 as text format '9.99EEEE') as to_txt0,
+         cast(col5::float8 as text format '9.99EEEE') as to_txt1,
+         cast(col5::float4 as text format '9.99EEEE') as to_txt2,
+         cast(col5::numeric as text format '9.99EEEE') as to_txt3,
+      --    cast(col5::int2 as text format '9.99EEEE') as to_txt4,
+         cast(col5::int4 as text format '9.99EEEE') as to_txt5
+   from tcast;
+select pg_get_viewdef('tcast_v2', true);
+select * from tcast_v2;
+drop function ret_settxt;
+drop view tcast_v1, tcast_v2;
+drop table tcast;
+drop domain d1, d2;
\ No newline at end of file
diff --git a/src/test/regress/sql/collate.linux.utf8.sql b/src/test/regress/sql/collate.linux.utf8.sql
index 132d13af0a8..f8ba5a0e815 100644
--- a/src/test/regress/sql/collate.linux.utf8.sql
+++ b/src/test/regress/sql/collate.linux.utf8.sql
@@ -182,12 +182,20 @@ SELECT to_char(date '2010-02-01', 'DD TMMON YYYY' COLLATE "tr_TR");
 SELECT to_char(date '2010-04-01', 'DD TMMON YYYY');
 SELECT to_char(date '2010-04-01', 'DD TMMON YYYY' COLLATE "tr_TR");
 
+SELECT CAST(date '2010-02-01' as text format 'DD TMMON YYYY');
+SELECT CAST(date '2010-02-01' as text format 'DD TMMON YYYY' COLLATE "tr_TR");
+SELECT CAST(date '2010-04-01' as text format 'DD TMMON YYYY');
+SELECT CAST(date '2010-04-01' as text format  'DD TMMON YYYY' COLLATE "tr_TR");
+
 -- to_date
 
 SELECT to_date('01 ŞUB 2010', 'DD TMMON YYYY');
 SELECT to_date('01 Şub 2010', 'DD TMMON YYYY');
 SELECT to_date('1234567890ab 2010', 'TMMONTH YYYY'); -- fail
 
+SELECT CAST('01 ŞUB 2010' as date format 'DD TMMON YYYY');
+SELECT CAST('01 ŞUB 2010' as date format 'DD TMMON YYYY'); -- ok
+SELECT CAST('1234567890ab 2010' as date format 'TMMONTH YYYY'); -- fail
 
 -- backwards parsing
 
diff --git a/src/test/regress/sql/create_type.sql b/src/test/regress/sql/create_type.sql
index 8ccf35dbf1b..2e72589f62c 100644
--- a/src/test/regress/sql/create_type.sql
+++ b/src/test/regress/sql/create_type.sql
@@ -117,6 +117,17 @@ CREATE TYPE int42 (
    passedbyvalue
 );
 
+CREATE OR REPLACE FUNCTION int42_fmtout(int42, text)
+RETURNS TEXT AS
+$$
+   BEGIN
+      RETURN TO_CHAR($1::text::int, $2);
+   END
+$$
+LANGUAGE PLPGSQL STRICT IMMUTABLE;
+
+ALTER TYPE int42 SET (TYPFORMAT_OUT = int42_fmtout);
+
 CREATE TYPE text_w_default (
    internallength = variable,
    input = text_w_default_in,
@@ -131,6 +142,13 @@ INSERT INTO default_test DEFAULT VALUES;
 
 SELECT * FROM default_test;
 
+SELECT CAST('42'::text::int42 AS TEXT FORMAT 'EEEE9'); -- error
+SELECT CAST('42'::text::int42 AS TEXT FORMAT 'S999S'); -- error
+SELECT CAST('42'::text::int42 AS TEXT FORMAT '9.9V9'); -- error
+SELECT CAST('42'::text::int42 AS TEXT FORMAT 'RNEEEE'); -- error
+SELECT CAST('42'::text::int42 AS TEXT FORMAT '9.9.9'); -- error
+SELECT CAST(g::text::int42 AS TEXT FORMAT 'FM000') FROM (VALUES (-1), (0), (5), (100), (99)) s(g);
+
 -- We need a shell type to test some CREATE TYPE failure cases with
 CREATE TYPE bogus_type;
 
@@ -183,6 +201,46 @@ DROP TYPE default_test_row CASCADE;
 
 DROP TABLE default_test;
 
+-- CAST(expr as type FORMAT 'fmt') with user-defined types
+CREATE TYPE datealilas;
+
+CREATE FUNCTION datealilas_in(cstring)
+   RETURNS datealilas
+   AS 'date_in'
+   LANGUAGE internal STRICT IMMUTABLE;
+
+CREATE FUNCTION datealilas_out(datealilas)
+   RETURNS cstring
+   AS 'date_out'
+   LANGUAGE internal STRICT IMMUTABLE;
+
+CREATE TYPE datealilas (
+   internallength = 4,
+   input = datealilas_in,
+   output = datealilas_out,
+   alignment = int4,
+   passedbyvalue
+);
+CREATE OR REPLACE FUNCTION datealilasfmtin(TEXT, TEXT)
+RETURNS datealilas AS
+$$
+   BEGIN
+      RETURN TO_DATE($1, $2)::text::datealilas;
+   END
+$$
+LANGUAGE PLPGSQL STRICT IMMUTABLE;
+
+ALTER TYPE datealilas SET (TYPFORMAT_IN = datealilasfmtin);
+
+CREATE TEMP TABLE castfmt(a0 int2 default 2, a datealilas, b int default 3);
+INSERT INTO castfmt(a)
+   SELECT cast('2022-01-13' AS datealilas FORMAT 'YYYY-MM-DD')
+   UNION ALL
+   SELECT cast('2022-13-11' AS datealilas FORMAT 'YYYY-DD-MM');
+
+SELECT CAST('2022-01-13' AS datealilas FORMAT 'YYYY-DD-MM'); -- error
+SELECT CAST('2022-01-13'::text AS datealilas FORMAT 'YYYY-DD-MM'); -- error
+
 -- Check dependencies are established when creating a new type
 CREATE TYPE base_type;
 CREATE FUNCTION base_fn_in(cstring) RETURNS base_type AS 'boolin'
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index 8978249a5dc..714e375b088 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -539,21 +539,46 @@ SELECT to_timestamp('2011-12-18 11:38 -05:20', 'YYYY-MM-DD HH12:MI TZH:TZM');
 SELECT to_timestamp('2011-12-18 11:38 20',     'YYYY-MM-DD HH12:MI TZM');
 
 SELECT to_timestamp('2011-12-18 11:38 EST', 'YYYY-MM-DD HH12:MI TZ');
+SELECT cast('2011-12-18 11:38 EST' as timestamptz format 'YYYY-MM-DD HH12:MI TZ');
+
 SELECT to_timestamp('2011-12-18 11:38 -05', 'YYYY-MM-DD HH12:MI TZ');
+SELECT cast('2011-12-18 11:38 -05' as timestamptz format 'YYYY-MM-DD HH12:MI TZ');
+
 SELECT to_timestamp('2011-12-18 11:38 +01:30', 'YYYY-MM-DD HH12:MI TZ');
+SELECT cast('2011-12-18 11:38 +01:30' as timestamptz format 'YYYY-MM-DD HH12:MI TZ');
+
 SELECT to_timestamp('2011-12-18 11:38 MSK', 'YYYY-MM-DD HH12:MI TZ');  -- dyntz
+SELECT cast('2011-12-18 11:38 MSK' as timestamptz format 'YYYY-MM-DD HH12:MI TZ');  -- dyntz
+
 SELECT to_timestamp('2011-12-18 00:00 LMT', 'YYYY-MM-DD HH24:MI TZ');  -- dyntz
+SELECT cast('2011-12-18 00:00 LMT' as timestamptz format 'YYYY-MM-DD HH24:MI TZ');  -- dyntz
+
 SELECT to_timestamp('2011-12-18 11:38ESTFOO24', 'YYYY-MM-DD HH12:MITZFOOSS');
+SELECT cast('2011-12-18 11:38ESTFOO24' as timestamptz format 'YYYY-MM-DD HH12:MITZFOOSS');
+
 SELECT to_timestamp('2011-12-18 11:38-05FOO24', 'YYYY-MM-DD HH12:MITZFOOSS');
+SELECT cast('2011-12-18 11:38-05FOO24' as timestamptz format 'YYYY-MM-DD HH12:MITZFOOSS');
+
 SELECT to_timestamp('2011-12-18 11:38 JUNK', 'YYYY-MM-DD HH12:MI TZ');  -- error
+SELECT cast('2011-12-18 11:38 JUNK' as timestamptz format 'YYYY-MM-DD HH12:MI TZ');  -- error
+
 SELECT to_timestamp('2011-12-18 11:38 ...', 'YYYY-MM-DD HH12:MI TZ');  -- error
+SELECT cast('2011-12-18 11:38 ...' as timestamptz format 'YYYY-MM-DD HH12:MI TZ');  -- error
 
 SELECT to_timestamp('2011-12-18 11:38 -05', 'YYYY-MM-DD HH12:MI OF');
+SELECT cast ('2011-12-18 11:38 -05' as timestamptz format 'YYYY-MM-DD HH12:MI OF');
+
 SELECT to_timestamp('2011-12-18 11:38 +01:30', 'YYYY-MM-DD HH12:MI OF');
+SELECT cast('2011-12-18 11:38 +01:30' as timestamptz format 'YYYY-MM-DD HH12:MI OF');
+
 SELECT to_timestamp('2011-12-18 11:38 +xyz', 'YYYY-MM-DD HH12:MI OF');  -- error
+SELECT cast('2011-12-18 11:38 +xyz' as timestamptz format 'YYYY-MM-DD HH12:MI OF');  -- error
+
 SELECT to_timestamp('2011-12-18 11:38 +01:xyz', 'YYYY-MM-DD HH12:MI OF');  -- error
+SELECT cast('2011-12-18 11:38 +01:xyz' as timestamptz format 'YYYY-MM-DD HH12:MI OF');  -- error
 
 SELECT to_timestamp('2018-11-02 12:34:56.025', 'YYYY-MM-DD HH24:MI:SS.MS');
+SELECT cast('2018-11-02 12:34:56.025' as timestamptz format 'YYYY-MM-DD HH24:MI:SS.MS');
 
 SELECT i, to_timestamp('2018-11-02 12:34:56', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
 SELECT i, to_timestamp('2018-11-02 12:34:56.1', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
@@ -563,11 +588,15 @@ SELECT i, to_timestamp('2018-11-02 12:34:56.1234', 'YYYY-MM-DD HH24:MI:SS.FF' ||
 SELECT i, to_timestamp('2018-11-02 12:34:56.12345', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
 SELECT i, to_timestamp('2018-11-02 12:34:56.123456', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
 SELECT i, to_timestamp('2018-11-02 12:34:56.123456789', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, cast('2018-11-02 12:34:56.123456789' as timestamptz format 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
 SELECT i, to_timestamp('20181102123456123456', 'YYYYMMDDHH24MISSFF' || i) FROM generate_series(1, 6) i;
 
 SELECT to_date('1 4 1902', 'Q MM YYYY');  -- Q is ignored
+SELECT cast('1 4 1902' as date format 'Q MM YYYY');  -- Q is ignored
 SELECT to_date('3 4 21 01', 'W MM CC YY');
+SELECT cast('3 4 21 01' as date format 'W MM CC YY');
 SELECT to_date('2458872', 'J');
+SELECT cast('2458872' as date format 'J');
 
 --
 -- Check handling of BC dates
@@ -677,7 +706,9 @@ SELECT to_date('2147483647 01', 'CC YY');
 
 -- to_char's TZ format code produces zone abbrev if known
 SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD HH:MI:SS TZ');
+SELECT cast('2012-12-12 12:00'::timestamptz as text format 'YYYY-MM-DD HH:MI:SS TZ');
 SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD HH:MI:SS tz');
+SELECT cast('2012-12-12 12:00'::timestamptz as text format 'YYYY-MM-DD HH:MI:SS tz');
 
 --
 -- Check behavior with SQL-style fixed-GMT-offset time zone (cf bug #8572)
@@ -692,6 +723,17 @@ SELECT '2012-12-12 12:00'::timestamptz;
 SELECT '2012-12-12 12:00 America/New_York'::timestamptz;
 
 SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD HH:MI:SS TZ');
+SELECT cast('2012-12-12 12:00'::timestamptz as text format 'YYYY-MM-DD HH:MI:SS TZ'),
+        to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD HH:MI:SS TZ');
+
+SELECT cast('2012-12-12 12:00'::date as text format 'YYYY-MM-DD HH:MI:SS TZ'),
+        to_char('2012-12-12 12:00'::date, 'YYYY-MM-DD HH:MI:SS TZ');
+
+SELECT cast('12:00'::time as text format 'HH:MI:SS'),
+        to_char('12:00'::time, 'HH:MI:SS');
+
+SELECT cast('2012-12-12 12:00'::timetz as text format 'YYYY-MM-DD HH:MI:SS TZ');
+
 SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD SSSS');
 SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD SSSSS');
 
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 43bc793925e..a5b4d63e9a4 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -801,7 +801,9 @@ SELECT 'infinity'::interval::time;
 SELECT '-infinity'::interval::time;
 
 SELECT to_char('infinity'::interval, 'YYYY');
+SELECT cast('infinity'::interval as text format 'YYYY');
 SELECT to_char('-infinity'::interval, 'YYYY');
+SELECT cast('-infinity'::interval as text format 'YYYY');
 
 -- "ago" can only appear once at the end of an interval.
 SELECT INTERVAL '42 days 2 seconds ago ago';
diff --git a/src/test/regress/sql/numeric.sql b/src/test/regress/sql/numeric.sql
index 640c6d92f4c..fd7bc26887f 100644
--- a/src/test/regress/sql/numeric.sql
+++ b/src/test/regress/sql/numeric.sql
@@ -1065,34 +1065,58 @@ SELECT to_char('100'::numeric, 'f"ool\\"999');
 --
 SET lc_numeric = 'C';
 SELECT to_number('-34,338,492', '99G999G999');
+SELECT CAST('-34,338,492' as numeric FORMAT  '99G999G999');
 SELECT to_number('-34,338,492.654,878', '99G999G999D999G999');
+SELECT CAST('-34,338,492.654,878' as numeric FORMAT '99G999G999D999G999');
 SELECT to_number('<564646.654564>', '999999.999999PR');
+SELECT CAST('<564646.654564>' as numeric FORMAT '999999.999999PR');
 SELECT to_number('0.00001-', '9.999999S');
+SELECT CAST('0.00001-' as numeric FORMAT '9.999999S');
 SELECT to_number('5.01-', 'FM9.999999S');
+SELECT CAST('5.01-' as numeric FORMAT 'FM9.999999S');
 SELECT to_number('5.01-', 'FM9.999999MI');
+SELECT CAST('5.01-' as numeric FORMAT 'FM9.999999MI');
 SELECT to_number('5 4 4 4 4 8 . 7 8', '9 9 9 9 9 9 . 9 9');
+SELECT CAST('5 4 4 4 4 8 . 7 8' as numeric FORMAT '9 9 9 9 9 9 . 9 9');
 SELECT to_number('.01', 'FM9.99');
+SELECT CAST('.01' as numeric FORMAT 'FM9.99');
 SELECT to_number('.0', '99999999.99999999');
+SELECT CAST('.0' as numeric FORMAT '99999999.99999999');
 SELECT to_number('0', '99.99');
+SELECT CAST('0' as numeric FORMAT '99.99');
 SELECT to_number('.-01', 'S99.99');
+SELECT CAST('.-01' as numeric FORMAT 'S99.99');
 SELECT to_number('.01-', '99.99S');
+SELECT CAST('.01-' as numeric FORMAT '99.99S');
 SELECT to_number(' . 0 1-', ' 9 9 . 9 9 S');
+SELECT CAST(' . 0 1-' as numeric FORMAT ' 9 9 . 9 9 S');
 SELECT to_number('34,50','999,99');
+SELECT CAST('34,50' as numeric FORMAT '999,99');
 SELECT to_number('123,000','999G');
+SELECT CAST('123,000' as numeric FORMAT '999G');
 SELECT to_number('123456','999G999');
+SELECT CAST('123456' as numeric FORMAT '999G999');
 SELECT to_number('$1234.56','L9,999.99');
+SELECT CAST('$1234.56' as numeric FORMAT 'L9,999.99');
 SELECT to_number('$1234.56','L99,999.99');
+SELECT CAST('$1234.56' as numeric FORMAT 'L99,999.99');
 SELECT to_number('$1,234.56','L99,999.99');
+SELECT CAST('$1,234.56' as numeric FORMAT 'L99,999.99');
 SELECT to_number('1234.56','L99,999.99');
+SELECT CAST('1234.56' as numeric FORMAT 'L99,999.99');
 SELECT to_number('1,234.56','L99,999.99');
+SELECT CAST('1,234.56' as numeric FORMAT 'L99,999.99');
 SELECT to_number('42nd', '99th');
+SELECT CAST('42nd' as numeric FORMAT '99th');
 SELECT to_number('123456', '99999V99');
+SELECT CAST('123456' as numeric FORMAT '99999V99');
 
 -- Test for correct conversion between numbers and Roman numerals
 WITH rows AS
   (SELECT i, to_char(i, 'RN') AS roman FROM generate_series(1, 3999) AS i)
 SELECT
-  bool_and(to_number(roman, 'RN') = i) as valid
+  bool_and(to_number(roman, 'RN') = i) as valid,
+  bool_and(cast(roman as numeric format 'RN') = i) as valid
 FROM rows;
 
 -- Some additional tests for RN input
-- 
2.34.1

