hi.
please check the attached v4 patch.
1. For binary-coercible casts, if the format template is specified,
raise an error.
Example:
SELECT CAST('1'::text AS text FORMAT 'YYYY'::text); -- error
2. limited implementation — currently only supports to_char, to_date,
to_number, and to_timestamp.
3. coerce_to_target_type function is used in many places, refactoring
add another
argument seems not practical. So, I introduced a new function
coerce_to_target_type_fmt. Similarly, since coerce_type is difficult to
refactor too, I created a new function coerce_type_fmt.
At this stage, we have not modified any pg_cast entries. Adding to_char,
to_date, etc., into pg_cast has implications that require more consideration
(see [1]).
Also for this patch, including these functions in pg_cast is not really
necessary to achieve the intended behavior.
[1]
https://postgr.es/m/CACJufxF4OW=x2rcwa+zmcgopdwgkdxha09qtftpcj3qstg6...@mail.gmail.com
From ef719a5fcf2e96d7fc756a021aebd8caf5b0b385 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Tue, 12 Aug 2025 14:23:02 +0800
Subject: [PATCH v4 1/1] CAST(val AS type FORMAT 'template')
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
1. For binary-coercible casts, if a format template is specified, raise an error.
Example:
SELECT CAST('1'::text AS text FORMAT 'YYYY'::text); -- error
2. limited implementation — currently only supports to_char, to_date, to_number, and to_timestamp.
3. coerce_to_target_type function is used in many places, so adding another
argument is not practical. So, I introduced a new function
coerce_to_target_type_fmt. Similarly, since coerce_type is difficult to
refactor, we use function coerce_type_fmt.
At this stage, we have not modified any pg_cast entries. Adding to_char,
to_date, etc., into pg_cast has implications that require more consideration
(see [1]).
Also for this patch, including these functions in pg_cast is not really
necessary to achieve the intended behavior.
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_p6uuyes...@mail.gmail.com
---
src/backend/nodes/nodeFuncs.c | 2 +
src/backend/parser/gram.y | 17 ++
src/backend/parser/parse_coerce.c | 335 +++++++++++++++++++++++++
src/backend/parser/parse_expr.c | 27 +-
src/backend/parser/parse_utilcmd.c | 1 +
src/backend/utils/adt/ruleutils.c | 71 ++++++
src/include/nodes/parsenodes.h | 1 +
src/include/parser/parse_coerce.h | 8 +
src/test/regress/expected/horology.out | 94 +++++++
src/test/regress/expected/misc.out | 198 +++++++++++++++
src/test/regress/expected/numeric.out | 49 +++-
src/test/regress/sql/horology.sql | 26 ++
src/test/regress/sql/misc.sql | 51 ++++
src/test/regress/sql/numeric.sql | 11 +-
14 files changed, 881 insertions(+), 10 deletions(-)
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 7bc823507f1..91560bd1844 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -4464,6 +4464,8 @@ raw_expression_tree_walker_impl(Node *node,
if (WALK(tc->arg))
return true;
+ if (WALK(tc->format))
+ return true;
if (WALK(tc->typeName))
return true;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index db43034b9db..ef08bba36ef 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -156,6 +156,8 @@ 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, Node *format,
+ TypeName *typename, 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);
@@ -15933,6 +15935,8 @@ func_expr_common_subexpr:
}
| CAST '(' a_expr AS Typename ')'
{ $$ = makeTypeCast($3, $5, @1); }
+ | CAST '(' a_expr AS Typename FORMAT a_expr ')'
+ { $$ = makeFormattedTypeCast($3, $7, $5, @1); }
| EXTRACT '(' extract_list ')'
{
$$ = (Node *) makeFuncCall(SystemFuncName("extract"),
@@ -18820,12 +18824,25 @@ makeColumnRef(char *colname, List *indirection,
return (Node *) c;
}
+static Node *
+makeFormattedTypeCast(Node *arg, Node *format, TypeName *typename, int location)
+{
+ TypeCast *n = makeNode(TypeCast);
+
+ n->arg = arg;
+ n->format = format;
+ n->typeName = typename;
+ 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_coerce.c b/src/backend/parser/parse_coerce.c
index 0b5b81c7f27..28be9b94637 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -130,6 +130,66 @@ coerce_to_target_type(ParseState *pstate, Node *expr, Oid exprtype,
return result;
}
+/*
+ * For CAST(A AS TYPE FORMAT 'template'),
+ * generate a FuncExpr representing the underlying function call.
+ * See coerce_to_target_type for type coerce don't involve format template
+ */
+Node *
+coerce_to_target_type_fmt(ParseState *pstate, Node *expr, Node *format,
+ Oid exprtype, Oid targettype, int32 targettypmod,
+ CoercionContext ccontext, CoercionForm cformat,
+ int location)
+{
+ Node *result;
+ Node *origexpr;
+
+ if (!can_coerce_type(1, &exprtype, &targettype, ccontext))
+ return NULL;
+
+ /*
+ * If the input has a CollateExpr at the top, strip it off, perform the
+ * coercion, and put a new one back on. This is annoying since it
+ * duplicates logic in coerce_type, but if we don't do this then it's too
+ * hard to tell whether coerce_type actually changed anything, and we
+ * *must* know that to avoid possibly calling hide_coercion_node on
+ * something that wasn't generated by coerce_type. Note that if there are
+ * multiple stacked CollateExprs, we just discard all but the topmost.
+ * Also, if the target type isn't collatable, we discard the CollateExpr.
+ */
+ origexpr = expr;
+ while (expr && IsA(expr, CollateExpr))
+ expr = (Node *) ((CollateExpr *) expr)->arg;
+
+ result = coerce_type_fmt(pstate, expr, format, exprtype,
+ targettype, targettypmod,
+ ccontext, cformat, 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)));
+
+ if (expr != origexpr && type_is_collatable(targettype))
+ {
+ /* Reinstall top CollateExpr */
+ CollateExpr *coll = (CollateExpr *) origexpr;
+ CollateExpr *newcoll = makeNode(CollateExpr);
+
+ newcoll->arg = (Expr *) result;
+ newcoll->collOid = coll->collOid;
+ newcoll->location = coll->location;
+ result = (Node *) newcoll;
+ }
+
+ return result;
+}
+
/*
* coerce_type()
@@ -546,6 +606,281 @@ coerce_type(ParseState *pstate, Node *node,
}
+static Oid
+get_fmt_function(Oid targetTypeId)
+{
+ Oid funcId = InvalidOid;
+
+ switch (targetTypeId)
+ {
+ case INT4OID:
+ funcId = fmgr_internal_function("int4_to_char");
+ break;
+ case INT8OID:
+ funcId = fmgr_internal_function("int8_to_char");
+ break;
+ case NUMERICOID:
+ funcId = fmgr_internal_function("numeric_to_char");
+ break;
+ case FLOAT4OID:
+ funcId = fmgr_internal_function("float4_to_char");
+ break;
+ case FLOAT8OID:
+ funcId = fmgr_internal_function("float8_to_char");
+ break;
+ case TIMESTAMPOID:
+ funcId = fmgr_internal_function("timestamp_to_char");
+ break;
+ case TIMESTAMPTZOID:
+ funcId = fmgr_internal_function("timestamptz_to_char");
+ break;
+ case INTERVALOID:
+ funcId = fmgr_internal_function("interval_to_char");
+ break;
+ default:
+ elog(ERROR, "unrecognized type: %d", (int) targetTypeId);
+ break;
+ }
+ return funcId;
+}
+
+Node *
+coerce_type_fmt(ParseState *pstate, Node *node, Node *format,
+ Oid inputTypeId, Oid targetTypeId, int32 targetTypeMod,
+ CoercionContext ccontext, CoercionForm cformat, int location)
+{
+ Node *result;
+ Node *fmt;
+ Node *source = NULL;
+ Oid funcId;
+ Oid targetBaseTypeId;
+ int32 baseTypeMod;
+ Oid inputBaseTypeId;
+ char t_typcategory;
+ char s_typcategory;
+ FuncExpr *fexpr;
+ Type textType;
+ List *args;
+
+ baseTypeMod = targetTypeMod;
+ targetBaseTypeId = getBaseTypeAndTypmod(targetTypeId, &baseTypeMod);
+ inputBaseTypeId = getBaseType(inputTypeId);
+ t_typcategory = TypeCategory(targetBaseTypeId);
+ s_typcategory = TypeCategory(inputTypeId);
+
+ if (targetTypeId == inputTypeId ||
+ node == NULL)
+ {
+ ereport(ERROR,
+ errcode(ERRCODE_CANNOT_COERCE),
+ errmsg("can not use FORMAT template for binary coerceable type cast"),
+ parser_errposition(pstate, exprLocation(format)));
+ }
+
+ textType = typeidType(TEXTOID);
+ if (IsA(format, Const) && exprType(format) == UNKNOWNOID)
+ {
+ Const *con = (Const *) format;
+ Const *fmtcon = NULL;
+ fmtcon = makeNode(Const);
+ fmtcon->consttype = TEXTOID;
+ fmtcon->consttypmod = -1;
+ fmtcon->constcollid = typeTypeCollation(textType);
+ fmtcon->constlen = typeLen(textType);
+ fmtcon->constbyval = typeByVal(textType);
+ fmtcon->constisnull = con->constisnull;
+ fmtcon->location = exprLocation(format);
+
+ /* format string can not be null */
+ Assert(!con->constisnull);
+ fmtcon->constvalue = stringTypeDatum(textType,
+ DatumGetCString(con->constvalue),
+ -1);
+ fmtcon->constvalue =
+ PointerGetDatum(PG_DETOAST_DATUM(fmtcon->constvalue));
+ fmt = (Node *) fmtcon;
+ }
+ else
+ {
+ if (TypeCategory(exprType(format)) != TYPCATEGORY_STRING)
+ ereport(ERROR,
+ errcode(ERRCODE_CANNOT_COERCE),
+ errmsg("FORMAT template is not string type"),
+ parser_errposition(pstate, exprLocation(format)));
+
+ if (expression_returns_set(format))
+ ereport(ERROR,
+ errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("FORMAT template expression must not return a set"),
+ parser_errposition(pstate, exprLocation(format)));
+
+ fmt = format;
+ }
+
+ if (targetBaseTypeId != NUMERICOID &&
+ targetBaseTypeId != TIMESTAMPTZOID &&
+ targetBaseTypeId != DATEOID &&
+ t_typcategory != TYPCATEGORY_STRING)
+ {
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot cast type %s to %s using formatted template",
+ format_type_be(inputTypeId),
+ format_type_be(targetTypeId)),
+ errhint("Only timestamptz, text, numeric and date data type are supported for formatted type casting currently"),
+ parser_coercion_errposition(pstate, location, node));
+ }
+
+ if (inputBaseTypeId != INT4OID &&
+ inputBaseTypeId != INT8OID &&
+ inputBaseTypeId != NUMERICOID &&
+ inputBaseTypeId != FLOAT4OID &&
+ inputBaseTypeId != FLOAT8OID &&
+ inputBaseTypeId != TIMESTAMPOID &&
+ inputBaseTypeId != TIMESTAMPTZOID &&
+ inputBaseTypeId != INTERVALOID &&
+ inputBaseTypeId != UNKNOWNOID &&
+ s_typcategory != TYPCATEGORY_STRING)
+ {
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot cast type %s to %s using formatted template",
+ format_type_be(inputTypeId),
+ format_type_be(targetTypeId)),
+ errhint("Only catgeory of numeric, string, datetime, and timespan source data type are supported for formatted type casting");
+ parser_coercion_errposition(pstate, location, node));
+ }
+
+ /*
+ * Unknown resolve to text Const eventually, but currently text cast to text
+ * with formatted template is not supported
+ */
+ if (t_typcategory == TYPCATEGORY_STRING &&
+ inputBaseTypeId == UNKNOWNOID)
+ {
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot cast type %s to %s using formatted template",
+ "text",
+ format_type_be(targetTypeId)),
+ parser_coercion_errposition(pstate, location, node));
+ }
+
+ if (targetBaseTypeId == NUMERICOID)
+ funcId = fmgr_internal_function("numeric_to_number");
+ else if (targetBaseTypeId == TIMESTAMPTZOID)
+ funcId = fmgr_internal_function("to_timestamp");
+ else if (targetBaseTypeId == DATEOID)
+ funcId = fmgr_internal_function("to_date");
+ else
+ funcId = get_fmt_function(inputBaseTypeId); /* to_char variant */
+
+ Assert(OidIsValid(funcId));
+
+ if (inputTypeId == UNKNOWNOID && IsA(node, Const))
+ {
+ /*
+ * We assume here that UNKNOWN's internal representation is the same as
+ * CSTRING.
+ */
+ Const *con = (Const *) node;
+ Const *newcon = NULL;
+
+ newcon = makeNode(Const);
+ newcon->consttype = TEXTOID;
+ newcon->consttypmod = -1;
+ newcon->constcollid = typeTypeCollation(textType);
+ newcon->constlen = typeLen(textType);
+ newcon->constbyval = typeByVal(textType);
+ newcon->constisnull = con->constisnull;
+ newcon->location = exprLocation(node);
+
+ if (con->constisnull)
+ newcon->constvalue = (Datum) 0;
+ else
+ {
+ newcon->constvalue = stringTypeDatum(textType,
+ DatumGetCString(con->constvalue),
+ -1);
+ /*
+ * If it's a varlena value, force it to be in non-expanded (non-toasted)
+ * format; this avoids any possible dependency on external values and
+ * improves consistency of representation.
+ */
+ newcon->constvalue =
+ PointerGetDatum(PG_DETOAST_DATUM(newcon->constvalue));
+ }
+ source = (Node *) newcon;
+ }
+ else
+ source = node;
+
+ /* FIXME: don't understand this part */
+ if (IsA(node, Param) &&
+ pstate != NULL && pstate->p_coerce_param_hook != NULL)
+ {
+ /*
+ * Allow the CoerceParamHook to decide what happens. It can return a
+ * transformed node (very possibly the same Param node), or return
+ * NULL to indicate we should proceed with normal coercion.
+ */
+ result = pstate->p_coerce_param_hook(pstate,
+ (Param *) node,
+ targetTypeId,
+ targetTypeMod,
+ location);
+ if (result)
+ return result;
+ }
+
+ if (IsA(node, CollateExpr))
+ {
+ /*
+ * If we have a COLLATE clause, we have to push the coercion
+ * underneath the COLLATE; or discard the COLLATE if the target type
+ * isn't collatable. This is really ugly, but there is little choice
+ * because the above hacks on Consts and Params wouldn't happen
+ * otherwise. This kluge has consequences in coerce_to_target_type.
+ */
+ CollateExpr *coll = (CollateExpr *) node;
+
+ result = coerce_type_fmt(pstate, (Node *) coll->arg, format,
+ inputTypeId, targetTypeId, targetTypeMod,
+ ccontext, cformat, location);
+ if (type_is_collatable(targetTypeId))
+ {
+ CollateExpr *newcoll = makeNode(CollateExpr);
+
+ newcoll->arg = (Expr *) result;
+ newcoll->collOid = coll->collOid;
+ newcoll->location = coll->location;
+ result = (Node *) newcoll;
+ }
+ return result;
+ }
+
+ args = list_make1(source);
+ args = lappend(args, fmt);
+ fexpr = makeFuncExpr(funcId, targetTypeId, args,
+ InvalidOid, InvalidOid, cformat);
+ fexpr->location = location;
+ result = (Node *) fexpr;
+
+ /*
+ * If domain, coerce to the domain type and relabel with domain type ID,
+ * hiding the previous coercion node.
+ */
+ if (targetTypeId != targetBaseTypeId)
+ result = coerce_to_domain(result, targetBaseTypeId, baseTypeMod,
+ targetTypeId,
+ ccontext, cformat, location,
+ true);
+
+ ReleaseSysCache(textType);
+
+ return result;
+}
+
/*
* can_coerce_type()
* Can input_typeids be coerced to target_typeids?
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index d66276801c6..ef7d103a998 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -2706,6 +2706,7 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
Node *result;
Node *arg = tc->arg;
Node *expr;
+ Node *format = NULL;
Oid inputType;
Oid targetType;
int32 targetTypmod;
@@ -2727,6 +2728,12 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
int32 targetBaseTypmod;
Oid elementType;
+ if(tc->format)
+ ereport(ERROR,
+ errcode(ERRCODE_CANNOT_COERCE),
+ errmsg("formmatted type cast does not apply to array type");
+ parser_coercion_errposition(pstate, exprLocation(arg), arg));
+
/*
* If target is a domain over array, work with the base array type
* here. Below, we'll cast the array type to the domain. In the
@@ -2754,6 +2761,9 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
if (inputType == InvalidOid)
return expr; /* do nothing if NULL input */
+ if(tc->format)
+ format = transformExprRecurse(pstate, tc->format);
+
/*
* Location of the coercion is preferentially the location of the :: or
* CAST symbol, but if there is none then use the location of the type
@@ -2763,11 +2773,18 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
if (location < 0)
location = tc->typeName->location;
- result = coerce_to_target_type(pstate, expr, inputType,
- targetType, targetTypmod,
- COERCION_EXPLICIT,
- COERCE_EXPLICIT_CAST,
- location);
+ if (format != NULL)
+ result = coerce_to_target_type_fmt(pstate, expr, format, inputType,
+ targetType, targetTypmod,
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ location);
+ else
+ result = coerce_to_target_type(pstate, expr, inputType,
+ targetType, targetTypmod,
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ location);
if (result == NULL)
ereport(ERROR,
(errcode(ERRCODE_CANNOT_COERCE),
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..b1c19e6b105 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -682,6 +682,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 3d6e6bdbfd2..187a776d963 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -474,6 +474,8 @@ static bool looks_like_function(Node *node);
static void get_oper_expr(OpExpr *expr, deparse_context *context);
static void get_func_expr(FuncExpr *expr, deparse_context *context,
bool showimplicit);
+static bool get_fmt_coercion_expr(FuncExpr *expr, deparse_context *context,
+ Oid resulttype, int32 resulttypmod);
static void get_agg_expr(Aggref *aggref, deparse_context *context,
Aggref *original_aggref);
static void get_agg_expr_helper(Aggref *aggref, deparse_context *context,
@@ -10808,6 +10810,10 @@ get_func_expr(FuncExpr *expr, deparse_context *context,
/* Get the typmod if this is a length-coercion function */
(void) exprIsLengthCoercion((Node *) expr, &coercedTypmod);
+ if (get_fmt_coercion_expr(expr, context,
+ rettype, coercedTypmod))
+ return;
+
get_coercion_expr(arg, context,
rettype, coercedTypmod,
(Node *) expr);
@@ -10864,6 +10870,71 @@ get_func_expr(FuncExpr *expr, deparse_context *context,
appendStringInfoChar(buf, ')');
}
+/*
+ * get_fmt_coercion_expr
+ *
+ * Parse back expression: CAST (expr AS type FORMAT 'fmt')
+ */
+static bool
+get_fmt_coercion_expr(FuncExpr *expr, deparse_context *context,
+ Oid resulttype, int32 resulttypmod)
+
+{
+ Node *arg;
+ Const *second_arg;
+ FuncExpr *func;
+ char *funcname;
+ Oid procnspid;
+ StringInfo buf = context->buf;
+
+ func = expr;
+ if (func->funcformat != COERCE_EXPLICIT_CAST)
+ return false;
+
+ if (list_length(func->args) != 2)
+ return false;
+
+ arg = linitial(func->args);
+ second_arg = (Const *) lsecond(func->args);
+
+ if (!IsA(second_arg, Const) ||
+ second_arg->consttype != TEXTOID ||
+ second_arg->constisnull)
+ return false;
+
+ procnspid = get_func_namespace(func->funcid);
+ if (!IsCatalogNamespace(procnspid))
+ return false;
+
+ funcname = get_func_name(func->funcid);
+ if (strcmp(funcname, "to_char") && strcmp(funcname, "to_date") &&
+ strcmp(funcname, "to_number") && strcmp(funcname, "to_timestamp"))
+ return false;
+
+ appendStringInfoString(buf, "CAST(");
+
+ if (!PRETTY_PAREN(context))
+ appendStringInfoChar(buf, '(');
+ get_rule_expr_paren(arg, context, false, (Node *) func);
+ if (!PRETTY_PAREN(context))
+ appendStringInfoChar(buf, ')');
+
+ /*
+ * Never emit resulttype(arg) functional notation. A pg_proc entry could
+ * take precedence, and a resulttype in pg_temp would require schema
+ * qualification that format_type_with_typemod() would usually omit. We've
+ * standardized on arg::resulttype, but CAST(arg AS resulttype) notation
+ * would work fine.
+ */
+ appendStringInfo(buf, " AS %s FORMAT ",
+ format_type_with_typemod(resulttype, resulttypmod));
+
+ get_const_expr((Const *) second_arg, context, -1);
+ appendStringInfoChar(buf, ')');
+
+ return true;
+}
+
/*
* get_agg_expr - Parse back an Aggref node
*/
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 86a236bd58b..b71c4135ae5 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -395,6 +395,7 @@ typedef struct TypeCast
{
NodeTag type;
Node *arg; /* the expression being casted */
+ Node *format; /* the cast format template Const*/
TypeName *typeName; /* the target type */
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 8d775c72c59..282f559c4e1 100644
--- a/src/include/parser/parse_coerce.h
+++ b/src/include/parser/parse_coerce.h
@@ -43,11 +43,19 @@ 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,Node *format,
+ Oid exprtype, Oid targettype,
+ int32 targettypmod, CoercionContext ccontext,
+ CoercionForm cformat, int location);
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,
Oid inputTypeId, Oid targetTypeId, int32 targetTypeMod,
CoercionContext ccontext, CoercionForm cformat, int location);
+Node *coerce_type_fmt(ParseState *pstate, Node *node, Node *format,
+ Oid inputTypeId, Oid targetTypeId, int32 targetTypeMod,
+ CoercionContext ccontext, CoercionForm cformat, int location);
extern Node *coerce_to_domain(Node *arg, Oid baseTypeId, int32 baseTypeMod,
Oid typeId,
CoercionContext ccontext, CoercionForm cformat, int location,
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index 5ae93d8e8a5..c6b8b65b6dc 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -3110,6 +3110,13 @@ SELECT to_timestamp('15 "text between quote marks" 98 54 45',
Thu Jan 01 15:54:45 1998 PST
(1 row)
+SELECT cast('15 "text between quote marks" 98 54 45' as timestamptz format
+ E'HH24 "\\"text between quote marks\\"" YY MI SS');
+ timestamptz
+------------------------------
+ Thu Jan 01 15:54:45 1998 PST
+(1 row)
+
SELECT to_timestamp('05121445482000', 'MMDDHH24MISSYYYY');
to_timestamp
------------------------------
@@ -3341,12 +3348,24 @@ SELECT to_timestamp('2011-12-18 11:38 MSK', 'YYYY-MM-DD HH12:MI TZ'); -- dyntz
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');
+ 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
------------------------------
@@ -3380,9 +3399,15 @@ SELECT to_timestamp('2011-12-18 11:38 +01:30', 'YYYY-MM-DD HH12:MI OF');
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"
+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"
+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
----------------------------------
@@ -3466,6 +3491,27 @@ SELECT i, to_timestamp('2018-11-02 12:34:56.123456', 'YYYY-MM-DD HH24:MI:SS.FF'
6 | Fri Nov 02 12:34:56.123456 2018 PDT
(6 rows)
+SELECT cast('2018-11-02 12:34:56.123456' as timestamptz(1) format 'YYYY-MM-DD HH24:MI:SS.FF6')
+UNION ALL
+SELECT cast('2018-11-02 12:34:56.123456' as timestamptz(2) format 'YYYY-MM-DD HH24:MI:SS.FF6')
+UNION ALL
+SELECT cast('2018-11-02 12:34:56.123456' as timestamptz(3) format 'YYYY-MM-DD HH24:MI:SS.FF6')
+UNION ALL
+SELECT cast('2018-11-02 12:34:56.123456' as timestamptz(4) format 'YYYY-MM-DD HH24:MI:SS.FF6')
+UNION ALL
+SELECT cast('2018-11-02 12:34:56.123456' as timestamptz(5) format 'YYYY-MM-DD HH24:MI:SS.FF6')
+UNION ALL
+SELECT cast('2018-11-02 12:34:56.123456' as timestamptz(6) format 'YYYY-MM-DD HH24:MI:SS.FF6');
+ timestamptz
+-------------------------------------
+ Fri Nov 02 12:34:56.1 2018 PDT
+ Fri Nov 02 12:34:56.12 2018 PDT
+ Fri Nov 02 12:34:56.123 2018 PDT
+ Fri Nov 02 12:34:56.1235 2018 PDT
+ Fri Nov 02 12:34:56.12346 2018 PDT
+ Fri Nov 02 12:34:56.123456 2018 PDT
+(6 rows)
+
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, to_timestamp('20181102123456123456', 'YYYYMMDDHH24MISSFF' || i) FROM generate_series(1, 6) i;
@@ -3485,18 +3531,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
--
@@ -3832,12 +3896,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)
--
@@ -3867,18 +3943,36 @@ 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');
+ text
+----------------------------
+ 2012-12-12 12:00:00 -01:30
+(1 row)
+
SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD SSSS');
to_char
------------------
2012-12-12 43200
(1 row)
+SELECT cast('2012-12-12 12:00'::timestamptz as text format 'YYYY-MM-DD SSSS');
+ text
+------------------
+ 2012-12-12 43200
+(1 row)
+
SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD SSSSS');
to_char
------------------
2012-12-12 43200
(1 row)
+SELECT cast('2012-12-12 12:00'::timestamptz as text format 'YYYY-MM-DD SSSSS');
+ text
+------------------
+ 2012-12-12 43200
+(1 row)
+
SET TIME ZONE '+2';
SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD HH:MI:SS TZ');
to_char
diff --git a/src/test/regress/expected/misc.out b/src/test/regress/expected/misc.out
index 6e816c57f1f..fc2ec8c5e3a 100644
--- a/src/test/regress/expected/misc.out
+++ b/src/test/regress/expected/misc.out
@@ -396,3 +396,201 @@ SELECT *, (equipment(CAST((h.*) AS hobbies_r))).name FROM hobbies_r h;
--
-- rewrite rules
--
+select cast('1' as text format 1); --error
+ERROR: FORMAT template is not string type
+LINE 1: select cast('1' as text format 1);
+ ^
+select cast('1' as text format '1'::text); --error
+ERROR: cannot cast type text to text using formatted template
+LINE 1: select cast('1' as text format '1'::text);
+ ^
+select cast('1'::text as text format '1'::text); --error
+ERROR: can not use FORMAT template for binary coerceable type cast
+LINE 1: select cast('1'::text as text format '1'::text);
+ ^
+select cast(array[1] as text format 'YYYY'); --error
+ERROR: formmatted type cast does not apply to array type
+LINE 1: select cast(array[1] as text format 'YYYY');
+ ^
+select cast('1' 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' as date format 'YYYY-MM-DD'); --error
+ERROR: date/time field value out of range: "2012-13-12"
+--type check
+select cast('1' as timestamp format 'YYYY-MM-DD'); --error
+ERROR: cannot cast type unknown to timestamp without time zone using formatted template
+LINE 1: select cast('1' as timestamp format 'YYYY-MM-DD');
+ ^
+HINT: Only timestamptz, text, numeric and date data type are supported for formatted type casting currently
+select cast('1' as timestamp[] format 'YYYY-MM-DD'); --error
+ERROR: cannot cast type unknown to timestamp without time zone[] using formatted template
+LINE 1: select cast('1' as timestamp[] format 'YYYY-MM-DD');
+ ^
+HINT: Only timestamptz, text, numeric and date data type are supported for formatted type casting currently
+select cast('1' as bool format 'YYYY-MM-DD'); --error
+ERROR: cannot cast type unknown to boolean using formatted template
+LINE 1: select cast('1' as bool format 'YYYY-MM-DD');
+ ^
+HINT: Only timestamptz, text, numeric and date data type are supported for formatted type casting currently
+select cast('1' as json format 'YYYY-MM-DD'); --error
+ERROR: cannot cast type unknown to json using formatted template
+LINE 1: select cast('1' as json format 'YYYY-MM-DD');
+ ^
+HINT: Only timestamptz, text, numeric and date data type are supported for formatted type casting currently
+select cast('1'::json as text format 'YYYY-MM-DD'); --error
+ERROR: cannot cast type json to text using formatted template
+LINE 1: select cast('1'::json as text format 'YYYY-MM-DD');
+ ^
+HINT: Only catgeory of numeric, string, datetime, and timespan source data type are supported for formatted type casting
+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)
+
+--domain check
+create domain d1 as date check (value <> '0001-01-01');
+select cast('1' as d1 format 'YYYY-MM-DD'); --error
+ERROR: value for domain d1 violates check constraint "d1_check"
+select cast('1' as d1 format 'MM-DD'); --ok
+ d1
+---------------
+ 01-01-0001 BC
+(1 row)
+
+create table tcast(col1 text, col2 text, col3 date, col4 timestamptz);
+insert into tcast(col1, col2) values('2022-12-13', 'YYYY-MM-DD'), ('2022-12-01', 'YYYY-DD-MM');
+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: FORMAT template is not string type
+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 stable_const() RETURNS TEXT AS $$ BEGIN RETURN 'YYYY-MM-DD'; END; $$ LANGUAGE plpgsql STABLE;
+select cast(col1 as date format stable_const()) from tcast;
+ col1
+------------
+ 12-13-2022
+ 12-01-2022
+(2 rows)
+
+create index s1 on tcast(cast(col1 as date format 'YYYY-MM-DD')); --error
+ERROR: functions in index expression must be marked IMMUTABLE
+create index s1 on tcast(cast(col1 as date format stable_const())); --error
+ERROR: functions in index expression must be marked IMMUTABLE
+create view tcast_v1 as select cast(col1 as date format 'YYYY-MM-DD') from tcast;
+select pg_get_viewdef('tcast_v1', false);
+ pg_get_viewdef
+----------------------------------------------------------
+ SELECT CAST((col1) AS date FORMAT 'YYYY-MM-DD') AS col1+
+ FROM tcast;
+(1 row)
+
+select pg_get_viewdef('tcast_v1', true);
+ pg_get_viewdef
+--------------------------------------------------------
+ SELECT CAST(col1 AS date FORMAT 'YYYY-MM-DD') AS col1+
+ FROM tcast;
+(1 row)
+
+--null value check
+select cast(NULL::text as date format 'YYYY-MM-DD');
+ date
+------
+
+(1 row)
+
+select cast(NULL::text as numeric format 'YYYY-MM-DD');
+ numeric
+---------
+
+(1 row)
+
+select cast(NULL::text as timestamptz format 'YYYY-MM-DD');
+ timestamptz
+-------------
+
+(1 row)
+
+select cast(NULL::bigint AS TEXT format 'YYYY-MM-DD');
+ text
+------
+
+(1 row)
+
+select cast(NULL::int AS TEXT format 'YYYY-MM-DD');
+ text
+------
+
+(1 row)
+
+select cast(NULL::numeric AS TEXT format 'YYYY-MM-DD');
+ text
+------
+
+(1 row)
+
+select cast(NULL::float8 AS TEXT format 'YYYY-MM-DD');
+ text
+------
+
+(1 row)
+
+select cast(NULL::float4 AS TEXT format 'YYYY-MM-DD');
+ text
+------
+
+(1 row)
+
+select cast(NULL::interval AS TEXT format 'YYYY-MM-DD');
+ text
+------
+
+(1 row)
+
+select cast(NULL::timestamp AS TEXT format 'YYYY-MM-DD');
+ text
+------
+
+(1 row)
+
+select cast(NULL::timestamptz AS TEXT format 'YYYY-MM-DD');
+ text
+------
+
+(1 row)
+
diff --git a/src/test/regress/expected/numeric.out b/src/test/regress/expected/numeric.out
index c58e232a263..b48fe4f3037 100644
--- a/src/test/regress/expected/numeric.out
+++ b/src/test/regress/expected/numeric.out
@@ -2270,6 +2270,12 @@ SELECT to_number('-34,338,492.654,878', '99G999G999D999G999');
-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
----------------
@@ -2300,6 +2306,12 @@ SELECT to_number('5 4 4 4 4 8 . 7 8', '9 9 9 9 9 9 . 9 9');
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
-----------
@@ -2372,6 +2384,12 @@ SELECT to_number('$1,234.56','L99,999.99');
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
-----------
@@ -2390,21 +2408,34 @@ SELECT to_number('42nd', '99th');
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
@@ -2414,6 +2445,12 @@ SELECT to_number('CvIiI', 'rn');
108
(1 row)
+SELECT cast('CvIiI' as numeric format 'rn');
+ numeric
+---------
+ 108
+(1 row)
+
SELECT to_number('MMXX ', 'RN');
to_number
-----------
@@ -2441,8 +2478,12 @@ SELECT to_number('M CC', 'RN');
-- error cases
SELECT to_number('viv', 'RN');
ERROR: invalid Roman numeral
+SELECT cast('viv' as numeric format 'RN');
+ERROR: invalid Roman numeral
SELECT to_number('DCCCD', 'RN');
ERROR: invalid Roman numeral
+SELECT cast('DCCCD' as numeric format 'RN');
+ERROR: invalid Roman numeral
SELECT to_number('XIXL', 'RN');
ERROR: invalid Roman numeral
SELECT to_number('MCCM', 'RN');
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index 8978249a5dc..9fb50016c79 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -476,6 +476,9 @@ SELECT to_timestamp('1,582nd VIII 21', 'Y,YYYth FMRM DD');
SELECT to_timestamp('15 "text between quote marks" 98 54 45',
E'HH24 "\\"text between quote marks\\"" YY MI SS');
+SELECT cast('15 "text between quote marks" 98 54 45' as timestamptz format
+ E'HH24 "\\"text between quote marks\\"" YY MI SS');
+
SELECT to_timestamp('05121445482000', 'MMDDHH24MISSYYYY');
SELECT to_timestamp('2000January09Sunday', 'YYYYFMMonthDDFMDay');
@@ -542,7 +545,9 @@ SELECT to_timestamp('2011-12-18 11:38 EST', 'YYYY-MM-DD HH12:MI TZ');
SELECT to_timestamp('2011-12-18 11:38 -05', 'YYYY-MM-DD HH12:MI TZ');
SELECT to_timestamp('2011-12-18 11:38 +01:30', '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');
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 to_timestamp('2011-12-18 11:38-05FOO24', 'YYYY-MM-DD HH12:MITZFOOSS');
SELECT to_timestamp('2011-12-18 11:38 JUNK', 'YYYY-MM-DD HH12:MI TZ'); -- error
@@ -551,7 +556,9 @@ SELECT to_timestamp('2011-12-18 11:38 ...', 'YYYY-MM-DD HH12:MI TZ'); -- error
SELECT to_timestamp('2011-12-18 11:38 -05', 'YYYY-MM-DD HH12:MI OF');
SELECT to_timestamp('2011-12-18 11:38 +01:30', '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');
@@ -562,12 +569,26 @@ SELECT i, to_timestamp('2018-11-02 12:34:56.123', 'YYYY-MM-DD HH24:MI:SS.FF' ||
SELECT i, to_timestamp('2018-11-02 12:34:56.1234', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
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 cast('2018-11-02 12:34:56.123456' as timestamptz(1) format 'YYYY-MM-DD HH24:MI:SS.FF6')
+UNION ALL
+SELECT cast('2018-11-02 12:34:56.123456' as timestamptz(2) format 'YYYY-MM-DD HH24:MI:SS.FF6')
+UNION ALL
+SELECT cast('2018-11-02 12:34:56.123456' as timestamptz(3) format 'YYYY-MM-DD HH24:MI:SS.FF6')
+UNION ALL
+SELECT cast('2018-11-02 12:34:56.123456' as timestamptz(4) format 'YYYY-MM-DD HH24:MI:SS.FF6')
+UNION ALL
+SELECT cast('2018-11-02 12:34:56.123456' as timestamptz(5) format 'YYYY-MM-DD HH24:MI:SS.FF6')
+UNION ALL
+SELECT cast('2018-11-02 12:34:56.123456' as timestamptz(6) format 'YYYY-MM-DD HH24:MI:SS.FF6');
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, 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 +698,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,8 +715,11 @@ 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');
SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD SSSS');
+SELECT cast('2012-12-12 12:00'::timestamptz as text format 'YYYY-MM-DD SSSS');
SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD SSSSS');
+SELECT cast('2012-12-12 12:00'::timestamptz as text format 'YYYY-MM-DD SSSSS');
SET TIME ZONE '+2';
diff --git a/src/test/regress/sql/misc.sql b/src/test/regress/sql/misc.sql
index 165a2e175fb..e7646d6a2a2 100644
--- a/src/test/regress/sql/misc.sql
+++ b/src/test/regress/sql/misc.sql
@@ -273,3 +273,54 @@ SELECT *, (equipment(CAST((h.*) AS hobbies_r))).name FROM hobbies_r h;
--
-- rewrite rules
--
+
+select cast('1' as text format 1); --error
+select cast('1' as text format '1'::text); --error
+select cast('1'::text as text format '1'::text); --error
+select cast(array[1] as text format 'YYYY'); --error
+select cast('1' 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' as date format 'YYYY-MM-DD'); --error
+
+--type check
+select cast('1' as timestamp format 'YYYY-MM-DD'); --error
+select cast('1' as timestamp[] format 'YYYY-MM-DD'); --error
+select cast('1' as bool format 'YYYY-MM-DD'); --error
+select cast('1' as json format 'YYYY-MM-DD'); --error
+select cast('1'::json as text format 'YYYY-MM-DD'); --error
+
+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;
+
+--domain check
+create domain d1 as date check (value <> '0001-01-01');
+select cast('1' as d1 format 'YYYY-MM-DD'); --error
+select cast('1' as d1 format 'MM-DD'); --ok
+
+create table tcast(col1 text, col2 text, col3 date, col4 timestamptz);
+insert into tcast(col1, col2) values('2022-12-13', 'YYYY-MM-DD'), ('2022-12-01', 'YYYY-DD-MM');
+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 stable_const() RETURNS TEXT AS $$ BEGIN RETURN 'YYYY-MM-DD'; END; $$ LANGUAGE plpgsql STABLE;
+select cast(col1 as date format stable_const()) from tcast;
+create index s1 on tcast(cast(col1 as date format 'YYYY-MM-DD')); --error
+create index s1 on tcast(cast(col1 as date format stable_const())); --error
+create view tcast_v1 as select cast(col1 as date format 'YYYY-MM-DD') from tcast;
+select pg_get_viewdef('tcast_v1', false);
+select pg_get_viewdef('tcast_v1', true);
+
+--null value check
+select cast(NULL::text as date format 'YYYY-MM-DD');
+select cast(NULL::text as numeric format 'YYYY-MM-DD');
+select cast(NULL::text as timestamptz format 'YYYY-MM-DD');
+select cast(NULL::bigint AS TEXT format 'YYYY-MM-DD');
+select cast(NULL::int AS TEXT format 'YYYY-MM-DD');
+select cast(NULL::numeric AS TEXT format 'YYYY-MM-DD');
+select cast(NULL::float8 AS TEXT format 'YYYY-MM-DD');
+select cast(NULL::float4 AS TEXT format 'YYYY-MM-DD');
+select cast(NULL::interval AS TEXT format 'YYYY-MM-DD');
+select cast(NULL::timestamp AS TEXT format 'YYYY-MM-DD');
+select cast(NULL::timestamptz AS TEXT format 'YYYY-MM-DD');
diff --git a/src/test/regress/sql/numeric.sql b/src/test/regress/sql/numeric.sql
index 640c6d92f4c..1092317815b 100644
--- a/src/test/regress/sql/numeric.sql
+++ b/src/test/regress/sql/numeric.sql
@@ -1066,11 +1066,13 @@ SELECT to_char('100'::numeric, 'f"ool\\"999');
SET lc_numeric = 'C';
SELECT to_number('-34,338,492', '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 to_number('0.00001-', '9.999999S');
SELECT to_number('5.01-', 'FM9.999999S');
SELECT to_number('5.01-', '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 to_number('.0', '99999999.99999999');
SELECT to_number('0', '99.99');
@@ -1083,27 +1085,34 @@ SELECT to_number('123456','999G999');
SELECT to_number('$1234.56','L9,999.99');
SELECT to_number('$1234.56','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 to_number('1,234.56','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
SELECT to_number('CvIiI', 'rn');
+SELECT cast('CvIiI' as numeric format 'rn');
SELECT to_number('MMXX ', 'RN');
SELECT to_number(' XIV', ' RN');
SELECT to_number(' XIV ', ' RN');
SELECT to_number('M CC', 'RN');
-- error cases
SELECT to_number('viv', 'RN');
+SELECT cast('viv' as numeric format 'RN');
SELECT to_number('DCCCD', 'RN');
+SELECT cast('DCCCD' as numeric format 'RN');
SELECT to_number('XIXL', 'RN');
SELECT to_number('MCCM', 'RN');
SELECT to_number('MMMM', 'RN');
--
2.34.1