On Mon, Jul 28, 2025 at 2:31 AM Vik Fearing <v...@postgresfriends.org> wrote: > > > On 27/07/2025 17:43, jian he wrote: > > hi. > > > > while working on CAST(... DEFAULT ON ERROR), I came across link[1]. I don't > > have access to the SQL standard, but based on the information in link[1], > > for > > CAST(val AS type FORMAT 'template'), I make the <cast template> as an > > A_Const > > node in gram.y. > > > Why does it have to be an A_const? Shouldn't any a_expr work there? >
you are right. a_expr should work. the attached patch changed accordingly. so now select cast(NULL as date format NULL::date); ---error select cast(NULL as date format lower('a')); --no error, returns NULL > > > so the attached patch is to implement > > CAST <left paren> > > <cast operand> AS <cast target> > > [ FORMAT <cast template> ] > > <right paren> > > This is correct syntax. Thanks for working on it! > > > This doesn't seem very postgres-y to me. Wouldn't it be better to add > something like castformatfuncid to pg_cast? That way any types that > have that would just call that. It would allow extensions to add > formatted casting to their types, for example. > select oid, castsource::regtype, casttarget::regtype, castfunc::regproc, castcontext, castmethod from pg_cast where casttarget::regtype::text in ('text') or castsource::regtype::text in ('text'); As you can see from the query output, cast from other type to text or cast from text to other type is not in the pg_cast catalog entry. there are in type input/output functions. it will be represented as a CoerceViaIO node. see function find_coercion_pathway (src/backend/parser/parse_coerce.c line:3577). adding these pg_cast entries seems tricky. for example: (assume castsource as numeric, casttarget as text) will (castsource as numeric, casttarget as text, castfunc as numeric_out, castformatfunc as numeric_to_char) ever work? but numeric_out' result type is cstring. so I tend to think adding castformatfunc to pg_cast will not work.
From bd4ae95df52319fd2bb50f653cc1ed49884e2ce7 Mon Sep 17 00:00:00 2001 From: jian he <jian.universal...@gmail.com> Date: Mon, 28 Jul 2025 16:19:32 +0800 Subject: [PATCH v2 1/1] CAST(val AS type FORMAT 'template') 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 | 318 +++++++++++++++++++++++++ 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 | 166 +++++++++++++ src/test/regress/expected/numeric.out | 49 +++- src/test/regress/sql/horology.sql | 26 ++ src/test/regress/sql/misc.sql | 47 ++++ src/test/regress/sql/numeric.sql | 11 +- 14 files changed, 828 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 73345bb3c70..8918adeae00 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); @@ -15945,6 +15947,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"), @@ -18832,12 +18836,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..ba171013ee7 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 normal type coerce. + */ +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,264 @@ 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 baseTypeId; + int32 baseTypeMod; + Oid inputBaseTypeId; + char t_typcategory; + char s_typcategory; + FuncExpr *fexpr; + Type textType; + List *args; + + baseTypeMod = targetTypeMod; + baseTypeId = getBaseTypeAndTypmod(targetTypeId, &baseTypeMod); + inputBaseTypeId = getBaseType(inputTypeId); + + s_typcategory = TypeCategory(inputBaseTypeId); + t_typcategory = TypeCategory(baseTypeId); + + if (targetTypeId == inputTypeId || + node == NULL) + { + /* no conversion needed */ + return node; + } + + 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 (baseTypeId != NUMERICOID && + baseTypeId != TIMESTAMPTZOID && + baseTypeId != DATEOID && + t_typcategory != TYPCATEGORY_STRING) + { + ereport(ERROR, + errcode(ERRCODE_CANNOT_COERCE), + errmsg("cannot cast type %s to %s", + format_type_be(inputTypeId), + format_type_be(targetTypeId)), + errhint("Formatted type cast target type can only be timestamptz, text, numeric or date"); + 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_CANNOT_COERCE), + errmsg("cannot cast type %s to %s", + format_type_be(inputTypeId), + format_type_be(targetTypeId)), + errhint("Formatted type cast source type must be catgeory of numeric, string, datetime, or timespan"); + parser_coercion_errposition(pstate, location, node)); + } + + + if (baseTypeId == NUMERICOID) + funcId = fmgr_internal_function("numeric_to_number"); + else if (baseTypeId == TIMESTAMPTZOID) + funcId = fmgr_internal_function("to_timestamp"); + else if (baseTypeId == 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; + + 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 != baseTypeId) + result = coerce_to_domain(result, baseTypeId, 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..f4a3b0e1219 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 a414bfd6252..8b31f697fa7 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 98fd300c35a..4fc79385c7c 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, @@ -10840,6 +10842,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); @@ -10896,6 +10902,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..673205e6aac 100644 --- a/src/test/regress/expected/misc.out +++ b/src/test/regress/expected/misc.out @@ -396,3 +396,169 @@ 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: unrecognized type: 705 +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'); + ^ +--type check +select cast('1' as timestamp format 'YYYY-MM-DD'); --error +ERROR: cannot cast type unknown to timestamp without time zone +LINE 1: select cast('1' as timestamp format 'YYYY-MM-DD'); + ^ +HINT: Formatted type cast target type can only be timestamptz, text, numeric or date +select cast('1' as timestamp[] format 'YYYY-MM-DD'); --error +ERROR: cannot cast type unknown to timestamp without time zone[] +LINE 1: select cast('1' as timestamp[] format 'YYYY-MM-DD'); + ^ +HINT: Formatted type cast target type can only be timestamptz, text, numeric or date +select cast('1' as bool format 'YYYY-MM-DD'); --error +ERROR: cannot cast type unknown to boolean +LINE 1: select cast('1' as bool format 'YYYY-MM-DD'); + ^ +HINT: Formatted type cast target type can only be timestamptz, text, numeric or date +select cast('1' as json format 'YYYY-MM-DD'); --error +ERROR: cannot cast type unknown to json +LINE 1: select cast('1' as json format 'YYYY-MM-DD'); + ^ +HINT: Formatted type cast target type can only be timestamptz, text, numeric or date +select cast('1'::json as text format 'YYYY-MM-DD'); --error +ERROR: cannot cast type json to text +LINE 1: select cast('1'::json as text format 'YYYY-MM-DD'); + ^ +HINT: Formatted type cast source type must be catgeory of numeric, string, datetime, or timespan +--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) + +select cast('1' as date); --error +ERROR: invalid input syntax for type date: "1" +LINE 1: select cast('1' as date); + ^ +select cast('1' 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) + +select cast('2012-13-12' as date format 'YYYY-MM-DD'); --error +ERROR: date/time field value out of range: "2012-13-12" +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 volatile_const() RETURNS TEXT AS $$ BEGIN RETURN 'YYYY-MM-DD'; END; $$ LANGUAGE plpgsql VOLATILE; +CREATE FUNCTION stable_const() RETURNS TEXT AS $$ BEGIN RETURN 'YYYY-MM-DD'; END; $$ LANGUAGE plpgsql STABLE; +select cast(col1 as date format volatile_const()) from tcast; + col1 +------------ + 12-13-2022 + 12-01-2022 +(2 rows) + +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 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) + +select cast('2012-13-12' as date format 'YYYY-DD-MM') is not null as expect_true; + expect_true +------------- + t +(1 row) + +--null value check +select cast(NULL as date format 'YYYY-MM-DD'); + date +------ + +(1 row) + +select cast(NULL as numeric format 'YYYY-MM-DD'); + numeric +--------- + +(1 row) + +select cast(NULL as timestamptz format 'YYYY-MM-DD'); + timestamptz +------------- + +(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) + +select cast(NULL::numeric 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..8d6bdea7654 100644 --- a/src/test/regress/sql/misc.sql +++ b/src/test/regress/sql/misc.sql @@ -273,3 +273,50 @@ 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(array[1] as text format 'YYYY'); --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 + +--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 + +select cast('1' as date); --error +select cast('1' as date format 'YYYY-MM-DD'); +select cast('1' as date format 'YYYY-MM-DD') = to_date('1', 'YYYY-MM-DD') as expect_true; +select cast('2012-13-12' as date format 'YYYY-MM-DD'); --error + +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 volatile_const() RETURNS TEXT AS $$ BEGIN RETURN 'YYYY-MM-DD'; END; $$ LANGUAGE plpgsql VOLATILE; +CREATE FUNCTION stable_const() RETURNS TEXT AS $$ BEGIN RETURN 'YYYY-MM-DD'; END; $$ LANGUAGE plpgsql STABLE; +select cast(col1 as date format volatile_const()) from tcast; +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 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); +select cast('2012-13-12' as date format 'YYYY-DD-MM') is not null as expect_true; + +--null value check +select cast(NULL as date format 'YYYY-MM-DD'); +select cast(NULL as numeric format 'YYYY-MM-DD'); +select cast(NULL as timestamptz 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'); +select cast(NULL::numeric 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