From 74aaf068a2d071f6fefab27309d7a0252f28ccee Mon Sep 17 00:00:00 2001 From: coreyhuinker Date: Mon, 19 Dec 2022 17:11:49 -0500 Subject: [PATCH 3/3] CAST ON DEFAULT work in progress --- src/backend/executor/execExpr.c | 173 +++++++++++------- src/backend/executor/execExprInterp.c | 35 +++- src/backend/jit/llvm/llvmjit_expr.c | 15 ++ src/backend/nodes/makefuncs.c | 4 +- src/backend/nodes/nodeFuncs.c | 15 +- src/backend/optimizer/util/clauses.c | 4 +- src/backend/parser/gram.y | 46 ++++- src/backend/parser/parse_agg.c | 15 +- src/backend/parser/parse_coerce.c | 215 ++++++++++++++++++----- src/backend/parser/parse_expr.c | 87 +++++++-- src/backend/partitioning/partbounds.c | 3 +- src/backend/rewrite/rewriteSearchCycle.c | 4 +- src/include/executor/execExpr.h | 4 + src/include/nodes/execnodes.h | 4 + src/include/nodes/makefuncs.h | 3 +- src/include/nodes/parsenodes.h | 1 + src/include/nodes/primnodes.h | 12 +- src/include/parser/kwlist.h | 1 + src/include/parser/parse_coerce.h | 15 ++ src/test/regress/expected/cast.out | 40 +++++ src/test/regress/parallel_schedule | 2 +- src/test/regress/sql/cast.sql | 11 ++ 22 files changed, 551 insertions(+), 158 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 81429b9f05..9aaa53b67e 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -40,6 +40,7 @@ #include "jit/jit.h" #include "miscadmin.h" #include "nodes/makefuncs.h" +#include "nodes/miscnodes.h" #include "nodes/nodeFuncs.h" #include "nodes/subscripting.h" #include "optimizer/optimizer.h" @@ -61,10 +62,10 @@ typedef struct LastAttnumInfo static void ExecReadyExpr(ExprState *state); static void ExecInitExprRec(Expr *node, ExprState *state, - Datum *resv, bool *resnull); + Datum *resv, bool *resnull, bool *reserror); static void ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid, Oid inputcollid, - ExprState *state); + ExprState *state, ErrorSaveContext *escontext); static void ExecInitExprSlots(ExprState *state, Node *node); static void ExecPushExprSlots(ExprState *state, LastAttnumInfo *info); static bool get_last_attnums_walker(Node *node, LastAttnumInfo *info); @@ -74,11 +75,11 @@ static void ExecInitWholeRowVar(ExprEvalStep *scratch, Var *variable, static void ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref, ExprState *state, - Datum *resv, bool *resnull); + Datum *resv, bool *resnull, bool *reserror); static bool isAssignmentIndirectionExpr(Expr *expr); static void ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest, ExprState *state, - Datum *resv, bool *resnull); + Datum *resv, bool *resnull, bool *reserror); static void ExecBuildAggTransCall(ExprState *state, AggState *aggstate, ExprEvalStep *scratch, FunctionCallInfo fcinfo, AggStatePerTrans pertrans, @@ -140,7 +141,7 @@ ExecInitExpr(Expr *node, PlanState *parent) ExecInitExprSlots(state, (Node *) node); /* Compile the expression proper */ - ExecInitExprRec(node, state, &state->resvalue, &state->resnull); + ExecInitExprRec(node, state, &state->resvalue, &state->resnull, &state->reserror); /* Finally, append a DONE step */ scratch.opcode = EEOP_DONE; @@ -177,7 +178,7 @@ ExecInitExprWithParams(Expr *node, ParamListInfo ext_params) ExecInitExprSlots(state, (Node *) node); /* Compile the expression proper */ - ExecInitExprRec(node, state, &state->resvalue, &state->resnull); + ExecInitExprRec(node, state, &state->resvalue, &state->resnull, &state->reserror); /* Finally, append a DONE step */ scratch.opcode = EEOP_DONE; @@ -251,7 +252,7 @@ ExecInitQual(List *qual, PlanState *parent) Expr *node = (Expr *) lfirst(lc); /* first evaluate expression */ - ExecInitExprRec(node, state, &state->resvalue, &state->resnull); + ExecInitExprRec(node, state, &state->resvalue, &state->resnull, &state->reserror); /* then emit EEOP_QUAL to detect if it's false (or null) */ scratch.d.qualexpr.jumpdone = -1; @@ -455,7 +456,7 @@ ExecBuildProjectionInfo(List *targetList, * into the ExprState's resvalue/resnull and then move. */ ExecInitExprRec(tle->expr, state, - &state->resvalue, &state->resnull); + &state->resvalue, &state->resnull, &state->reserror); /* * Column might be referenced multiple times in upper nodes, so @@ -659,7 +660,7 @@ ExecBuildUpdateProjection(List *targetList, * path and it doesn't seem worth expending code for that. */ ExecInitExprRec(tle->expr, state, - &state->resvalue, &state->resnull); + &state->resvalue, &state->resnull, &state->reserror); /* Needn't worry about read-only-ness here, either. */ scratch.opcode = EEOP_ASSIGN_TMP; scratch.d.assign_tmp.resultnum = targetattnum - 1; @@ -688,7 +689,7 @@ ExecBuildUpdateProjection(List *targetList, Assert(tle->resjunk); ExecInitExprRec(tle->expr, state, - &state->resvalue, &state->resnull); + &state->resvalue, &state->resnull, &state->reserror); } } @@ -899,7 +900,7 @@ ExecReadyExpr(ExprState *state) */ static void ExecInitExprRec(Expr *node, ExprState *state, - Datum *resv, bool *resnull) + Datum *resv, bool *resnull, bool *reserror) { ExprEvalStep scratch = {0}; @@ -910,6 +911,7 @@ ExecInitExprRec(Expr *node, ExprState *state, Assert(resv != NULL && resnull != NULL); scratch.resvalue = resv; scratch.resnull = resnull; + scratch.reserror = reserror; /* cases should be ordered as they are in enum NodeTag */ switch (nodeTag(node)) @@ -1126,17 +1128,24 @@ ExecInitExprRec(Expr *node, ExprState *state, { SubscriptingRef *sbsref = (SubscriptingRef *) node; - ExecInitSubscriptingRef(&scratch, sbsref, state, resv, resnull); + ExecInitSubscriptingRef(&scratch, sbsref, state, resv, resnull, NULL); break; } case T_FuncExpr: { - FuncExpr *func = (FuncExpr *) node; + FuncExpr *func = (FuncExpr *) node; + ErrorSaveContext *escontext = NULL; + + if (unlikely(func->safe_mode)) + { + escontext = palloc0(sizeof(ErrorSaveContext)); + escontext->type = T_ErrorSaveContext; + } ExecInitFunc(&scratch, node, func->args, func->funcid, func->inputcollid, - state); + state, escontext); ExprEvalPushStep(state, &scratch); break; } @@ -1147,7 +1156,7 @@ ExecInitExprRec(Expr *node, ExprState *state, ExecInitFunc(&scratch, node, op->args, op->opfuncid, op->inputcollid, - state); + state, NULL); ExprEvalPushStep(state, &scratch); break; } @@ -1158,7 +1167,7 @@ ExecInitExprRec(Expr *node, ExprState *state, ExecInitFunc(&scratch, node, op->args, op->opfuncid, op->inputcollid, - state); + state, NULL); /* * Change opcode of call instruction to EEOP_DISTINCT. @@ -1180,7 +1189,7 @@ ExecInitExprRec(Expr *node, ExprState *state, ExecInitFunc(&scratch, node, op->args, op->opfuncid, op->inputcollid, - state); + state, NULL); /* * Change opcode of call instruction to EEOP_NULLIF. @@ -1263,7 +1272,7 @@ ExecInitExprRec(Expr *node, ExprState *state, { /* Evaluate scalar directly into left function argument */ ExecInitExprRec(scalararg, state, - &fcinfo->args[0].value, &fcinfo->args[0].isnull); + &fcinfo->args[0].value, &fcinfo->args[0].isnull, NULL); /* * Evaluate array argument into our return value. There's @@ -1272,7 +1281,7 @@ ExecInitExprRec(Expr *node, ExprState *state, * EEOP_HASHED_SCALARARRAYOP, and will not be passed to * any other expression. */ - ExecInitExprRec(arrayarg, state, resv, resnull); + ExecInitExprRec(arrayarg, state, resv, resnull, NULL); /* And perform the operation */ scratch.opcode = EEOP_HASHED_SCALARARRAYOP; @@ -1289,7 +1298,7 @@ ExecInitExprRec(Expr *node, ExprState *state, /* Evaluate scalar directly into left function argument */ ExecInitExprRec(scalararg, state, &fcinfo->args[0].value, - &fcinfo->args[0].isnull); + &fcinfo->args[0].isnull, NULL); /* * Evaluate array argument into our return value. There's @@ -1297,7 +1306,7 @@ ExecInitExprRec(Expr *node, ExprState *state, * guaranteed to be overwritten by EEOP_SCALARARRAYOP, and * will not be passed to any other expression. */ - ExecInitExprRec(arrayarg, state, resv, resnull); + ExecInitExprRec(arrayarg, state, resv, resnull, NULL); /* And perform the operation */ scratch.opcode = EEOP_SCALARARRAYOP; @@ -1342,7 +1351,7 @@ ExecInitExprRec(Expr *node, ExprState *state, Expr *arg = (Expr *) lfirst(lc); /* Evaluate argument into our output variable */ - ExecInitExprRec(arg, state, resv, resnull); + ExecInitExprRec(arg, state, resv, resnull, NULL); /* Perform the appropriate step type */ switch (boolexpr->boolop) @@ -1423,7 +1432,7 @@ ExecInitExprRec(Expr *node, ExprState *state, FieldSelect *fselect = (FieldSelect *) node; /* evaluate row/record argument into result area */ - ExecInitExprRec(fselect->arg, state, resv, resnull); + ExecInitExprRec(fselect->arg, state, resv, resnull, NULL); /* and extract field */ scratch.opcode = EEOP_FIELDSELECT; @@ -1460,7 +1469,7 @@ ExecInitExprRec(Expr *node, ExprState *state, rowcachep->cacheptr = NULL; /* emit code to evaluate the composite input value */ - ExecInitExprRec(fstore->arg, state, resv, resnull); + ExecInitExprRec(fstore->arg, state, resv, resnull, NULL); /* next, deform the input tuple into our workspace */ scratch.opcode = EEOP_FIELDSTORE_DEFORM; @@ -1514,7 +1523,7 @@ ExecInitExprRec(Expr *node, ExprState *state, ExecInitExprRec(e, state, &values[fieldnum - 1], - &nulls[fieldnum - 1]); + &nulls[fieldnum - 1], NULL); state->innermost_caseval = save_innermost_caseval; state->innermost_casenull = save_innermost_casenull; @@ -1536,7 +1545,7 @@ ExecInitExprRec(Expr *node, ExprState *state, /* relabel doesn't need to do anything at runtime */ RelabelType *relabel = (RelabelType *) node; - ExecInitExprRec(relabel->arg, state, resv, resnull); + ExecInitExprRec(relabel->arg, state, resv, resnull, NULL); break; } @@ -1547,9 +1556,10 @@ ExecInitExprRec(Expr *node, ExprState *state, bool typisvarlena; Oid typioparam; FunctionCallInfo fcinfo_in; + ErrorSaveContext *escontext = NULL; /* evaluate argument into step's result area */ - ExecInitExprRec(iocoerce->arg, state, resv, resnull); + ExecInitExprRec(iocoerce->arg, state, resv, resnull, reserror); /* * Prepare both output and input function calls, to be @@ -1581,9 +1591,17 @@ ExecInitExprRec(Expr *node, ExprState *state, &iofunc, &typioparam); fmgr_info(iofunc, scratch.d.iocoerce.finfo_in); fmgr_info_set_expr((Node *) node, scratch.d.iocoerce.finfo_in); + + /* if this is a safe-mode coerce */ + if (unlikely(iocoerce->safe_mode)) + { + escontext = palloc0(sizeof(ErrorSaveContext)); + escontext->type = T_ErrorSaveContext; + } + InitFunctionCallInfoData(*scratch.d.iocoerce.fcinfo_data_in, scratch.d.iocoerce.finfo_in, - 3, InvalidOid, NULL, NULL); + 3, InvalidOid, (fmNodePtr) escontext, NULL); /* * We can preload the second and third arguments for the input @@ -1606,7 +1624,7 @@ ExecInitExprRec(Expr *node, ExprState *state, ExprState *elemstate; /* evaluate argument into step's result area */ - ExecInitExprRec(acoerce->arg, state, resv, resnull); + ExecInitExprRec(acoerce->arg, state, resv, resnull, reserror); resultelemtype = get_element_type(acoerce->resulttype); if (!OidIsValid(resultelemtype)) @@ -1627,9 +1645,11 @@ ExecInitExprRec(Expr *node, ExprState *state, elemstate->innermost_caseval = (Datum *) palloc(sizeof(Datum)); elemstate->innermost_casenull = (bool *) palloc(sizeof(bool)); + elemstate->innermost_caseerror = (bool *) palloc(sizeof(bool)); ExecInitExprRec(acoerce->elemexpr, elemstate, - &elemstate->resvalue, &elemstate->resnull); + &elemstate->resvalue, &elemstate->resnull, + &elemstate->reserror); if (elemstate->steps_len == 1 && elemstate->steps[0].opcode == EEOP_CASE_TESTVAL) @@ -1677,7 +1697,7 @@ ExecInitExprRec(Expr *node, ExprState *state, rowcachep[1].cacheptr = NULL; /* evaluate argument into step's result area */ - ExecInitExprRec(convert->arg, state, resv, resnull); + ExecInitExprRec(convert->arg, state, resv, resnull, NULL); /* and push conversion step */ scratch.opcode = EEOP_CONVERT_ROWTYPE; @@ -1699,6 +1719,7 @@ ExecInitExprRec(Expr *node, ExprState *state, List *adjust_jumps = NIL; Datum *caseval = NULL; bool *casenull = NULL; + bool *caseerror = NULL; ListCell *lc; /* @@ -1711,9 +1732,10 @@ ExecInitExprRec(Expr *node, ExprState *state, /* Evaluate testexpr into caseval/casenull workspace */ caseval = palloc(sizeof(Datum)); casenull = palloc(sizeof(bool)); + caseerror = palloc(sizeof(bool)); ExecInitExprRec(caseExpr->arg, state, - caseval, casenull); + caseval, casenull, caseerror); /* * Since value might be read multiple times, force to R/O @@ -1725,8 +1747,10 @@ ExecInitExprRec(Expr *node, ExprState *state, scratch.opcode = EEOP_MAKE_READONLY; scratch.resvalue = caseval; scratch.resnull = casenull; + scratch.reserror = caseerror; scratch.d.make_readonly.value = caseval; scratch.d.make_readonly.isnull = casenull; + scratch.d.make_readonly.iserror = caseerror; ExprEvalPushStep(state, &scratch); /* restore normal settings of scratch fields */ scratch.resvalue = resv; @@ -1745,6 +1769,7 @@ ExecInitExprRec(Expr *node, ExprState *state, CaseWhen *when = (CaseWhen *) lfirst(lc); Datum *save_innermost_caseval; bool *save_innermost_casenull; + bool *save_innermost_caseerror; int whenstep; /* @@ -1759,14 +1784,17 @@ ExecInitExprRec(Expr *node, ExprState *state, */ save_innermost_caseval = state->innermost_caseval; save_innermost_casenull = state->innermost_casenull; + save_innermost_caseerror = state->innermost_caseerror; state->innermost_caseval = caseval; state->innermost_casenull = casenull; + state->innermost_caseerror = caseerror; /* evaluate condition into CASE's result variables */ - ExecInitExprRec(when->expr, state, resv, resnull); + ExecInitExprRec(when->expr, state, resv, resnull, NULL); state->innermost_caseval = save_innermost_caseval; state->innermost_casenull = save_innermost_casenull; + state->innermost_caseerror = save_innermost_caseerror; /* If WHEN result isn't true, jump to next CASE arm */ scratch.opcode = EEOP_JUMP_IF_NOT_TRUE; @@ -1778,7 +1806,7 @@ ExecInitExprRec(Expr *node, ExprState *state, * If WHEN result is true, evaluate THEN result, storing * it into the CASE's result variables. */ - ExecInitExprRec(when->result, state, resv, resnull); + ExecInitExprRec(when->result, state, resv, resnull, reserror); /* Emit JUMP step to jump to end of CASE's code */ scratch.opcode = EEOP_JUMP; @@ -1804,7 +1832,7 @@ ExecInitExprRec(Expr *node, ExprState *state, /* evaluate ELSE expr into CASE's result variables */ ExecInitExprRec(caseExpr->defresult, state, - resv, resnull); + resv, resnull, reserror); /* adjust jump targets */ foreach(lc, adjust_jumps) @@ -1833,6 +1861,7 @@ ExecInitExprRec(Expr *node, ExprState *state, scratch.opcode = EEOP_CASE_TESTVAL; scratch.d.casetest.value = state->innermost_caseval; scratch.d.casetest.isnull = state->innermost_casenull; + scratch.d.casetest.iserror = state->innermost_caseerror; ExprEvalPushStep(state, &scratch); break; @@ -1875,7 +1904,7 @@ ExecInitExprRec(Expr *node, ExprState *state, ExecInitExprRec(e, state, &scratch.d.arrayexpr.elemvalues[elemoff], - &scratch.d.arrayexpr.elemnulls[elemoff]); + &scratch.d.arrayexpr.elemnulls[elemoff], NULL); elemoff++; } @@ -1969,7 +1998,7 @@ ExecInitExprRec(Expr *node, ExprState *state, /* Evaluate column expr into appropriate workspace slot */ ExecInitExprRec(e, state, &scratch.d.row.elemvalues[i], - &scratch.d.row.elemnulls[i]); + &scratch.d.row.elemnulls[i], NULL); i++; } @@ -2047,9 +2076,9 @@ ExecInitExprRec(Expr *node, ExprState *state, /* evaluate left and right args directly into fcinfo */ ExecInitExprRec(left_expr, state, - &fcinfo->args[0].value, &fcinfo->args[0].isnull); + &fcinfo->args[0].value, &fcinfo->args[0].isnull, NULL); ExecInitExprRec(right_expr, state, - &fcinfo->args[1].value, &fcinfo->args[1].isnull); + &fcinfo->args[1].value, &fcinfo->args[1].isnull, NULL); scratch.opcode = EEOP_ROWCOMPARE_STEP; scratch.d.rowcompare_step.finfo = finfo; @@ -2104,6 +2133,13 @@ ExecInitExprRec(Expr *node, ExprState *state, CoalesceExpr *coalesce = (CoalesceExpr *) node; List *adjust_jumps = NIL; ListCell *lc; + ExprEvalOp jump_test_opcode; + + /* Coalesce can handle resnull and reserror tests */ + if (likely(coalesce->op == NULL_TEST)) + jump_test_opcode = EEOP_JUMP_IF_NOT_NULL; + else + jump_test_opcode = EEOP_JUMP_IF_NOT_ERROR; /* We assume there's at least one arg */ Assert(coalesce->args != NIL); @@ -2117,10 +2153,10 @@ ExecInitExprRec(Expr *node, ExprState *state, Expr *e = (Expr *) lfirst(lc); /* evaluate argument, directly into result datum */ - ExecInitExprRec(e, state, resv, resnull); + ExecInitExprRec(e, state, resv, resnull, reserror); - /* if it's not null, skip to end of COALESCE expr */ - scratch.opcode = EEOP_JUMP_IF_NOT_NULL; + /* if it's not null/error, skip to end of COALESCE expr */ + scratch.opcode = jump_test_opcode; scratch.d.jump.jumpdone = -1; /* adjust later */ ExprEvalPushStep(state, &scratch); @@ -2139,7 +2175,7 @@ ExecInitExprRec(Expr *node, ExprState *state, { ExprEvalStep *as = &state->steps[lfirst_int(lc)]; - Assert(as->opcode == EEOP_JUMP_IF_NOT_NULL); + Assert(as->opcode == jump_test_opcode); Assert(as->d.jump.jumpdone == -1); as->d.jump.jumpdone = state->steps_len; } @@ -2201,7 +2237,7 @@ ExecInitExprRec(Expr *node, ExprState *state, ExecInitExprRec(e, state, &scratch.d.minmax.values[off], - &scratch.d.minmax.nulls[off]); + &scratch.d.minmax.nulls[off], NULL); off++; } @@ -2256,7 +2292,7 @@ ExecInitExprRec(Expr *node, ExprState *state, ExecInitExprRec(e, state, &scratch.d.xmlexpr.named_argvalue[off], - &scratch.d.xmlexpr.named_argnull[off]); + &scratch.d.xmlexpr.named_argnull[off], NULL); off++; } @@ -2267,7 +2303,7 @@ ExecInitExprRec(Expr *node, ExprState *state, ExecInitExprRec(e, state, &scratch.d.xmlexpr.argvalue[off], - &scratch.d.xmlexpr.argnull[off]); + &scratch.d.xmlexpr.argnull[off], NULL); off++; } @@ -2304,7 +2340,7 @@ ExecInitExprRec(Expr *node, ExprState *state, /* first evaluate argument into result variable */ ExecInitExprRec(ntest->arg, state, - resv, resnull); + resv, resnull, NULL); /* then push the test of that argument */ ExprEvalPushStep(state, &scratch); @@ -2321,7 +2357,7 @@ ExecInitExprRec(Expr *node, ExprState *state, * and will get overwritten by the below EEOP_BOOLTEST_IS_* * step. */ - ExecInitExprRec(btest->arg, state, resv, resnull); + ExecInitExprRec(btest->arg, state, resv, resnull, NULL); switch (btest->booltesttype) { @@ -2359,7 +2395,7 @@ ExecInitExprRec(Expr *node, ExprState *state, CoerceToDomain *ctest = (CoerceToDomain *) node; ExecInitCoerceToDomain(&scratch, ctest, state, - resv, resnull); + resv, resnull, NULL); break; } @@ -2442,7 +2478,7 @@ ExprEvalPushStep(ExprState *es, const ExprEvalStep *s) */ static void ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid, - Oid inputcollid, ExprState *state) + Oid inputcollid, ExprState *state, ErrorSaveContext *escontext) { int nargs = list_length(args); AclResult aclresult; @@ -2483,7 +2519,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, (fmNodePtr) escontext, NULL); /* Keep extra copies of this info to save an indirection at runtime */ scratch->d.func.fn_addr = flinfo->fn_addr; @@ -2519,7 +2555,7 @@ ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid, { ExecInitExprRec(arg, state, &fcinfo->args[argno].value, - &fcinfo->args[argno].isnull); + &fcinfo->args[argno].isnull, NULL); } argno++; } @@ -2570,6 +2606,7 @@ ExecPushExprSlots(ExprState *state, LastAttnumInfo *info) scratch.resvalue = NULL; scratch.resnull = NULL; + scratch.reserror = NULL; /* Emit steps as needed */ if (info->last_inner > 0) @@ -2834,7 +2871,7 @@ ExecInitWholeRowVar(ExprEvalStep *scratch, Var *variable, ExprState *state) */ static void ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref, - ExprState *state, Datum *resv, bool *resnull) + ExprState *state, Datum *resv, bool *resnull, bool *reserror) { bool isAssignment = (sbsref->refassgnexpr != NULL); int nupper = list_length(sbsref->refupperindexpr); @@ -2897,7 +2934,7 @@ ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref, * be overwritten by the final EEOP_SBSREF_FETCH/ASSIGN step, which is * pushed last. */ - ExecInitExprRec(sbsref->refexpr, state, resv, resnull); + ExecInitExprRec(sbsref->refexpr, state, resv, resnull, NULL); /* * If refexpr yields NULL, and the operation should be strict, then result @@ -2931,7 +2968,7 @@ ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref, /* Each subscript is evaluated into appropriate array entry */ ExecInitExprRec(e, state, &sbsrefstate->upperindex[i], - &sbsrefstate->upperindexnull[i]); + &sbsrefstate->upperindexnull[i], NULL); } i++; } @@ -2954,7 +2991,7 @@ ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref, /* Each subscript is evaluated into appropriate array entry */ ExecInitExprRec(e, state, &sbsrefstate->lowerindex[i], - &sbsrefstate->lowerindexnull[i]); + &sbsrefstate->lowerindexnull[i], NULL); } i++; } @@ -3018,7 +3055,7 @@ ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref, /* evaluate replacement value into replacevalue/replacenull */ ExecInitExprRec(sbsref->refassgnexpr, state, - &sbsrefstate->replacevalue, &sbsrefstate->replacenull); + &sbsrefstate->replacevalue, &sbsrefstate->replacenull, NULL); state->innermost_caseval = save_innermost_caseval; state->innermost_casenull = save_innermost_casenull; @@ -3107,11 +3144,12 @@ isAssignmentIndirectionExpr(Expr *expr) */ static void ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest, - ExprState *state, Datum *resv, bool *resnull) + ExprState *state, Datum *resv, bool *resnull, bool *reserror) { DomainConstraintRef *constraint_ref; Datum *domainval = NULL; bool *domainnull = NULL; + bool *domainerror = NULL; ListCell *l; scratch->d.domaincheck.resulttype = ctest->resulttype; @@ -3124,7 +3162,7 @@ ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest, * if there's constraint failures there'll be errors, otherwise it's what * needs to be returned. */ - ExecInitExprRec(ctest->arg, state, resv, resnull); + ExecInitExprRec(ctest->arg, state, resv, resnull, NULL); /* * Note: if the argument is of varlena type, it could be a R/W expanded @@ -3196,11 +3234,13 @@ ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest, /* Yes, so make output workspace for MAKE_READONLY */ domainval = (Datum *) palloc(sizeof(Datum)); domainnull = (bool *) palloc(sizeof(bool)); + domainerror = (bool *) palloc(sizeof(bool)); /* Emit MAKE_READONLY */ scratch2.opcode = EEOP_MAKE_READONLY; scratch2.resvalue = domainval; scratch2.resnull = domainnull; + scratch2.reserror = domainerror; scratch2.d.make_readonly.value = resv; scratch2.d.make_readonly.isnull = resnull; ExprEvalPushStep(state, &scratch2); @@ -3227,7 +3267,7 @@ ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest, /* evaluate check expression value */ ExecInitExprRec(con->check_expr, state, scratch->d.domaincheck.checkvalue, - scratch->d.domaincheck.checknull); + scratch->d.domaincheck.checknull, NULL); state->innermost_domainval = save_innermost_domainval; state->innermost_domainnull = save_innermost_domainnull; @@ -3319,7 +3359,7 @@ ExecBuildAggTrans(AggState *aggstate, AggStatePerPhase phase, { /* evaluate filter expression */ ExecInitExprRec(pertrans->aggref->aggfilter, state, - &state->resvalue, &state->resnull); + &state->resvalue, &state->resnull, NULL); /* and jump out if false */ scratch.opcode = EEOP_JUMP_IF_NOT_TRUE; scratch.d.jump.jumpdone = -1; /* adjust later */ @@ -3359,7 +3399,7 @@ ExecBuildAggTrans(AggState *aggstate, AggStatePerPhase phase, */ ExecInitExprRec(source_tle->expr, state, &trans_fcinfo->args[argno + 1].value, - &trans_fcinfo->args[argno + 1].isnull); + &trans_fcinfo->args[argno + 1].isnull, NULL); } else { @@ -3368,7 +3408,7 @@ ExecBuildAggTrans(AggState *aggstate, AggStatePerPhase phase, /* evaluate argument */ ExecInitExprRec(source_tle->expr, state, &ds_fcinfo->args[0].value, - &ds_fcinfo->args[0].isnull); + &ds_fcinfo->args[0].isnull, NULL); /* Dummy second argument for type-safety reasons */ ds_fcinfo->args[1].value = PointerGetDatum(NULL); @@ -3430,7 +3470,7 @@ ExecBuildAggTrans(AggState *aggstate, AggStatePerPhase phase, */ ExecInitExprRec(source_tle->expr, state, &trans_fcinfo->args[argno + 1].value, - &trans_fcinfo->args[argno + 1].isnull); + &trans_fcinfo->args[argno + 1].isnull, NULL); argno++; } Assert(pertrans->numTransInputs == argno); @@ -3448,7 +3488,7 @@ ExecBuildAggTrans(AggState *aggstate, AggStatePerPhase phase, ExecInitExprRec(source_tle->expr, state, &state->resvalue, - &state->resnull); + &state->resnull, NULL); strictnulls = &state->resnull; argno++; @@ -3471,7 +3511,7 @@ ExecBuildAggTrans(AggState *aggstate, AggStatePerPhase phase, TargetEntry *source_tle = (TargetEntry *) lfirst(arg); ExecInitExprRec(source_tle->expr, state, - &values[argno], &nulls[argno]); + &values[argno], &nulls[argno], NULL); argno++; } Assert(pertrans->numInputs == argno); @@ -3585,6 +3625,7 @@ ExecBuildAggTrans(AggState *aggstate, AggStatePerPhase phase, scratch.resvalue = NULL; scratch.resnull = NULL; + scratch.reserror = NULL; scratch.opcode = EEOP_DONE; ExprEvalPushStep(state, &scratch); diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index 1dab2787b7..6cc6fa1717 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -64,6 +64,7 @@ #include "funcapi.h" #include "miscadmin.h" #include "nodes/nodeFuncs.h" +#include "nodes/miscnodes.h" #include "parser/parsetree.h" #include "pgstat.h" #include "utils/array.h" @@ -434,6 +435,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) &&CASE_EEOP_JUMP, &&CASE_EEOP_JUMP_IF_NULL, &&CASE_EEOP_JUMP_IF_NOT_NULL, + &&CASE_EEOP_JUMP_IF_NOT_ERROR, &&CASE_EEOP_JUMP_IF_NOT_TRUE, &&CASE_EEOP_NULLTEST_ISNULL, &&CASE_EEOP_NULLTEST_ISNOTNULL, @@ -729,6 +731,13 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) *op->resvalue = d; *op->resnull = fcinfo->isnull; + if (SOFT_ERROR_OCCURRED(fcinfo->context)) + { + ErrorSaveContext *escontext = (ErrorSaveContext *) fcinfo->context; + escontext->error_occurred = false; + *op->reserror = true; + } + EEO_NEXT(); } @@ -966,6 +975,21 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) EEO_NEXT(); } + EEO_CASE(EEOP_JUMP_IF_NOT_ERROR) + { + /* Transfer control if current result is non-error */ + if (!*op->reserror) + { + *op->reserror = false; + EEO_JUMP(op->d.jump.jumpdone); + } + + /* reset error flag */ + *op->reserror = false; + + EEO_NEXT(); + } + EEO_CASE(EEOP_JUMP_IF_NOT_TRUE) { /* Transfer control if current result is null or false */ @@ -1181,10 +1205,17 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) fcinfo_in->isnull = false; d = FunctionCallInvoke(fcinfo_in); + *op->resvalue = d; - /* Should get null result if and only if str is NULL */ - if (str == NULL) + if (SOFT_ERROR_OCCURRED(fcinfo_in->context)) + { + ErrorSaveContext *escontext = (ErrorSaveContext *) fcinfo_in->context; + escontext->error_occurred = false; + *op->reserror = true; + } + /* If no error, should get null result if and only if str is NULL */ + else if (str == NULL) { Assert(*op->resnull); Assert(fcinfo_in->isnull); diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c index f114337f8e..f99b108ceb 100644 --- a/src/backend/jit/llvm/llvmjit_expr.c +++ b/src/backend/jit/llvm/llvmjit_expr.c @@ -919,6 +919,21 @@ llvm_compile_expr(ExprState *state) break; } + case EEOP_JUMP_IF_NOT_ERROR: + { + LLVMValueRef v_reserror; + + /* Transfer control if current result is non-error */ + + v_resnull = LLVMBuildLoad(b, v_reserrorp, ""); + + LLVMBuildCondBr(b, + LLVMBuildICmp(b, LLVMIntEQ, v_reserror, + l_sbool_const(0), ""), + opblocks[op->d.jump.jumpdone], + opblocks[opno + 1]); + break; + } case EEOP_JUMP_IF_NOT_TRUE: { diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c index c85d8fe975..229d1e84c8 100644 --- a/src/backend/nodes/makefuncs.c +++ b/src/backend/nodes/makefuncs.c @@ -517,7 +517,8 @@ makeColumnDef(const char *colname, Oid typeOid, int32 typmod, Oid collOid) */ FuncExpr * makeFuncExpr(Oid funcid, Oid rettype, List *args, - Oid funccollid, Oid inputcollid, CoercionForm fformat) + Oid funccollid, Oid inputcollid, CoercionForm fformat, + bool safe_mode) { FuncExpr *funcexpr; @@ -530,6 +531,7 @@ makeFuncExpr(Oid funcid, Oid rettype, List *args, funcexpr->funccollid = funccollid; funcexpr->inputcollid = inputcollid; funcexpr->args = args; + funcexpr->safe_mode = safe_mode; funcexpr->location = -1; return funcexpr; diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index af8620ceb7..7bc3e13208 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -1522,13 +1522,16 @@ exprLocation(const Node *expr) { const TypeCast *tc = (const TypeCast *) expr; - /* - * This could represent CAST(), ::, or TypeName 'literal', so - * any of the components might be leftmost. - */ loc = exprLocation(tc->arg); - loc = leftmostLoc(loc, tc->typeName->location); - loc = leftmostLoc(loc, tc->location); + if (likely(!tc->safe_mode)) + { + /* + * This could represent CAST(), ::, or TypeName 'literal', + * so any of the components might be leftmost. + */ + loc = leftmostLoc(loc, tc->typeName->location); + loc = leftmostLoc(loc, tc->location); + } } break; case T_CollateClause: diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index bffc8112aa..e0b0ffb9a6 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -2894,6 +2894,7 @@ eval_const_expressions_mutator(Node *node, newexpr->resulttype = expr->resulttype; newexpr->resultcollid = expr->resultcollid; newexpr->coerceformat = expr->coerceformat; + newexpr->safe_mode = expr->safe_mode; newexpr->location = expr->location; return (Node *) newexpr; } @@ -3158,7 +3159,7 @@ eval_const_expressions_mutator(Node *node, * drop following arguments since they will never be * reached. */ - if (IsA(e, Const)) + if ((coalesceexpr->op == NULL_TEST) && IsA(e, Const)) { if (((Const *) e)->constisnull) continue; /* drop null constant */ @@ -3183,6 +3184,7 @@ eval_const_expressions_mutator(Node *node, newcoalesce->coalescetype = coalesceexpr->coalescetype; newcoalesce->coalescecollid = coalesceexpr->coalescecollid; newcoalesce->args = newargs; + newcoalesce->op = coalesceexpr->op; newcoalesce->location = coalesceexpr->location; return (Node *) newcoalesce; } diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index adc3f8ced3..3e24fe5f70 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 PartitionBoundSpec %type hash_partbound %type hash_partbound_elem +%type cast_on_error_clause +%type cast_on_error_action /* @@ -690,7 +692,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP - EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT + EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ERROR_P ESCAPE EVENT EXCEPT EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN EXPRESSION EXTENSION EXTERNAL EXTRACT @@ -14399,8 +14401,7 @@ interval_second: * you expect! So we use %prec annotations freely to set precedences. */ a_expr: c_expr { $$ = $1; } - | a_expr TYPECAST Typename - { $$ = makeTypeCast($1, $3, @2); } + | a_expr TYPECAST Typename { $$ = makeTypeCast($1, $3, @2); } | a_expr COLLATE any_name { CollateClause *n = makeNode(CollateClause); @@ -15330,8 +15331,26 @@ func_expr_common_subexpr: COERCE_SQL_SYNTAX, @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 + { + /* + * On-error actions must themselves be typecast to the + * same type as the original expression. + */ + TypeCast *on_err = (TypeCast *) makeTypeCast($6, $5, @6); + CoalesceExpr *c = makeNode(CoalesceExpr); + cast->safe_mode = true; + c->args = list_make2(cast, on_err); + c->op = ERROR_TEST; + c->location = @1; + $$ = (Node *) c; + } + } | EXTRACT '(' extract_list ')' { $$ = (Node *) makeFuncCall(SystemFuncName("extract"), @@ -15462,6 +15481,7 @@ func_expr_common_subexpr: CoalesceExpr *c = makeNode(CoalesceExpr); c->args = $3; + c->op = NULL_TEST; c->location = @1; $$ = (Node *) c; } @@ -15551,6 +15571,15 @@ func_expr_common_subexpr: } ; +cast_on_error_clause: cast_on_error_action ON ERROR_P { $$ = $1; } + | /* EMPTY */ { $$ = NULL; } + ; + +cast_on_error_action: ERROR_P { $$ = NULL; } + | NULL_P { $$ = makeNullAConst(-1); } + | DEFAULT a_expr { $$ = $2; } + ; + /* * SQL/XML support */ @@ -16138,8 +16167,8 @@ substr_list: * is unknown or doesn't have an implicit cast to int4. */ $$ = list_make3($1, makeIntConst(1, -1), - makeTypeCast($3, - SystemTypeName("int4"), -1)); + makeTypeCast($3, SystemTypeName("int4"), + -1)); } | a_expr SIMILAR a_expr ESCAPE a_expr { @@ -16799,6 +16828,7 @@ unreserved_keyword: | ENCODING | ENCRYPTED | ENUM_P + | ERROR_P | ESCAPE | EVENT | EXCLUDE @@ -17346,6 +17376,7 @@ bare_label_keyword: | ENCRYPTED | END_P | ENUM_P + | ERROR_P | ESCAPE | EVENT | EXCLUDE @@ -17749,6 +17780,7 @@ makeTypeCast(Node *arg, TypeName *typename, int location) n->arg = arg; n->typeName = typename; + n->safe_mode = false; n->location = location; return (Node *) n; } diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c index 3ef9e8ee5e..b08422297d 100644 --- a/src/backend/parser/parse_agg.c +++ b/src/backend/parser/parse_agg.c @@ -2004,7 +2004,8 @@ build_aggregate_transfn_expr(Oid *agg_input_types, args, InvalidOid, agg_input_collation, - COERCE_EXPLICIT_CALL); + COERCE_EXPLICIT_CALL, + NULL); fexpr->funcvariadic = agg_variadic; *transfnexpr = (Expr *) fexpr; @@ -2020,7 +2021,8 @@ build_aggregate_transfn_expr(Oid *agg_input_types, args, InvalidOid, agg_input_collation, - COERCE_EXPLICIT_CALL); + COERCE_EXPLICIT_CALL, + NULL); fexpr->funcvariadic = agg_variadic; *invtransfnexpr = (Expr *) fexpr; } @@ -2048,7 +2050,8 @@ build_aggregate_serialfn_expr(Oid serialfn_oid, args, InvalidOid, InvalidOid, - COERCE_EXPLICIT_CALL); + COERCE_EXPLICIT_CALL, + NULL); *serialfnexpr = (Expr *) fexpr; } @@ -2072,7 +2075,8 @@ build_aggregate_deserialfn_expr(Oid deserialfn_oid, args, InvalidOid, InvalidOid, - COERCE_EXPLICIT_CALL); + COERCE_EXPLICIT_CALL, + NULL); *deserialfnexpr = (Expr *) fexpr; } @@ -2109,7 +2113,8 @@ build_aggregate_finalfn_expr(Oid *agg_input_types, args, InvalidOid, agg_input_collation, - COERCE_EXPLICIT_CALL); + COERCE_EXPLICIT_CALL, + NULL); /* finalfn is currently never treated as variadic */ } diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c index 60908111c8..c00ef1bd70 100644 --- a/src/backend/parser/parse_coerce.c +++ b/src/backend/parser/parse_coerce.c @@ -36,13 +36,15 @@ static Node *coerce_type_typmod(Node *node, Oid targetTypeId, int32 targetTypMod, CoercionContext ccontext, CoercionForm cformat, int location, - bool hideInputCoercion); + bool hideInputCoercion, + bool *cast_error_found); static void hide_coercion_node(Node *node); static Node *build_coercion_expression(Node *node, CoercionPathType pathtype, Oid funcId, Oid targetTypeId, int32 targetTypMod, CoercionContext ccontext, CoercionForm cformat, + bool *cast_error_found, int location); static Node *coerce_record_to_complex(ParseState *pstate, Node *node, Oid targetTypeId, @@ -80,6 +82,19 @@ coerce_to_target_type(ParseState *pstate, Node *expr, Oid exprtype, CoercionContext ccontext, CoercionForm cformat, int location) +{ + return coerce_to_target_type_safe(pstate, expr, exprtype, targettype, + targettypmod, ccontext, cformat, + NULL, location); +} + +Node * +coerce_to_target_type_safe(ParseState *pstate, Node *expr, Oid exprtype, + Oid targettype, int32 targettypmod, + CoercionContext ccontext, + CoercionForm cformat, + bool *cast_error_found, + int location) { Node *result; Node *origexpr; @@ -101,19 +116,30 @@ coerce_to_target_type(ParseState *pstate, Node *expr, Oid exprtype, while (expr && IsA(expr, CollateExpr)) expr = (Node *) ((CollateExpr *) expr)->arg; - result = coerce_type(pstate, expr, exprtype, - targettype, targettypmod, - ccontext, cformat, location); + result = coerce_type_safe(pstate, expr, exprtype, + targettype, targettypmod, + ccontext, cformat, + cast_error_found, location); + + /* + * If this coercion failed on bad input, we want to report that to the + * caller. + */ + if (cast_error_found != NULL && *cast_error_found) + return NULL; /* * If the target is a fixed-length type, it may need a length coercion as * well as a type coercion. If we find ourselves adding both, force the * inner coercion node to implicit display form. */ - result = coerce_type_typmod(result, - targettype, targettypmod, + result = coerce_type_typmod(result, targettype, targettypmod, ccontext, cformat, location, - (result != expr && !IsA(result, Const))); + (result != expr && !IsA(result, Const)), + cast_error_found); + + if (cast_error_found != NULL && *cast_error_found) + return NULL; if (expr != origexpr && type_is_collatable(targettype)) { @@ -157,6 +183,18 @@ Node * coerce_type(ParseState *pstate, Node *node, Oid inputTypeId, Oid targetTypeId, int32 targetTypeMod, CoercionContext ccontext, CoercionForm cformat, int location) +{ + return coerce_type_safe(pstate, node, inputTypeId, targetTypeId, + targetTypeMod, ccontext, cformat, + NULL, location); +} + +Node * +coerce_type_safe(ParseState *pstate, Node *node, + Oid inputTypeId, Oid targetTypeId, int32 targetTypeMod, + CoercionContext ccontext, CoercionForm cformat, + bool *cast_error_found, + int location) { Node *result; CoercionPathType pathtype; @@ -255,6 +293,7 @@ coerce_type(ParseState *pstate, Node *node, int32 inputTypeMod; Type baseType; ParseCallbackState pcbstate; + bool coerce_failed = false; /* * If the target type is a domain, we want to call its base type's @@ -307,21 +346,47 @@ coerce_type(ParseState *pstate, Node *node, * We assume here that UNKNOWN's internal representation is the same * as CSTRING. */ - if (!con->constisnull) - newcon->constvalue = stringTypeDatum(baseType, - DatumGetCString(con->constvalue), - inputTypeMod); + if (cast_error_found == NULL) + { + if (!con->constisnull) + newcon->constvalue = stringTypeDatum(baseType, + DatumGetCString(con->constvalue), + inputTypeMod); + else + newcon->constvalue = stringTypeDatum(baseType, + NULL, + inputTypeMod); + } else - newcon->constvalue = stringTypeDatum(baseType, - NULL, - inputTypeMod); + { + /* If we find an error, just carry it up to the caller */ + Datum val2; + bool success; + + if (!con->constisnull) + success = stringTypeDatumSafe(baseType, + DatumGetCString(con->constvalue), + inputTypeMod, &val2); + else + success = stringTypeDatumSafe(baseType, NULL, inputTypeMod, + &val2); + + if (success) + newcon->constvalue = val2; + else + { + *cast_error_found = true; + coerce_failed = true; + result = NULL; + } + } /* * If it's a varlena value, force it to be in non-expanded * (non-toasted) format; this avoids any possible dependency on * external values and improves consistency of representation. */ - if (!con->constisnull && newcon->constlen == -1) + if (!coerce_failed && !con->constisnull && newcon->constlen == -1) newcon->constvalue = PointerGetDatum(PG_DETOAST_DATUM(newcon->constvalue)); @@ -338,32 +403,53 @@ coerce_type(ParseState *pstate, Node *node, * identical may not get recognized as such. See pgsql-hackers * discussion of 2008-04-04. */ - if (!con->constisnull && !newcon->constbyval) + if (!coerce_failed && !con->constisnull && !newcon->constbyval) { Datum val2; - val2 = stringTypeDatum(baseType, - DatumGetCString(con->constvalue), - inputTypeMod); - if (newcon->constlen == -1) - val2 = PointerGetDatum(PG_DETOAST_DATUM(val2)); - if (!datumIsEqual(newcon->constvalue, val2, false, newcon->constlen)) - elog(WARNING, "type %s has unstable input conversion for \"%s\"", - typeTypeName(baseType), DatumGetCString(con->constvalue)); + if (cast_error_found != NULL) + { + if (!stringTypeDatumSafe(baseType, + DatumGetCString(con->constvalue), + inputTypeMod, &val2)) + { + coerce_failed = true; + *cast_error_found = true; + result = NULL; + } + } + else + val2 = stringTypeDatum(baseType, + DatumGetCString(con->constvalue), + inputTypeMod); + if (!coerce_failed) + { + if (newcon->constlen == -1) + val2 = PointerGetDatum(PG_DETOAST_DATUM(val2)); + if (!datumIsEqual(newcon->constvalue, val2, + false, newcon->constlen)) + elog(WARNING, + "type %s has unstable input conversion for \"%s\"", + typeTypeName(baseType), + DatumGetCString(con->constvalue)); + } } #endif cancel_parser_errposition_callback(&pcbstate); - result = (Node *) newcon; + if (!coerce_failed) + { + result = (Node *) newcon; - /* If target is a domain, apply constraints. */ - if (baseTypeId != targetTypeId) - result = coerce_to_domain(result, - baseTypeId, baseTypeMod, - targetTypeId, - ccontext, cformat, location, - false); + /* If target is a domain, apply constraints. */ + if (baseTypeId != targetTypeId) + result = coerce_to_domain_safe(result, + baseTypeId, baseTypeMod, + targetTypeId, + ccontext, cformat, location, + false, cast_error_found); + } ReleaseSysCache(baseType); @@ -396,9 +482,15 @@ coerce_type(ParseState *pstate, Node *node, */ CollateExpr *coll = (CollateExpr *) node; - result = coerce_type(pstate, (Node *) coll->arg, - inputTypeId, targetTypeId, targetTypeMod, - ccontext, cformat, location); + result = coerce_type_safe(pstate, (Node *) coll->arg, + inputTypeId, targetTypeId, targetTypeMod, + ccontext, cformat, + cast_error_found, + location); + + if (cast_error_found != NULL && *cast_error_found) + return NULL; + if (type_is_collatable(targetTypeId)) { CollateExpr *newcoll = makeNode(CollateExpr); @@ -431,17 +523,23 @@ coerce_type(ParseState *pstate, Node *node, result = build_coercion_expression(node, pathtype, funcId, baseTypeId, baseTypeMod, - ccontext, cformat, location); + ccontext, cformat, + cast_error_found, + location); /* * If domain, coerce to the domain type and relabel with domain * type ID, hiding the previous coercion node. */ if (targetTypeId != baseTypeId) - result = coerce_to_domain(result, baseTypeId, baseTypeMod, + { + result = coerce_to_domain_safe(result, baseTypeId, baseTypeMod, targetTypeId, ccontext, cformat, location, - true); + true, cast_error_found); + if (cast_error_found != NULL && *cast_error_found) + return NULL; + } } else { @@ -454,9 +552,11 @@ 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, InvalidOid, -1, targetTypeId, + result = coerce_to_domain_safe(node, InvalidOid, -1, targetTypeId, ccontext, cformat, location, - false); + false, cast_error_found); + if (cast_error_found != NULL && *cast_error_found) + return NULL; if (result == node) { /* @@ -676,6 +776,17 @@ Node * coerce_to_domain(Node *arg, Oid baseTypeId, int32 baseTypeMod, Oid typeId, CoercionContext ccontext, CoercionForm cformat, int location, bool hideInputCoercion) +{ + return coerce_to_domain_safe(arg, baseTypeId, baseTypeMod, typeId, + ccontext, cformat, location, + hideInputCoercion, NULL); +} + +Node * +coerce_to_domain_safe(Node *arg, Oid baseTypeId, int32 baseTypeMod, Oid typeId, + CoercionContext ccontext, CoercionForm cformat, + int location, bool hideInputCoercion, + bool *cast_error_found) { CoerceToDomain *result; @@ -706,7 +817,7 @@ coerce_to_domain(Node *arg, Oid baseTypeId, int32 baseTypeMod, Oid typeId, */ arg = coerce_type_typmod(arg, baseTypeId, baseTypeMod, ccontext, COERCE_IMPLICIT_CAST, location, - false); + false, cast_error_found); /* * Now build the domain coercion node. This represents run-time checking @@ -753,7 +864,7 @@ static Node * coerce_type_typmod(Node *node, Oid targetTypeId, int32 targetTypMod, CoercionContext ccontext, CoercionForm cformat, int location, - bool hideInputCoercion) + bool hideInputCoercion, bool *cast_error_found) { CoercionPathType pathtype; Oid funcId; @@ -780,7 +891,9 @@ coerce_type_typmod(Node *node, Oid targetTypeId, int32 targetTypMod, { node = build_coercion_expression(node, pathtype, funcId, targetTypeId, targetTypMod, - ccontext, cformat, location); + ccontext, cformat, + cast_error_found, + location); } else { @@ -841,9 +954,10 @@ build_coercion_expression(Node *node, Oid funcId, Oid targetTypeId, int32 targetTypMod, CoercionContext ccontext, CoercionForm cformat, - int location) + bool *cast_error_found, int location) { int nargs = 0; + bool safe_mode = (cast_error_found != NULL); if (OidIsValid(funcId)) { @@ -913,13 +1027,14 @@ build_coercion_expression(Node *node, } fexpr = makeFuncExpr(funcId, targetTypeId, args, - InvalidOid, InvalidOid, cformat); + InvalidOid, InvalidOid, cformat, safe_mode); fexpr->location = location; return (Node *) fexpr; } else if (pathtype == COERCION_PATH_ARRAYCOERCE) { /* We need to build an ArrayCoerceExpr */ + /* TODO pass in Safe param */ ArrayCoerceExpr *acoerce = makeNode(ArrayCoerceExpr); CaseTestExpr *ctest = makeNode(CaseTestExpr); Oid sourceBaseTypeId; @@ -951,14 +1066,20 @@ build_coercion_expression(Node *node, targetElementType = get_element_type(targetTypeId); Assert(OidIsValid(targetElementType)); - elemexpr = coerce_to_target_type(NULL, + /* + * No node gets resolved here, so this cast cannot fail + * at parse time. + */ + elemexpr = coerce_to_target_type_safe(NULL, (Node *) ctest, ctest->typeId, targetElementType, targetTypMod, ccontext, cformat, + cast_error_found, location); + if (elemexpr == NULL) /* shouldn't happen */ elog(ERROR, "failed to coerce array element type as expected"); @@ -973,6 +1094,7 @@ build_coercion_expression(Node *node, acoerce->resulttypmod = exprTypmod(elemexpr); /* resultcollid will be set by parse_collate.c */ acoerce->coerceformat = cformat; + acoerce->safe_mode = safe_mode; acoerce->location = location; return (Node *) acoerce; @@ -988,6 +1110,7 @@ build_coercion_expression(Node *node, iocoerce->resulttype = targetTypeId; /* resultcollid will be set by parse_collate.c */ iocoerce->coerceformat = cformat; + iocoerce->safe_mode = safe_mode; iocoerce->location = location; return (Node *) iocoerce; diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 150a8099c2..54224b9f06 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -57,7 +57,8 @@ 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 *cast_error_found); static Node *transformRowExpr(ParseState *pstate, RowExpr *r, bool allowDefault); static Node *transformCoalesceExpr(ParseState *pstate, CoalesceExpr *c); static Node *transformMinMaxExpr(ParseState *pstate, MinMaxExpr *m); @@ -137,7 +138,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: @@ -1907,7 +1908,8 @@ 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 *cast_error_found) { ArrayExpr *newa = makeNode(ArrayExpr); List *newelems = NIL; @@ -1938,7 +1940,12 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a, (A_ArrayExpr *) e, array_type, element_type, - typmod); + typmod, + cast_error_found); + + if (cast_error_found != NULL && *cast_error_found) + return NULL; + /* we certainly have an array here */ Assert(array_type == InvalidOid || array_type == exprType(newe)); newa->multidims = true; @@ -2027,13 +2034,18 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a, if (coerce_hard) { - newe = coerce_to_target_type(pstate, e, + newe = coerce_to_target_type_safe(pstate, e, exprType(e), coerce_type, typmod, COERCION_EXPLICIT, COERCE_EXPLICIT_CAST, + cast_error_found, -1); + + if (cast_error_found != NULL && *cast_error_found) + return NULL; + if (newe == NULL) ereport(ERROR, (errcode(ERRCODE_CANNOT_COERCE), @@ -2105,13 +2117,19 @@ transformCoalesceExpr(ParseState *pstate, CoalesceExpr *c) List *newcoercedargs = NIL; ListCell *args; + foreach(args, c->args) { Node *e = (Node *) lfirst(args); Node *newe; newe = transformExprRecurse(pstate, e); - newargs = lappend(newargs, newe); + /* + * Some child nodes can return NULL if they would result in a + * safe_mode error. Filter those out here + */ + if (newe != NULL) + newargs = lappend(newargs, newe); } newc->coalescetype = select_common_type(pstate, newargs, "COALESCE", NULL); @@ -2141,6 +2159,7 @@ transformCoalesceExpr(ParseState *pstate, CoalesceExpr *c) exprLocation(pstate->p_last_srf)))); newc->args = newcoercedargs; + newc->op = c->op; newc->location = c->location; return (Node *) newc; } @@ -2345,8 +2364,7 @@ transformXmlSerialize(ParseState *pstate, XmlSerialize *xs) result = coerce_to_target_type(pstate, (Node *) xexpr, TEXTOID, targetType, targetTypmod, COERCION_IMPLICIT, - COERCE_IMPLICIT_CAST, - -1); + COERCE_IMPLICIT_CAST, -1); if (result == NULL) ereport(ERROR, (errcode(ERRCODE_CANNOT_COERCE), @@ -2524,10 +2542,25 @@ transformTypeCast(ParseState *pstate, TypeCast *tc) Oid inputType; Oid targetType; int32 targetTypmod; - int location; + int location = tc->location; + TypeName *typeName = tc->typeName; + bool cast_error; + bool *cast_error_found; + + + if (tc->safe_mode) + { + cast_error = false; + cast_error_found = &cast_error; + } + else + { + cast_error_found = NULL; + } + /* Look up the type name first */ - typenameTypeIdAndMod(pstate, tc->typeName, &targetType, &targetTypmod); + typenameTypeIdAndMod(pstate, typeName, &targetType, &targetTypmod); /* * If the subject of the typecast is an ARRAY[] construct and the target @@ -2557,7 +2590,16 @@ transformTypeCast(ParseState *pstate, TypeCast *tc) (A_ArrayExpr *) arg, targetBaseType, elementType, - targetBaseTypmod); + targetBaseTypmod, + cast_error_found); + + /* + * if any element of the tranformation failed, then that means that the + * larger cast has failed. Returning NULL here is safe when the + * parent node (CoalesceExpr) knows to look for it (and filter it out). + */ + if (cast_error_found != NULL && *cast_error_found) + return NULL; } else expr = transformExprRecurse(pstate, arg); @@ -2574,15 +2616,24 @@ transformTypeCast(ParseState *pstate, TypeCast *tc) * 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 = tc->location; if (location < 0) - location = tc->typeName->location; + location = typeName->location; + + result = coerce_to_target_type_safe(pstate, expr, inputType, + targetType, targetTypmod, + COERCION_EXPLICIT, + COERCE_EXPLICIT_CAST, + cast_error_found, + location); + + /* + * If this typecast reported a cast error, and the cast failed outright, + * then we want to replace this node entirely with the default, + * and we signal the parent node to do that by returning NULL. + */ + if (cast_error_found != NULL && *cast_error_found) + return NULL; - result = coerce_to_target_type(pstate, expr, inputType, - targetType, targetTypmod, - COERCION_EXPLICIT, - COERCE_EXPLICIT_CAST, - location); if (result == NULL) ereport(ERROR, (errcode(ERRCODE_CANNOT_COERCE), diff --git a/src/backend/partitioning/partbounds.c b/src/backend/partitioning/partbounds.c index 29643fb4ab..92a3c60955 100644 --- a/src/backend/partitioning/partbounds.c +++ b/src/backend/partitioning/partbounds.c @@ -4049,7 +4049,8 @@ get_qual_for_hash(Relation parent, PartitionBoundSpec *spec) args, InvalidOid, InvalidOid, - COERCE_EXPLICIT_CALL); + COERCE_EXPLICIT_CALL, + NULL); return list_make1(fexpr); } diff --git a/src/backend/rewrite/rewriteSearchCycle.c b/src/backend/rewrite/rewriteSearchCycle.c index 58f684cd52..ae93c06702 100644 --- a/src/backend/rewrite/rewriteSearchCycle.c +++ b/src/backend/rewrite/rewriteSearchCycle.c @@ -191,7 +191,7 @@ make_path_cat_expr(RowExpr *rowexpr, AttrNumber path_varattno) fexpr = makeFuncExpr(F_ARRAY_CAT, RECORDARRAYOID, list_make2(makeVar(1, path_varattno, RECORDARRAYOID, -1, 0, 0), arr), - InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); + InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL, NULL); return (Expr *) fexpr; } @@ -521,7 +521,7 @@ rewriteSearchAndCycle(CommonTableExpr *cte) fs->resulttype = INT8OID; fs->resulttypmod = -1; - fexpr = makeFuncExpr(F_INT8INC, INT8OID, list_make1(fs), InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); + fexpr = makeFuncExpr(F_INT8INC, INT8OID, list_make1(fs), InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL, NULL); lfirst(list_head(search_col_rowexpr->args)) = fexpr; diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h index 0557302b92..3ab5f8b000 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -138,6 +138,7 @@ typedef enum ExprEvalOp /* conditional jumps based on current result value */ EEOP_JUMP_IF_NULL, EEOP_JUMP_IF_NOT_NULL, + EEOP_JUMP_IF_NOT_ERROR, EEOP_JUMP_IF_NOT_TRUE, /* perform NULL tests for scalar values */ @@ -273,6 +274,7 @@ typedef struct ExprEvalStep /* where to store the result of this step */ Datum *resvalue; bool *resnull; + bool *reserror; /* * Inline data for the operation. Inline data is faster to access, but @@ -395,6 +397,7 @@ typedef struct ExprEvalStep { Datum *value; /* value to return */ bool *isnull; + bool *iserror; } casetest; /* for EEOP_MAKE_READONLY */ @@ -402,6 +405,7 @@ typedef struct ExprEvalStep { Datum *value; /* value to coerce to read-only */ bool *isnull; + bool *iserror; } make_readonly; /* for EEOP_IOCOERCE */ diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 9a64a830a2..fb9b2f7963 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -95,6 +95,9 @@ typedef struct ExprState #define FIELDNO_EXPRSTATE_RESULTSLOT 4 TupleTableSlot *resultslot; +#define FIELDNO_EXPRSTATE_RESERROR 5 + bool reserror; + /* * Instructions to compute expression's return value. */ @@ -126,6 +129,7 @@ typedef struct ExprState Datum *innermost_caseval; bool *innermost_casenull; + bool *innermost_caseerror; Datum *innermost_domainval; bool *innermost_domainnull; diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h index 50de4c62af..50a9bdde59 100644 --- a/src/include/nodes/makefuncs.h +++ b/src/include/nodes/makefuncs.h @@ -77,7 +77,8 @@ extern ColumnDef *makeColumnDef(const char *colname, Oid typeOid, int32 typmod, Oid collOid); extern FuncExpr *makeFuncExpr(Oid funcid, Oid rettype, List *args, - Oid funccollid, Oid inputcollid, CoercionForm fformat); + Oid funccollid, Oid inputcollid, + CoercionForm fformat, bool safe_mode); extern FuncCall *makeFuncCall(List *name, List *args, CoercionForm funcformat, int location); diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 8fe9b2fcfe..c4ee9c46d0 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -340,6 +340,7 @@ typedef struct TypeCast NodeTag type; Node *arg; /* the expression being casted */ TypeName *typeName; /* the target type */ + bool safe_mode; /* cast can ereturn error vs ereport */ int location; /* token location, or -1 if unknown */ } TypeCast; diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 74f228d959..2ea23914b1 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -604,6 +604,7 @@ typedef struct FuncExpr Oid funccollid; /* OID of collation of result */ Oid inputcollid; /* OID of collation that function should use */ List *args; /* arguments to the function */ + bool safe_mode; /* use safe mode */ int location; /* token location, or -1 if unknown */ } FuncExpr; @@ -1023,6 +1024,7 @@ typedef struct CoerceViaIO /* output typmod is not stored, but is presumed -1 */ Oid resultcollid; /* OID of collation, or InvalidOid if none */ CoercionForm coerceformat; /* how to display this node */ + bool safe_mode; /* use safe mode */ int location; /* token location, or -1 if unknown */ } CoerceViaIO; @@ -1048,6 +1050,7 @@ typedef struct ArrayCoerceExpr int32 resulttypmod; /* output typmod (also element typmod) */ Oid resultcollid; /* OID of collation, or InvalidOid if none */ CoercionForm coerceformat; /* how to display this node */ + bool safe_mode; /* use safe mode */ int location; /* token location, or -1 if unknown */ } ArrayCoerceExpr; @@ -1260,8 +1263,14 @@ typedef struct RowCompareExpr List *rargs; /* the right-hand input arguments */ } RowCompareExpr; +typedef enum CoalesceOp +{ + NULL_TEST, /* Test for NULL, like SQL COALECE() */ + ERROR_TEST /* Test for error flag */ +} CoalesceOp; + /* - * CoalesceExpr - a COALESCE expression + * CoalesceExpr - a COALESCE expression or OnError expression */ typedef struct CoalesceExpr { @@ -1269,6 +1278,7 @@ typedef struct CoalesceExpr Oid coalescetype; /* type of expression result */ Oid coalescecollid; /* OID of collation, or InvalidOid if none */ List *args; /* the arguments */ + CoalesceOp op; /* test for NULL or test for ERROR */ int location; /* token location, or -1 if unknown */ } CoalesceExpr; diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index 957ee18d84..ed7d670819 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -151,6 +151,7 @@ PG_KEYWORD("encoding", ENCODING, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("encrypted", ENCRYPTED, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("end", END_P, RESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("enum", ENUM_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("error", ERROR_P, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("escape", ESCAPE, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("event", EVENT, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("except", EXCEPT, RESERVED_KEYWORD, AS_LABEL) diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h index ddbc995077..db8e15f225 100644 --- a/src/include/parser/parse_coerce.h +++ b/src/include/parser/parse_coerce.h @@ -43,15 +43,30 @@ extern Node *coerce_to_target_type(ParseState *pstate, CoercionContext ccontext, CoercionForm cformat, int location); +extern Node *coerce_to_target_type_safe(ParseState *pstate, + Node *expr, Oid exprtype, + Oid targettype, int32 targettypmod, + CoercionContext ccontext, + CoercionForm cformat, + bool *cast_error_found, + int location); extern bool can_coerce_type(int nargs, const Oid *input_typeids, const Oid *target_typeids, CoercionContext ccontext); extern Node *coerce_type(ParseState *pstate, Node *node, Oid inputTypeId, Oid targetTypeId, int32 targetTypeMod, CoercionContext ccontext, CoercionForm cformat, int location); +extern Node *coerce_type_safe(ParseState *pstate, Node *node, + Oid inputTypeId, Oid targetTypeId, int32 targetTypeMod, + CoercionContext ccontext, CoercionForm cformat, + bool *cast_error_found, int location); extern Node *coerce_to_domain(Node *arg, Oid baseTypeId, int32 baseTypeMod, Oid typeId, CoercionContext ccontext, CoercionForm cformat, int location, bool hideInputCoercion); +extern Node *coerce_to_domain_safe(Node *arg, Oid baseTypeId, int32 baseTypeMod, + Oid typeId, + CoercionContext ccontext, CoercionForm cformat, int location, + bool hideInputCoercion, bool *cast_error_found); extern Node *coerce_to_boolean(ParseState *pstate, Node *node, const char *constructName); diff --git a/src/test/regress/expected/cast.out b/src/test/regress/expected/cast.out new file mode 100644 index 0000000000..87cd1c101b --- /dev/null +++ b/src/test/regress/expected/cast.out @@ -0,0 +1,40 @@ +/* ON ERROR behavior */ +VALUES (CAST('error' AS integer)); +ERROR: invalid input syntax for type integer: "error" +LINE 2: VALUES (CAST('error' AS integer)); + ^ +VALUES (CAST('error' AS integer ERROR ON ERROR)); +ERROR: invalid input syntax for type integer: "error" +LINE 1: VALUES (CAST('error' AS integer ERROR ON ERROR)); + ^ +VALUES (CAST('error' AS integer NULL ON ERROR)); + column1 +--------- + +(1 row) + +VALUES (CAST('error' AS integer DEFAULT 42 ON ERROR)); + column1 +--------- + 42 +(1 row) + +SELECT CAST('{123,abc,456}' AS integer[] DEFAULT '{-789}' ON ERROR) as array_test1; + array_test1 +------------- + {-789} +(1 row) + +SELECT CAST('{234,def,567}'::text[] AS integer[] DEFAULT '{-1011}' ON ERROR) as array_test2; + array_test2 +------------- + {-1011} +(1 row) + +SELECT CAST(u.arg AS integer DEFAULT -1 ON ERROR) AS unnest_test1 FROM unnest('{345,ghi,678}'::text[]) AS u(arg); + unnest_test1 +-------------- + 345 + -1 + 678 +(3 rows) diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 9a139f1e24..1d49b9b95f 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -41,7 +41,7 @@ test: geometry horology tstypes regex type_sanity opr_sanity misc_sanity comment # execute two copy tests in parallel, to check that copy itself # is concurrent safe. # ---------- -test: copy copyselect copydml insert insert_conflict +test: copy copyselect copydml insert insert_conflict cast # ---------- # More groups of parallel tests diff --git a/src/test/regress/sql/cast.sql b/src/test/regress/sql/cast.sql new file mode 100644 index 0000000000..fab8cb7d33 --- /dev/null +++ b/src/test/regress/sql/cast.sql @@ -0,0 +1,11 @@ +/* ON ERROR behavior */ + +VALUES (CAST('error' AS integer)); +VALUES (CAST('error' AS integer ERROR ON ERROR)); +VALUES (CAST('error' AS integer NULL ON ERROR)); +VALUES (CAST('error' AS integer DEFAULT 42 ON ERROR)); + +SELECT CAST('{123,abc,456}' AS integer[] DEFAULT '{-789}' ON ERROR) as array_test1; +SELECT CAST('{234,def,567}'::text[] AS integer[] DEFAULT '{-1011}' ON ERROR) as array_test2; + +SELECT CAST(u.arg AS integer DEFAULT -1 ON ERROR) AS unnest_test1 FROM unnest('{345,ghi,678}'::text[]) AS u(arg); -- 2.38.1