From 215fb468585617fbcd5ce9c19e688696d2fb7606 Mon Sep 17 00:00:00 2001 From: jian he Date: Fri, 30 Oct 2026 09:36:03 +0800 Subject: [PATCH v35 3/5] CAST(expr AS newtype DEFAULT expr ON CONVERSION ERROR) Introduce SQL-standard safe type cast syntax: CAST(expr AS newtype DEFAULT expr ON CONVERSION ERROR) This allow users to specify a default fallback expression if a type conversion fails, rather than raising an error. With this patchset, almost all of the cast functions in pg_cast.castfunc are now error-safe. CoerceViaIO and CoerceToDomain were already error-safe in the HEAD, see [0], this patch extends error-safe behavior to ArrayCoerceExpr. # Bumps catversion required [0]: https://git.postgresql.org/cgit/postgresql.git/commit/?id=aaaf9449ec6be62cb0d30ed3588dc384f56274b Related: https://postgr.es/m/4c602ca2-2c1a-420c-bbf2-906d65c93e77%40postgresfriends.org Author: jian he Reviewed-by: Amul Sul Reviewed-by: Andrew Dunstan Reviewed-by: Corey Huinker Reviewed-by: Kirill Reshke Reviewed-by: Matheus Alcantara Reviewed-by: Henson Choi Discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com Commitfest: https://commitfest.postgresql.org/patch/5941 --- contrib/citext/expected/citext.out | 3 + contrib/citext/expected/citext_1.out | 3 + contrib/citext/sql/citext.sql | 2 + doc/src/sgml/syntax.sgml | 39 + src/backend/executor/execExpr.c | 210 +++- src/backend/executor/execExprInterp.c | 182 ++++ src/backend/jit/llvm/llvmjit_expr.c | 80 +- src/backend/jit/llvm/llvmjit_types.c | 2 + src/backend/nodes/nodeFuncs.c | 77 ++ src/backend/optimizer/util/clauses.c | 202 +++- src/backend/parser/gram.y | 21 + src/backend/parser/parse_coerce.c | 109 +- src/backend/parser/parse_collate.c | 26 + src/backend/parser/parse_expr.c | 374 ++++++- src/backend/parser/parse_target.c | 2 +- src/backend/parser/parse_type.c | 22 + src/backend/parser/parse_utilcmd.c | 3 +- src/backend/utils/adt/arrayfuncs.c | 9 + src/backend/utils/adt/ruleutils.c | 28 + src/backend/utils/fmgr/README | 15 +- src/include/executor/execExpr.h | 15 + src/include/executor/executor.h | 1 + src/include/nodes/execnodes.h | 23 + src/include/nodes/parsenodes.h | 1 + src/include/nodes/primnodes.h | 71 ++ src/include/optimizer/optimizer.h | 2 +- src/include/parser/parse_coerce.h | 3 +- src/include/parser/parse_node.h | 6 + src/include/parser/parse_type.h | 2 + src/test/regress/expected/cast.out | 1194 +++++++++++++++++++++ src/test/regress/expected/create_cast.out | 10 + src/test/regress/expected/equivclass.out | 19 + src/test/regress/expected/xml.out | 6 + src/test/regress/parallel_schedule | 2 +- src/test/regress/sql/cast.sql | 461 ++++++++ src/test/regress/sql/create_cast.sql | 3 + src/test/regress/sql/equivclass.sql | 5 + src/test/regress/sql/xml.sql | 2 + src/tools/pgindent/typedefs.list | 3 + 39 files changed, 3180 insertions(+), 58 deletions(-) create mode 100644 src/test/regress/expected/cast.out create mode 100644 src/test/regress/sql/cast.sql diff --git a/contrib/citext/expected/citext.out b/contrib/citext/expected/citext.out index 8c0bf54f0f3..e9189ef3a5a 100644 --- a/contrib/citext/expected/citext.out +++ b/contrib/citext/expected/citext.out @@ -10,6 +10,9 @@ WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid); --------+--------- (0 rows) +SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR); -- error +ERROR: cannot cast type character to citext with DEFAULT expression in CAST ... ON CONVERSION ERROR +DETAIL: Explicit cast is defined but definition is not error safe. -- Test the operators and indexing functions -- Test = and <>. SELECT 'a'::citext = 'a'::citext AS t; diff --git a/contrib/citext/expected/citext_1.out b/contrib/citext/expected/citext_1.out index c5e5f180f2b..4cbaf5359d8 100644 --- a/contrib/citext/expected/citext_1.out +++ b/contrib/citext/expected/citext_1.out @@ -10,6 +10,9 @@ WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid); --------+--------- (0 rows) +SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR); -- error +ERROR: cannot cast type character to citext with DEFAULT expression in CAST ... ON CONVERSION ERROR +DETAIL: Explicit cast is defined but definition is not error safe. -- Test the operators and indexing functions -- Test = and <>. SELECT 'a'::citext = 'a'::citext AS t; diff --git a/contrib/citext/sql/citext.sql b/contrib/citext/sql/citext.sql index aa1cf9abd5c..c820b0bd4d9 100644 --- a/contrib/citext/sql/citext.sql +++ b/contrib/citext/sql/citext.sql @@ -9,6 +9,8 @@ SELECT amname, opcname FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid); +SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR); -- error + -- Test the operators and indexing functions -- Test = and <>. diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml index a697218c8b3..bb42ab30a49 100644 --- a/doc/src/sgml/syntax.sgml +++ b/doc/src/sgml/syntax.sgml @@ -2133,6 +2133,45 @@ CAST ( expression AS type . + + + Safe Type Casts + + + A type cast will fail if the input value cannot be resolved to a value of + the destination type. To guard against such failures, you can provide an + ON CONVERSION ERROR clause to control what happens when + a cast fails. The available forms are: + +CAST ( expression AS type DEFAULT expression ON CONVERSION ERROR ) +CAST ( expression AS type ERROR ON CONVERSION ERROR ) + + + When DEFAULT expression ON CONVERSION ERROR + is specified, if the first expression cannot be + cast to the destination type, then the specified + DEFAULTexpression + is evaluated and cast to the destination type, which itself will raise an + error if the value cannot be cast to the destination type. + + + + Casts created with CREATE CAST + are also supported, provided that the underlying cast function is marked ERROR SAFE; + see CREATE FUNCTION. + + + + Some examples: + +SELECT CAST('not-a-date' AS date DEFAULT '1970-01-01' ON CONVERSION ERROR); +Result: 1970-01-01 +SELECT CAST(ARRAY['x'] AS integer[] DEFAULT NULL ON CONVERSION ERROR); +Result: NULL + + + + diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index fd216c4978d..dbc17c0be61 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -99,6 +99,12 @@ static void ExecBuildAggTransCall(ExprState *state, AggState *aggstate, static void ExecInitJsonExpr(JsonExpr *jsexpr, ExprState *state, Datum *resv, bool *resnull, ExprEvalStep *scratch); +static void ExecInitSafeCastElement(SafeCastElement *elem, ExprState *state, + Datum *resv, bool *resnull, + ExprEvalStep *scratch); +static void ExecInitSafeTypeCastExpr(SafeTypeCastExpr *stcexpr, ExprState *state, + Datum *resv, bool *resnull, + ExprEvalStep *scratch); static void ExecInitJsonCoercion(ExprState *state, JsonReturning *returning, ErrorSaveContext *escontext, bool omit_quotes, bool exists_coerce, @@ -141,6 +147,26 @@ static void ExecInitJsonCoercion(ExprState *state, JsonReturning *returning, */ ExprState * ExecInitExpr(Expr *node, PlanState *parent) +{ + return ExecInitExprWithContext(node, parent, NULL); +} + +/* + * ExecInitExprWithContext: same as ExecInitExpr, but with an optional + * ErrorSaveContext for soft error handling. + * + * When 'escontext' is non-NULL, expression nodes that support soft errors + * (currently CoerceToDomain's NOT NULL and CHECK constraint steps, and function + * calls, including operator will use errsave() instead of ereport(), allowing + * the caller to detect and handle failures without a transaction abort. + * + * The escontext must be provided at initialization time (not after), because + * it is copied into per-step data during expression compilation. + * + * Not all expression node types support soft errors. If in doubt, pass NULL. + */ +ExprState * +ExecInitExprWithContext(Expr *node, PlanState *parent, Node *escontext) { ExprState *state; ExprEvalStep scratch = {0}; @@ -154,6 +180,7 @@ ExecInitExpr(Expr *node, PlanState *parent) state->expr = node; state->parent = parent; state->ext_params = NULL; + state->escontext = (ErrorSaveContext *) escontext; /* Insert setup steps as needed */ ExecCreateExprSetupSteps(state, (Node *) node); @@ -1701,6 +1728,7 @@ ExecInitExprRec(Expr *node, ExprState *state, elemstate->innermost_caseval = palloc_object(Datum); elemstate->innermost_casenull = palloc_object(bool); + elemstate->escontext = state->escontext; ExecInitExprRec(acoerce->elemexpr, elemstate, &elemstate->resvalue, &elemstate->resnull); @@ -1921,6 +1949,7 @@ ExecInitExprRec(Expr *node, ExprState *state, int nelems = list_length(arrayexpr->elements); ListCell *lc; int elemoff; + List *skipjumps = NIL; /* * Evaluate by computing each element, and then forming the @@ -1953,11 +1982,32 @@ ExecInitExprRec(Expr *node, ExprState *state, ExecInitExprRec(e, state, &scratch.d.arrayexpr.elemvalues[elemoff], &scratch.d.arrayexpr.elemnulls[elemoff]); + + /* + * If this ArrayExpr is being evaluated softly has already + * failed, the result is going to be discarded, so don't + * check the remaining against it; doing so might throw a + * hard error or overwrite the saved error. + */ + if (state->escontext != NULL) + { + ExprEvalStep jump = {0}; + + jump.opcode = EEOP_JUMP_IF_NULL; + jump.resnull = &state->escontext->error_occurred; + jump.d.jump.jumpdone = -1; /* set below */ + skipjumps = lappend_int(skipjumps, state->steps_len); + ExprEvalPushStep(state, &jump); + } elemoff++; } /* and then collect all into an array */ ExprEvalPushStep(state, &scratch); + + foreach_int(jumpno, skipjumps) + state->steps[jumpno].d.jump.jumpdone = state->steps_len; + break; } @@ -2176,6 +2226,26 @@ ExecInitExprRec(Expr *node, ExprState *state, break; } + case T_SafeTypeCastExpr: + { + SafeTypeCastExpr *stcexpr = castNode(SafeTypeCastExpr, node); + + ExecInitSafeTypeCastExpr(stcexpr, state, resv, resnull, + &scratch); + + break; + } + + case T_SafeCastElement: + { + SafeCastElement *elem = castNode(SafeCastElement, node); + + ExecInitSafeCastElement(elem, state, resv, resnull, + &scratch); + + break; + } + case T_CoalesceExpr: { CoalesceExpr *coalesce = (CoalesceExpr *) node; @@ -2702,6 +2772,8 @@ ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid, FunctionCallInfo fcinfo; int argno; ListCell *lc; + bool strict; + bool fusage; /* Check permission to call function */ aclresult = object_aclcheck(ProcedureRelationId, funcid, GetUserId(), ACL_EXECUTE); @@ -2735,7 +2807,8 @@ ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid, /* Initialize function call parameter structure too */ InitFunctionCallInfoData(*fcinfo, flinfo, - nargs, inputcollid, NULL, NULL); + nargs, inputcollid, + (Node *) state->escontext, NULL); /* Keep extra copies of this info to save an indirection at runtime */ scratch->d.func.fn_addr = flinfo->fn_addr; @@ -2776,10 +2849,35 @@ ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid, argno++; } - /* Insert appropriate opcode depending on strictness and stats level */ - if (pgstat_track_functions <= flinfo->fn_stats) + strict = (flinfo->fn_strict && nargs > 0); + fusage = (pgstat_track_functions > flinfo->fn_stats); + + /* + * Insert appropriate opcode depending on strictness and stats level. + * Under an ErrorSaveContext, use the _SAFE variants, which pass the + * context to the function and return NULL when it reports an error + * softly. + */ + if (state->escontext != NULL) { - if (flinfo->fn_strict && nargs > 0) + if (!fusage) + { + if (strict) + scratch->opcode = EEOP_FUNCEXPR_STRICT_SAFE; + else + scratch->opcode = EEOP_FUNCEXPR_SAFE; + } + else + { + if (strict) + scratch->opcode = EEOP_FUNCEXPR_STRICT_SAFE_FUSAGE; + else + scratch->opcode = EEOP_FUNCEXPR_SAFE_FUSAGE; + } + } + else if (!fusage) + { + if (strict) { /* Choose nargs optimized implementation if available. */ if (nargs == 1) @@ -2794,7 +2892,7 @@ ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid, } else { - if (flinfo->fn_strict && nargs > 0) + if (strict) scratch->opcode = EEOP_FUNCEXPR_STRICT_FUSAGE; else scratch->opcode = EEOP_FUNCEXPR_FUSAGE; @@ -4759,6 +4857,108 @@ ExecBuildParamSetEqual(TupleDesc desc, return state; } +/* + * Push steps to evaluate a SafeTypeCastExpr and its various subsidiary + * expressions. + */ +static void +ExecInitSafeTypeCastExpr(SafeTypeCastExpr *stcexpr, ExprState *state, + Datum *resv, bool *resnull, + ExprEvalStep *scratch) +{ + SafeTypeCastState *stcstate; + ErrorSaveContext *saved_escontext = state->escontext; + Datum *saved_caseval = state->innermost_caseval; + bool *saved_casenull = state->innermost_casenull; + + stcstate = palloc0_object(SafeTypeCastState); + stcstate->stcexpr = stcexpr; + stcstate->escontext.type = T_ErrorSaveContext; + stcstate->escontext.error_occurred = false; + stcstate->escontext.details_wanted = false; + stcstate->escontext.error_data = NULL; + + /* + * arg normally throws its own errors, so it is compiled under the + * caller's context. If arg_errorsafe, it holds SafeCastElement nodes + * converting its elements; it is then compiled under our context, which + * makes those conversions error-safe. The element expressions themselves + * still throw their errors. + */ + if (stcexpr->arg_errorsafe) + state->escontext = &stcstate->escontext; + ExecInitExprRec(stcexpr->arg, state, + &stcstate->args.value, &stcstate->args.isnull); + + /* + * No coercion path exists at all (castexpr is NULL), so the result is + * always the DEFAULT expression. The argument above is still evaluated, + * so that its own errors are thrown, but its value is unused. + */ + if (stcexpr->castexpr == NULL) + { + state->escontext = saved_escontext; + ExecInitExprRec(stcexpr->defexpr, state, resv, resnull); + return; + } + + state->escontext = &stcstate->escontext; + + /* The conversion itself is always evaluated error-safe */ + state->innermost_caseval = &stcstate->args.value; + state->innermost_casenull = &stcstate->args.isnull; + ExecInitExprRec(stcexpr->castexpr, state, resv, resnull); + scratch->d.stcexpr.stcstate = stcstate; + scratch->opcode = EEOP_SAFETYPE_CAST; + ExprEvalPushStep(state, scratch); + + state->innermost_caseval = saved_caseval; + state->innermost_casenull = saved_casenull; + state->escontext = saved_escontext; + /* DEFAULT, under the caller's context */ + ExecInitExprRec(stcexpr->defexpr, state, resv, resnull); + stcstate->jump_end = state->steps_len; +} + +/* + * Push steps to evaluate a SafeCastElement. + * + * In CAST(ARRAY[...] AS type DEFAULT ... ON CONVERSION ERROR), each element + * expression must be evaluated in a non-error-safe way, while its conversion to + * the target element type must be error-safe. + */ +static void +ExecInitSafeCastElement(SafeCastElement *elem, ExprState *state, + Datum *resv, bool *resnull, + ExprEvalStep *scratch) +{ + ErrorSaveContext *saved_escontext = state->escontext; + Datum *saved_caseval = state->innermost_caseval; + bool *saved_casenull = state->innermost_casenull; + NullableDatum *argval = palloc_object(NullableDatum); + + state->escontext = NULL; + ExecInitExprRec(elem->arg, state, &argval->value, &argval->isnull); + state->escontext = saved_escontext; + + if (elem->castexpr == NULL) + { + scratch->opcode = EEOP_CONST; + scratch->resvalue = resv; + scratch->resnull = resnull; + scratch->d.constval.value = (Datum) 0; + scratch->d.constval.isnull = true; + ExprEvalPushStep(state, scratch); + return; + } + + state->innermost_caseval = &argval->value; + state->innermost_casenull = &argval->isnull; + ExecInitExprRec(elem->castexpr, state, resv, resnull); + state->innermost_caseval = saved_caseval; + state->innermost_casenull = saved_casenull; +} + /* * Push steps to evaluate a JsonExpr and its various subsidiary expressions. */ diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index 8f2c814c15f..c94baf883a6 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -514,6 +514,10 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) &&CASE_EEOP_FUNCEXPR_STRICT_2, &&CASE_EEOP_FUNCEXPR_FUSAGE, &&CASE_EEOP_FUNCEXPR_STRICT_FUSAGE, + &&CASE_EEOP_FUNCEXPR_SAFE, + &&CASE_EEOP_FUNCEXPR_STRICT_SAFE, + &&CASE_EEOP_FUNCEXPR_SAFE_FUSAGE, + &&CASE_EEOP_FUNCEXPR_STRICT_SAFE_FUSAGE, &&CASE_EEOP_BOOL_AND_STEP_FIRST, &&CASE_EEOP_BOOL_AND_STEP, &&CASE_EEOP_BOOL_AND_STEP_LAST, @@ -578,6 +582,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) &&CASE_EEOP_XMLEXPR, &&CASE_EEOP_JSON_CONSTRUCTOR, &&CASE_EEOP_IS_JSON, + &&CASE_EEOP_SAFETYPE_CAST, &&CASE_EEOP_JSONEXPR_PATH, &&CASE_EEOP_JSONEXPR_COERCION, &&CASE_EEOP_JSONEXPR_COERCION_FINISH, @@ -1032,6 +1037,77 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) EEO_NEXT(); } + EEO_CASE(EEOP_FUNCEXPR_SAFE) + { + FunctionCallInfo fcinfo = op->d.func.fcinfo_data; + Datum d; + + fcinfo->isnull = false; + d = op->d.func.fn_addr(fcinfo); + *op->resvalue = d; + *op->resnull = fcinfo->isnull; + + /* + * A function that reported an error softly returned a dummy + * datum. + */ + if (SOFT_ERROR_OCCURRED(fcinfo->context)) + { + *op->resnull = true; + *op->resvalue = (Datum) 0; + } + + EEO_NEXT(); + } + + EEO_CASE(EEOP_FUNCEXPR_STRICT_SAFE) + { + FunctionCallInfo fcinfo = op->d.func.fcinfo_data; + NullableDatum *args = fcinfo->args; + Datum d; + + for (int argno = 0; argno < op->d.func.nargs; argno++) + { + if (args[argno].isnull) + { + *op->resnull = true; + *op->resvalue = (Datum) 0; + goto strictsafe_fail; + } + } + + fcinfo->isnull = false; + d = op->d.func.fn_addr(fcinfo); + *op->resvalue = d; + *op->resnull = fcinfo->isnull; + + /* + * A function that reported an error softly returned a dummy + * datum. + */ + if (SOFT_ERROR_OCCURRED(fcinfo->context)) + { + *op->resnull = true; + *op->resvalue = (Datum) 0; + } + strictsafe_fail: + EEO_NEXT(); + } + + EEO_CASE(EEOP_FUNCEXPR_SAFE_FUSAGE) + { + ExecEvalFuncSafeFusage(state, op, econtext); + + EEO_NEXT(); + } + + EEO_CASE(EEOP_FUNCEXPR_STRICT_SAFE_FUSAGE) + { + ExecEvalFuncStrictSafeFusage(state, op, econtext); + + EEO_NEXT(); + } + /* * If any of its clauses is FALSE, an AND's result is FALSE regardless * of the states of the rest of the clauses, so we can stop evaluating @@ -1936,6 +2012,27 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) EEO_NEXT(); } + EEO_CASE(EEOP_SAFETYPE_CAST) + { + SafeTypeCastState *stcstate = op->d.stcexpr.stcstate; + + if (SOFT_ERROR_OCCURRED(&stcstate->escontext)) + { + /* + * The conversion failed softly, so fall through to evaluate + * the DEFAULT expression. Clear the error first, so that the + * next evaluation starts afresh. + */ + stcstate->escontext.error_occurred = false; + *op->resnull = true; + *op->resvalue = (Datum) 0; + + EEO_NEXT(); + } + else + EEO_JUMP(stcstate->jump_end); + } + EEO_CASE(EEOP_JSONEXPR_PATH) { /* too complex for an inline implementation */ @@ -3014,6 +3111,36 @@ ExecEvalFuncExprFusage(ExprState *state, ExprEvalStep *op, pgstat_end_function_usage(&fcusage, true); } +/* + * Evaluate EEOP_FUNCEXPR_SAFE_FUSAGE + */ +void +ExecEvalFuncSafeFusage(ExprState *state, ExprEvalStep *op, ExprContext *econtext) +{ + FunctionCallInfo fcinfo = op->d.func.fcinfo_data; + PgStat_FunctionCallUsage fcusage; + Datum d; + + pgstat_init_function_usage(fcinfo, &fcusage); + + fcinfo->isnull = false; + d = op->d.func.fn_addr(fcinfo); + *op->resvalue = d; + *op->resnull = fcinfo->isnull; + + /* + * A function that reported an error softly returned a dummy datum. Hand a + * NULL to whatever consumes the result instead. + */ + if (SOFT_ERROR_OCCURRED(fcinfo->context)) + { + *op->resnull = true; + *op->resvalue = (Datum) 0; + } + + pgstat_end_function_usage(&fcusage, true); +} + /* * Evaluate EEOP_FUNCEXPR_STRICT_FUSAGE */ @@ -3048,6 +3175,50 @@ ExecEvalFuncExprStrictFusage(ExprState *state, ExprEvalStep *op, pgstat_end_function_usage(&fcusage, true); } +/* + * Evaluate EEOP_FUNCEXPR_STRICT_SAFE_FUSAGE + */ +void +ExecEvalFuncStrictSafeFusage(ExprState *state, ExprEvalStep *op, + ExprContext *econtext) +{ + FunctionCallInfo fcinfo = op->d.func.fcinfo_data; + PgStat_FunctionCallUsage fcusage; + NullableDatum *args = fcinfo->args; + int nargs = op->d.func.nargs; + Datum d; + + /* strict function, so check for NULL args */ + for (int argno = 0; argno < nargs; argno++) + { + if (args[argno].isnull) + { + *op->resnull = true; + *op->resvalue = (Datum) 0; + return; + } + } + + pgstat_init_function_usage(fcinfo, &fcusage); + + fcinfo->isnull = false; + d = op->d.func.fn_addr(fcinfo); + *op->resvalue = d; + *op->resnull = fcinfo->isnull; + + /* + * A function that reported an error softly returned a dummy datum. Hand a + * NULL to whatever consumes the result instead. + */ + if (SOFT_ERROR_OCCURRED(fcinfo->context)) + { + *op->resnull = true; + *op->resvalue = (Datum) 0; + } + + pgstat_end_function_usage(&fcusage, true); +} + /* * Evaluate a PARAM_EXEC parameter. * @@ -3654,6 +3825,17 @@ ExecEvalArrayCoerce(ExprState *state, ExprEvalStep *op, ExprContext *econtext) econtext, op->d.arraycoerce.resultelemtype, op->d.arraycoerce.amstate); + + if (SOFT_ERROR_OCCURRED(op->d.arraycoerce.elemexprstate->escontext)) + { + *op->resvalue = (Datum) 0; + *op->resnull = true; + + /* + * A soft error occurred. The caller may need to reset + * ExprState.ErrorSaveContext.error_occurred for the next evaluation. + */ + } } /* diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c index fc80d3e55fe..fd607b878dd 100644 --- a/src/backend/jit/llvm/llvmjit_expr.c +++ b/src/backend/jit/llvm/llvmjit_expr.c @@ -665,6 +665,8 @@ llvm_compile_expr(ExprState *state) case EEOP_FUNCEXPR_STRICT: case EEOP_FUNCEXPR_STRICT_1: case EEOP_FUNCEXPR_STRICT_2: + case EEOP_FUNCEXPR_SAFE: + case EEOP_FUNCEXPR_STRICT_SAFE: { FunctionCallInfo fcinfo = op->d.func.fcinfo_data; LLVMValueRef v_fcinfo_isnull; @@ -672,7 +674,8 @@ llvm_compile_expr(ExprState *state) if (opcode == EEOP_FUNCEXPR_STRICT || opcode == EEOP_FUNCEXPR_STRICT_1 || - opcode == EEOP_FUNCEXPR_STRICT_2) + opcode == EEOP_FUNCEXPR_STRICT_2 || + opcode == EEOP_FUNCEXPR_STRICT_SAFE) { LLVMBasicBlockRef b_nonull; LLVMBasicBlockRef *b_checkargnulls; @@ -741,6 +744,34 @@ llvm_compile_expr(ExprState *state) v_retval = BuildV1Call(context, b, mod, fcinfo, &v_fcinfo_isnull); + + /* + * A function that reported an error softly returned a + * dummy datum; return NULL instead. + */ + if (opcode == EEOP_FUNCEXPR_SAFE || + opcode == EEOP_FUNCEXPR_STRICT_SAFE) + { + ErrorSaveContext *escontext = (ErrorSaveContext *) fcinfo->context; + LLVMValueRef v_error_occurred; + LLVMValueRef v_error; + + Assert(escontext != NULL && IsA(escontext, ErrorSaveContext)); + + v_error_occurred = + l_load(b, TypeStorageBool, + l_ptr_const(&escontext->error_occurred, + l_ptr(TypeStorageBool)), + ""); + v_error = LLVMBuildICmp(b, LLVMIntNE, v_error_occurred, + l_sbool_const(0), ""); + v_retval = LLVMBuildSelect(b, v_error, l_datum_const(0), + v_retval, ""); + v_fcinfo_isnull = LLVMBuildSelect(b, v_error, + l_sbool_const(1), + v_fcinfo_isnull, ""); + } + LLVMBuildStore(b, v_retval, v_resvaluep); LLVMBuildStore(b, v_fcinfo_isnull, v_resnullp); @@ -761,6 +792,18 @@ llvm_compile_expr(ExprState *state) LLVMBuildBr(b, opblocks[opno + 1]); break; + case EEOP_FUNCEXPR_SAFE_FUSAGE: + build_EvalXFunc(b, mod, "ExecEvalFuncSafeFusage", + v_state, op, v_econtext); + LLVMBuildBr(b, opblocks[opno + 1]); + break; + + case EEOP_FUNCEXPR_STRICT_SAFE_FUSAGE: + build_EvalXFunc(b, mod, "ExecEvalFuncStrictSafeFusage", + v_state, op, v_econtext); + LLVMBuildBr(b, opblocks[opno + 1]); + break; + /* * Treat them the same for now, optimizer can remove * redundancy. Could be worthwhile to optimize during emission @@ -2257,6 +2300,41 @@ llvm_compile_expr(ExprState *state) LLVMBuildBr(b, opblocks[opno + 1]); break; + case EEOP_SAFETYPE_CAST: + { + SafeTypeCastState *stcstate = op->d.stcexpr.stcstate; + LLVMBasicBlockRef b_error; + LLVMValueRef v_error_occurred_p; + LLVMValueRef v_error_occurred; + + b_error = l_bb_before_v(opblocks[opno + 1], + "op.%d.error", opno); + + v_error_occurred_p = l_ptr_const(&stcstate->escontext.error_occurred, + l_ptr(TypeStorageBool)); + v_error_occurred = l_load(b, TypeStorageBool, + v_error_occurred_p, ""); + + /* no error: skip the DEFAULT expression */ + LLVMBuildCondBr(b, + LLVMBuildICmp(b, LLVMIntEQ, v_error_occurred, + l_sbool_const(0), ""), + opblocks[stcstate->jump_end], + b_error); + + /* + * The conversion failed softly: clear the error so that + * the next evaluation starts afresh, and fall through to + * the DEFAULT expression. + */ + LLVMPositionBuilderAtEnd(b, b_error); + LLVMBuildStore(b, l_sbool_const(0), v_error_occurred_p); + LLVMBuildStore(b, l_sbool_const(1), v_resnullp); + LLVMBuildStore(b, l_datum_const(0), v_resvaluep); + LLVMBuildBr(b, opblocks[opno + 1]); + break; + } + case EEOP_JSONEXPR_PATH: { JsonExprState *jsestate = op->d.jsonexpr.jsestate; diff --git a/src/backend/jit/llvm/llvmjit_types.c b/src/backend/jit/llvm/llvmjit_types.c index c8a1f841293..aa850ad89f3 100644 --- a/src/backend/jit/llvm/llvmjit_types.c +++ b/src/backend/jit/llvm/llvmjit_types.c @@ -153,6 +153,8 @@ void *referenced_functions[] = ExecEvalFieldStoreForm, ExecEvalFuncExprFusage, ExecEvalFuncExprStrictFusage, + ExecEvalFuncSafeFusage, + ExecEvalFuncStrictSafeFusage, ExecEvalGroupingFunc, ExecEvalMergeSupportFunc, ExecEvalMinMax, diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index 678b9a046ce..49ff4319b7d 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -206,6 +206,12 @@ exprType(const Node *expr) case T_RowCompareExpr: type = BOOLOID; break; + case T_SafeTypeCastExpr: + type = ((const SafeTypeCastExpr *) expr)->resulttype; + break; + case T_SafeCastElement: + type = ((const SafeCastElement *) expr)->resulttype; + break; case T_CoalesceExpr: type = ((const CoalesceExpr *) expr)->coalescetype; break; @@ -450,6 +456,10 @@ exprTypmod(const Node *expr) return typmod; } break; + case T_SafeTypeCastExpr: + return ((const SafeTypeCastExpr *) expr)->resulttypmod; + case T_SafeCastElement: + return ((const SafeCastElement *) expr)->resulttypmod; case T_CoalesceExpr: { /* @@ -965,6 +975,12 @@ exprCollation(const Node *expr) /* RowCompareExpr's result is boolean ... */ coll = InvalidOid; /* ... so it has no collation */ break; + case T_SafeTypeCastExpr: + coll = ((const SafeTypeCastExpr *) expr)->resultcollid; + break; + case T_SafeCastElement: + coll = ((const SafeCastElement *) expr)->resultcollid; + break; case T_CoalesceExpr: coll = ((const CoalesceExpr *) expr)->coalescecollid; break; @@ -1240,6 +1256,12 @@ exprSetCollation(Node *expr, Oid collation) /* RowCompareExpr's result is boolean ... */ Assert(!OidIsValid(collation)); /* ... so never set a collation */ break; + case T_SafeTypeCastExpr: + ((SafeTypeCastExpr *) expr)->resultcollid = collation; + break; + case T_SafeCastElement: + ((SafeCastElement *) expr)->resultcollid = collation; + break; case T_CoalesceExpr: ((CoalesceExpr *) expr)->coalescecollid = collation; break; @@ -1561,6 +1583,12 @@ exprLocation(const Node *expr) /* just use leftmost argument's location */ loc = exprLocation((Node *) ((const RowCompareExpr *) expr)->largs); break; + case T_SafeTypeCastExpr: + loc = ((const SafeTypeCastExpr *) expr)->location; + break; + case T_SafeCastElement: + loc = ((const SafeCastElement *) expr)->location; + break; case T_CoalesceExpr: /* COALESCE keyword should always be the first thing */ loc = ((const CoalesceExpr *) expr)->location; @@ -2336,6 +2364,28 @@ expression_tree_walker_impl(Node *node, return true; } break; + case T_SafeTypeCastExpr: + { + SafeTypeCastExpr *stcexpr = castNode(SafeTypeCastExpr, node); + + if (WALK(stcexpr->arg)) + return true; + if (WALK(stcexpr->castexpr)) + return true; + if (WALK(stcexpr->defexpr)) + return true; + } + break; + case T_SafeCastElement: + { + SafeCastElement *elem = castNode(SafeCastElement, node); + + if (WALK(elem->arg)) + return true; + if (WALK(elem->castexpr)) + return true; + } + break; case T_CoalesceExpr: return WALK(((CoalesceExpr *) node)->args); case T_MinMaxExpr: @@ -3348,6 +3398,31 @@ expression_tree_mutator_impl(Node *node, return (Node *) newnode; } break; + case T_SafeTypeCastExpr: + { + SafeTypeCastExpr *stcexpr = castNode(SafeTypeCastExpr, node); + SafeTypeCastExpr *newnode; + + FLATCOPY(newnode, stcexpr, SafeTypeCastExpr); + MUTATE(newnode->arg, stcexpr->arg, Expr *); + MUTATE(newnode->castexpr, stcexpr->castexpr, Expr *); + MUTATE(newnode->defexpr, stcexpr->defexpr, Expr *); + + return (Node *) newnode; + } + break; + case T_SafeCastElement: + { + SafeCastElement *elem = castNode(SafeCastElement, node); + SafeCastElement *newnode; + + FLATCOPY(newnode, elem, SafeCastElement); + MUTATE(newnode->arg, elem->arg, Expr *); + MUTATE(newnode->castexpr, elem->castexpr, Expr *); + + return (Node *) newnode; + } + break; case T_CoalesceExpr: { CoalesceExpr *coalesceexpr = (CoalesceExpr *) node; @@ -4485,6 +4560,8 @@ raw_expression_tree_walker_impl(Node *node, return true; if (WALK(tc->typeName)) return true; + if (WALK(tc->defexpr)) + return true; } break; case T_CollateClause: diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 9cfba66b5a0..532028f794c 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -71,6 +71,7 @@ typedef struct List *active_fns; Node *case_val; bool estimate; + ErrorSaveContext *escontext; } eval_const_expressions_context; typedef struct @@ -1197,6 +1198,8 @@ contain_nonstrict_functions_walker(Node *node, void *context) return true; else if (IsA(node, JsonConstructorExpr)) return true; + else if (IsA(node, SafeTypeCastExpr)) + return true; else { /* Check other function-containing nodes */ @@ -1349,6 +1352,41 @@ contain_context_dependent_node_walker(Node *node, int *flags) *flags = save_flags; return res; } + else if (IsA(node, SafeTypeCastExpr)) + { + SafeTypeCastExpr *stc = (SafeTypeCastExpr *) node; + int save_flags; + bool res; + + if (contain_context_dependent_node_walker((Node *) stc->arg, flags) || + contain_context_dependent_node_walker((Node *) stc->defexpr, flags)) + return true; + + /* castexpr is built over its own CaseTestExpr placeholder */ + save_flags = *flags; + *flags |= CCDN_CASETESTEXPR_OK; + res = contain_context_dependent_node_walker((Node *) stc->castexpr, + flags); + *flags = save_flags; + return res; + } + else if (IsA(node, SafeCastElement)) + { + SafeCastElement *elem = (SafeCastElement *) node; + int save_flags; + bool res; + + if (contain_context_dependent_node_walker((Node *) elem->arg, flags)) + return true; + + /* castexpr is built over its own CaseTestExpr placeholder */ + save_flags = *flags; + *flags |= CCDN_CASETESTEXPR_OK; + res = contain_context_dependent_node_walker((Node *) elem->castexpr, + flags); + *flags = save_flags; + return res; + } return expression_tree_walker(node, contain_context_dependent_node_walker, flags); } @@ -2632,6 +2670,12 @@ rowtype_field_matches(Oid rowtypeid, int fieldnum, * NOTE: another critical effect is that any function calls that require * default arguments will be expanded, and named-argument calls will be * converted to positional notation. The executor won't handle either. + * + * NOTE: eval_const_expressions_context->escontext, when set, makes the + * evaluation of constant subexpressions report errors softly instead of + * throwing them. Currently only the SafeTypeCastExpr case sets it, but + * any other node type wanting the same behavior can do so. + * *-------------------- */ Node * @@ -2647,6 +2691,7 @@ eval_const_expressions(PlannerInfo *root, Node *node) context.active_fns = NIL; /* nothing being recursively simplified */ context.case_val = NULL; /* no CASE being examined */ context.estimate = false; /* safe transformations only */ + context.escontext = NULL; /* for error-safe expression evaluation */ return eval_const_expressions_mutator(node, &context); } @@ -2789,6 +2834,7 @@ estimate_expression_value(PlannerInfo *root, Node *node) context.active_fns = NIL; /* nothing being recursively simplified */ context.case_val = NULL; /* no CASE being examined */ context.estimate = true; /* unsafe transformations OK */ + context.escontext = NULL; /* for error-safe expression evaluation */ return eval_const_expressions_mutator(node, &context); } @@ -2814,11 +2860,12 @@ estimate_expression_value(PlannerInfo *root, Node *node) (!expression_tree_walker((Node *) (node), contain_non_const_walker, NULL)) /* Generic macro for applying evaluate_expr */ -#define ece_evaluate_expr(node) \ +#define ece_evaluate_expr(node, escontext) \ ((Node *) evaluate_expr((Expr *) (node), \ exprType((Node *) (node)), \ exprTypmod((Node *) (node)), \ - exprCollation((Node *) (node)))) + exprCollation((Node *) (node)), \ + (Node *) escontext)) /* * Recursive guts of eval_const_expressions/estimate_expression_value @@ -3289,7 +3336,7 @@ eval_const_expressions_mutator(Node *node, if (!has_nonconst_input && ece_function_is_safe(expr->opfuncid, context)) - return ece_evaluate_expr(expr); + return ece_evaluate_expr(expr, context->escontext); return (Node *) expr; } @@ -3309,7 +3356,7 @@ eval_const_expressions_mutator(Node *node, */ if (ece_all_arguments_const(saop) && ece_function_is_safe(saop->opfuncid, context)) - return ece_evaluate_expr(saop); + return ece_evaluate_expr(saop, context->escontext); return (Node *) saop; } case T_BoolExpr: @@ -3463,6 +3510,113 @@ eval_const_expressions_mutator(Node *node, return (Node *) newjce; } + case T_SafeTypeCastExpr: + { + SafeTypeCastExpr *stc = castNode(SafeTypeCastExpr, node); + SafeTypeCastExpr *newexpr; + Node *arg; + Node *castexpr = NULL; + Node *defexpr; + ErrorSaveContext *save_escontext = context->escontext; + Node *save_case_val = context->case_val; + ErrorSaveContext *escontext = makeNode(ErrorSaveContext); + + if (stc->castexpr == NULL) + return eval_const_expressions_mutator((Node *) stc->defexpr, + context); + + defexpr = eval_const_expressions_mutator((Node *) stc->defexpr, + context); + + /* + * arg is normally evaluated under the caller's context. If + * arg_errorsafe, it is evaluated under ours, since its + * SafeCastElement nodes report conversion errors into it. + */ + if (stc->arg_errorsafe) + context->escontext = escontext; + arg = eval_const_expressions_mutator((Node *) stc->arg, context); + context->escontext = save_escontext; + + /* + * A SafeCastElement failed to convert its constant element, + * so the conversion is known to fail: the result is defexpr. + */ + if (escontext->error_occurred) + return defexpr; + + /* only the conversion itself is evaluated error-safe */ + context->escontext = escontext; + context->case_val = IsA(arg, Const) ? arg : NULL; + castexpr = eval_const_expressions_mutator((Node *) stc->castexpr, + context); + context->case_val = save_case_val; + context->escontext = save_escontext; + + /* + * A constant conversion that failed softly would fail the + * same way at runtime. + */ + if (escontext->error_occurred) + return defexpr; + if (IsA(castexpr, Const)) + return castexpr; + + newexpr = makeNode(SafeTypeCastExpr); + newexpr->arg = (Expr *) arg; + newexpr->arg_errorsafe = stc->arg_errorsafe; + newexpr->castexpr = (Expr *) castexpr; + newexpr->defexpr = (Expr *) defexpr; + newexpr->resulttype = stc->resulttype; + newexpr->resulttypmod = stc->resulttypmod; + newexpr->resultcollid = stc->resultcollid; + newexpr->location = stc->location; + + return (Node *) newexpr; + } + case T_SafeCastElement: + { + SafeCastElement *elem = castNode(SafeCastElement, node); + SafeCastElement *newelem; + ErrorSaveContext *save_escontext = context->escontext; + Node *save_case_val = context->case_val; + + /* + * Once an element has failed, the enclosing SafeTypeCastExpr + * returns its DEFAULT and discards the array, so the + * remaining elements need not be processed. + */ + if (SOFT_ERROR_OCCURRED(save_escontext)) + return node; + + /* + * An element without a conversion makes the whole cast fail + * at parse time, and the SafeTypeCastExpr case then never + * gets here. + */ + Assert(elem->castexpr != NULL); + + newelem = makeNode(SafeCastElement); + memcpy(newelem, elem, sizeof(SafeCastElement)); + + /* the element expression itself raises its own errors */ + context->escontext = NULL; + newelem->arg = (Expr *) + eval_const_expressions_mutator((Node *) elem->arg, context); + context->escontext = save_escontext; + + /* its conversion reports into the enclosing cast's context */ + context->case_val = IsA(newelem->arg, Const) ? + (Node *) newelem->arg : NULL; + newelem->castexpr = (Expr *) + eval_const_expressions_mutator((Node *) elem->castexpr, context); + context->case_val = save_case_val; + + if (IsA(newelem->castexpr, Const)) + return (Node *) newelem->castexpr; + + return (Node *) newelem; + } case T_SubPlan: case T_AlternativeSubPlan: @@ -3614,7 +3768,7 @@ eval_const_expressions_mutator(Node *node, if (ac->arg && IsA(ac->arg, Const) && ac->elemexpr && !IsA(ac->elemexpr, CoerceToDomain) && !contain_mutable_functions((Node *) ac->elemexpr)) - return ece_evaluate_expr(ac); + return ece_evaluate_expr(ac, context->escontext); return (Node *) ac; } @@ -3817,7 +3971,7 @@ eval_const_expressions_mutator(Node *node, node = ece_generic_processing(node); /* If all arguments are Consts, we can fold to a constant */ if (ece_all_arguments_const(node)) - return ece_evaluate_expr(node); + return ece_evaluate_expr(node, context->escontext); return node; } case T_CoalesceExpr: @@ -3901,7 +4055,8 @@ eval_const_expressions_mutator(Node *node, return (Node *) evaluate_expr((Expr *) svf, svf->type, svf->typmod, - InvalidOid); + InvalidOid, + NULL); else return copyObject((Node *) svf); } @@ -3916,7 +4071,7 @@ eval_const_expressions_mutator(Node *node, if ((context->estimate || xmlexpr_is_immutable((XmlExpr *) node)) && ece_all_arguments_const(node)) - return ece_evaluate_expr(node); + return ece_evaluate_expr(node, context->escontext); return node; } case T_FieldSelect: @@ -4012,7 +4167,7 @@ eval_const_expressions_mutator(Node *node, newfselect->resulttype, newfselect->resulttypmod, newfselect->resultcollid)) - return ece_evaluate_expr(newfselect); + return ece_evaluate_expr(newfselect, context->escontext); } return (Node *) newfselect; } @@ -4350,7 +4505,7 @@ eval_const_expressions_mutator(Node *node, newcre->arg = (Expr *) arg; if (arg != NULL && IsA(arg, Const)) - return ece_evaluate_expr((Node *) newcre); + return ece_evaluate_expr((Node *) newcre, context->escontext); return (Node *) newcre; } default: @@ -5399,6 +5554,7 @@ evaluate_function(Oid funcid, Oid result_type, int32 result_typmod, bool has_null_input = false; ListCell *arg; FuncExpr *newexpr; + Node *result; /* * Can't simplify if it returns a set. @@ -5478,8 +5634,13 @@ evaluate_function(Oid funcid, Oid result_type, int32 result_typmod, newexpr->args = args; newexpr->location = -1; - return evaluate_expr((Expr *) newexpr, result_type, result_typmod, - result_collid); + result = (Node *) evaluate_expr((Expr *) newexpr, result_type, result_typmod, + result_collid, (Node *) context->escontext); + + if (result == (Node *) newexpr) + return NULL; + + return (Expr *) result; } /* @@ -5933,10 +6094,14 @@ sql_inline_error_callback(void *arg) * * We use the executor's routine ExecEvalExpr() to avoid duplication of * code and ensure we get the same result as the executor would get. + * + * If escontext is non-NULL, evaluation errors are reported softly. When one + * has occurred, now or in an earlier call under the same context, the + * expression is returned unchanged instead of a Const. */ Expr * evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod, - Oid result_collation) + Oid result_collation, Node *escontext) { EState *estate; ExprState *exprstate; @@ -5946,6 +6111,9 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod, int16 resultTypLen; bool resultTypByVal; + if (SOFT_ERROR_OCCURRED(escontext)) + return expr; + /* * To use the executor, we need an EState. */ @@ -5961,7 +6129,7 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod, * Prepare expr for execution. (Note: we can't use ExecPrepareExpr * because it'd result in recursively invoking eval_const_expressions.) */ - exprstate = ExecInitExpr(expr, NULL); + exprstate = ExecInitExprWithContext(expr, NULL, escontext); /* * And evaluate it. @@ -5981,6 +6149,12 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod, /* Get back to outer memory context */ MemoryContextSwitchTo(oldcontext); + if (SOFT_ERROR_OCCURRED(exprstate->escontext)) + { + FreeExecutorState(estate); + return expr; + } + /* * Must copy result out of sub-context used by expression eval. * diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 24a13ebbafe..37e30d703e2 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -157,6 +157,8 @@ static void updateRawStmtEnd(RawStmt *rs, int end_location); static Node *makeColumnRef(char *colname, List *indirection, int location, core_yyscan_t yyscanner); static Node *makeTypeCast(Node *arg, TypeName *typename, int location); +static Node *makeTypeCastOpt(Node *arg, TypeName *typename, + Node *raw_default, int location); static Node *makeStringConstCast(char *str, int location, TypeName *typename); static Node *makeIntConst(int val, int location); static Node *makeFloatConst(char *str, int location); @@ -16250,6 +16252,10 @@ func_expr_common_subexpr: } | CAST '(' a_expr AS Typename ')' { $$ = makeTypeCast($3, $5, @1); } + | CAST '(' a_expr AS Typename ERROR_P ON CONVERSION_P ERROR_P ')' + { $$ = makeTypeCast($3, $5, @1); } + | CAST '(' a_expr AS Typename DEFAULT a_expr ON CONVERSION_P ERROR_P ')' + { $$ = makeTypeCastOpt($3, $5, $7, @1); } | EXTRACT '(' extract_list ')' { $$ = (Node *) makeFuncCall(SystemFuncName("extract"), @@ -19179,7 +19185,22 @@ makeTypeCast(Node *arg, TypeName *typename, int location) n->arg = arg; n->typeName = typename; + n->defexpr = NULL; + n->location = location; + return (Node *) n; +} + +static Node * +makeTypeCastOpt(Node *arg, TypeName *typename, + Node *defexpr, int location) +{ + TypeCast *n = makeNode(TypeCast); + + n->arg = arg; + n->typeName = typename; + n->defexpr = defexpr; n->location = location; + return (Node *) n; } diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c index d3240f4b265..a75a70add28 100644 --- a/src/backend/parser/parse_coerce.c +++ b/src/backend/parser/parse_coerce.c @@ -17,6 +17,7 @@ #include "access/htup_details.h" #include "catalog/pg_cast.h" #include "catalog/pg_class.h" +#include "catalog/pg_collation.h" #include "catalog/pg_inherits.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" @@ -33,13 +34,15 @@ #include "utils/typcache.h" -static Node *coerce_type_typmod(Node *node, +static Node *coerce_type_typmod(ParseState *pstate, + Node *node, Oid targetTypeId, int32 targetTypMod, CoercionContext ccontext, CoercionForm cformat, int location, bool hideInputCoercion); static void hide_coercion_node(Node *node); -static Node *build_coercion_expression(Node *node, +static Node *build_coercion_expression(ParseState *pstate, + Node *node, CoercionPathType pathtype, Oid funcId, Oid targetTypeId, int32 targetTypMod, @@ -53,6 +56,12 @@ static Node *coerce_record_to_complex(ParseState *pstate, Node *node, static bool is_complex_array(Oid typid); static bool typeIsOfTypedTable(Oid reltypeId, Oid reloftypeId); +/* pstate may be NULL in parse_coerce.c entry points */ +static inline Node * +parse_escontext(ParseState *pstate) +{ + return pstate ? pstate->p_escontext : NULL; +} /* * coerce_to_target_type() @@ -111,10 +120,12 @@ coerce_to_target_type(ParseState *pstate, Node *expr, Oid exprtype, * 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, + result = coerce_type_typmod(pstate, result, targettype, targettypmod, ccontext, cformat, location, (result != expr && !IsA(result, Const))); + if (result == NULL) /* shouldn't happen */ + elog(ERROR, "failed to coerce type modifier for type %u", targettype); if (expr != origexpr && type_is_collatable(targettype)) { @@ -256,6 +267,8 @@ coerce_type(ParseState *pstate, Node *node, int32 inputTypeMod; Type baseType; ParseCallbackState pcbstate; + char *string = NULL; + ErrorSaveContext escontext = {.type = T_ErrorSaveContext}; /* * If the target type is a domain, we want to call its base type's @@ -309,13 +322,35 @@ coerce_type(ParseState *pstate, Node *node, * as CSTRING. */ if (!con->constisnull) - newcon->constvalue = stringTypeDatum(baseType, - DatumGetCString(con->constvalue), - inputTypeMod); - else - newcon->constvalue = stringTypeDatum(baseType, - NULL, - inputTypeMod); + string = DatumGetCString(con->constvalue); + + /* + * Under an ErrorSaveContext (CAST ... DEFAULT ... ON CONVERSION + * ERROR), an invalid literal is not thrown: we flag the error in + * pstate->p_escontext and return the literal as a text constant. + */ + if (!stringTypeDatumSafe(baseType, string, inputTypeMod, + parse_escontext(pstate) ? (Node *) &escontext : NULL, + &newcon->constvalue)) + { + Const *textcon; + + ((ErrorSaveContext *) pstate->p_escontext)->error_occurred = true; + + cancel_parser_errposition_callback(&pcbstate); + ReleaseSysCache(baseType); + + textcon = makeConst(TEXTOID, + -1, + DEFAULT_COLLATION_OID, + -1, + con->constisnull ? (Datum) 0 : CStringGetTextDatum(string), + con->constisnull, + false); + textcon->location = con->location; + + return (Node *) textcon; + } /* * If it's a varlena value, force it to be in non-expanded @@ -360,7 +395,7 @@ coerce_type(ParseState *pstate, Node *node, /* If target is a domain, apply constraints. */ if (baseTypeId != targetTypeId) - result = coerce_to_domain(result, + result = coerce_to_domain(pstate, result, baseTypeId, baseTypeMod, targetTypeId, ccontext, cformat, location, @@ -430,7 +465,7 @@ coerce_type(ParseState *pstate, Node *node, * and we need to extract the correct typmod to use from the * domain's typtypmod. */ - result = build_coercion_expression(node, pathtype, funcId, + result = build_coercion_expression(pstate, node, pathtype, funcId, baseTypeId, baseTypeMod, ccontext, cformat, location); @@ -439,7 +474,7 @@ coerce_type(ParseState *pstate, Node *node, * type ID, hiding the previous coercion node. */ if (targetTypeId != baseTypeId) - result = coerce_to_domain(result, baseTypeId, baseTypeMod, + result = coerce_to_domain(pstate, result, baseTypeId, baseTypeMod, targetTypeId, ccontext, cformat, location, true); @@ -455,7 +490,7 @@ coerce_type(ParseState *pstate, Node *node, * that must be accounted for. If the destination is a domain * then we won't need a RelabelType node. */ - result = coerce_to_domain(node, baseTypeId, baseTypeMod, + result = coerce_to_domain(pstate, node, baseTypeId, baseTypeMod, targetTypeId, ccontext, cformat, location, false); @@ -478,6 +513,20 @@ coerce_type(ParseState *pstate, Node *node, } return result; } + + /* + * What remains are row-type casts. These are not supported under CAST + * ... DEFAULT ... ON CONVERSION ERROR for now; that could be added later. + */ + if (parse_escontext(pstate) != NULL) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot cast type %s to %s with DEFAULT expression in CAST ... ON CONVERSION ERROR", + format_type_be(inputTypeId), + format_type_be(targetTypeId)), + errdetail("Casts between row types are not supported here."), + parser_errposition(pstate, exprLocation(node))); + if (inputTypeId == RECORDOID && ISCOMPLEX(targetTypeId)) { @@ -665,6 +714,7 @@ can_coerce_type(int nargs, const Oid *input_typeids, const Oid *target_typeids, /* * Create an expression tree to represent coercion to a domain type. * + * 'pstate': parse state * 'arg': input expression * 'baseTypeId': base type of domain * 'baseTypeMod': base type typmod of domain @@ -677,7 +727,8 @@ can_coerce_type(int nargs, const Oid *input_typeids, const Oid *target_typeids, * If the target type isn't a domain, the given 'arg' is returned as-is. */ Node * -coerce_to_domain(Node *arg, Oid baseTypeId, int32 baseTypeMod, Oid typeId, +coerce_to_domain(ParseState *pstate, + Node *arg, Oid baseTypeId, int32 baseTypeMod, Oid typeId, CoercionContext ccontext, CoercionForm cformat, int location, bool hideInputCoercion) { @@ -707,9 +758,11 @@ coerce_to_domain(Node *arg, Oid baseTypeId, int32 baseTypeMod, Oid typeId, * would be safe to do anyway, without lots of knowledge about what the * base type thinks the typmod means. */ - arg = coerce_type_typmod(arg, baseTypeId, baseTypeMod, + arg = coerce_type_typmod(pstate, arg, baseTypeId, baseTypeMod, ccontext, COERCE_IMPLICIT_CAST, location, false); + if (arg == NULL) /* shouldn't happen */ + elog(ERROR, "failed to coerce type modifier as expected"); /* * Now build the domain coercion node. This represents run-time checking @@ -753,7 +806,8 @@ coerce_to_domain(Node *arg, Oid baseTypeId, int32 baseTypeMod, Oid typeId, * needed to produce the domain value in the first place. So, no getBaseType. */ static Node * -coerce_type_typmod(Node *node, Oid targetTypeId, int32 targetTypMod, +coerce_type_typmod(ParseState *pstate, + Node *node, Oid targetTypeId, int32 targetTypMod, CoercionContext ccontext, CoercionForm cformat, int location, bool hideInputCoercion) @@ -781,7 +835,7 @@ coerce_type_typmod(Node *node, Oid targetTypeId, int32 targetTypMod, if (pathtype != COERCION_PATH_NONE) { - node = build_coercion_expression(node, pathtype, funcId, + node = build_coercion_expression(pstate, node, pathtype, funcId, targetTypeId, targetTypMod, ccontext, cformat, location); } @@ -839,7 +893,8 @@ hide_coercion_node(Node *node) * since there is no difference in terms of the calling convention. */ static Node * -build_coercion_expression(Node *node, +build_coercion_expression(ParseState *pstate, + Node *node, CoercionPathType pathtype, Oid funcId, Oid targetTypeId, int32 targetTypMod, @@ -873,6 +928,15 @@ build_coercion_expression(Node *node, Assert(nargs < 2 || procstruct->proargtypes.values[1] == INT4OID); Assert(nargs < 3 || procstruct->proargtypes.values[2] == BOOLOID); + if (parse_escontext(pstate) != NULL && !procstruct->proerrorsafe) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot cast type %s to %s with DEFAULT expression in CAST ... ON CONVERSION ERROR", + format_type_be(exprType(node)), + format_type_be(targetTypeId)), + errdetail("Explicit cast is defined but definition is not error safe."), + parser_errposition(pstate, exprLocation(node))); + ReleaseSysCache(tp); } @@ -954,7 +1018,7 @@ build_coercion_expression(Node *node, targetElementType = get_element_type(targetTypeId); Assert(OidIsValid(targetElementType)); - elemexpr = coerce_to_target_type(NULL, + elemexpr = coerce_to_target_type(pstate, (Node *) ctest, ctest->typeId, targetElementType, @@ -1139,7 +1203,7 @@ coerce_record_to_complex(ParseState *pstate, Node *node, if (baseTypeId != targetTypeId) { rowexpr->row_format = COERCE_IMPLICIT_CAST; - return coerce_to_domain((Node *) rowexpr, + return coerce_to_domain(NULL, (Node *) rowexpr, baseTypeId, baseTypeMod, targetTypeId, ccontext, cformat, location, @@ -1292,7 +1356,8 @@ coerce_null_to_domain(Oid typid, int32 typmod, Oid collation, true, /* isnull */ typbyval); if (typid != baseTypeId) - result = coerce_to_domain(result, + result = coerce_to_domain(NULL, + result, baseTypeId, baseTypeMod, typid, COERCION_IMPLICIT, diff --git a/src/backend/parser/parse_collate.c b/src/backend/parser/parse_collate.c index ba7df2a7789..043cebd23de 100644 --- a/src/backend/parser/parse_collate.c +++ b/src/backend/parser/parse_collate.c @@ -441,6 +441,32 @@ assign_collations_walker(Node *node, assign_collations_context *context) exprSetCollation(node, collation); } break; + + case T_SafeTypeCastExpr: + { + /* + * Like CaseExpr, we do not recurse into the input expression. + * It was already marked with collations during + * transformSafeTypeCast, and its collation reaches the result + * only through castexpr's placeholder. + */ + SafeTypeCastExpr *stc = (SafeTypeCastExpr *) node; + + (void) assign_collations_walker((Node *) stc->castexpr, + &loccontext); + (void) assign_collations_walker((Node *) stc->defexpr, + &loccontext); + } + break; + case T_SafeCastElement: + { + /* Likewise, see T_SafeTypeCastExpr above */ + SafeCastElement *elem = (SafeCastElement *) node; + + (void) assign_collations_walker((Node *) elem->castexpr, + &loccontext); + } + break; case T_TargetEntry: (void) expression_tree_walker(node, assign_collations_walker, diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 728392f436f..89e3bd94af2 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -61,7 +61,9 @@ static Node *transformMultiAssignRef(ParseState *pstate, MultiAssignRef *maref); static Node *transformCaseExpr(ParseState *pstate, CaseExpr *c); static Node *transformSubLink(ParseState *pstate, SubLink *sublink); static Node *transformArrayExpr(ParseState *pstate, A_ArrayExpr *a, - Oid array_type, Oid element_type, int32 typmod); + Oid array_type, Oid element_type, int32 typmod, + bool *coercion_failed); + static Node *transformRowExpr(ParseState *pstate, RowExpr *r, bool allowDefault); static Node *transformCoalesceExpr(ParseState *pstate, CoalesceExpr *c); static Node *transformMinMaxExpr(ParseState *pstate, MinMaxExpr *m); @@ -109,6 +111,9 @@ static Expr *make_distinct_op(ParseState *pstate, List *opname, static Node *make_nulltest_from_distinct(ParseState *pstate, A_Expr *distincta, Node *arg); +static Node *transformSafeTypeCast(ParseState *pstate, TypeCast *tc); +static Node *transformSafeCastSource(ParseState *pstate, Node *expr, + Oid targetType, int32 targetTypmod); /* * transformExpr - @@ -165,7 +170,7 @@ transformExprRecurse(ParseState *pstate, Node *expr) case T_A_ArrayExpr: result = transformArrayExpr(pstate, (A_ArrayExpr *) expr, - InvalidOid, InvalidOid, -1); + InvalidOid, InvalidOid, -1, NULL); break; case T_TypeCast: @@ -2024,12 +2029,14 @@ transformSubLink(ParseState *pstate, SubLink *sublink) */ static Node * transformArrayExpr(ParseState *pstate, A_ArrayExpr *a, - Oid array_type, Oid element_type, int32 typmod) + Oid array_type, Oid element_type, int32 typmod, + bool *coercion_failed) { ArrayExpr *newa = makeNode(ArrayExpr); List *newelems = NIL; List *newcoercedelems = NIL; ListCell *element; + ListCell *rawelement; Oid coerce_type; bool coerce_hard; @@ -2055,7 +2062,8 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a, (A_ArrayExpr *) e, array_type, element_type, - typmod); + typmod, + coercion_failed); /* we certainly have an array here */ Assert(array_type == InvalidOid || array_type == exprType(newe)); newa->multidims = true; @@ -2135,6 +2143,111 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a, coerce_hard = false; } + if (coercion_failed != NULL) + { + Assert(coerce_hard); + + /* + * Build a SafeCastElement converting each element of an ARRAY[] + * constructor argument of (CAST ... DEFAULT ... ON CONVERSION ERROR) + * to the target element type. + */ + forboth(element, newelems, rawelement, a->elements) + { + Node *e = (Node *) lfirst(element); + Node *newe; + + if (IsA(lfirst(rawelement), A_ArrayExpr)) + newe = e; + else + { + Node *castexpr; + SafeCastElement *elem; + int location = exprLocation(e); + Node *saved_escontext = pstate->p_escontext; + ErrorSaveContext *escontext = makeNode(ErrorSaveContext); + + /* + * each element gets its own context, so one failure doesn't + * hide others + */ + pstate->p_escontext = (Node *) escontext; + + e = transformSafeCastSource(pstate, e, coerce_type, typmod); + + /* + * Run collation assignment on the argument so that we know + * what collation to mark the placeholder with, as + * transformCaseExpr does. + */ + assign_expr_collations(pstate, e); + + if (escontext->error_occurred) + castexpr = NULL; + else + { + CaseTestExpr *placeholder; + + placeholder = makeNode(CaseTestExpr); + placeholder->typeId = exprType(e); + placeholder->typeMod = exprTypmod(e); + placeholder->collation = exprCollation(e); + + castexpr = coerce_to_target_type(pstate, (Node *) placeholder, + placeholder->typeId, + coerce_type, typmod, + COERCION_EXPLICIT, + COERCE_EXPLICIT_CAST, + location); + if (escontext->error_occurred) + castexpr = NULL; + + /* + * The placeholder carries the argument's collation only + * at implicit strength. Keep an explicit COLLATE on the + * argument explicit + */ + if (castexpr != NULL && IsA(e, CollateExpr) && + type_is_collatable(coerce_type)) + { + CollateExpr *coll = makeNode(CollateExpr); + + coll->arg = (Expr *) castexpr; + coll->collOid = ((CollateExpr *) e)->collOid; + coll->location = ((CollateExpr *) e)->location; + castexpr = (Node *) coll; + } + } + + if (castexpr == NULL) + *coercion_failed = true; + + elem = makeNode(SafeCastElement); + elem->arg = (Expr *) e; + elem->castexpr = (Expr *) castexpr; + elem->resulttype = coerce_type; + elem->resulttypmod = typmod; + elem->resultcollid = get_typcollation(coerce_type); + elem->location = location; + newe = (Node *) elem; + + pstate->p_escontext = saved_escontext; + } + + newcoercedelems = lappend(newcoercedelems, newe); + } + + newa->array_typeid = array_type; + /* array_collid will be set by transformSafeTypeCast() */ + newa->element_typeid = element_type; + newa->elements = newcoercedelems; + newa->list_start = a->list_start; + newa->list_end = a->list_end; + newa->location = a->location; + + return (Node *) newa; + } + /* * Coerce elements to target type * @@ -2722,6 +2835,9 @@ transformTypeCast(ParseState *pstate, TypeCast *tc) int32 targetTypmod; int location; + if (tc->defexpr) + return transformSafeTypeCast(pstate, tc); + /* Look up the type name first */ typenameTypeIdAndMod(pstate, tc->typeName, &targetType, &targetTypmod); @@ -2753,7 +2869,8 @@ transformTypeCast(ParseState *pstate, TypeCast *tc) (A_ArrayExpr *) arg, targetBaseType, elementType, - targetBaseTypmod); + targetBaseTypmod, + NULL); } else expr = transformExprRecurse(pstate, arg); @@ -2823,6 +2940,253 @@ transformCollateClause(ParseState *pstate, CollateClause *c) return (Node *) newc; } +/* + * Transform CAST(expr AS type DEFAULT defexpr ON CONVERSION ERROR). + * + * The argument is stored once, in "arg", and is not coerced itself; instead we + * coerce a CaseTestExpr placeholder of its type, so castexpr holds only the + * coercion and no tree walker sees the argument twice. At runtime the executor + * feeds the value of "arg" to the placeholder, as ArrayCoerceExpr does for + * elemexpr. + */ +static Node * +transformSafeTypeCast(ParseState *pstate, TypeCast *tc) +{ + Node *defexpr; + Node *castexpr; + Node *arg = tc->arg; + Node *expr = NULL; + SafeTypeCastExpr *stc; + Oid targetType; + int32 targetTypmod; + Oid targetTypecoll; + bool arg_errorsafe = false; + bool array_failed = false; + int defloc; + Node *last_srf; + Node *saved_escontext; + + last_srf = pstate->p_last_srf; + defloc = exprLocation(tc->defexpr); + saved_escontext = pstate->p_escontext; + + /* Look up the type name first */ + typenameTypeIdAndMod(pstate, tc->typeName, &targetType, &targetTypmod); + + targetTypecoll = get_typcollation(targetType); + + defexpr = transformExprRecurse(pstate, tc->defexpr); + + /* if the DEFAULT contained a SRF, complain */ + if (pstate->p_last_srf != last_srf) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-returning functions are not allowed in CAST DEFAULT expression"), + parser_errposition(pstate, defloc)); + + defexpr = coerce_to_target_type(pstate, defexpr, exprType(defexpr), + targetType, targetTypmod, + COERCION_EXPLICIT, + COERCE_EXPLICIT_CAST, + exprLocation(defexpr)); + if (defexpr == NULL) + ereport(ERROR, + errcode(ERRCODE_CANNOT_COERCE), + errmsg("cannot coerce %s expression to type %s", + "CAST DEFAULT", + format_type_be(targetType)), + parser_coercion_errposition(pstate, exprLocation(tc->defexpr), defexpr)); + + /* + * If the subject of the typecast is an ARRAY[] construct and the target + * type is an array type, we invoke transformArrayExpr() directly so that + * we can pass down the type information. This avoids some cases where + * transformArrayExpr() might not infer the correct type. It wraps each + * element in a SafeCastElement converting it to the target element type, + * and sets array_failed if an element has no conversion. + */ + if (IsA(arg, A_ArrayExpr)) + { + Oid targetBaseType; + int32 targetBaseTypmod; + Oid elementType; + + /* + * 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 + * usual case that the target is not a domain, the remaining steps + * will be a no-op. + */ + targetBaseTypmod = targetTypmod; + targetBaseType = getBaseTypeAndTypmod(targetType, &targetBaseTypmod); + elementType = get_element_type(targetBaseType); + if (OidIsValid(elementType)) + { + expr = transformArrayExpr(pstate, + (A_ArrayExpr *) arg, + targetBaseType, + elementType, + targetBaseTypmod, + &array_failed); + arg_errorsafe = true; + } + else + expr = transformExprRecurse(pstate, arg); + } + else + expr = transformExprRecurse(pstate, arg); + + if (array_failed) + { + assign_expr_collations(pstate, expr); + castexpr = NULL; + } + else + { + ErrorSaveContext *escontext = NULL; + + /* + * Build the coercion expression under our ErrorSaveContext, so that + * cast functions not marked ERROR SAFE are rejected, and an invalid + * literal is recorded rather than thrown. + */ + escontext = makeNode(ErrorSaveContext); + pstate->p_escontext = (Node *) escontext; + + expr = transformSafeCastSource(pstate, expr, targetType, targetTypmod); + + /* + * Run collation assignment on the argument so that we know what + * collation to mark the placeholder with, as transformCaseExpr does. + */ + assign_expr_collations(pstate, expr); + + if (SOFT_ERROR_OCCURRED(pstate->p_escontext)) + castexpr = NULL; + else + { + CaseTestExpr *placeholder; + + placeholder = makeNode(CaseTestExpr); + placeholder->typeId = exprType(expr); + placeholder->typeMod = exprTypmod(expr); + placeholder->collation = exprCollation(expr); + + castexpr = coerce_to_target_type(pstate, (Node *) placeholder, exprType(expr), + targetType, targetTypmod, + COERCION_EXPLICIT, + COERCE_EXPLICIT_CAST, + tc->location); + + if (SOFT_ERROR_OCCURRED(pstate->p_escontext)) + castexpr = NULL; + + /* + * The placeholder carries the argument's collation only at + * implicit strength. Keep an explicit COLLATE on the argument + * explicit + */ + if (castexpr != NULL && IsA(expr, CollateExpr) && + type_is_collatable(targetType)) + { + CollateExpr *coll = makeNode(CollateExpr); + + coll->arg = (Expr *) castexpr; + coll->collOid = ((CollateExpr *) expr)->collOid; + coll->location = ((CollateExpr *) expr)->location; + castexpr = (Node *) coll; + } + } + } + + pstate->p_escontext = saved_escontext; + + stc = makeNode(SafeTypeCastExpr); + stc->arg = (Expr *) expr; + stc->arg_errorsafe = arg_errorsafe; + stc->castexpr = (Expr *) castexpr; + stc->defexpr = (Expr *) defexpr; + stc->resulttype = targetType; + stc->resulttypmod = targetTypmod; + stc->resultcollid = targetTypecoll; + stc->location = tc->location; + + return (Node *) stc; +} + +/* + * Prepare the source expression of CAST ... DEFAULT ... ON CONVERSION ERROR, or + * of one of its ARRAY[] elements, for conversion over a placeholder. + * + * Only the placeholder is coerced to the target type, but an unknown literal + * still needs a real type. It is first converted to the target's base type, as + * in a plain CAST; for INTERVAL that passes the typmod (see coerce_type()). So + * '1'::interval hour gives 01:00:00 + * '1'::text::interval hour gives 00:00:00 + */ +static Node * +transformSafeCastSource(ParseState *pstate, Node *expr, + Oid targetType, int32 targetTypmod) +{ + Node *origexpr = expr; + Node *result = NULL; + + Assert(pstate->p_escontext != NULL && + !SOFT_ERROR_OCCURRED(pstate->p_escontext)); + + while (expr && IsA(expr, CollateExpr)) + expr = (Node *) ((CollateExpr *) expr)->arg; + + if (exprType(expr) != UNKNOWNOID) + result = expr; + else + { + if (IsA(expr, Const)) + { + Oid baseType; + int32 baseTypmod = targetTypmod; + + baseType = getBaseTypeAndTypmod(targetType, &baseTypmod); + + result = coerce_type(pstate, expr, UNKNOWNOID, + baseType, baseTypmod, + COERCION_EXPLICIT, + COERCE_EXPLICIT_CAST, + exprLocation(expr)); + + if (SOFT_ERROR_OCCURRED(pstate->p_escontext) || + exprType(result) == UNKNOWNOID) + result = NULL; + } + + if (result == NULL) + result = coerce_type(pstate, expr, UNKNOWNOID, + TEXTOID, -1, + COERCION_IMPLICIT, + COERCE_IMPLICIT_CAST, + exprLocation(expr)); + } + + /* + * Reinstall the top CollateExpr, unless the type is not collatable, as + * coerce_type() does for a plain CAST. + */ + if (IsA(origexpr, CollateExpr) && type_is_collatable(exprType(result))) + { + CollateExpr *coll = (CollateExpr *) origexpr; + CollateExpr *newcoll = makeNode(CollateExpr); + + newcoll->arg = (Expr *) result; + newcoll->collOid = coll->collOid; + newcoll->location = coll->location; + result = (Node *) newcoll; + } + + Assert(result != NULL); + + return result; +} + /* * Transform a "row compare-op row" construct * diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index 728936b502e..445323d23fa 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -835,7 +835,7 @@ transformAssignmentIndirection(ParseState *pstate, * the fields that the INSERT or UPDATE means to. */ if (baseTypeId != targetTypeId) - return coerce_to_domain((Node *) fstore, + return coerce_to_domain(NULL, (Node *) fstore, baseTypeId, baseTypeMod, targetTypeId, COERCION_IMPLICIT, diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c index 262f2742b02..fb984d4fb96 100644 --- a/src/backend/parser/parse_type.c +++ b/src/backend/parser/parse_type.c @@ -660,6 +660,28 @@ stringTypeDatum(Type tp, const char *string, int32 atttypmod) return OidInputFunctionCall(typinput, string, typioparam, atttypmod); } +/* + * Error-safe version of stringTypeDatum. + * + * Returns true if "string" is a valid representation of type "tp". + * On success, the internal Datum representation is written to "result". + */ +bool +stringTypeDatumSafe(Type tp, const char *string, int32 atttypmod, + Node *escontext, Datum *result) +{ + FmgrInfo flinfo; + + Form_pg_type typform = (Form_pg_type) GETSTRUCT(tp); + Oid typinput = typform->typinput; + Oid typioparam = getTypeIOParam(tp); + + fmgr_info(typinput, &flinfo); + + return InputFunctionCallSafe(&flinfo, string, typioparam, atttypmod, + escontext, result); +} + /* * 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 583346bf531..9c204274ade 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -688,6 +688,7 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) castnode = makeNode(TypeCast); castnode->typeName = SystemTypeName("regclass"); castnode->arg = (Node *) snamenode; + castnode->defexpr = NULL; castnode->location = -1; funccallnode = makeFuncCall(SystemFuncName("nextval"), list_make1(castnode), @@ -4866,7 +4867,7 @@ transformPartitionBoundValue(ParseState *pstate, Node *val, assign_expr_collations(pstate, value); value = (Node *) expression_planner((Expr *) value); value = (Node *) evaluate_expr((Expr *) value, colType, colTypmod, - partCollation); + partCollation, NULL); if (!IsA(value, Const)) elog(ERROR, "could not evaluate partition bound expression"); } diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index f1f6336e5c8..041da42877f 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -3287,6 +3287,15 @@ array_map(Datum arrayd, /* Apply the given expression to source element */ values[i] = ExecEvalExpr(exprstate, econtext, &nulls[i]); + /* Exit early if evaluation failed */ + if (SOFT_ERROR_OCCURRED(exprstate->escontext)) + { + pfree(values); + pfree(nulls); + + return (Datum) 0; + } + if (nulls[i]) hasnulls = true; else diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 294228bad82..95db325ba1d 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -10618,6 +10618,34 @@ get_rule_expr(Node *node, deparse_context *context, } break; + case T_SafeTypeCastExpr: + { + SafeTypeCastExpr *stcexpr = castNode(SafeTypeCastExpr, node); + + appendStringInfoString(buf, "CAST("); + get_rule_expr((Node *) stcexpr->arg, context, showimplicit); + + appendStringInfo(buf, " AS %s ", + format_type_with_typemod(stcexpr->resulttype, + stcexpr->resulttypmod)); + + appendStringInfoString(buf, "DEFAULT "); + + get_rule_expr((Node *) stcexpr->defexpr, context, showimplicit); + + appendStringInfoString(buf, " ON CONVERSION ERROR)"); + } + break; + + case T_SafeCastElement: + { + SafeCastElement *elem = castNode(SafeCastElement, node); + + /* the conversion is implied by the enclosing CAST */ + get_rule_expr((Node *) elem->arg, context, showimplicit); + } + break; + case T_JsonExpr: { JsonExpr *jexpr = (JsonExpr *) node; diff --git a/src/backend/utils/fmgr/README b/src/backend/utils/fmgr/README index 9958d38992b..56d899d35a2 100644 --- a/src/backend/utils/fmgr/README +++ b/src/backend/utils/fmgr/README @@ -267,12 +267,15 @@ See windowapi.h for more information. information about the context of the CALL statement, particularly whether it is within an "atomic" execution context. -* Some callers of datatype input functions (and in future perhaps -other classes of functions) pass an instance of ErrorSaveContext. -This indicates that the caller wishes to handle "soft" errors without -a transaction-terminating exception being thrown: instead, the callee -should store information about the error cause in the ErrorSaveContext -struct and return a dummy result value. Further details appear in +* Some callers pass an instance of ErrorSaveContext, indicating that they +wish to handle "soft" errors themselves rather than have a +transaction-terminating exception thrown. In that case the callee should +record the error cause in the ErrorSaveContext struct and return a dummy +result value. Datatype input functions are called this way. So is every +function call within an expression that the executor compiles with an +ErrorSaveContext, for example the conversion in CAST ... DEFAULT ... ON +CONVERSION ERROR. Reporting errors softly is up to the callee: any error +it does not report that way is thrown as usual. Further details appear in "Handling Soft Errors" below. diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h index c61b3d624d5..64eac197758 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -125,6 +125,10 @@ typedef enum ExprEvalOp EEOP_FUNCEXPR_STRICT_2, EEOP_FUNCEXPR_FUSAGE, EEOP_FUNCEXPR_STRICT_FUSAGE, + EEOP_FUNCEXPR_SAFE, /* called with an ErrorSaveContext */ + EEOP_FUNCEXPR_STRICT_SAFE, + EEOP_FUNCEXPR_SAFE_FUSAGE, + EEOP_FUNCEXPR_STRICT_SAFE_FUSAGE, /* * Evaluate boolean AND expression, one step per subexpression. FIRST/LAST @@ -265,6 +269,7 @@ typedef enum ExprEvalOp EEOP_XMLEXPR, EEOP_JSON_CONSTRUCTOR, EEOP_IS_JSON, + EEOP_SAFETYPE_CAST, EEOP_JSONEXPR_PATH, EEOP_JSONEXPR_COERCION, EEOP_JSONEXPR_COERCION_FINISH, @@ -754,6 +759,12 @@ typedef struct ExprEvalStep JsonIsPredicate *pred; /* original expression node */ } is_json; + /* for EEOP_SAFETYPE_CAST */ + struct + { + struct SafeTypeCastState *stcstate; + } stcexpr; + /* for EEOP_JSONEXPR_PATH */ struct { @@ -854,8 +865,12 @@ extern void CheckExprStillValid(ExprState *state, ExprContext *econtext); */ extern void ExecEvalFuncExprFusage(ExprState *state, ExprEvalStep *op, ExprContext *econtext); +extern void ExecEvalFuncSafeFusage(ExprState *state, ExprEvalStep *op, + ExprContext *econtext); extern void ExecEvalFuncExprStrictFusage(ExprState *state, ExprEvalStep *op, ExprContext *econtext); +extern void ExecEvalFuncStrictSafeFusage(ExprState *state, ExprEvalStep *op, + ExprContext *econtext); extern void ExecEvalParamExec(ExprState *state, ExprEvalStep *op, ExprContext *econtext); extern void ExecEvalParamSet(ExprState *state, ExprEvalStep *op, diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index 152a1dfa568..657af8269ef 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -332,6 +332,7 @@ ExecProcNode(PlanState *node) * prototypes from functions in execExpr.c */ extern ExprState *ExecInitExpr(Expr *node, PlanState *parent); +extern ExprState *ExecInitExprWithContext(Expr *node, PlanState *parent, Node *escontext); extern ExprState *ExecInitExprWithParams(Expr *node, ParamListInfo ext_params); extern ExprState *ExecInitQual(List *qual, PlanState *parent); extern ExprState *ExecInitCheck(List *qual, PlanState *parent); diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 8087a986204..9b7ccbe4e12 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -1074,6 +1074,29 @@ typedef struct DomainConstraintState ExprState *check_exprstate; /* check_expr's eval state, or NULL */ } DomainConstraintState; +typedef struct SafeTypeCastState +{ + SafeTypeCastExpr *stcexpr; + + NullableDatum args; + + /* + * The jump target used to skip all remaining steps when the default + * expression evaluation is bypassed. + */ + int jump_end; + + /* + * Error-safe context for type coercion evaluation. A pointer to this is + * passed to ExecInitExprRec() during type coercion expression + * initialization (see ExecInitSafeTypeCastExpr). + * + * At runtime, this is reset before each evaluation of EEOP_SAFETYPE_CAST. + */ + ErrorSaveContext escontext; + +} SafeTypeCastState; + /* * State for JsonExpr evaluation, too big to inline. * diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 0debcd193ab..e385f3afa96 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -398,6 +398,7 @@ typedef struct TypeCast NodeTag type; Node *arg; /* the expression being casted */ TypeName *typeName; /* the target type */ + Node *defexpr; /* DEFAULT expression */ ParseLoc location; /* token location, or -1 if unknown */ } TypeCast; diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 726c186c7d2..cda32f14301 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -764,6 +764,77 @@ typedef enum CoercionForm COERCE_SQL_SYNTAX, /* display with SQL-mandated special syntax */ } CoercionForm; +/* + * SafeTypeCastExpr - + * Transformed representation of + * CAST(expr AS typename DEFAULT expr ON CONVERSION ERROR) + */ +typedef struct SafeTypeCastExpr +{ + Expr xpr; + + /* + * The transformed input expression, evaluated once and fed to castexpr. + * Normally arg is not evaluated in an error-safe manner. For an ARRAY[] + * input cast to an array type, however, its elements are SafeCastElement + * nodes converting them to the target element type. arg_errorsafe is + * then true, and arg is evaluated under this cast's error context, so + * that those conversions fail softly. + */ + Expr *arg; + bool arg_errorsafe; + + /* + * coercion of arg to the target type, built over a CaseTestExpr that + * stands for its value; NULL means no cast path exists, in which case the + * result is always defexpr + */ + Expr *castexpr; + /* Fall-back to the default expression if cast evaluation fails */ + Expr *defexpr; + + /* target type Oid */ + Oid resulttype; + /* target type modifier */ + int32 resulttypmod; + /* OID of collation, or InvalidOid if none */ + Oid resultcollid; + /* token location, or -1 if unknown */ + ParseLoc location; +} SafeTypeCastExpr; + +/* + * SafeCastElement - + * One element of an ARRAY[] SafeTypeCastExpr.arg (see arg_errorsafe) + * + * arg, the element itself, is evaluated with hard errors. castexpr converts + * its value to the target element type and reports errors into the enclosing + * SafeTypeCastExpr's context, which then returns its DEFAULT. + */ +typedef struct SafeCastElement +{ + Expr xpr; + + /* the element expression */ + Expr *arg; + + /* + * coercion of arg to the target element type, built over a CaseTestExpr + * that stands for arg's value; NULL means no cast path exists, in which + * case the element yields NULL and the enclosing cast has no castexpr + */ + Expr *castexpr; + + /* target element type Oid */ + Oid resulttype; + /* target element type modifier */ + int32 resulttypmod; + /* OID of collation, or InvalidOid if none */ + Oid resultcollid; + /* token location, or -1 if unknown */ + ParseLoc location; +} SafeCastElement; + /* * FuncExpr - expression node for a function call */ diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h index cb6241e2bdd..fb81ed6e551 100644 --- a/src/include/optimizer/optimizer.h +++ b/src/include/optimizer/optimizer.h @@ -151,7 +151,7 @@ extern void convert_saop_to_hashed_saop(Node *node); extern Node *estimate_expression_value(PlannerInfo *root, Node *node); extern Expr *evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod, - Oid result_collation); + Oid result_collation, Node *escontext); extern bool var_is_nonnullable(PlannerInfo *root, Var *var, NotNullSource source); diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h index aabacd49b65..98caa18d73e 100644 --- a/src/include/parser/parse_coerce.h +++ b/src/include/parser/parse_coerce.h @@ -48,7 +48,8 @@ extern bool can_coerce_type(int nargs, const Oid *input_typeids, const Oid *targ extern Node *coerce_type(ParseState *pstate, Node *node, Oid inputTypeId, Oid targetTypeId, int32 targetTypeMod, CoercionContext ccontext, CoercionForm cformat, int location); -extern Node *coerce_to_domain(Node *arg, Oid baseTypeId, int32 baseTypeMod, +extern Node *coerce_to_domain(ParseState *pstate, Node *arg, + Oid baseTypeId, int32 baseTypeMod, Oid typeId, CoercionContext ccontext, CoercionForm cformat, int location, bool hideInputCoercion); diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h index f23e21f318b..36412e5e26c 100644 --- a/src/include/parser/parse_node.h +++ b/src/include/parser/parse_node.h @@ -180,6 +180,10 @@ typedef Node *(*CoerceParamHook) (ParseState *pstate, Param *param, * p_last_srf: the set-returning FuncExpr or OpExpr most recently found in * the query, or NULL if none. * + * p_escontext: if not NULL, an ErrorSaveContext under which expressions are + * evaluated during parse analysis, so that conversion errors are reported + * softly instead of being thrown. + * * p_pre_columnref_hook, etc: optional parser hook functions for modifying the * interpretation of ColumnRefs and ParamRefs. * @@ -226,6 +230,8 @@ struct ParseState Node *p_last_srf; /* most recent set-returning func/op found */ + Node *p_escontext; /* ErrorSaveContext pointer */ + /* * Optional hook functions for parser callbacks. These are null unless * set up by the caller of make_parsestate. diff --git a/src/include/parser/parse_type.h b/src/include/parser/parse_type.h index 79f10b5b5b2..cad8d019e84 100644 --- a/src/include/parser/parse_type.h +++ b/src/include/parser/parse_type.h @@ -47,6 +47,8 @@ extern char *typeTypeName(Type t); extern Oid typeTypeRelid(Type typ); extern Oid typeTypeCollation(Type typ); extern Datum stringTypeDatum(Type tp, const char *string, int32 atttypmod); +extern bool stringTypeDatumSafe(Type tp, const char *string, int32 atttypmod, + Node *escontext, Datum *result); extern Oid typeidTypeRelid(Oid type_id); extern Oid typeOrDomainTypeRelid(Oid type_id); diff --git a/src/test/regress/expected/cast.out b/src/test/regress/expected/cast.out new file mode 100644 index 00000000000..c46aab71546 --- /dev/null +++ b/src/test/regress/expected/cast.out @@ -0,0 +1,1194 @@ +SET extra_float_digits = 0; +SET lc_monetary TO "C"; +-- CAST DEFAULT ON CONVERSION ERROR +VALUES (CAST('error' AS integer ERROR ON CONVERSION ERROR)); -- error +ERROR: invalid input syntax for type integer: "error" +LINE 1: VALUES (CAST('error' AS integer ERROR ON CONVERSION ERROR)); + ^ +-- The DEFAULT expression can contain aggreagte/window function or subquery +SELECT CAST('a' as int DEFAULT sum(1) ON CONVERSION ERROR); + int4 +------ + 1 +(1 row) + +SELECT CAST('a' as int DEFAULT sum(1) over() ON CONVERSION ERROR); + int4 +------ + 1 +(1 row) + +SELECT CAST('a' as int DEFAULT (SELECT NULL) ON CONVERSION ERROR); + int4 +------ + +(1 row) + +SELECT CAST('a' as int DEFAULT (SELECT 1 where false) ON CONVERSION ERROR); + int4 +------ + +(1 row) + +-- The DEFAULT expression cannot contain set expression +SELECT CAST('a' as int DEFAULT generate_series(1, 1) ON CONVERSION ERROR); +ERROR: set-returning functions are not allowed in CAST DEFAULT expression +LINE 1: SELECT CAST('a' as int DEFAULT generate_series(1, 1) ON CONV... + ^ +VALUES (CAST('error' AS text DEFAULT '1' COLLATE "C" ON CONVERSION ERROR)); + column1 +--------- + error +(1 row) + +SELECT CAST('a' as int DEFAULT 'b' ON CONVERSION ERROR); +ERROR: invalid input syntax for type integer: "b" +LINE 1: SELECT CAST('a' as int DEFAULT 'b' ON CONVERSION ERROR); + ^ +SELECT CAST(1 AS int DEFAULT 70000::int2::int4 ON CONVERSION ERROR); +ERROR: smallint out of range +SELECT CAST('70000' AS int2 DEFAULT -1 ON CONVERSION ERROR); + int2 +------ + -1 +(1 row) + +SELECT CAST(B'01' AS date DEFAULT NULL ON CONVERSION ERROR); + date +------ + +(1 row) + +SELECT CAST(BIT'01' AS date DEFAULT NULL ON CONVERSION ERROR); + date +------ + +(1 row) + +SELECT CAST(TRUE AS date DEFAULT NULL ON CONVERSION ERROR); + date +------ + +(1 row) + +SELECT CAST(1.1 AS date DEFAULT NULL ON CONVERSION ERROR); + date +------ + +(1 row) + +SELECT CAST(1 AS date DEFAULT NULL ON CONVERSION ERROR); + date +------ + +(1 row) + +SELECT CAST(1111 AS "char" DEFAULT 'A' ON CONVERSION ERROR); + char +------ + A +(1 row) + +SELECT CAST('def'::text AS integer DEFAULT NULL ON CONVERSION ERROR); + int4 +------ + +(1 row) + +SELECT CAST('2'::jsonb AS text DEFAULT 'fallback' ON CONVERSION ERROR); + text +------ + 2 +(1 row) + +SELECT CAST('1' COLLATE "C" || 'h' AS text DEFAULT 'fallback' ON CONVERSION ERROR); + text +------ + 1h +(1 row) + +SELECT CAST(concat('1' collate "C", 'h') AS int DEFAULT NULL ON CONVERSION ERROR); + concat +-------- + +(1 row) + +SELECT CAST(65536 AS int2 DEFAULT NULL ON CONVERSION ERROR); + int2 +------ + +(1 row) + +SELECT CAST(int8(65536) AS int2 DEFAULT NULL ON CONVERSION ERROR); + int8 +------ + +(1 row) + +SELECT CAST(12.111 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale; + to_numeric_scale +------------------ + 12.1 +(1 row) + +SELECT CAST('\x01'::bytea as uuid DEFAULT NULL ON CONVERSION ERROR); + uuid +------ + +(1 row) + +SELECT CAST(CAST('{2020-01-01}'::text[] AS date[]) AS int[] DEFAULT '{7}' ON CONVERSION ERROR); + int4 +------ + {7} +(1 row) + +SELECT (CAST('error' AS int2vector DEFAULT '1 3' ON CONVERSION ERROR)); + int2vector +------------ + 1 3 +(1 row) + +SELECT (CAST('error' AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR)); + int2vector +------------ + {"1 3"} +(1 row) + +SELECT CAST ('b' AS integer + DEFAULT CAST('c' AS INTEGER DEFAULT 0 ON CONVERSION ERROR) + ON CONVERSION ERROR); + int4 +------ + 0 +(1 row) + +-- source expression contain subquery +SELECT CAST((SELECT b FROM generate_series(1, 1), (VALUES('H')) s(b)) + AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR); + b +--------- + {"1 3"} +(1 row) + +SELECT CAST((SELECT ARRAY((SELECT b FROM generate_series(1, 2) g, (VALUES('H')) s(b)))) + AS INT[] + DEFAULT NULL ON CONVERSION ERROR); + array +------- + +(1 row) + +CREATE FUNCTION ret_int8() RETURNS BIGINT AS $$ BEGIN RETURN 2147483648; END; $$ LANGUAGE plpgsql IMMUTABLE; +SELECT CAST('a' as int DEFAULT ret_int8() ON CONVERSION ERROR); -- error +ERROR: integer out of range +SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERROR); -- error +ERROR: cannot coerce CAST DEFAULT expression to type date +LINE 1: SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERR... + ^ +-- Test JIT: run the same cast with JIT forced on, then interpreted +SET jit_above_cost = 0; +SELECT CAST(a AS int2 DEFAULT NULL ON CONVERSION ERROR) FROM (VALUES (1), (65536)) t(a); + a +--- + 1 + +(2 rows) + +RESET jit_above_cost; +SELECT CAST(a AS int2 DEFAULT NULL ON CONVERSION ERROR) FROM (VALUES (1), (65536)) t(a); + a +--- + 1 + +(2 rows) + +CREATE TABLE tcast1(a int DEFAULT CAST('x' AS int DEFAULT a ON CONVERSION ERROR)); -- error +ERROR: cannot use column reference in DEFAULT expression +LINE 1: ...ABLE tcast1(a int DEFAULT CAST('x' AS int DEFAULT a ON CONVE... + ^ +CREATE DOMAIN d_nota AS text CHECK (VALUE <> 'a'); +CREATE TABLE tcast1(a text, b text[], c int, d int8, e int8[]); +ALTER TABLE tcast1 ADD COLUMN f int + DEFAULT CAST('x' AS int DEFAULT a ON CONVERSION ERROR); -- error +ERROR: cannot use column reference in DEFAULT expression +INSERT INTO tcast1(a, b, d, e) values('a', '{a}', 2147483648, '{2147483648}'); +-- domain constraints must not be checked once the conversion to the base type +-- has failed softly +CREATE DOMAIN dint AS int CHECK ((1 / coalesce(VALUE, 0)) IS NOT NULL); +CREATE DOMAIN dintnn AS int NOT NULL CHECK ((1 / coalesce(VALUE, 0)) IS NOT NULL); +SELECT CAST(concat('a') AS dint DEFAULT 2 ON CONVERSION ERROR); + concat +-------- + 2 +(1 row) + +SELECT CAST(nullif(concat('a'), 'a') AS dintnn DEFAULT 2 ON CONVERSION ERROR); + nullif +-------- + 2 +(1 row) + +SELECT CAST(ARRAY[concat('a')] AS dint[] DEFAULT '{2}' ON CONVERSION ERROR); + array +------- + {2} +(1 row) + +SELECT CAST(concat('{a}')::text[] AS dint[] DEFAULT '{2}' ON CONVERSION ERROR); + concat +-------- + {2} +(1 row) + +SELECT CAST(concat('0') AS dint DEFAULT 2 ON CONVERSION ERROR); +ERROR: division by zero +-- Only the conversion itself is evaluated in an error-safe manner. The source +-- expression is evaluated normally, so errors raised inside it are not caught. +-- none of the following should be accepted +SELECT CAST (CAST(a AS int) AS text DEFAULT NULL ON CONVERSION ERROR) FROM tcast1; +ERROR: invalid input syntax for type integer: "a" +SELECT CAST (CAST(a AS d_nota) AS text DEFAULT NULL ON CONVERSION ERROR) FROM tcast1; +ERROR: value for domain d_nota violates check constraint "d_nota_check" +SELECT CAST (CAST(b AS d_nota[]) AS text[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1; +ERROR: value for domain d_nota violates check constraint "d_nota_check" +SELECT CAST (CAST(b AS int[]) AS text[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1; +ERROR: invalid input syntax for type integer: "a" +SELECT CAST (CAST(d AS int) AS text DEFAULT NULL ON CONVERSION ERROR) FROM tcast1; +ERROR: integer out of range +SELECT CAST(ARRAY[a::int, concat('x')] AS int[] DEFAULT '{0}' ON CONVERSION ERROR) FROM tcast1; +ERROR: invalid input syntax for type integer: "a" +SELECT CAST(ARRAY['x', a::int] AS text[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1; +ERROR: invalid input syntax for type integer: "a" +SELECT CAST(ARRAY[a::int] AS text[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1; +ERROR: invalid input syntax for type integer: "a" +SELECT CAST(ARRAY['x', a::int] AS text DEFAULT '{0}' ON CONVERSION ERROR) FROM tcast1; +ERROR: invalid input syntax for type integer: "x" +LINE 1: SELECT CAST(ARRAY['x', a::int] AS text DEFAULT '{0}' ON CONV... + ^ +SELECT CAST(ARRAY['x', a::int] AS date DEFAULT NULL ON CONVERSION ERROR) FROM tcast1; +ERROR: invalid input syntax for type integer: "x" +LINE 1: SELECT CAST(ARRAY['x', a::int] AS date DEFAULT NULL ON CONVE... + ^ +SELECT CAST (CAST('{a}'::text[] AS int[]) AS text[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1; +ERROR: invalid input syntax for type integer: "a" +SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); +ERROR: invalid input syntax for type integer: "three" +LINE 1: SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] ... + ^ +SELECT CAST('70000'::int2 AS int2 DEFAULT -1 ON CONVERSION ERROR); +ERROR: value "70000" is out of range for type smallint +LINE 1: SELECT CAST('70000'::int2 AS int2 DEFAULT -1 ON CONVERSION E... + ^ +SELECT CAST(1 + '70000'::int2::int4 AS int DEFAULT -1 ON CONVERSION ERROR); +ERROR: value "70000" is out of range for type smallint +LINE 1: SELECT CAST(1 + '70000'::int2::int4 AS int DEFAULT -1 ON CON... + ^ +SELECT CAST('\x01'::bytea::uuid AS text DEFAULT 'x1' ON CONVERSION ERROR); +ERROR: invalid input length for type uuid +DETAIL: Expected 16 bytes, got 1. +SELECT CAST(ARRAY[int4pl(1, 'x')] AS int[] DEFAULT NULL ON CONVERSION ERROR); +ERROR: invalid input syntax for type integer: "x" +LINE 1: SELECT CAST(ARRAY[int4pl(1, 'x')] AS int[] DEFAULT NULL ON C... + ^ +SELECT CAST(ARRAY[1 + 'x'] AS int[] DEFAULT NULL ON CONVERSION ERROR); +ERROR: invalid input syntax for type integer: "x" +LINE 1: SELECT CAST(ARRAY[1 + 'x'] AS int[] DEFAULT NULL ON CONVERSI... + ^ +-- none of the above should be accepted +-- Test ARRAY constructor and target type is ARRAY type +SELECT CAST(ARRAY[e] as int[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1; + array +------- + +(1 row) + +-- all of the following should be accepted. For an ARRAY[] argument, each +-- element is converted to the target element type +SELECT CAST(ARRAY['x', a::int] AS int[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1; + array +------- + +(1 row) + +SELECT CAST(ARRAY[1,2] || 3 AS boolean DEFAULT false ON CONVERSION ERROR); + bool +------ + f +(1 row) + +SELECT CAST(ARRAY[] AS int[] DEFAULT NULL ON CONVERSION ERROR); + array +------- + {} +(1 row) + +SELECT CAST(ARRAY[['abc'],[456]] AS int[] DEFAULT NULL ON CONVERSION ERROR); + array +------- + +(1 row) + +SELECT CAST(ARRAY[1, 'x'] AS point[] DEFAULT NULL ON CONVERSION ERROR); + array +------- + +(1 row) + +SELECT CAST(ARRAY[11.12] AS date[] DEFAULT NULL ON CONVERSION ERROR); + array +------- + +(1 row) + +SELECT CAST(ARRAY[11111111111111111] AS int[] DEFAULT NULL ON CONVERSION ERROR); + array +------- + +(1 row) + +SELECT CAST(ARRAY['a'::text] AS int[] DEFAULT NULL ON CONVERSION ERROR); + array +------- + +(1 row) + +SELECT CAST(ARRAY[['1'], ['three'],['a']] AS int[] DEFAULT '{1,2}' ON CONVERSION ERROR); + array +------- + {1,2} +(1 row) + +SELECT CAST(ARRAY[['1', '2'], ['three', 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); + array +--------- + {21,22} +(1 row) + +SELECT CAST(e as int4[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1; + e +--- + +(1 row) + +SELECT CAST('{123,abc,def}' AS int[] DEFAULT '{-789}' ON CONVERSION ERROR); + int4 +-------- + {-789} +(1 row) + +SELECT CAST('{234,def,567}'::text[] AS integer[] DEFAULT '{-1011}' ON CONVERSION ERROR); + int4 +--------- + {-1011} +(1 row) + +-- all of the above should be accepted +-- test with user-defined type, domain, ARRAY over domain, domain over ARRAY +CREATE DOMAIN d_int42 as int check (value = 42) NOT NULL; +CREATE DOMAIN d_int42arr as d_int42[]; +CREATE DOMAIN d_int42arr1 as d_int42arr; +CREATE DOMAIN d_int8 as int8 check (value > 0); +CREATE DOMAIN d_int8arr as d_int8[]; +CREATE DOMAIN d_char3_not_null as char(3) NOT NULL; +CREATE DOMAIN d_varchar as varchar(3) NOT NULL; +CREATE DOMAIN d_int_arr as int[] check (value = '{41, 43}') NOT NULL; +CREATE TYPE comp_domain_with_typmod AS (a d_char3_not_null, b int); +CREATE TYPE comp2 AS (a d_varchar); +CREATE DOMAIN d_numeric as numeric(4,4); +SELECT CAST(1.0::float4 AS d_numeric DEFAULT NULL ON CONVERSION ERROR); + d_numeric +----------- + +(1 row) + +SELECT CAST('{1.0}' AS numeric(4,1)[] DEFAULT NULL ON CONVERSION ERROR); + numeric +--------- + {1.0} +(1 row) + +SELECT CAST('{0.1, 1.0}' AS d_numeric[] DEFAULT NULL ON CONVERSION ERROR); + d_numeric +----------- + +(1 row) + +SELECT CAST(11 AS d_int42 DEFAULT 41 ON CONVERSION ERROR); -- error +ERROR: value for domain d_int42 violates check constraint "d_int42_check" +SELECT CAST(NULL AS d_int42 DEFAULT NULL ON CONVERSION ERROR); -- error +ERROR: domain d_int42 does not allow null values +SELECT CAST(11 AS d_int42 DEFAULT 42 ON CONVERSION ERROR); + d_int42 +--------- + 42 +(1 row) + +SELECT CAST(NULL AS d_int42 DEFAULT 42 ON CONVERSION ERROR); + d_int42 +--------- + 42 +(1 row) + +SELECT CAST(ARRAY[42]::d_int42arr1 AS d_int8 DEFAULT '1' ON CONVERSION ERROR); + array +------- + 1 +(1 row) + +SELECT CAST(ARRAY[42]::d_int42arr1 AS d_int8[] DEFAULT '{1}' ON CONVERSION ERROR); + array +------- + {42} +(1 row) + +SELECT CAST(ARRAY[42]::d_int42arr1 AS d_int8arr DEFAULT '{1}' ON CONVERSION ERROR); + array +------- + {42} +(1 row) + +SELECT CAST(ARRAY[42]::d_int42arr1 AS d_int8arr[] DEFAULT NULL ON CONVERSION ERROR); + array +------- + +(1 row) + +SELECT CAST(ARRAY[42,41] AS d_int42[] DEFAULT '{42, 42}' ON CONVERSION ERROR); + array +--------- + {42,42} +(1 row) + +SELECT CAST(ARRAY[42,41] AS d_int_arr DEFAULT '{41, 43}' ON CONVERSION ERROR); + array +--------- + {41,43} +(1 row) + +-- error. ARRAY[42, 41]::d_int42arr1 is the source expression, so it is not +-- evaluated in an error-safe manner +SELECT CAST(ARRAY[42, 41]::d_int42arr1 AS d_int8arr + DEFAULT '{1,2,3}' ON CONVERSION ERROR); +ERROR: value for domain d_int42 violates check constraint "d_int42_check" +SELECT CAST(ARRAY[42, 41] AS d_int8arr DEFAULT '{1,2,3}' ON CONVERSION ERROR); + array +--------- + {42,41} +(1 row) + +SELECT CAST(ARRAY[42, 41] AS d_int42arr1 DEFAULT '{42, 42}' ON CONVERSION ERROR); + array +--------- + {42,42} +(1 row) + +SELECT CAST(CAST(ARRAY[CAST(ARRAY[42, 42] AS d_int42arr)] AS d_int42arr1[]) + AS d_int8arr[] + DEFAULT NULL ON CONVERSION ERROR); + array +------------- + {"{42,42}"} +(1 row) + +SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION ERROR); -- error +ERROR: value too long for type character varying(3) +LINE 1: SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION... + ^ +SELECT CAST('(NULL)' AS comp2 DEFAULT '(123)' ON CONVERSION ERROR); + comp2 +------- + (123) +(1 row) + +SELECT CAST('(,42)' AS comp_domain_with_typmod DEFAULT NULL ON CONVERSION ERROR); + comp_domain_with_typmod +------------------------- + +(1 row) + +SELECT CAST('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1,2)' ON CONVERSION ERROR); + comp_domain_with_typmod +------------------------- + ("1 ",2) +(1 row) + +SELECT CAST(A AS comp_domain_with_typmod DEFAULT '(1,2)' ON CONVERSION ERROR) +FROM (VALUES('(NULL,42)'::text), ('(,42)')) sub(a); + a +----------- + ("1 ",2) + ("1 ",2) +(2 rows) + +SELECT CAST('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1234,2)' ON CONVERSION ERROR); -- error +ERROR: value too long for type character(3) +LINE 1: ...ST('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1234,2)'... + ^ +SELECT CAST(ROW(NULL,42) AS comp_domain_with_typmod DEFAULT NULL ON CONVERSION ERROR); -- error +ERROR: cannot cast type record to comp_domain_with_typmod with DEFAULT expression in CAST ... ON CONVERSION ERROR +DETAIL: Casts between row types are not supported here. +-- safe cast with geometry data type +SELECT CAST('(1,2)'::point AS box DEFAULT NULL ON CONVERSION ERROR); + box +------------- + (1,2),(1,2) +(1 row) + +SELECT CAST('[(NaN,1),(NaN,infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR); + point +---------------- + (NaN,Infinity) +(1 row) + +SELECT CAST('[(1e+300,Infinity),(1e+300,Infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR); + point +------------------- + (1e+300,Infinity) +(1 row) + +SELECT CAST('[(1,2),(3,4)]'::path as polygon DEFAULT NULL ON CONVERSION ERROR); + polygon +--------- + +(1 row) + +SELECT CAST('(NaN,1.0,NaN,infinity)'::box AS point DEFAULT NULL ON CONVERSION ERROR); + point +---------------- + (NaN,Infinity) +(1 row) + +SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS lseg DEFAULT NULL ON CONVERSION ERROR); + lseg +--------------- + [(2,2),(0,0)] +(1 row) + +SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS polygon DEFAULT NULL ON CONVERSION ERROR); + polygon +--------------------------- + ((0,0),(0,2),(2,2),(2,0)) +(1 row) + +SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS path DEFAULT NULL ON CONVERSION ERROR); + path +------ + +(1 row) + +SELECT CAST('(2.0,infinity,NaN,infinity)'::box AS circle DEFAULT NULL ON CONVERSION ERROR); + circle +---------------------- + <(NaN,Infinity),NaN> +(1 row) + +SELECT CAST('(NaN,0.0),(2.0,4.0),(0.0,infinity)'::polygon AS point DEFAULT NULL ON CONVERSION ERROR); + point +---------------- + (NaN,Infinity) +(1 row) + +SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS path DEFAULT NULL ON CONVERSION ERROR); + path +--------------------- + ((2,0),(2,4),(0,0)) +(1 row) + +SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS box DEFAULT NULL ON CONVERSION ERROR); + box +------------- + (2,4),(0,0) +(1 row) + +SELECT CAST('(NaN,infinity),(2.0,4.0),(0.0,infinity)'::polygon AS circle DEFAULT NULL ON CONVERSION ERROR); + circle +---------------------- + <(NaN,Infinity),NaN> +(1 row) + +SELECT CAST('<(5,1),3>'::circle AS point DEFAULT NULL ON CONVERSION ERROR); + point +------- + (5,1) +(1 row) + +SELECT CAST('<(3,5),0>'::circle as box DEFAULT NULL ON CONVERSION ERROR); + box +------------- + (3,5),(3,5) +(1 row) + +SELECT CAST('<(3,5),0>'::circle as polygon DEFAULT NULL ON CONVERSION ERROR); + polygon +--------- + +(1 row) + +-- safe cast with money data type +-- cast from money to other type is not error safe +SELECT CAST('123456789012345678'::numeric AS MONEY DEFAULT NULL ON CONVERSION ERROR); + money +------- + +(1 row) + +SELECT CAST('123456789012345678'::int8 AS MONEY DEFAULT NULL ON CONVERSION ERROR); + money +------- + +(1 row) + +SELECT CAST('2147483647'::int4 AS MONEY DEFAULT NULL ON CONVERSION ERROR); + money +------------------- + $2,147,483,647.00 +(1 row) + +SELECT CAST('-2147483648'::int4 AS MONEY DEFAULT NULL ON CONVERSION ERROR); + money +-------------------- + -$2,147,483,648.00 +(1 row) + +SELECT CAST('-0'::int4 AS MONEY DEFAULT NULL ON CONVERSION ERROR); + money +------- + $0.00 +(1 row) + +SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSION ERROR); +ERROR: cannot cast type money to numeric with DEFAULT expression in CAST ... ON CONVERSION ERROR +DETAIL: Explicit cast is defined but definition is not error safe. +SELECT CAST(NULL::money[] AS numeric[] DEFAULT NULL ON CONVERSION ERROR); +ERROR: cannot cast type money to numeric with DEFAULT expression in CAST ... ON CONVERSION ERROR +DETAIL: Explicit cast is defined but definition is not error safe. +-- safe cast from boolean type to other data types +SELECT CAST(true AS numeric DEFAULT NULL ON CONVERSION ERROR); + numeric +--------- + +(1 row) + +SELECT CAST(false AS int DEFAULT NULL ON CONVERSION ERROR); + int4 +------ + 0 +(1 row) + +SELECT CAST(false AS text DEFAULT NULL ON CONVERSION ERROR); + text +------- + false +(1 row) + +-- safe cast from bytea type to other data types +SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT 19 ON CONVERSION ERROR); + int8 +------ + 19 +(1 row) + +SELECT CAST('\x123456789A'::bytea AS int4 DEFAULT 20 ON CONVERSION ERROR); + int4 +------ + 20 +(1 row) + +SELECT CAST('\x123456'::bytea AS int2 DEFAULT 21 ON CONVERSION ERROR); + int2 +------ + 21 +(1 row) + +SELECT CAST('\x1234567890abcdef'::bytea AS uuid DEFAULT NULL ON CONVERSION ERROR); + uuid +------ + +(1 row) + +-- safe cast from uuid type to other data types +SELECT CAST('5b35380a-7143-4912-9b55-f322699c6770'::uuid AS bytea DEFAULT NULL ON CONVERSION ERROR); + bytea +------------------------------------ + \x5b35380a714349129b55f322699c6770 +(1 row) + +-- safe cast related to network address data type +SELECT CAST('192.168.1.x' as inet DEFAULT NULL ON CONVERSION ERROR); + inet +------ + +(1 row) + +SELECT CAST('192.168.1.2/30' as cidr DEFAULT NULL ON CONVERSION ERROR); + cidr +------ + +(1 row) + +SELECT CAST('22:00:5c:08:55:08:01:02'::macaddr8 as macaddr DEFAULT NULL ON CONVERSION ERROR); + macaddr +--------- + +(1 row) + +-- safe cast from bit type to other data types +SELECT CAST('111111111100001'::bit(100) AS INT DEFAULT 22 ON CONVERSION ERROR); + int4 +------ + 22 +(1 row) + +SELECT CAST ('111111111100001'::bit(100) AS INT8 DEFAULT 23 ON CONVERSION ERROR); + int8 +------ + 23 +(1 row) + +-- safe cast related to oid data type +SELECT CAST(12345678901::bigint AS oid DEFAULT NULL ON CONVERSION ERROR); + oid +----- + +(1 row) + +SELECT CAST(12345678901::bigint AS regproc DEFAULT NULL ON CONVERSION ERROR); + regproc +--------- + +(1 row) + +SELECT CAST(12345678901::bigint AS regprocedure DEFAULT NULL ON CONVERSION ERROR); + regprocedure +-------------- + +(1 row) + +SELECT CAST(12345678901::bigint AS regclass DEFAULT NULL ON CONVERSION ERROR); + regclass +---------- + +(1 row) + +SELECT CAST(1234567890::bigint AS oid DEFAULT NULL ON CONVERSION ERROR); + oid +------------ + 1234567890 +(1 row) + +SELECT CAST(1234567890::bigint AS regrole DEFAULT NULL ON CONVERSION ERROR); + regrole +------------ + 1234567890 +(1 row) + +SELECT CAST(1234567890::bigint AS regnamespace DEFAULT NULL ON CONVERSION ERROR); + regnamespace +-------------- + 1234567890 +(1 row) + +SELECT CAST(1234567890::bigint AS regdatabase DEFAULT NULL ON CONVERSION ERROR); + regdatabase +------------- + 1234567890 +(1 row) + +-- safe cast from text type to other data types +SELECT CAST('pg_temp_1.foo'::text AS regclass DEFAULT NULL ON CONVERSION ERROR); + regclass +---------- + +(1 row) + +SELECT CAST('a.b.c.d'::text as regclass DEFAULT NULL ON CONVERSION ERROR); + regclass +---------- + +(1 row) + +CREATE TABLE test_safecast0(col0 text, col1 jsonb); +INSERT INTO test_safecast0 VALUES ('oneoneone1, max=>1) ON CONVERSION ERROR))); -- error +ERROR: functions in index expression must be marked IMMUTABLE +LINE 1: CREATE INDEX cast_error_idx ON test_safecast0((CAST(col1 as ... + ^ +CREATE INDEX cast_error_idx ON test_safecast0((CAST(col1 as xid DEFAULT NULL ON CONVERSION ERROR))); -- error +ERROR: data type xid has no default operator class for access method "btree" +HINT: You must specify an operator class for the index or define a default operator class for the data type. +CREATE INDEX cast_error_idx ON test_safecast0((CAST(col1 as int DEFAULT NULL ON CONVERSION ERROR))); -- ok +CREATE INDEX cast_error_idx ON test_safecast0((CAST(col1 as int DEFAULT (SELECT NULL) ON CONVERSION ERROR))); -- error +ERROR: cannot use subquery in index expression +LINE 1: ...r_idx ON test_safecast0((CAST(col1 as int DEFAULT (SELECT NU... + ^ +SELECT pg_get_indexdef('cast_error_idx'::regclass); + pg_get_indexdef +-------------------------------------------------------------------------------------------------------------------------------------- + CREATE INDEX cast_error_idx ON public.test_safecast0 USING btree ((CAST(col1 AS integer DEFAULT NULL::integer ON CONVERSION ERROR))) +(1 row) + +-- safe cast betweeen range data types +SELECT CAST('[1,2]'::int4range AS int4multirange DEFAULT NULL ON CONVERSION ERROR); + int4multirange +---------------- + {[1,3)} +(1 row) + +SELECT CAST('[1,2]'::int8range AS int8multirange DEFAULT NULL ON CONVERSION ERROR); + int8multirange +---------------- + {[1,3)} +(1 row) + +SELECT CAST('[1,2]'::numrange AS nummultirange DEFAULT NULL ON CONVERSION ERROR); + nummultirange +--------------- + {[1,2]} +(1 row) + +SELECT CAST('[-infinity,infinity]'::daterange AS datemultirange DEFAULT NULL ON CONVERSION ERROR); + datemultirange +------------------------ + {[-infinity,infinity]} +(1 row) + +SELECT CAST('[-infinity,infinity]'::tsrange AS tsmultirange DEFAULT NULL ON CONVERSION ERROR); + tsmultirange +------------------------ + {[-infinity,infinity]} +(1 row) + +SELECT CAST('[-infinity,infinity]'::tstzrange AS tstzmultirange DEFAULT NULL ON CONVERSION ERROR); + tstzmultirange +------------------------ + {[-infinity,infinity]} +(1 row) + +-- safe cast betweeen numeric data types +CREATE TABLE test_safecast1( + col1 float4, col2 float8, col3 numeric, col4 numeric[], + col5 int2 default 32767, + col6 int4 default 32768, + col7 int8 default 4294967296); +INSERT INTO test_safecast1 VALUES('11.1234', '11.1234', '9223372036854775808', '{11.1234}'::numeric[]); +INSERT INTO test_safecast1 VALUES('inf', 'inf', 'inf', '{11.1234, 12, inf, NaN}'::numeric[]); +INSERT INTO test_safecast1 VALUES('-inf', '-inf', '-inf', '{11.1234, 12, -inf, NaN}'::numeric[]); +INSERT INTO test_safecast1 VALUES('NaN', 'NaN', 'NaN', '{11.1234, 12, -inf, NaN}'::numeric[]); +SELECT col5 as int2, + CAST(col5 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2, + CAST(col5 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4, + CAST(col5 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid, + CAST(col5 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8, + CAST(col5 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4, + CAST(col5 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8, + CAST(col5 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric, + CAST(col5 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale +FROM test_safecast1; + int2 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale +-------+---------+---------+--------+---------+-----------+-----------+------------+------------------ + 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0 + 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0 + 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0 + 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0 +(4 rows) + +SELECT col6 as int4, + CAST(col6 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2, + CAST(col6 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4, + CAST(col6 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid, + CAST(col6 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8, + CAST(col6 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4, + CAST(col6 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8, + CAST(col6 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric, + CAST(col6 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale +FROM test_safecast1; + int4 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale +-------+---------+---------+--------+---------+-----------+-----------+------------+------------------ + 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0 + 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0 + 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0 + 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0 +(4 rows) + +SELECT col7 as int8, + CAST(col7 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2, + CAST(col7 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4, + CAST(col7 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid, + CAST(col7 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8, + CAST(col7 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4, + CAST(col7 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8, + CAST(col7 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric, + CAST(col7 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale +FROM test_safecast1; + int8 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale +------------+---------+---------+--------+------------+-------------+------------+------------+------------------ + 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 | + 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 | + 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 | + 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 | +(4 rows) + +SELECT col3 as numeric, + CAST(col3 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2, + CAST(col3 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4, + CAST(col3 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid, + CAST(col3 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8, + CAST(col3 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4, + CAST(col3 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8, + CAST(col3 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric, + CAST(col3 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale +FROM test_safecast1; + numeric | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale +---------------------+---------+---------+--------+---------+-------------+----------------------+---------------------+------------------ + 9223372036854775808 | | | | | 9.22337e+18 | 9.22337203685478e+18 | 9223372036854775808 | + Infinity | | | | | Infinity | Infinity | Infinity | + -Infinity | | | | | -Infinity | -Infinity | -Infinity | + NaN | | | | | NaN | NaN | NaN | NaN +(4 rows) + +SELECT col4 as num_arr, + CAST(col4 AS int2[] DEFAULT NULL ON CONVERSION ERROR) as int2arr, + CAST(col4 AS int4[] DEFAULT NULL ON CONVERSION ERROR) as int4arr, + CAST(col4 as int8[] DEFAULT NULL ON CONVERSION ERROR) as int8arr, + CAST(col4 as float4[] DEFAULT NULL ON CONVERSION ERROR) as f4arr, + CAST(col4 as float8[] DEFAULT NULL ON CONVERSION ERROR) as f8arr, + CAST(col4 as numeric(10,1)[] DEFAULT NULL ON CONVERSION ERROR) as numarr +FROM test_safecast1; + num_arr | int2arr | int4arr | int8arr | f4arr | f8arr | numarr +----------------------------+---------+---------+---------+----------------------------+----------------------------+-------- + {11.1234} | {11} | {11} | {11} | {11.1234} | {11.1234} | {11.1} + {11.1234,12,Infinity,NaN} | | | | {11.1234,12,Infinity,NaN} | {11.1234,12,Infinity,NaN} | + {11.1234,12,-Infinity,NaN} | | | | {11.1234,12,-Infinity,NaN} | {11.1234,12,-Infinity,NaN} | + {11.1234,12,-Infinity,NaN} | | | | {11.1234,12,-Infinity,NaN} | {11.1234,12,-Infinity,NaN} | +(4 rows) + +SELECT col1 as float4, + CAST(col1 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2, + CAST(col1 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4, + CAST(col1 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid, + CAST(col1 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8, + CAST(col1 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4, + CAST(col1 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8, + CAST(col1 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric, + CAST(col1 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale +FROM test_safecast1; + float4 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale +-----------+---------+---------+--------+---------+-----------+------------------+------------+------------------ + 11.1234 | 11 | 11 | | 11 | 11.1234 | 11.1233997344971 | 11.1234 | 11.1 + Infinity | | | | | Infinity | Infinity | Infinity | + -Infinity | | | | | -Infinity | -Infinity | -Infinity | + NaN | | | | | NaN | NaN | NaN | NaN +(4 rows) + +SELECT col2 as float8, + CAST(col2 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2, + CAST(col2 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4, + CAST(col2 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid, + CAST(col2 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8, + CAST(col2 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4, + CAST(col2 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8, + CAST(col2 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric, + CAST(col2 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale +FROM test_safecast1; + float8 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale +-----------+---------+---------+--------+---------+-----------+-----------+------------+------------------ + 11.1234 | 11 | 11 | | 11 | 11.1234 | 11.1234 | 11.1234 | 11.1 + Infinity | | | | | Infinity | Infinity | Infinity | + -Infinity | | | | | -Infinity | -Infinity | -Infinity | + NaN | | | | | NaN | NaN | NaN | NaN +(4 rows) + +-- safe type cast for type date/timestamp/timestamptz/interval +CREATE TABLE test_safecast2( + col0 date, col1 timestamp, col2 timestamptz, + col3 interval, col4 time, col5 timetz); +INSERT INTO test_safecast2 VALUES +('-infinity', '-infinity', '-infinity', '-infinity', + '2003-03-07 15:36:39 America/New_York', '2003-07-07 15:36:39 America/New_York'), +('-infinity', 'infinity', 'infinity', 'infinity', '11:59:59.99 PM', '11:59:59.99 PM PDT'); +SELECT col0 as date, + CAST(col0 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz, + CAST(col0 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date, + CAST(col0 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times, + CAST(col0 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz, + CAST(col0 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale +FROM test_safecast2; + date | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale +-----------+----------------+-----------+----------+-----------+-------------------- + -infinity | -infinity | -infinity | | | -infinity + -infinity | -infinity | -infinity | | | -infinity +(2 rows) + +SELECT col1 as timestamp, + CAST(col1 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz, + CAST(col1 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date, + CAST(col1 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times, + CAST(col1 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz, + CAST(col1 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale +FROM test_safecast2; + timestamp | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale +-----------+----------------+-----------+----------+-----------+-------------------- + -infinity | -infinity | -infinity | | | -infinity + infinity | infinity | infinity | | | infinity +(2 rows) + +SELECT col2 as timestamptz, + CAST(col2 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz, + CAST(col2 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date, + CAST(col2 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times, + CAST(col2 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz, + CAST(col2 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale +FROM test_safecast2; + timestamptz | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale +-------------+----------------+-----------+----------+-----------+-------------------- + -infinity | -infinity | -infinity | | | -infinity + infinity | infinity | infinity | | | infinity +(2 rows) + +SELECT col3 as interval, + CAST(col3 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz, + CAST(col3 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date, + CAST(col3 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times, + CAST(col3 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz, + CAST(col3 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale, + CAST(col3 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale +FROM test_safecast2; + interval | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale | to_interval_scale +-----------+----------------+---------+----------+-----------+--------------------+------------------- + -infinity | | | | | | -infinity + infinity | | | | | | infinity +(2 rows) + +SELECT col4 as time, + CAST(col4 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_times, + CAST(col4 AS timetz DEFAULT NULL ON CONVERSION ERROR) IS NOT NULL as to_timetz, + CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval, + CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale +FROM test_safecast2; + time | to_times | to_timetz | to_interval | to_interval_scale +-------------+-------------+-----------+-------------------------------+------------------------------- + 15:36:39 | 15:36:39 | t | @ 15 hours 36 mins 39 secs | @ 15 hours 36 mins 39 secs + 23:59:59.99 | 23:59:59.99 | t | @ 23 hours 59 mins 59.99 secs | @ 23 hours 59 mins 59.99 secs +(2 rows) + +SELECT col5 as timetz, + CAST(col5 AS time DEFAULT NULL ON CONVERSION ERROR) as to_time, + CAST(col5 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_time_scale, + CAST(col5 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz, + CAST(col5 AS timetz(6) DEFAULT NULL ON CONVERSION ERROR) as to_timetz_scale, + CAST(col5 AS interval DEFAULT NULL ON CONVERSION ERROR) as to_interval, + CAST(col5 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale +FROM test_safecast2; + timetz | to_time | to_time_scale | to_timetz | to_timetz_scale | to_interval | to_interval_scale +----------------+-------------+---------------+----------------+-----------------+-------------+------------------- + 15:36:39-04 | 15:36:39 | 15:36:39 | 15:36:39-04 | 15:36:39-04 | | + 23:59:59.99-07 | 23:59:59.99 | 23:59:59.99 | 23:59:59.99-07 | 23:59:59.99-07 | | +(2 rows) + +-- test deparse +SET datestyle TO ISO, YMD; +CREATE VIEW safecastview AS +SELECT + CAST('2022-01-01' COLLATE "C" COLLATE "POSIX" as DATE DEFAULT NULL ON CONVERSION ERROR) as cast, + CAST('1234' as char(3) DEFAULT -1111 ON CONVERSION ERROR) as cast0, + CAST(1 as date DEFAULT (('2025-Dec-06'::date + random(min=>1, max=>1::int))) ON CONVERSION ERROR) as cast1, + CAST(ARRAY[['1'], ['three'],['a']] AS int[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast2, + CAST(ARRAY[['1', '2'], ['three', 'a']] AS date[] DEFAULT NULL ON CONVERSION ERROR) as cast3, + CAST(ARRAY['three'] AS INT[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast4, + CAST(CAST(1 AS int DEFAULT 0 ON CONVERSION ERROR) + 70000::int4 AS int DEFAULT -1 ON CONVERSION ERROR) as cast5; +\sv safecastview +CREATE OR REPLACE VIEW public.safecastview AS + SELECT CAST('2022-01-01'::date AS date DEFAULT NULL::date ON CONVERSION ERROR) AS "cast", + CAST('1234'::bpchar AS character(3) DEFAULT '-1111'::integer::character(3) ON CONVERSION ERROR) AS cast0, + CAST(1 AS date DEFAULT '2025-12-06'::date + random(min => 1, max => 1) ON CONVERSION ERROR) AS cast1, + CAST(ARRAY[ARRAY[1], ARRAY['three'::text], ARRAY['a'::text]] AS integer[] DEFAULT '{1,2}'::integer[] ON CONVERSION ERROR) AS cast2, + CAST(ARRAY[ARRAY['1'::text, '2'::text], ARRAY['three'::text, 'a'::text]] AS date[] DEFAULT NULL::date[] ON CONVERSION ERROR) AS cast3, + CAST(ARRAY['three'::text] AS integer[] DEFAULT '{1,2}'::integer[] ON CONVERSION ERROR) AS cast4, + CAST((CAST(1 AS integer DEFAULT 0 ON CONVERSION ERROR)) + 70000 AS integer DEFAULT '-1'::integer ON CONVERSION ERROR) AS cast5 +SELECT * FROM safecastview; + cast | cast0 | cast1 | cast2 | cast3 | cast4 | cast5 +------------+-------+------------+-------+-------+-------+------- + 2022-01-01 | 123 | 2025-12-07 | {1,2} | | {1,2} | 70001 +(1 row) + +CREATE VIEW safecastview1 AS +SELECT CAST(ARRAY[['1'], ['three'],['a']] AS d_int_arr + DEFAULT '{41,43}' ON CONVERSION ERROR) as cast1, + CAST(ARRAY[['1', '2', 1.1], ['three', true, B'01']] AS d_int_arr + DEFAULT '{41,43}' ON CONVERSION ERROR) as cast2, + CAST('a'::text AS int DEFAULT 3 ON CONVERSION ERROR) AS cast3; +\sv safecastview1 +CREATE OR REPLACE VIEW public.safecastview1 AS + SELECT CAST(ARRAY[ARRAY[1], ARRAY['three'::text], ARRAY['a'::text]] AS d_int_arr DEFAULT '{41,43}'::integer[]::d_int_arr ON CONVERSION ERROR) AS cast1, + CAST(ARRAY[ARRAY[1, 2, 1.1], ARRAY['three'::text, true, '01'::"bit"]] AS d_int_arr DEFAULT '{41,43}'::integer[]::d_int_arr ON CONVERSION ERROR) AS cast2, + CAST('a'::text AS integer DEFAULT 3 ON CONVERSION ERROR) AS cast3 +SELECT * FROM safecastview1; + cast1 | cast2 | cast3 +---------+---------+------- + {41,43} | {41,43} | 3 +(1 row) + +CREATE VIEW safecastview2 AS +SELECT CAST(ARRAY[b] AS int[] DEFAULT NULL ON CONVERSION ERROR) FROM (values ('1'), ('a'::text)) t(b); +\sv safecastview2 +CREATE OR REPLACE VIEW public.safecastview2 AS + SELECT CAST(ARRAY[b] AS integer[] DEFAULT NULL::integer[] ON CONVERSION ERROR) AS "array" + FROM ( VALUES ('1'::text), ('a'::text)) t(b) +SELECT * FROM safecastview2; + array +------- + {1} + +(2 rows) + +RESET datestyle; +DROP VIEW safecastview; +DROP VIEW safecastview1; +DROP TABLE test_safecast0; +DROP TABLE test_safecast1; +DROP TABLE test_safecast2; +DROP TABLE tcast1; +DROP FUNCTION ret_int8; +DROP TYPE comp_domain_with_typmod; +DROP TYPE comp2; +DROP DOMAIN d_nota; +DROP DOMAIN d_varchar; +DROP DOMAIN d_int8arr; +DROP DOMAIN d_int8; +DROP DOMAIN d_int42arr1; +DROP DOMAIN d_int42arr; +DROP DOMAIN d_int42; +DROP DOMAIN d_char3_not_null; +DROP DOMAIN d_numeric; +DROP DOMAIN dint; +DROP DOMAIN dintnn; +RESET extra_float_digits; +RESET lc_monetary; diff --git a/src/test/regress/expected/create_cast.out b/src/test/regress/expected/create_cast.out index 0e69644bca2..c9e26912caf 100644 --- a/src/test/regress/expected/create_cast.out +++ b/src/test/regress/expected/create_cast.out @@ -88,6 +88,16 @@ SELECT 1234::int4::casttesttype; -- Should work now bar1234 (1 row) +SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- error +ERROR: cannot cast type integer to casttesttype with DEFAULT expression in CAST ... ON CONVERSION ERROR +DETAIL: Explicit cast is defined but definition is not error safe. +ALTER FUNCTION bar_int4_text(int4) ERROR SAFE; +SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- ok + casttesttype +-------------- + bar1234 +(1 row) + -- check dependencies generated for that SELECT pg_describe_object(classid, objid, objsubid) as obj, pg_describe_object(refclassid, refobjid, refobjsubid) as objref, diff --git a/src/test/regress/expected/equivclass.out b/src/test/regress/expected/equivclass.out index ad8ab294ff6..3af737c220a 100644 --- a/src/test/regress/expected/equivclass.out +++ b/src/test/regress/expected/equivclass.out @@ -95,6 +95,25 @@ create function int8alias1cmp(int8, int8alias1) returns int strict immutable language internal as 'btint8cmp'; alter operator family integer_ops using btree add function 1 int8alias1cmp (int8, int8alias1); +-- int8alias2 binary-coercible to int8, this should not fail +select cast('1'::int8 as int8alias2 default null on conversion error); + int8alias2 +------------ + 1 +(1 row) + +select cast('1'::int8alias2 as int8 default null on conversion error); + int8 +------ + 1 +(1 row) + +select cast('a' as int8alias2 default null on conversion error); + int8alias2 +------------ + +(1 row) + create table ec0 (ff int8 primary key, f1 int8, f2 int8); create table ec1 (ff int8 primary key, f1 int8alias1, f2 int8alias2); create table ec2 (xf int8 primary key, x1 int8alias1, x2 int8alias2); diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out index 6998745ee03..2855f88317f 100644 --- a/src/test/regress/expected/xml.out +++ b/src/test/regress/expected/xml.out @@ -24,6 +24,12 @@ SELECT * FROM xmltest; 2 | two (2 rows) +SELECT CAST('one', 'xml'); pg_input_is_valid diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 75063f87a4a..f37516c1af7 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..35c3bddd325 --- /dev/null +++ b/src/test/regress/sql/cast.sql @@ -0,0 +1,461 @@ +SET extra_float_digits = 0; +SET lc_monetary TO "C"; + +-- CAST DEFAULT ON CONVERSION ERROR +VALUES (CAST('error' AS integer ERROR ON CONVERSION ERROR)); -- error + +-- The DEFAULT expression can contain aggreagte/window function or subquery +SELECT CAST('a' as int DEFAULT sum(1) ON CONVERSION ERROR); +SELECT CAST('a' as int DEFAULT sum(1) over() ON CONVERSION ERROR); +SELECT CAST('a' as int DEFAULT (SELECT NULL) ON CONVERSION ERROR); +SELECT CAST('a' as int DEFAULT (SELECT 1 where false) ON CONVERSION ERROR); + +-- The DEFAULT expression cannot contain set expression +SELECT CAST('a' as int DEFAULT generate_series(1, 1) ON CONVERSION ERROR); + +VALUES (CAST('error' AS text DEFAULT '1' COLLATE "C" ON CONVERSION ERROR)); +SELECT CAST('a' as int DEFAULT 'b' ON CONVERSION ERROR); +SELECT CAST(1 AS int DEFAULT 70000::int2::int4 ON CONVERSION ERROR); +SELECT CAST('70000' AS int2 DEFAULT -1 ON CONVERSION ERROR); +SELECT CAST(B'01' AS date DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST(BIT'01' AS date DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST(TRUE AS date DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST(1.1 AS date DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST(1 AS date DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST(1111 AS "char" DEFAULT 'A' ON CONVERSION ERROR); +SELECT CAST('def'::text AS integer DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('2'::jsonb AS text DEFAULT 'fallback' ON CONVERSION ERROR); +SELECT CAST('1' COLLATE "C" || 'h' AS text DEFAULT 'fallback' ON CONVERSION ERROR); +SELECT CAST(concat('1' collate "C", 'h') AS int DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST(65536 AS int2 DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST(int8(65536) AS int2 DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST(12.111 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale; +SELECT CAST('\x01'::bytea as uuid DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST(CAST('{2020-01-01}'::text[] AS date[]) AS int[] DEFAULT '{7}' ON CONVERSION ERROR); +SELECT (CAST('error' AS int2vector DEFAULT '1 3' ON CONVERSION ERROR)); +SELECT (CAST('error' AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR)); +SELECT CAST ('b' AS integer + DEFAULT CAST('c' AS INTEGER DEFAULT 0 ON CONVERSION ERROR) + ON CONVERSION ERROR); + +-- source expression contain subquery +SELECT CAST((SELECT b FROM generate_series(1, 1), (VALUES('H')) s(b)) + AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR); +SELECT CAST((SELECT ARRAY((SELECT b FROM generate_series(1, 2) g, (VALUES('H')) s(b)))) + AS INT[] + DEFAULT NULL ON CONVERSION ERROR); + +CREATE FUNCTION ret_int8() RETURNS BIGINT AS $$ BEGIN RETURN 2147483648; END; $$ LANGUAGE plpgsql IMMUTABLE; +SELECT CAST('a' as int DEFAULT ret_int8() ON CONVERSION ERROR); -- error +SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERROR); -- error + +-- Test JIT: run the same cast with JIT forced on, then interpreted +SET jit_above_cost = 0; +SELECT CAST(a AS int2 DEFAULT NULL ON CONVERSION ERROR) FROM (VALUES (1), (65536)) t(a); +RESET jit_above_cost; +SELECT CAST(a AS int2 DEFAULT NULL ON CONVERSION ERROR) FROM (VALUES (1), (65536)) t(a); + +CREATE TABLE tcast1(a int DEFAULT CAST('x' AS int DEFAULT a ON CONVERSION ERROR)); -- error +CREATE DOMAIN d_nota AS text CHECK (VALUE <> 'a'); +CREATE TABLE tcast1(a text, b text[], c int, d int8, e int8[]); +ALTER TABLE tcast1 ADD COLUMN f int + DEFAULT CAST('x' AS int DEFAULT a ON CONVERSION ERROR); -- error +INSERT INTO tcast1(a, b, d, e) values('a', '{a}', 2147483648, '{2147483648}'); + +-- domain constraints must not be checked once the conversion to the base type +-- has failed softly +CREATE DOMAIN dint AS int CHECK ((1 / coalesce(VALUE, 0)) IS NOT NULL); +CREATE DOMAIN dintnn AS int NOT NULL CHECK ((1 / coalesce(VALUE, 0)) IS NOT NULL); +SELECT CAST(concat('a') AS dint DEFAULT 2 ON CONVERSION ERROR); +SELECT CAST(nullif(concat('a'), 'a') AS dintnn DEFAULT 2 ON CONVERSION ERROR); +SELECT CAST(ARRAY[concat('a')] AS dint[] DEFAULT '{2}' ON CONVERSION ERROR); +SELECT CAST(concat('{a}')::text[] AS dint[] DEFAULT '{2}' ON CONVERSION ERROR); +SELECT CAST(concat('0') AS dint DEFAULT 2 ON CONVERSION ERROR); + +-- Only the conversion itself is evaluated in an error-safe manner. The source +-- expression is evaluated normally, so errors raised inside it are not caught. + +-- none of the following should be accepted +SELECT CAST (CAST(a AS int) AS text DEFAULT NULL ON CONVERSION ERROR) FROM tcast1; +SELECT CAST (CAST(a AS d_nota) AS text DEFAULT NULL ON CONVERSION ERROR) FROM tcast1; +SELECT CAST (CAST(b AS d_nota[]) AS text[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1; +SELECT CAST (CAST(b AS int[]) AS text[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1; +SELECT CAST (CAST(d AS int) AS text DEFAULT NULL ON CONVERSION ERROR) FROM tcast1; +SELECT CAST(ARRAY[a::int, concat('x')] AS int[] DEFAULT '{0}' ON CONVERSION ERROR) FROM tcast1; +SELECT CAST(ARRAY['x', a::int] AS text[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1; +SELECT CAST(ARRAY[a::int] AS text[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1; +SELECT CAST(ARRAY['x', a::int] AS text DEFAULT '{0}' ON CONVERSION ERROR) FROM tcast1; +SELECT CAST(ARRAY['x', a::int] AS date DEFAULT NULL ON CONVERSION ERROR) FROM tcast1; +SELECT CAST (CAST('{a}'::text[] AS int[]) AS text[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1; +SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); +SELECT CAST('70000'::int2 AS int2 DEFAULT -1 ON CONVERSION ERROR); +SELECT CAST(1 + '70000'::int2::int4 AS int DEFAULT -1 ON CONVERSION ERROR); +SELECT CAST('\x01'::bytea::uuid AS text DEFAULT 'x1' ON CONVERSION ERROR); +SELECT CAST(ARRAY[int4pl(1, 'x')] AS int[] DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST(ARRAY[1 + 'x'] AS int[] DEFAULT NULL ON CONVERSION ERROR); +-- none of the above should be accepted + +-- Test ARRAY constructor and target type is ARRAY type +SELECT CAST(ARRAY[e] as int[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1; + +-- all of the following should be accepted. For an ARRAY[] argument, each +-- element is converted to the target element type +SELECT CAST(ARRAY['x', a::int] AS int[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1; +SELECT CAST(ARRAY[1,2] || 3 AS boolean DEFAULT false ON CONVERSION ERROR); +SELECT CAST(ARRAY[] AS int[] DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST(ARRAY[['abc'],[456]] AS int[] DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST(ARRAY[1, 'x'] AS point[] DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST(ARRAY[11.12] AS date[] DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST(ARRAY[11111111111111111] AS int[] DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST(ARRAY['a'::text] AS int[] DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST(ARRAY[['1'], ['three'],['a']] AS int[] DEFAULT '{1,2}' ON CONVERSION ERROR); +SELECT CAST(ARRAY[['1', '2'], ['three', 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); +SELECT CAST(e as int4[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1; +SELECT CAST('{123,abc,def}' AS int[] DEFAULT '{-789}' ON CONVERSION ERROR); +SELECT CAST('{234,def,567}'::text[] AS integer[] DEFAULT '{-1011}' ON CONVERSION ERROR); +-- all of the above should be accepted + +-- test with user-defined type, domain, ARRAY over domain, domain over ARRAY +CREATE DOMAIN d_int42 as int check (value = 42) NOT NULL; +CREATE DOMAIN d_int42arr as d_int42[]; +CREATE DOMAIN d_int42arr1 as d_int42arr; +CREATE DOMAIN d_int8 as int8 check (value > 0); +CREATE DOMAIN d_int8arr as d_int8[]; +CREATE DOMAIN d_char3_not_null as char(3) NOT NULL; +CREATE DOMAIN d_varchar as varchar(3) NOT NULL; +CREATE DOMAIN d_int_arr as int[] check (value = '{41, 43}') NOT NULL; +CREATE TYPE comp_domain_with_typmod AS (a d_char3_not_null, b int); +CREATE TYPE comp2 AS (a d_varchar); +CREATE DOMAIN d_numeric as numeric(4,4); + +SELECT CAST(1.0::float4 AS d_numeric DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('{1.0}' AS numeric(4,1)[] DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('{0.1, 1.0}' AS d_numeric[] DEFAULT NULL ON CONVERSION ERROR); + +SELECT CAST(11 AS d_int42 DEFAULT 41 ON CONVERSION ERROR); -- error +SELECT CAST(NULL AS d_int42 DEFAULT NULL ON CONVERSION ERROR); -- error +SELECT CAST(11 AS d_int42 DEFAULT 42 ON CONVERSION ERROR); +SELECT CAST(NULL AS d_int42 DEFAULT 42 ON CONVERSION ERROR); + +SELECT CAST(ARRAY[42]::d_int42arr1 AS d_int8 DEFAULT '1' ON CONVERSION ERROR); +SELECT CAST(ARRAY[42]::d_int42arr1 AS d_int8[] DEFAULT '{1}' ON CONVERSION ERROR); +SELECT CAST(ARRAY[42]::d_int42arr1 AS d_int8arr DEFAULT '{1}' ON CONVERSION ERROR); +SELECT CAST(ARRAY[42]::d_int42arr1 AS d_int8arr[] DEFAULT NULL ON CONVERSION ERROR); + +SELECT CAST(ARRAY[42,41] AS d_int42[] DEFAULT '{42, 42}' ON CONVERSION ERROR); +SELECT CAST(ARRAY[42,41] AS d_int_arr DEFAULT '{41, 43}' ON CONVERSION ERROR); +-- error. ARRAY[42, 41]::d_int42arr1 is the source expression, so it is not +-- evaluated in an error-safe manner +SELECT CAST(ARRAY[42, 41]::d_int42arr1 AS d_int8arr + DEFAULT '{1,2,3}' ON CONVERSION ERROR); +SELECT CAST(ARRAY[42, 41] AS d_int8arr DEFAULT '{1,2,3}' ON CONVERSION ERROR); +SELECT CAST(ARRAY[42, 41] AS d_int42arr1 DEFAULT '{42, 42}' ON CONVERSION ERROR); +SELECT CAST(CAST(ARRAY[CAST(ARRAY[42, 42] AS d_int42arr)] AS d_int42arr1[]) + AS d_int8arr[] + DEFAULT NULL ON CONVERSION ERROR); + +SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION ERROR); -- error +SELECT CAST('(NULL)' AS comp2 DEFAULT '(123)' ON CONVERSION ERROR); +SELECT CAST('(,42)' AS comp_domain_with_typmod DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1,2)' ON CONVERSION ERROR); +SELECT CAST(A AS comp_domain_with_typmod DEFAULT '(1,2)' ON CONVERSION ERROR) +FROM (VALUES('(NULL,42)'::text), ('(,42)')) sub(a); +SELECT CAST('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1234,2)' ON CONVERSION ERROR); -- error +SELECT CAST(ROW(NULL,42) AS comp_domain_with_typmod DEFAULT NULL ON CONVERSION ERROR); -- error + +-- safe cast with geometry data type +SELECT CAST('(1,2)'::point AS box DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('[(NaN,1),(NaN,infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('[(1e+300,Infinity),(1e+300,Infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('[(1,2),(3,4)]'::path as polygon DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('(NaN,1.0,NaN,infinity)'::box AS point DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS lseg DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS polygon DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS path DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('(2.0,infinity,NaN,infinity)'::box AS circle DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('(NaN,0.0),(2.0,4.0),(0.0,infinity)'::polygon AS point DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS path DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS box DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('(NaN,infinity),(2.0,4.0),(0.0,infinity)'::polygon AS circle DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('<(5,1),3>'::circle AS point DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('<(3,5),0>'::circle as box DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('<(3,5),0>'::circle as polygon DEFAULT NULL ON CONVERSION ERROR); + +-- safe cast with money data type +-- cast from money to other type is not error safe +SELECT CAST('123456789012345678'::numeric AS MONEY DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('123456789012345678'::int8 AS MONEY DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('2147483647'::int4 AS MONEY DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('-2147483648'::int4 AS MONEY DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('-0'::int4 AS MONEY DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST(NULL::money[] AS numeric[] DEFAULT NULL ON CONVERSION ERROR); + +-- safe cast from boolean type to other data types +SELECT CAST(true AS numeric DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST(false AS int DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST(false AS text DEFAULT NULL ON CONVERSION ERROR); + +-- safe cast from bytea type to other data types +SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT 19 ON CONVERSION ERROR); +SELECT CAST('\x123456789A'::bytea AS int4 DEFAULT 20 ON CONVERSION ERROR); +SELECT CAST('\x123456'::bytea AS int2 DEFAULT 21 ON CONVERSION ERROR); +SELECT CAST('\x1234567890abcdef'::bytea AS uuid DEFAULT NULL ON CONVERSION ERROR); + +-- safe cast from uuid type to other data types +SELECT CAST('5b35380a-7143-4912-9b55-f322699c6770'::uuid AS bytea DEFAULT NULL ON CONVERSION ERROR); + +-- safe cast related to network address data type +SELECT CAST('192.168.1.x' as inet DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('192.168.1.2/30' as cidr DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('22:00:5c:08:55:08:01:02'::macaddr8 as macaddr DEFAULT NULL ON CONVERSION ERROR); + +-- safe cast from bit type to other data types +SELECT CAST('111111111100001'::bit(100) AS INT DEFAULT 22 ON CONVERSION ERROR); +SELECT CAST ('111111111100001'::bit(100) AS INT8 DEFAULT 23 ON CONVERSION ERROR); + +-- safe cast related to oid data type +SELECT CAST(12345678901::bigint AS oid DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST(12345678901::bigint AS regproc DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST(12345678901::bigint AS regprocedure DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST(12345678901::bigint AS regclass DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST(1234567890::bigint AS oid DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST(1234567890::bigint AS regrole DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST(1234567890::bigint AS regnamespace DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST(1234567890::bigint AS regdatabase DEFAULT NULL ON CONVERSION ERROR); + +-- safe cast from text type to other data types +SELECT CAST('pg_temp_1.foo'::text AS regclass DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('a.b.c.d'::text as regclass DEFAULT NULL ON CONVERSION ERROR); +CREATE TABLE test_safecast0(col0 text, col1 jsonb); +INSERT INTO test_safecast0 VALUES ('one1, max=>1) ON CONVERSION ERROR))); -- error +CREATE INDEX cast_error_idx ON test_safecast0((CAST(col1 as xid DEFAULT NULL ON CONVERSION ERROR))); -- error +CREATE INDEX cast_error_idx ON test_safecast0((CAST(col1 as int DEFAULT NULL ON CONVERSION ERROR))); -- ok +CREATE INDEX cast_error_idx ON test_safecast0((CAST(col1 as int DEFAULT (SELECT NULL) ON CONVERSION ERROR))); -- error +SELECT pg_get_indexdef('cast_error_idx'::regclass); + +-- safe cast betweeen range data types +SELECT CAST('[1,2]'::int4range AS int4multirange DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('[1,2]'::int8range AS int8multirange DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('[1,2]'::numrange AS nummultirange DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('[-infinity,infinity]'::daterange AS datemultirange DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('[-infinity,infinity]'::tsrange AS tsmultirange DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST('[-infinity,infinity]'::tstzrange AS tstzmultirange DEFAULT NULL ON CONVERSION ERROR); + +-- safe cast betweeen numeric data types +CREATE TABLE test_safecast1( + col1 float4, col2 float8, col3 numeric, col4 numeric[], + col5 int2 default 32767, + col6 int4 default 32768, + col7 int8 default 4294967296); +INSERT INTO test_safecast1 VALUES('11.1234', '11.1234', '9223372036854775808', '{11.1234}'::numeric[]); +INSERT INTO test_safecast1 VALUES('inf', 'inf', 'inf', '{11.1234, 12, inf, NaN}'::numeric[]); +INSERT INTO test_safecast1 VALUES('-inf', '-inf', '-inf', '{11.1234, 12, -inf, NaN}'::numeric[]); +INSERT INTO test_safecast1 VALUES('NaN', 'NaN', 'NaN', '{11.1234, 12, -inf, NaN}'::numeric[]); + +SELECT col5 as int2, + CAST(col5 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2, + CAST(col5 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4, + CAST(col5 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid, + CAST(col5 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8, + CAST(col5 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4, + CAST(col5 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8, + CAST(col5 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric, + CAST(col5 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale +FROM test_safecast1; + +SELECT col6 as int4, + CAST(col6 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2, + CAST(col6 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4, + CAST(col6 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid, + CAST(col6 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8, + CAST(col6 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4, + CAST(col6 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8, + CAST(col6 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric, + CAST(col6 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale +FROM test_safecast1; + +SELECT col7 as int8, + CAST(col7 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2, + CAST(col7 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4, + CAST(col7 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid, + CAST(col7 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8, + CAST(col7 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4, + CAST(col7 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8, + CAST(col7 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric, + CAST(col7 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale +FROM test_safecast1; + +SELECT col3 as numeric, + CAST(col3 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2, + CAST(col3 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4, + CAST(col3 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid, + CAST(col3 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8, + CAST(col3 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4, + CAST(col3 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8, + CAST(col3 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric, + CAST(col3 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale +FROM test_safecast1; + +SELECT col4 as num_arr, + CAST(col4 AS int2[] DEFAULT NULL ON CONVERSION ERROR) as int2arr, + CAST(col4 AS int4[] DEFAULT NULL ON CONVERSION ERROR) as int4arr, + CAST(col4 as int8[] DEFAULT NULL ON CONVERSION ERROR) as int8arr, + CAST(col4 as float4[] DEFAULT NULL ON CONVERSION ERROR) as f4arr, + CAST(col4 as float8[] DEFAULT NULL ON CONVERSION ERROR) as f8arr, + CAST(col4 as numeric(10,1)[] DEFAULT NULL ON CONVERSION ERROR) as numarr +FROM test_safecast1; + +SELECT col1 as float4, + CAST(col1 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2, + CAST(col1 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4, + CAST(col1 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid, + CAST(col1 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8, + CAST(col1 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4, + CAST(col1 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8, + CAST(col1 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric, + CAST(col1 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale +FROM test_safecast1; + +SELECT col2 as float8, + CAST(col2 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2, + CAST(col2 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4, + CAST(col2 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid, + CAST(col2 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8, + CAST(col2 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4, + CAST(col2 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8, + CAST(col2 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric, + CAST(col2 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale +FROM test_safecast1; + +-- safe type cast for type date/timestamp/timestamptz/interval +CREATE TABLE test_safecast2( + col0 date, col1 timestamp, col2 timestamptz, + col3 interval, col4 time, col5 timetz); +INSERT INTO test_safecast2 VALUES +('-infinity', '-infinity', '-infinity', '-infinity', + '2003-03-07 15:36:39 America/New_York', '2003-07-07 15:36:39 America/New_York'), +('-infinity', 'infinity', 'infinity', 'infinity', '11:59:59.99 PM', '11:59:59.99 PM PDT'); + +SELECT col0 as date, + CAST(col0 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz, + CAST(col0 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date, + CAST(col0 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times, + CAST(col0 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz, + CAST(col0 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale +FROM test_safecast2; + +SELECT col1 as timestamp, + CAST(col1 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz, + CAST(col1 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date, + CAST(col1 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times, + CAST(col1 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz, + CAST(col1 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale +FROM test_safecast2; + +SELECT col2 as timestamptz, + CAST(col2 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz, + CAST(col2 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date, + CAST(col2 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times, + CAST(col2 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz, + CAST(col2 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale +FROM test_safecast2; + +SELECT col3 as interval, + CAST(col3 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz, + CAST(col3 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date, + CAST(col3 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times, + CAST(col3 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz, + CAST(col3 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale, + CAST(col3 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale +FROM test_safecast2; + +SELECT col4 as time, + CAST(col4 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_times, + CAST(col4 AS timetz DEFAULT NULL ON CONVERSION ERROR) IS NOT NULL as to_timetz, + CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval, + CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale +FROM test_safecast2; + +SELECT col5 as timetz, + CAST(col5 AS time DEFAULT NULL ON CONVERSION ERROR) as to_time, + CAST(col5 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_time_scale, + CAST(col5 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz, + CAST(col5 AS timetz(6) DEFAULT NULL ON CONVERSION ERROR) as to_timetz_scale, + CAST(col5 AS interval DEFAULT NULL ON CONVERSION ERROR) as to_interval, + CAST(col5 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale +FROM test_safecast2; + +-- test deparse +SET datestyle TO ISO, YMD; +CREATE VIEW safecastview AS +SELECT + CAST('2022-01-01' COLLATE "C" COLLATE "POSIX" as DATE DEFAULT NULL ON CONVERSION ERROR) as cast, + CAST('1234' as char(3) DEFAULT -1111 ON CONVERSION ERROR) as cast0, + CAST(1 as date DEFAULT (('2025-Dec-06'::date + random(min=>1, max=>1::int))) ON CONVERSION ERROR) as cast1, + CAST(ARRAY[['1'], ['three'],['a']] AS int[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast2, + CAST(ARRAY[['1', '2'], ['three', 'a']] AS date[] DEFAULT NULL ON CONVERSION ERROR) as cast3, + CAST(ARRAY['three'] AS INT[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast4, + CAST(CAST(1 AS int DEFAULT 0 ON CONVERSION ERROR) + 70000::int4 AS int DEFAULT -1 ON CONVERSION ERROR) as cast5; +\sv safecastview +SELECT * FROM safecastview; + +CREATE VIEW safecastview1 AS +SELECT CAST(ARRAY[['1'], ['three'],['a']] AS d_int_arr + DEFAULT '{41,43}' ON CONVERSION ERROR) as cast1, + CAST(ARRAY[['1', '2', 1.1], ['three', true, B'01']] AS d_int_arr + DEFAULT '{41,43}' ON CONVERSION ERROR) as cast2, + CAST('a'::text AS int DEFAULT 3 ON CONVERSION ERROR) AS cast3; +\sv safecastview1 +SELECT * FROM safecastview1; + +CREATE VIEW safecastview2 AS +SELECT CAST(ARRAY[b] AS int[] DEFAULT NULL ON CONVERSION ERROR) FROM (values ('1'), ('a'::text)) t(b); +\sv safecastview2 +SELECT * FROM safecastview2; + +RESET datestyle; + +DROP VIEW safecastview; +DROP VIEW safecastview1; +DROP TABLE test_safecast0; +DROP TABLE test_safecast1; +DROP TABLE test_safecast2; +DROP TABLE tcast1; +DROP FUNCTION ret_int8; +DROP TYPE comp_domain_with_typmod; +DROP TYPE comp2; +DROP DOMAIN d_nota; +DROP DOMAIN d_varchar; +DROP DOMAIN d_int8arr; +DROP DOMAIN d_int8; +DROP DOMAIN d_int42arr1; +DROP DOMAIN d_int42arr; +DROP DOMAIN d_int42; +DROP DOMAIN d_char3_not_null; +DROP DOMAIN d_numeric; +DROP DOMAIN dint; +DROP DOMAIN dintnn; +RESET extra_float_digits; +RESET lc_monetary; diff --git a/src/test/regress/sql/create_cast.sql b/src/test/regress/sql/create_cast.sql index 32187853cc7..88377a7b467 100644 --- a/src/test/regress/sql/create_cast.sql +++ b/src/test/regress/sql/create_cast.sql @@ -62,6 +62,9 @@ $$ SELECT ('bar'::text || $1::text); $$; CREATE CAST (int4 AS casttesttype) WITH FUNCTION bar_int4_text(int4) AS IMPLICIT; SELECT 1234::int4::casttesttype; -- Should work now +SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- error +ALTER FUNCTION bar_int4_text(int4) ERROR SAFE; +SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- ok -- check dependencies generated for that SELECT pg_describe_object(classid, objid, objsubid) as obj, diff --git a/src/test/regress/sql/equivclass.sql b/src/test/regress/sql/equivclass.sql index 7fc2159349b..96e6f73c7b6 100644 --- a/src/test/regress/sql/equivclass.sql +++ b/src/test/regress/sql/equivclass.sql @@ -98,6 +98,11 @@ create function int8alias1cmp(int8, int8alias1) returns int alter operator family integer_ops using btree add function 1 int8alias1cmp (int8, int8alias1); +-- int8alias2 binary-coercible to int8, this should not fail +select cast('1'::int8 as int8alias2 default null on conversion error); +select cast('1'::int8alias2 as int8 default null on conversion error); +select cast('a' as int8alias2 default null on conversion error); + create table ec0 (ff int8 primary key, f1 int8, f2 int8); create table ec1 (ff int8 primary key, f1 int8alias1, f2 int8alias2); create table ec2 (xf int8 primary key, x1 int8alias1, x2 int8alias2); diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql index 0581d21b03f..8c19c03488a 100644 --- a/src/test/regress/sql/xml.sql +++ b/src/test/regress/sql/xml.sql @@ -16,6 +16,8 @@ SELECT count(*) = 0 AS skip_test FROM xmltest \gset SELECT * FROM xmltest; +SELECT CAST('one', 'xml'); SELECT pg_input_is_valid('one