hi. fix the regress tests failure in https://api.cirrus-ci.com/v1/artifact/task/5894868779663360/testrun/build/testrun/regress/regress/regression.diffs
From e60e5190511326568eba8e6748062adb47f1134c Mon Sep 17 00:00:00 2001 From: jian he <jian.universal...@gmail.com> Date: Fri, 18 Jul 2025 13:00:19 +0800 Subject: [PATCH v4 2/3] make ArrayCoerceExpr error safe
similar to https://git.postgresql.org/cgit/postgresql.git/commit/?id=aaaf9449ec6be62cb0d30ed3588dc384f56274bf --- src/backend/executor/execExpr.c | 3 +++ src/backend/executor/execExprInterp.c | 4 ++++ src/backend/utils/adt/arrayfuncs.c | 7 +++++++ 3 files changed, 14 insertions(+) diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index f1569879b5..1f3f899874 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -1702,6 +1702,9 @@ ExecInitExprRec(Expr *node, ExprState *state, elemstate->innermost_caseval = (Datum *) palloc(sizeof(Datum)); elemstate->innermost_casenull = (bool *) palloc(sizeof(bool)); + if (state->escontext != NULL) + elemstate->escontext = state->escontext; + ExecInitExprRec(acoerce->elemexpr, elemstate, &elemstate->resvalue, &elemstate->resnull); diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index 8a72b5e70a..636d9ef9e6 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -3644,6 +3644,10 @@ 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; } /* diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index c8f53c6fbe..b5f98bf22f 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -3288,6 +3288,13 @@ array_map(Datum arrayd, /* Apply the given expression to source element */ values[i] = ExecEvalExpr(exprstate, econtext, &nulls[i]); + if (SOFT_ERROR_OCCURRED(exprstate->escontext)) + { + pfree(values); + pfree(nulls); + return (Datum) 0; + } + if (nulls[i]) hasnulls = true; else -- 2.34.1
From e3854ee1d362646ea97a8baa4e262cc3a9e95913 Mon Sep 17 00:00:00 2001 From: jian he <jian.universal...@gmail.com> Date: Fri, 1 Aug 2025 13:22:59 +0800 Subject: [PATCH v4 1/3] make some numeric cast function error safe The following function are changed to make it error safe. numeric_int2 numeric_int4 numeric_int8 numeric_float4 numeric_float8 dtoi8 dtoi4 dtoi2 ftoi8 ftoi4 ftoi2 ftod dtof float4_numeric float8_numeric numeric (oid 1703) This is need for evaulation CAST(... DEFAULT... ON CONVERSION ERROR). discussion: https://postgr.es/m/CADkLM=fv1jfy4ufa-jcwwnbjqixnviskq8jzu3tz_p656i_...@mail.gmail.com --- src/backend/utils/adt/float.c | 16 +++++--- src/backend/utils/adt/int8.c | 4 +- src/backend/utils/adt/numeric.c | 67 +++++++++++++++++++++++++++------ 3 files changed, 67 insertions(+), 20 deletions(-) diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c index 7b97d2be6c..6461e9c94b 100644 --- a/src/backend/utils/adt/float.c +++ b/src/backend/utils/adt/float.c @@ -1199,9 +1199,13 @@ dtof(PG_FUNCTION_ARGS) result = (float4) num; if (unlikely(isinf(result)) && !isinf(num)) - float_overflow_error(); + ereturn(fcinfo->context, (Datum) 0, + errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value out of range: overflow")); if (unlikely(result == 0.0f) && num != 0.0) - float_underflow_error(); + ereturn(fcinfo->context, (Datum) 0, + errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value out of range: underflow")); PG_RETURN_FLOAT4(result); } @@ -1224,7 +1228,7 @@ dtoi4(PG_FUNCTION_ARGS) /* Range check */ if (unlikely(isnan(num) || !FLOAT8_FITS_IN_INT32(num))) - ereport(ERROR, + ereturn(fcinfo->context, (Datum) 0, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("integer out of range"))); @@ -1249,7 +1253,7 @@ dtoi2(PG_FUNCTION_ARGS) /* Range check */ if (unlikely(isnan(num) || !FLOAT8_FITS_IN_INT16(num))) - ereport(ERROR, + ereturn(fcinfo->context, (Datum) 0, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("smallint out of range"))); @@ -1298,7 +1302,7 @@ ftoi4(PG_FUNCTION_ARGS) /* Range check */ if (unlikely(isnan(num) || !FLOAT4_FITS_IN_INT32(num))) - ereport(ERROR, + ereturn(fcinfo->context, (Datum) 0, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("integer out of range"))); @@ -1323,7 +1327,7 @@ ftoi2(PG_FUNCTION_ARGS) /* Range check */ if (unlikely(isnan(num) || !FLOAT4_FITS_IN_INT16(num))) - ereport(ERROR, + ereturn(fcinfo->context, (Datum) 0, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("smallint out of range"))); diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c index 9dd5889f34..4b9a762067 100644 --- a/src/backend/utils/adt/int8.c +++ b/src/backend/utils/adt/int8.c @@ -1307,7 +1307,7 @@ dtoi8(PG_FUNCTION_ARGS) /* Range check */ if (unlikely(isnan(num) || !FLOAT8_FITS_IN_INT64(num))) - ereport(ERROR, + ereturn(fcinfo->context, (Datum) 0, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("bigint out of range"))); @@ -1342,7 +1342,7 @@ ftoi8(PG_FUNCTION_ARGS) /* Range check */ if (unlikely(isnan(num) || !FLOAT4_FITS_IN_INT64(num))) - ereport(ERROR, + ereturn(fcinfo->context, (Datum) 0, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("bigint out of range"))); diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c index c9233565d5..cf498627a9 100644 --- a/src/backend/utils/adt/numeric.c +++ b/src/backend/utils/adt/numeric.c @@ -1261,7 +1261,8 @@ numeric (PG_FUNCTION_ARGS) */ if (NUMERIC_IS_SPECIAL(num)) { - (void) apply_typmod_special(num, typmod, NULL); + if (!apply_typmod_special(num, typmod, fcinfo->context)) + PG_RETURN_NULL(); PG_RETURN_NUMERIC(duplicate_numeric(num)); } @@ -4564,7 +4565,22 @@ numeric_int4(PG_FUNCTION_ARGS) { Numeric num = PG_GETARG_NUMERIC(0); - PG_RETURN_INT32(numeric_int4_opt_error(num, NULL)); + if (likely(fcinfo->context == NULL)) + PG_RETURN_INT32(numeric_int4_opt_error(num, NULL)); + else + { + bool has_error; + int32 result; + Node *escontext = fcinfo->context; + + result = numeric_int4_opt_error(num, &has_error); + if (has_error) + ereturn(escontext, (Datum) 0, + errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("integer out of range")); + + PG_RETURN_INT32(result); + } } /* @@ -4652,7 +4668,22 @@ numeric_int8(PG_FUNCTION_ARGS) { Numeric num = PG_GETARG_NUMERIC(0); - PG_RETURN_INT64(numeric_int8_opt_error(num, NULL)); + if (likely(fcinfo->context == NULL)) + PG_RETURN_INT64(numeric_int8_opt_error(num, NULL)); + else + { + bool has_error; + int64 result; + Node *escontext = fcinfo->context; + + result = numeric_int8_opt_error(num, &has_error); + if (has_error) + ereturn(escontext, (Datum) 0, + errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("bigint out of range")); + + PG_RETURN_INT64(result); + } } @@ -4669,6 +4700,7 @@ Datum numeric_int2(PG_FUNCTION_ARGS) { Numeric num = PG_GETARG_NUMERIC(0); + Node *escontext = fcinfo->context; NumericVar x; int64 val; int16 result; @@ -4676,11 +4708,11 @@ numeric_int2(PG_FUNCTION_ARGS) if (NUMERIC_IS_SPECIAL(num)) { if (NUMERIC_IS_NAN(num)) - ereport(ERROR, + ereturn(escontext, (Datum) 0, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot convert NaN to %s", "smallint"))); else - ereport(ERROR, + ereturn(escontext, (Datum) 0, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot convert infinity to %s", "smallint"))); } @@ -4689,12 +4721,12 @@ numeric_int2(PG_FUNCTION_ARGS) init_var_from_num(num, &x); if (!numericvar_to_int64(&x, &val)) - ereport(ERROR, + ereturn(escontext, (Datum) 0, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("smallint out of range"))); if (unlikely(val < PG_INT16_MIN) || unlikely(val > PG_INT16_MAX)) - ereport(ERROR, + ereturn(escontext, (Datum) 0, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("smallint out of range"))); @@ -4759,10 +4791,14 @@ numeric_float8(PG_FUNCTION_ARGS) tmp = DatumGetCString(DirectFunctionCall1(numeric_out, NumericGetDatum(num))); - - result = DirectFunctionCall1(float8in, CStringGetDatum(tmp)); - - pfree(tmp); + if (!DirectInputFunctionCallSafe(float8in, tmp, + InvalidOid, -1, + (Node *) fcinfo->context, + &result)) + { + pfree(tmp); + PG_RETURN_NULL(); + } PG_RETURN_DATUM(result); } @@ -4854,7 +4890,14 @@ numeric_float4(PG_FUNCTION_ARGS) tmp = DatumGetCString(DirectFunctionCall1(numeric_out, NumericGetDatum(num))); - result = DirectFunctionCall1(float4in, CStringGetDatum(tmp)); + if (!DirectInputFunctionCallSafe(float4in, tmp, + InvalidOid, -1, + (Node *) fcinfo->context, + &result)) + { + pfree(tmp); + PG_RETURN_NULL(); + } pfree(tmp); -- 2.34.1
From 138c6db6386d5c937fce6d903b3608f50950ee3a Mon Sep 17 00:00:00 2001 From: jian he <jian.universal...@gmail.com> Date: Mon, 4 Aug 2025 09:03:22 +0800 Subject: [PATCH v4 3/3] CAST(expr AS newtype DEFAULT ON ERROR) When casting data types, we generally uses one of three node types: FuncExpr, CoerceViaIO, or ArrayCoerceExpr represent the type coercion. * CoerceViaIO is already error-safe, see[0] * ArrayCoerceExpr is also error safe, see previous commit. * However, not all **FuncExpr** nodes is error-safe. For example, a function like int84 is not error safe. In these situations, we create a CoerceViaIO node and use the original expression as its argument, allowing us to safely handle the cast. Now that the type coercion node is error-safe, we also need to ensure that when a coercion fails, it falls back to evaluating the default node. [0]: https://git.postgresql.org/cgit/postgresql.git/commit/?id=aaaf9449ec6be62cb0d30ed3588dc384f56274bf discussion: https://postgr.es/m/CADkLM=fv1jfy4ufa-jcwwnbjqixnviskq8jzu3tz_p656i_...@mail.gmail.com demo: SELECT CAST('1' AS date DEFAULT '2011-01-01' ON ERROR), CAST('{234,def,567}'::text[] AS integer[] DEFAULT '{-1011}' ON ERROR); date | int4 ------------+--------- 2011-01-01 | {-1011} --- src/backend/executor/execExpr.c | 117 +++++++++- src/backend/executor/execExprInterp.c | 30 +++ src/backend/jit/llvm/llvmjit_expr.c | 49 ++++ src/backend/nodes/nodeFuncs.c | 67 ++++++ src/backend/nodes/queryjumblefuncs.c | 14 ++ src/backend/optimizer/util/clauses.c | 19 ++ src/backend/parser/gram.y | 31 ++- src/backend/parser/parse_expr.c | 316 +++++++++++++++++++++++++- src/backend/parser/parse_target.c | 14 ++ src/backend/parser/parse_type.c | 13 ++ src/backend/utils/adt/arrayfuncs.c | 6 + src/backend/utils/adt/ruleutils.c | 15 ++ src/backend/utils/fmgr/fmgr.c | 13 ++ src/include/executor/execExpr.h | 8 + src/include/fmgr.h | 3 + src/include/nodes/execnodes.h | 30 +++ src/include/nodes/parsenodes.h | 6 + src/include/nodes/primnodes.h | 35 +++ src/include/parser/parse_type.h | 2 + src/test/regress/expected/cast.out | 230 +++++++++++++++++++ src/test/regress/parallel_schedule | 2 +- src/test/regress/sql/cast.sql | 109 +++++++++ src/tools/pgindent/typedefs.list | 3 + 23 files changed, 1122 insertions(+), 10 deletions(-) create mode 100644 src/test/regress/expected/cast.out create mode 100644 src/test/regress/sql/cast.sql diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index 1f3f899874..03cafad525 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -99,6 +99,9 @@ static void ExecBuildAggTransCall(ExprState *state, AggState *aggstate, static void ExecInitJsonExpr(JsonExpr *jsexpr, 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, @@ -2180,6 +2183,14 @@ ExecInitExprRec(Expr *node, ExprState *state, break; } + case T_SafeTypeCastExpr: + { + SafeTypeCastExpr *stcexpr = castNode(SafeTypeCastExpr, node); + + ExecInitSafeTypeCastExpr(stcexpr, state, resv, resnull, &scratch); + break; + } + case T_CoalesceExpr: { CoalesceExpr *coalesce = (CoalesceExpr *) node; @@ -2746,7 +2757,7 @@ 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; @@ -4744,6 +4755,110 @@ ExecBuildParamSetEqual(TupleDesc desc, return state; } +/* + * Push steps to evaluate a SafeTypeCastExpr and its various subsidiary + * expressions. We already handle errors softly for coercion nodes like + * CoerceViaIO, CoerceToDomain, ArrayCoerceExpr, and some FuncExprs. However, + * most of FuncExprs node (for example, int84) is not error-safe. For these + * cases, we instead wrap the source expression and target type information + * within a CoerceViaIO node. + */ +static void +ExecInitSafeTypeCastExpr(SafeTypeCastExpr *stcexpr , ExprState *state, + Datum *resv, bool *resnull, + ExprEvalStep *scratch) +{ + /* + * If coercion to the target type fails, fallback to the default expression + * specified in the ON CONVERSION ERROR clause. + */ + if (stcexpr->cast_expr == NULL) + { + ExecInitExprRec((Expr *) stcexpr->default_expr, + state, resv, resnull); + return; + } + else + { + CoerceViaIO *newexpr; + SafeTypeCastState *stcstate; + ErrorSaveContext *escontext; + ErrorSaveContext *saved_escontext; + List *jumps_to_end = NIL; + bool numeric_fraction = false; + Oid source_oid; + + stcstate = palloc0(sizeof(SafeTypeCastState)); + stcstate->stcexpr = stcexpr; + stcstate->escontext.type = T_ErrorSaveContext; + escontext = &stcstate->escontext; + state->escontext = escontext; + + source_oid = getBaseType(exprType(stcexpr->source_expr)); + + /* + * We can't use CoerceViaIO to safely cast numeric values with + * fractional parts to other numerical types, because of rounding + * issues. For example, '11.1'::NUMERIC::INT would fail because '11.1' + * isn't a valid string representation for an integer. Instead, we + * should use FuncExpr for these cases. + */ + if (source_oid == NUMERICOID || + source_oid == FLOAT4OID || + source_oid == FLOAT8OID) + numeric_fraction = true; + + /* evaluate argument expression into step's result area */ + if (IsA(stcexpr->cast_expr, CoerceViaIO) || + IsA(stcexpr->cast_expr, CoerceToDomain) || + IsA(stcexpr->cast_expr, ArrayCoerceExpr) || + IsA(stcexpr->cast_expr, Var) || + numeric_fraction) + ExecInitExprRec((Expr *) stcexpr->cast_expr, + state, resv, resnull); + else + { + newexpr = makeNode(CoerceViaIO); + newexpr->arg = (Expr *) stcexpr->source_expr; + newexpr->resulttype = stcexpr->resulttype; + newexpr->resultcollid = exprCollation(stcexpr->source_expr); + newexpr->coerceformat = COERCE_EXPLICIT_CAST; + newexpr->location = exprLocation(stcexpr->source_expr); + + ExecInitExprRec((Expr *) newexpr, + state, resv, resnull); + } + + scratch->opcode = EEOP_SAFETYPE_CAST; + scratch->d.stcexpr.stcstate = stcstate; + ExprEvalPushStep(state, scratch); + + stcstate->jump_error = state->steps_len; + /* JUMP to end if false, that is, skip the ON ERROR expression. */ + jumps_to_end = lappend_int(jumps_to_end, state->steps_len); + scratch->opcode = EEOP_JUMP_IF_NOT_TRUE; + scratch->resvalue = &stcstate->error.value; + scratch->resnull = &stcstate->error.isnull; + scratch->d.jump.jumpdone = -1; /* set below */ + ExprEvalPushStep(state, scratch); + + /* Steps to evaluate the ON ERROR expression */ + saved_escontext = state->escontext; + state->escontext = NULL; + ExecInitExprRec((Expr *) stcstate->stcexpr->default_expr, + state, resv, resnull); + state->escontext = saved_escontext; + + foreach_int(lc, jumps_to_end) + { + ExprEvalStep *as = &state->steps[lc]; + + as->d.jump.jumpdone = state->steps_len; + } + stcstate->jump_end = state->steps_len; + } +} + /* * 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 636d9ef9e6..f6ed5953c2 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -568,6 +568,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, @@ -1926,6 +1927,11 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) EEO_NEXT(); } + EEO_CASE(EEOP_SAFETYPE_CAST) + { + EEO_JUMP(ExecEvalSafeTypeCast(state, op)); + } + EEO_CASE(EEOP_JSONEXPR_PATH) { /* too complex for an inline implementation */ @@ -5187,6 +5193,30 @@ GetJsonBehaviorValueString(JsonBehavior *behavior) return pstrdup(behavior_names[behavior->btype]); } +int +ExecEvalSafeTypeCast(ExprState *state, ExprEvalStep *op) +{ + SafeTypeCastState *stcstate = op->d.stcexpr.stcstate; + + if (SOFT_ERROR_OCCURRED(&stcstate->escontext)) + { + *op->resvalue = (Datum) 0; + *op->resnull = true; + + stcstate->error.value = BoolGetDatum(true); + + /* + * Reset for next use such as for catching errors when coercing a + * stcexpr expression. + */ + stcstate->escontext.error_occurred = false; + stcstate->escontext.details_wanted = false; + + return stcstate->jump_error; + } + return stcstate->jump_end; +} + /* * Checks if an error occurred in ExecEvalJsonCoercion(). If so, this sets * JsonExprState.error to trigger the ON ERROR handling steps, unless the diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c index 890bcb0b0a..a2dfb82393 100644 --- a/src/backend/jit/llvm/llvmjit_expr.c +++ b/src/backend/jit/llvm/llvmjit_expr.c @@ -2256,6 +2256,55 @@ llvm_compile_expr(ExprState *state) LLVMBuildBr(b, opblocks[opno + 1]); break; + case EEOP_SAFETYPE_CAST: + { + SafeTypeCastState *stcstate = op->d.stcexpr.stcstate; + LLVMValueRef v_ret; + + /* + * Call ExecEvalSafeTypeCast(). It returns the address of + * the step to perform next. + */ + v_ret = build_EvalXFunc(b, mod, "ExecEvalSafeTypeCast", + v_state, op, v_econtext); + + /* + * Build a switch to map the return value (v_ret above), + * which is a runtime value of the step address to perform + * next to jump_error + */ + if (stcstate->jump_error >= 0) + { + LLVMValueRef v_jump_error; + LLVMValueRef v_switch; + LLVMBasicBlockRef b_done, + b_error; + + b_error = + l_bb_before_v(opblocks[opno + 1], + "op.%d.stcexpr_error", opno); + b_done = + l_bb_before_v(opblocks[opno + 1], + "op.%d.stcexpr_done", opno); + + v_switch = LLVMBuildSwitch(b, + v_ret, + b_done, + 1); + + /* Returned stcstate->jump_error? */ + v_jump_error = l_int32_const(lc, stcstate->jump_error); + LLVMAddCase(v_switch, v_jump_error, b_error); + + /* ON ERROR code */ + LLVMPositionBuilderAtEnd(b, b_error); + LLVMBuildBr(b, opblocks[stcstate->jump_error]); + + LLVMPositionBuilderAtEnd(b, b_done); + } + LLVMBuildBr(b, opblocks[stcstate->jump_end]); + break; + } case EEOP_JSONEXPR_PATH: { JsonExprState *jsestate = op->d.jsonexpr.jsestate; diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index 7bc823507f..212f3e3c50 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -206,6 +206,9 @@ exprType(const Node *expr) case T_RowCompareExpr: type = BOOLOID; break; + case T_SafeTypeCastExpr: + type = ((const SafeTypeCastExpr *) expr)->resulttype; + break; case T_CoalesceExpr: type = ((const CoalesceExpr *) expr)->coalescetype; break; @@ -450,6 +453,8 @@ exprTypmod(const Node *expr) return typmod; } break; + case T_SafeTypeCastExpr: + return ((const SafeTypeCastExpr *) expr)->resulttypmod; case T_CoalesceExpr: { /* @@ -965,6 +970,9 @@ 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_CoalesceExpr: coll = ((const CoalesceExpr *) expr)->coalescecollid; break; @@ -1232,6 +1240,9 @@ 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_CoalesceExpr: ((CoalesceExpr *) expr)->coalescecollid = collation; break; @@ -1554,6 +1565,15 @@ exprLocation(const Node *expr) /* just use leftmost argument's location */ loc = exprLocation((Node *) ((const RowCompareExpr *) expr)->largs); break; + case T_SafeTypeCastExpr: + { + const SafeTypeCastExpr *cast_expr = (const SafeTypeCastExpr *) expr; + if (cast_expr->cast_expr) + loc = exprLocation(cast_expr->cast_expr); + else + loc = exprLocation(cast_expr->default_expr); + break; + } case T_CoalesceExpr: /* COALESCE keyword should always be the first thing */ loc = ((const CoalesceExpr *) expr)->location; @@ -2325,6 +2345,18 @@ expression_tree_walker_impl(Node *node, return true; } break; + case T_SafeTypeCastExpr: + { + SafeTypeCastExpr *scexpr = (SafeTypeCastExpr *) node; + + if (WALK(scexpr->source_expr)) + return true; + if (WALK(scexpr->cast_expr)) + return true; + if (WALK(scexpr->default_expr)) + return true; + } + break; case T_CoalesceExpr: return WALK(((CoalesceExpr *) node)->args); case T_MinMaxExpr: @@ -3334,6 +3366,19 @@ expression_tree_mutator_impl(Node *node, return (Node *) newnode; } break; + case T_SafeTypeCastExpr: + { + SafeTypeCastExpr *scexpr = (SafeTypeCastExpr *) node; + SafeTypeCastExpr *newnode; + + FLATCOPY(newnode, scexpr, SafeTypeCastExpr); + MUTATE(newnode->source_expr, scexpr->source_expr, Node *); + MUTATE(newnode->cast_expr, scexpr->cast_expr, Node *); + MUTATE(newnode->default_expr, scexpr->default_expr, Node *); + + return (Node *) newnode; + } + break; case T_CoalesceExpr: { CoalesceExpr *coalesceexpr = (CoalesceExpr *) node; @@ -4468,6 +4513,28 @@ raw_expression_tree_walker_impl(Node *node, return true; } break; + case T_SafeTypeCast: + { + SafeTypeCast *sc = (SafeTypeCast *) node; + + if (WALK(sc->cast)) + return true; + if (WALK(sc->expr)) + return true; + } + break; + case T_SafeTypeCastExpr: + { + SafeTypeCastExpr *stc = (SafeTypeCastExpr *) node; + + if (WALK(stc->source_expr)) + return true; + if (WALK(stc->cast_expr)) + return true; + if (WALK(stc->default_expr)) + return true; + } + break; case T_CollateClause: return WALK(((CollateClause *) node)->arg); case T_SortBy: diff --git a/src/backend/nodes/queryjumblefuncs.c b/src/backend/nodes/queryjumblefuncs.c index 31f9715197..76426c88e9 100644 --- a/src/backend/nodes/queryjumblefuncs.c +++ b/src/backend/nodes/queryjumblefuncs.c @@ -74,6 +74,7 @@ static void _jumbleElements(JumbleState *jstate, List *elements, Node *node); static void _jumbleParam(JumbleState *jstate, Node *node); static void _jumbleA_Const(JumbleState *jstate, Node *node); static void _jumbleVariableSetStmt(JumbleState *jstate, Node *node); +static void _jumbleSafeTypeCastExpr(JumbleState *jstate, Node *node); static void _jumbleRangeTblEntry_eref(JumbleState *jstate, RangeTblEntry *rte, Alias *expr); @@ -758,6 +759,19 @@ _jumbleVariableSetStmt(JumbleState *jstate, Node *node) JUMBLE_LOCATION(location); } +static void +_jumbleSafeTypeCastExpr(JumbleState *jstate, Node *node) +{ + SafeTypeCastExpr *expr = (SafeTypeCastExpr *) node; + + if (expr->cast_expr == NULL) + JUMBLE_NODE(source_expr); + else + JUMBLE_NODE(cast_expr); + + JUMBLE_NODE(default_expr); +} + /* * Custom query jumble function for RangeTblEntry.eref. */ diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index f45131c34c..a7b4e80971 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -2938,6 +2938,25 @@ eval_const_expressions_mutator(Node *node, copyObject(jve->format)); } + case T_SafeTypeCastExpr: + { + SafeTypeCastExpr *stc = (SafeTypeCastExpr *) node; + SafeTypeCastExpr *newexpr; + Node *source_expr = stc->source_expr; + Node *default_expr = stc->default_expr; + + source_expr = eval_const_expressions_mutator(source_expr, context); + default_expr = eval_const_expressions_mutator(default_expr, context); + + newexpr = makeNode(SafeTypeCastExpr); + newexpr->source_expr = source_expr; + newexpr->cast_expr = stc->cast_expr; + newexpr->default_expr = default_expr; + newexpr->resulttype = stc->resulttype; + newexpr->resulttypmod = stc->resulttypmod; + newexpr->resultcollid = stc->resultcollid; + return (Node *) newexpr; + } case T_SubPlan: case T_AlternativeSubPlan: diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 70a0d832a1..5176d7e4d2 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -642,6 +642,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type <partboundspec> PartitionBoundSpec %type <list> hash_partbound %type <defelt> hash_partbound_elem +%type <node> cast_on_error_clause +%type <node> cast_on_error_action %type <node> json_format_clause json_format_clause_opt @@ -15936,8 +15938,25 @@ func_expr_common_subexpr: { $$ = makeSQLValueFunction(SVFOP_CURRENT_SCHEMA, -1, @1); } - | CAST '(' a_expr AS Typename ')' - { $$ = makeTypeCast($3, $5, @1); } + | CAST '(' a_expr AS Typename cast_on_error_clause ')' + { + TypeCast *cast = (TypeCast *) makeTypeCast($3, $5, @1); + if ($6 == NULL) + $$ = (Node *) cast; + else + { + SafeTypeCast *safecast = makeNode(SafeTypeCast); + + safecast->cast = (Node *) cast; + safecast->expr = $6; + + /* + * On-error actions must themselves be typecast to the + * same type as the original expression. + */ + $$ = (Node *) safecast; + } + } | EXTRACT '(' extract_list ')' { $$ = (Node *) makeFuncCall(SystemFuncName("extract"), @@ -16323,6 +16342,14 @@ func_expr_common_subexpr: } ; +cast_on_error_clause: cast_on_error_action ON CONVERSION_P ERROR_P { $$ = $1; } + | /* EMPTY */ { $$ = NULL; } + ; + +cast_on_error_action: ERROR_P { $$ = NULL; } + | NULL_P { $$ = makeNullAConst(-1); } + | DEFAULT a_expr { $$ = $2; } + ; /* * SQL/XML support diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index d66276801c..2f5a3059ac 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -16,6 +16,7 @@ #include "postgres.h" #include "catalog/pg_aggregate.h" +#include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "commands/dbcommands.h" #include "miscadmin.h" @@ -37,6 +38,7 @@ #include "utils/date.h" #include "utils/fmgroids.h" #include "utils/lsyscache.h" +#include "utils/syscache.h" #include "utils/timestamp.h" #include "utils/xml.h" @@ -60,7 +62,10 @@ 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 *can_coerce); +static Node *transformArrayExprSafe(ParseState *pstate, A_ArrayExpr *a, + Oid array_type, Oid element_type, int32 typmod); static Node *transformRowExpr(ParseState *pstate, RowExpr *r, bool allowDefault); static Node *transformCoalesceExpr(ParseState *pstate, CoalesceExpr *c); static Node *transformMinMaxExpr(ParseState *pstate, MinMaxExpr *m); @@ -76,6 +81,7 @@ static Node *transformWholeRowRef(ParseState *pstate, int sublevels_up, int location); static Node *transformIndirection(ParseState *pstate, A_Indirection *ind); static Node *transformTypeCast(ParseState *pstate, TypeCast *tc); +static Node *transformTypeSafeCast(ParseState *pstate, SafeTypeCast *tc); static Node *transformCollateClause(ParseState *pstate, CollateClause *c); static Node *transformJsonObjectConstructor(ParseState *pstate, JsonObjectConstructor *ctor); @@ -106,6 +112,8 @@ static Expr *make_distinct_op(ParseState *pstate, List *opname, Node *ltree, Node *rtree, int location); static Node *make_nulltest_from_distinct(ParseState *pstate, A_Expr *distincta, Node *arg); +static bool CovertUnknownConstSafe(ParseState *pstate, Node *node, + Oid targetType, int32 targetTypeMod); /* @@ -163,13 +171,17 @@ 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: result = transformTypeCast(pstate, (TypeCast *) expr); break; + case T_SafeTypeCast: + result = transformTypeSafeCast(pstate, (SafeTypeCast *) expr); + break; + case T_CollateClause: result = transformCollateClause(pstate, (CollateClause *) expr); break; @@ -2004,16 +2016,127 @@ transformSubLink(ParseState *pstate, SubLink *sublink) return result; } +/* + * Return true iff successfuly coerced a Unknown Const to targetType +*/ +static bool CovertUnknownConstSafe(ParseState *pstate, Node *node, + Oid targetType, int32 targetTypeMod) +{ + Oid baseTypeId; + int32 baseTypeMod; + int32 inputTypeMod; + Type baseType; + char *string; + Datum datum; + bool converted; + Const *con; + + Assert(IsA(node, Const)); + Assert(exprType(node) == UNKNOWNOID); + + con = (Const *) node; + baseTypeMod = targetTypeMod; + baseTypeId = getBaseTypeAndTypmod(targetType, &baseTypeMod); + + if (baseTypeId == INTERVALOID) + inputTypeMod = baseTypeMod; + else + inputTypeMod = -1; + baseType = typeidType(baseTypeId); + + /* + * We assume here that UNKNOWN's internal representation is the same as + * CSTRING. + */ + if (!con->constisnull) + string = DatumGetCString(con->constvalue); + else + string = NULL; + + converted = stringTypeDatumSafe(baseType, + string, + inputTypeMod, + &datum); + + ReleaseSysCache(baseType); + + return converted; +} + +/* + * As with transformArrayExpr, we need to correctly parse back a query like + * CAST(ARRAY['three', 'a'] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR). We + * cannot allow eval_const_expressions to fold the A_ArrayExpr into a Const + * node, as this may cause an error too early. The A_ArrayExpr still need + * transformed into an ArrayExpr for the deparse purpose. + */ +static Node * +transformArrayExprSafe(ParseState *pstate, A_ArrayExpr *a, + Oid array_type, Oid element_type, int32 typmod) +{ + ArrayExpr *newa = makeNode(ArrayExpr); + List *newelems = NIL; + ListCell *element; + + newa->multidims = false; + foreach(element, a->elements) + { + Node *e = (Node *) lfirst(element); + Node *newe; + + /* + * If an element is itself an A_ArrayExpr, recurse directly so that we + * can pass down any target type we were given. + */ + if (IsA(e, A_ArrayExpr)) + { + newe = transformArrayExprSafe(pstate,(A_ArrayExpr *) e, array_type, element_type, typmod); + /* we certainly have an array here */ + Assert(array_type == InvalidOid || array_type == exprType(newe)); + newa->multidims = true; + } + else + { + newe = transformExprRecurse(pstate, e); + + if (!newa->multidims) + { + Oid newetype = exprType(newe); + + if (newetype != INT2VECTOROID && newetype != OIDVECTOROID && + type_is_array(newetype)) + newa->multidims = true; + } + } + + newelems = lappend(newelems, newe); + } + + newa->array_typeid = array_type; + /* array_collid will be set by parse_collate.c */ + newa->element_typeid = element_type; + newa->elements = newelems; + newa->list_start = a->list_start; + newa->list_end = -1; + newa->location = -1; + + return (Node *) newa; +} + /* * transformArrayExpr * * If the caller specifies the target type, the resulting array will * be of exactly that type. Otherwise we try to infer a common type * for the elements using select_common_type(). + * + * can_coerce is not null only when CAST(DEFAULT... ON CONVERSION ERROR) is + * specified. If we found out we can not cast to target type and can_coerce is + * not null, return NULL earlier and set can_coerce set false. */ 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 *can_coerce) { ArrayExpr *newa = makeNode(ArrayExpr); List *newelems = NIL; @@ -2044,9 +2167,10 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a, (A_ArrayExpr *) e, array_type, element_type, - typmod); + typmod, + can_coerce); /* we certainly have an array here */ - Assert(array_type == InvalidOid || array_type == exprType(newe)); + Assert(can_coerce || array_type == InvalidOid || array_type == exprType(newe)); newa->multidims = true; } else @@ -2072,6 +2196,9 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a, newelems = lappend(newelems, newe); } + if (can_coerce && !*can_coerce) + return NULL; + /* * Select a target type for the elements. * @@ -2139,6 +2266,17 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a, Node *e = (Node *) lfirst(element); Node *newe; + if (can_coerce && (*can_coerce) && IsA(e, Const) && exprType(e) == UNKNOWNOID) + { + if (!CovertUnknownConstSafe(pstate, e, coerce_type, typmod)) + { + *can_coerce = false; + list_free(newcoercedelems); + newcoercedelems = NIL; + return NULL; + } + } + if (coerce_hard) { newe = coerce_to_target_type(pstate, e, @@ -2742,7 +2880,8 @@ transformTypeCast(ParseState *pstate, TypeCast *tc) (A_ArrayExpr *) arg, targetBaseType, elementType, - targetBaseTypmod); + targetBaseTypmod, + NULL); } else expr = transformExprRecurse(pstate, arg); @@ -2779,6 +2918,171 @@ transformTypeCast(ParseState *pstate, TypeCast *tc) return result; } + +/* + * Handle an explicit CAST(... DEFAULT ... ON CONVERSION ERROR) construct. + * + * Transform SafeTypeCast node, look up the type name, and apply any necessary + * coercion function(s). + */ +static Node * +transformTypeSafeCast(ParseState *pstate, SafeTypeCast *tc) +{ + SafeTypeCastExpr *result; + Node *def_expr; + Node *cast_expr = NULL; + Node *source_expr = NULL; + Node *array_expr = NULL; + Oid inputType = InvalidOid; + Oid targetType; + int32 targetTypmod; + int location; + TypeCast *tcast = (TypeCast *) tc->cast; + Node *tc_arg = tcast->arg; + bool can_coerce = true; + int def_expr_loc = -1; + + result = makeNode(SafeTypeCastExpr); + /* Look up the type name first */ + typenameTypeIdAndMod(pstate, tcast->typeName, &targetType, &targetTypmod); + + result->resulttype = targetType; + result->resulttypmod = targetTypmod; + + /* now looking at cast fail default expression */ + def_expr_loc = exprLocation(tc->expr); + def_expr = transformExprRecurse(pstate, tc->expr); + + if (expression_returns_set(def_expr)) + ereport(ERROR, + errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("DEFAULT expression must not return a set"), + parser_coercion_errposition(pstate, def_expr_loc, def_expr)); + + if (IsA(def_expr, Aggref) || IsA(def_expr, WindowFunc)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("DEFAULT expression function must be a normal function"), + parser_coercion_errposition(pstate, def_expr_loc, def_expr)); + + if (IsA(def_expr, FuncExpr)) + { + if (get_func_prokind(((FuncExpr *) def_expr)->funcid) != PROKIND_FUNCTION) + ereport(ERROR, + errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("DEFAULT expression function must be a normal function"), + parser_coercion_errposition(pstate, def_expr_loc, def_expr)); + } + + def_expr = coerce_to_target_type(pstate, def_expr, exprType(def_expr), + targetType, targetTypmod, + COERCION_EXPLICIT, + COERCE_EXPLICIT_CAST, + exprLocation(def_expr)); + if (def_expr == NULL) + ereport(ERROR, + errcode(ERRCODE_CANNOT_COERCE), + errmsg("cannot cast on_error default expression to type %s", + format_type_be(targetType)), + parser_coercion_errposition(pstate, def_expr_loc, def_expr)); + + /* + * 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. Otherwise, just + * transform the argument normally. + */ + if (IsA(tc_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)) + { + array_expr = copyObject(tc_arg); + + source_expr = transformArrayExpr(pstate, + (A_ArrayExpr *) tc_arg, + targetBaseType, + elementType, + targetBaseTypmod, + &can_coerce); + if (!can_coerce) + source_expr = transformArrayExprSafe(pstate, + (A_ArrayExpr *) array_expr, + targetBaseType, + elementType, + targetBaseTypmod); + } + else + source_expr = transformExprRecurse(pstate, tc_arg); + } + else + source_expr = transformExprRecurse(pstate, tc_arg); + + inputType = exprType(source_expr); + if (inputType == InvalidOid && can_coerce) + return (Node *) result; /* do nothing if NULL input */ + + + if (can_coerce && IsA(source_expr, Const) && exprType(source_expr) == UNKNOWNOID) + can_coerce = CovertUnknownConstSafe(pstate, + source_expr, + targetType, + targetTypmod); + + /* + * Location of the coercion is preferentially the location of the :: or + * CAST symbol, but if there is none then use the location of the type + * name (this can happen in TypeName 'string' syntax, for instance). + */ + location = tcast->location; + if (location < 0) + location = tcast->typeName->location; + + if (can_coerce) + { + Oid inputbase = getBaseType(inputType); + Oid targetbase = getBaseType(targetType); + + /* + * It's hard to make function numeric_cash error safe, especially callee + * numeric_mul. So we have to disallow safe cast from numeric to money + */ + if (inputbase == NUMERICOID && targetbase == MONEYOID) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot cast type %s to %s when %s clause behavior is not %s", + format_type_be(inputType), + format_type_be(targetType), + "CAST ... ON CONVERSION ERROR", "ERROR"), + parser_errposition(pstate, exprLocation(source_expr))); + cast_expr = coerce_to_target_type(pstate, source_expr, inputType, + targetType, targetTypmod, + COERCION_EXPLICIT, + COERCE_EXPLICIT_CAST, + location); + } + + result->source_expr = source_expr; + result->cast_expr = cast_expr; + result->default_expr = def_expr; + + return (Node *) result; +} + /* * Handle an explicit COLLATE clause. * diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index 4aba0d9d4d..812ed18c16 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -1823,6 +1823,20 @@ FigureColnameInternal(Node *node, char **name) } } break; + case T_SafeTypeCast: + strength = FigureColnameInternal(((SafeTypeCast *) node)->cast, + name); + if (strength <= 1) + { + TypeCast *node_cast; + node_cast = (TypeCast *)((SafeTypeCast *) node)->cast; + if (node_cast->typeName != NULL) + { + *name = strVal(llast(node_cast->typeName->names)); + return 1; + } + } + break; case T_CollateClause: return FigureColnameInternal(((CollateClause *) node)->arg, name); case T_GroupingFunc: diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c index 7713bdc6af..02e5f9c92d 100644 --- a/src/backend/parser/parse_type.c +++ b/src/backend/parser/parse_type.c @@ -19,6 +19,7 @@ #include "catalog/pg_type.h" #include "lib/stringinfo.h" #include "nodes/makefuncs.h" +#include "nodes/miscnodes.h" #include "parser/parse_type.h" #include "parser/parser.h" #include "utils/array.h" @@ -660,6 +661,18 @@ stringTypeDatum(Type tp, char *string, int32 atttypmod) return OidInputFunctionCall(typinput, string, typioparam, atttypmod); } +bool +stringTypeDatumSafe(Type tp, char *string, int32 atttypmod, Datum *result) +{ + Form_pg_type typform = (Form_pg_type) GETSTRUCT(tp); + Oid typinput = typform->typinput; + Oid typioparam = getTypeIOParam(tp); + ErrorSaveContext escontext = {T_ErrorSaveContext}; + + return OidInputFunctionCallSafe(typinput, string, typioparam, atttypmod, + (fmNodePtr) &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/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index b5f98bf22f..6bd8a989db 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -3295,6 +3295,12 @@ array_map(Datum arrayd, return (Datum) 0; } + 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 3d6e6bdbfd..ee868de2c6 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -10534,6 +10534,21 @@ get_rule_expr(Node *node, deparse_context *context, } break; + case T_SafeTypeCastExpr: + { + SafeTypeCastExpr *stcexpr = castNode(SafeTypeCastExpr, node); + + appendStringInfoString(buf, "CAST("); + get_rule_expr(stcexpr->source_expr, context, showimplicit); + + appendStringInfo(buf, " AS %s ", + format_type_with_typemod(stcexpr->resulttype, stcexpr->resulttypmod)); + + appendStringInfoString(buf, "DEFAULT "); + get_rule_expr(stcexpr->default_expr, context, showimplicit); + appendStringInfoString(buf, " ON CONVERSION ERROR)"); + } + break; case T_JsonExpr: { JsonExpr *jexpr = (JsonExpr *) node; diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c index 782291d999..9de895e682 100644 --- a/src/backend/utils/fmgr/fmgr.c +++ b/src/backend/utils/fmgr/fmgr.c @@ -1759,6 +1759,19 @@ OidInputFunctionCall(Oid functionId, char *str, Oid typioparam, int32 typmod) return InputFunctionCall(&flinfo, str, typioparam, typmod); } +bool +OidInputFunctionCallSafe(Oid functionId, char *str, Oid typioparam, + int32 typmod, fmNodePtr escontext, + Datum *result) +{ + FmgrInfo flinfo; + + fmgr_info(functionId, &flinfo); + + return InputFunctionCallSafe(&flinfo, str, typioparam, typmod, + escontext, result); +} + char * OidOutputFunctionCall(Oid functionId, Datum val) { diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h index 7536620370..0afcf09c08 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -265,6 +265,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, @@ -750,6 +751,12 @@ typedef struct ExprEvalStep JsonIsPredicate *pred; /* original expression node */ } is_json; + /* for EEOP_SAFECAST */ + struct + { + struct SafeTypeCastState *stcstate; /* original expression node */ + } stcexpr; + /* for EEOP_JSONEXPR_PATH */ struct { @@ -892,6 +899,7 @@ extern int ExecEvalJsonExprPath(ExprState *state, ExprEvalStep *op, ExprContext *econtext); extern void ExecEvalJsonCoercion(ExprState *state, ExprEvalStep *op, ExprContext *econtext); +int ExecEvalSafeTypeCast(ExprState *state, ExprEvalStep *op); extern void ExecEvalJsonCoercionFinish(ExprState *state, ExprEvalStep *op); extern void ExecEvalGroupingFunc(ExprState *state, ExprEvalStep *op); extern void ExecEvalMergeSupportFunc(ExprState *state, ExprEvalStep *op, diff --git a/src/include/fmgr.h b/src/include/fmgr.h index 0fe7b4ebc7..299d4eef4e 100644 --- a/src/include/fmgr.h +++ b/src/include/fmgr.h @@ -750,6 +750,9 @@ extern bool DirectInputFunctionCallSafe(PGFunction func, char *str, Datum *result); extern Datum OidInputFunctionCall(Oid functionId, char *str, Oid typioparam, int32 typmod); +extern bool OidInputFunctionCallSafe(Oid functionId, char *str, Oid typioparam, + int32 typmod, fmNodePtr escontext, + Datum *result); extern char *OutputFunctionCall(FmgrInfo *flinfo, Datum val); extern char *OidOutputFunctionCall(Oid functionId, Datum val); extern Datum ReceiveFunctionCall(FmgrInfo *flinfo, fmStringInfo buf, diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index e107d6e5f8..282bfa770e 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -1058,6 +1058,36 @@ typedef struct DomainConstraintState ExprState *check_exprstate; /* check_expr's eval state, or NULL */ } DomainConstraintState; +typedef struct SafeTypeCastState +{ + SafeTypeCastExpr *stcexpr; + + /* Set to true if type cast cause an error. */ + NullableDatum error; + + /* + * Addresses of steps that implement DEFAULT expr ON CONVERSION ERROR for + * safe type cast. + */ + int jump_error; + + /* + * Address to jump to when skipping all the steps to evaulate the default + * expression after performing ExecEvalSafeTypeCast(). + */ + int jump_end; + + /* + * For error-safe evaluation of coercions. When DEFAULT expr ON CONVERSION + * ON ERROR is specified, a pointer to this is passed to ExecInitExprRec() + * when initializing the coercion expressions, see ExecInitSafeTypeCastExpr. + * + * Reset for 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 28e2e8dc0f..2e071b2abf 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -399,6 +399,12 @@ typedef struct TypeCast ParseLoc location; /* token location, or -1 if unknown */ } TypeCast; +typedef struct SafeTypeCast +{ + NodeTag type; + Node *cast; + Node *expr; /* default expr */ +} SafeTypeCast; /* * CollateClause - a COLLATE expression */ diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 6dfca3cb35..d2df7c2893 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -756,6 +756,41 @@ typedef enum CoercionForm COERCE_SQL_SYNTAX, /* display with SQL-mandated special syntax */ } CoercionForm; +/* + * SafeTypeCastExpr - + * Transformed representation of + * CAST(expr AS typename DEFAULT expr2 ON ERROR) + * CAST(expr AS typename NULL ON ERROR) + */ +typedef struct SafeTypeCastExpr +{ + pg_node_attr(custom_query_jumble) + + Expr xpr; + + /* transformed expression being casted */ + Node *source_expr; + + /* + * The transformed cast expression; this may be NULL if the two types can't + * be cast. + */ + Node *cast_expr; + + /* Fall back to the default expression if the cast evaluation fails. */ + Node *default_expr; + + /* cast result data type */ + Oid resulttype pg_node_attr(query_jumble_ignore); + + /* cast result data type typmod (usually -1) */ + int32 resulttypmod pg_node_attr(query_jumble_ignore); + + /* cast result data type collation (usually -1) */ + Oid resultcollid pg_node_attr(query_jumble_ignore); + +} SafeTypeCastExpr; + /* * FuncExpr - expression node for a function call * diff --git a/src/include/parser/parse_type.h b/src/include/parser/parse_type.h index 0d919d8bfa..12381aed64 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, char *string, int32 atttypmod); +extern bool stringTypeDatumSafe(Type tp, char *string, int32 atttypmod, + 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 0000000000..a7d7d6d674 --- /dev/null +++ b/src/test/regress/expected/cast.out @@ -0,0 +1,230 @@ +SET extra_float_digits = 0; +-- 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)); + ^ +VALUES (CAST('error' AS integer NULL ON CONVERSION ERROR)); + column1 +--------- + +(1 row) + +VALUES (CAST('error' AS integer DEFAULT 42 ON CONVERSION ERROR)); + column1 +--------- + 42 +(1 row) + +SELECT CAST(1 AS date DEFAULT NULL ON CONVERSION ERROR); + date +------ + +(1 row) + +SELECT CAST(1::numeric AS money DEFAULT NULL ON CONVERSION ERROR); +ERROR: cannot cast type numeric to money when CAST ... ON CONVERSION ERROR clause behavior is not ERROR +LINE 1: SELECT CAST(1::numeric AS money DEFAULT NULL ON CONVERSION E... + ^ +CREATE OR REPLACE 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 cast on_error default expression to type date +LINE 1: SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERR... + ^ +-- test array coerce +SELECT CAST('{123,abc,456}' AS integer[] 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) + +SELECT CAST(ARRAY[['1'], ['three'],['a']] AS INTEGER[] DEFAULT '{1,2}' ON CONVERSION ERROR); + array +------- + {1,2} +(1 row) + +SELECT CAST(ARRAY[['1', '2'], ['three', 'a']] AS text[] DEFAULT '{21,22}' ON CONVERSION ERROR); + array +------------------- + {{1,2},{three,a}} +(1 row) + +-- test valid DEFAULT expression for CAST = ON CONVERSION ERROR +CREATE OR REPLACE FUNCTION ret_setint() RETURNS SETOF integer AS +$$ +BEGIN RETURN QUERY EXECUTE 'select 1 union all select 1'; END; +$$ +LANGUAGE plpgsql IMMUTABLE; +CREATE TABLE tcast(a text[], b int GENERATED BY DEFAULT AS IDENTITY, c text default '1'); +INSERT INTO tcast VALUES ('{12}'), ('{1,a, b}'), ('{{1,2}, {c,d}}'), ('{13}'); +SELECT CAST('a' as int DEFAULT ret_setint() ON CONVERSION ERROR) FROM tcast; --error +ERROR: DEFAULT expression must not return a set +LINE 1: SELECT CAST('a' as int DEFAULT ret_setint() ON CONVERSION ER... + ^ +SELECT CAST('a' as int DEFAULT sum(1) ON CONVERSION ERROR); --error +ERROR: DEFAULT expression function must be a normal function +LINE 1: SELECT CAST('a' as int DEFAULT sum(1) ON CONVERSION ERROR); + ^ +SELECT CAST('a' as int DEFAULT sum(1) over() ON CONVERSION ERROR); --error +ERROR: DEFAULT expression function must be a normal function +LINE 1: SELECT CAST('a' as int DEFAULT sum(1) over() ON CONVERSION E... + ^ +SELECT CAST('a' as int DEFAULT 'b' ON CONVERSION ERROR); --error +ERROR: invalid input syntax for type integer: "b" +LINE 1: SELECT CAST('a' as int DEFAULT 'b' ON CONVERSION ERROR); + ^ +SELECT CAST(t AS text[] DEFAULT '{21,22, ' || b || '}' ON CONVERSION ERROR) FROM tcast as t; + t +----------- + {21,22,1} + {21,22,2} + {21,22,3} + {21,22,4} +(4 rows) + +SELECT CAST(t.a AS int[] DEFAULT '{21,22}'::int[] || b ON CONVERSION ERROR) FROM tcast as t; + a +----------- + {12} + {21,22,2} + {21,22,3} + {13} +(4 rows) + +-- test with domain +CREATE DOMAIN d_int42 as int check (value = 42) NOT NULL; +CREATE DOMAIN d_char3_not_null as char(3) NOT NULL; +CREATE TYPE comp_domain_with_typmod AS (a d_char3_not_null, b int); +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(11 AS d_int42 DEFAULT 42 ON CONVERSION ERROR); --ok + d_int42 +--------- + 42 +(1 row) + +SELECT CAST(NULL AS d_int42 DEFAULT NULL ON CONVERSION ERROR); --error +ERROR: domain d_int42 does not allow null values +SELECT CAST(NULL AS d_int42 DEFAULT 42 ON CONVERSION ERROR); --ok + d_int42 +--------- + 42 +(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('(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)'... + ^ +--test cast numeric value with fraction to another numeric value +CREATE TABLE safecast(col1 float4, col2 float8, col3 numeric, col4 numeric[]); +INSERT INTO safecast VALUES('11.1234', '11.1234', '11.1234', '{11.1234}'::numeric[]); +INSERT INTO safecast VALUES('inf', 'inf', 'inf', '{11.1234, 12, inf, NaN}'::numeric[]); +INSERT INTO safecast VALUES('-inf', '-inf', '-inf', '{11.1234, 12, -inf, NaN}'::numeric[]); +INSERT INTO safecast VALUES('NaN', 'NaN', 'NaN', '{11.1234, 12, -inf, NaN}'::numeric[]); +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 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 safecast; + float4 | to_int2 | to_int4 | 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 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 safecast; + float8 | to_int2 | to_int4 | 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) + +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 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 safecast; + numeric | to_int2 | to_int4 | 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) + +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 safecast; + 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) + +-- test deparse +CREATE VIEW safecastview AS +SELECT CAST('1234' as char(3) DEFAULT -1111 ON CONVERSION ERROR), + CAST(1 as date DEFAULT ((now()::date + random(min=>1, max=>1::int))) ON CONVERSION ERROR) as safecast, + CAST(ARRAY[['1'], ['three'],['a']] AS INTEGER[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1, + CAST(ARRAY[['1', '2'], ['three', 'a']] AS text[] DEFAULT '{21,22}' ON CONVERSION ERROR) as cast2; +\sv safecastview +CREATE OR REPLACE VIEW public.safecastview AS + SELECT CAST('1234' AS character(3) DEFAULT '-1111'::integer::character(3) ON CONVERSION ERROR) AS bpchar, + CAST(1 AS date DEFAULT now()::date + random(min => 1, max => 1) ON CONVERSION ERROR) AS safecast, + CAST(ARRAY[ARRAY['1'], ARRAY['three'], ARRAY['a']] AS integer[] DEFAULT '{1,2}'::integer[] ON CONVERSION ERROR) AS cast1, + CAST(ARRAY[ARRAY['1'::text, '2'::text], ARRAY['three'::text, 'a'::text]] AS text[] DEFAULT '{21,22}'::text[] ON CONVERSION ERROR) AS cast2 +CREATE INDEX cast_error_idx ON tcast((cast(c as int DEFAULT random(min=>1, max=>1) ON CONVERSION ERROR))); --error +ERROR: functions in index expression must be marked IMMUTABLE +RESET extra_float_digits; diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index a424be2a6b..7c3ed55f90 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -81,7 +81,7 @@ test: brin_bloom brin_multi test: create_table_like alter_generic alter_operator misc async dbsize merge misc_functions sysviews tsrf tid tidscan tidrangescan collate.utf8 collate.icu.utf8 incremental_sort create_role without_overlaps generated_virtual # collate.linux.utf8 and collate.icu.utf8 tests cannot be run in parallel with each other -test: rules psql psql_crosstab psql_pipeline amutils stats_ext collate.linux.utf8 collate.windows.win1252 +test: rules psql psql_crosstab psql_pipeline amutils stats_ext collate.linux.utf8 collate.windows.win1252 cast # ---------- # Run these alone so they don't run out of parallel workers diff --git a/src/test/regress/sql/cast.sql b/src/test/regress/sql/cast.sql new file mode 100644 index 0000000000..95a813e5ff --- /dev/null +++ b/src/test/regress/sql/cast.sql @@ -0,0 +1,109 @@ +SET extra_float_digits = 0; + +-- CAST DEFAULT ON CONVERSION ERROR +VALUES (CAST('error' AS integer ERROR ON CONVERSION ERROR)); --error +VALUES (CAST('error' AS integer NULL ON CONVERSION ERROR)); +VALUES (CAST('error' AS integer DEFAULT 42 ON CONVERSION ERROR)); +SELECT CAST(1 AS date DEFAULT NULL ON CONVERSION ERROR); +SELECT CAST(1::numeric AS money DEFAULT NULL ON CONVERSION ERROR); + +CREATE OR REPLACE 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 array coerce +SELECT CAST('{123,abc,456}' AS integer[] DEFAULT '{-789}' ON CONVERSION ERROR); +SELECT CAST('{234,def,567}'::text[] AS integer[] DEFAULT '{-1011}' ON CONVERSION ERROR); +SELECT CAST(ARRAY[['1'], ['three'],['a']] AS INTEGER[] DEFAULT '{1,2}' ON CONVERSION ERROR); +SELECT CAST(ARRAY[['1', '2'], ['three', 'a']] AS text[] DEFAULT '{21,22}' ON CONVERSION ERROR); + +-- test valid DEFAULT expression for CAST = ON CONVERSION ERROR +CREATE OR REPLACE FUNCTION ret_setint() RETURNS SETOF integer AS +$$ +BEGIN RETURN QUERY EXECUTE 'select 1 union all select 1'; END; +$$ +LANGUAGE plpgsql IMMUTABLE; + +CREATE TABLE tcast(a text[], b int GENERATED BY DEFAULT AS IDENTITY, c text default '1'); +INSERT INTO tcast VALUES ('{12}'), ('{1,a, b}'), ('{{1,2}, {c,d}}'), ('{13}'); +SELECT CAST('a' as int DEFAULT ret_setint() ON CONVERSION ERROR) FROM tcast; --error +SELECT CAST('a' as int DEFAULT sum(1) ON CONVERSION ERROR); --error +SELECT CAST('a' as int DEFAULT sum(1) over() ON CONVERSION ERROR); --error +SELECT CAST('a' as int DEFAULT 'b' ON CONVERSION ERROR); --error + +SELECT CAST(t AS text[] DEFAULT '{21,22, ' || b || '}' ON CONVERSION ERROR) FROM tcast as t; +SELECT CAST(t.a AS int[] DEFAULT '{21,22}'::int[] || b ON CONVERSION ERROR) FROM tcast as t; + +-- test with domain +CREATE DOMAIN d_int42 as int check (value = 42) NOT NULL; +CREATE DOMAIN d_char3_not_null as char(3) NOT NULL; +CREATE TYPE comp_domain_with_typmod AS (a d_char3_not_null, b int); +SELECT CAST(11 AS d_int42 DEFAULT 41 ON CONVERSION ERROR); --error +SELECT CAST(11 AS d_int42 DEFAULT 42 ON CONVERSION ERROR); --ok +SELECT CAST(NULL AS d_int42 DEFAULT NULL ON CONVERSION ERROR); --error +SELECT CAST(NULL AS d_int42 DEFAULT 42 ON CONVERSION ERROR); --ok +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('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1234,2)' ON CONVERSION ERROR); --error + +--test cast numeric value with fraction to another numeric value +CREATE TABLE safecast(col1 float4, col2 float8, col3 numeric, col4 numeric[]); +INSERT INTO safecast VALUES('11.1234', '11.1234', '11.1234', '{11.1234}'::numeric[]); +INSERT INTO safecast VALUES('inf', 'inf', 'inf', '{11.1234, 12, inf, NaN}'::numeric[]); +INSERT INTO safecast VALUES('-inf', '-inf', '-inf', '{11.1234, 12, -inf, NaN}'::numeric[]); +INSERT INTO safecast VALUES('NaN', 'NaN', 'NaN', '{11.1234, 12, -inf, NaN}'::numeric[]); + +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 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 safecast; + +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 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 safecast; + +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 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 safecast; + +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 safecast; + +-- test deparse +CREATE VIEW safecastview AS +SELECT CAST('1234' as char(3) DEFAULT -1111 ON CONVERSION ERROR), + CAST(1 as date DEFAULT ((now()::date + random(min=>1, max=>1::int))) ON CONVERSION ERROR) as safecast, + CAST(ARRAY[['1'], ['three'],['a']] AS INTEGER[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1, + CAST(ARRAY[['1', '2'], ['three', 'a']] AS text[] DEFAULT '{21,22}' ON CONVERSION ERROR) as cast2; +\sv safecastview + +CREATE INDEX cast_error_idx ON tcast((cast(c as int DEFAULT random(min=>1, max=>1) ON CONVERSION ERROR))); --error +RESET extra_float_digits; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8319203857..c22f3ed9e6 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2648,6 +2648,9 @@ STRLEN SV SYNCHRONIZATION_BARRIER SYSTEM_INFO +SafeTypeCast +SafeTypeCastExpr +SafeTypeCastState SampleScan SampleScanGetSampleSize_function SampleScanState -- 2.34.1