CAST(... ON DEFAULT) - WIP build on top of Error-Safe User Functions
Attached is my work in progress to implement the changes to the CAST()
function as proposed by Vik Fearing.
This work builds upon the Error-safe User Functions work currently ongoing.
The proposed changes are as follows:
CAST(expr AS typename)
continues to behave as before.
CAST(expr AS typename ERROR ON ERROR)
has the identical behavior as the unadorned CAST() above.
CAST(expr AS typename NULL ON ERROR)
will use error-safe functions to do the cast of expr, and will return
NULL if the cast fails.
CAST(expr AS typename DEFAULT expr2 ON ERROR)
will use error-safe functions to do the cast of expr, and will return
expr2 if the cast fails.
There is an additional FORMAT parameter that I have not yet implemented, my
understanding is that it is largely intended for DATE/TIME field
conversions, but others are certainly possible.
CAST(expr AS typename FORMAT fmt DEFAULT expr2 ON ERROR)
What is currently working:
- Any scalar expression that can be evaluated at parse time. These tests
from cast.sql all currently work:
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;
- Scalar values evaluated at runtime.
CREATE TEMPORARY TABLE t(t text);
INSERT INTO t VALUES ('a'), ('1'), ('b'), (2);
SELECT CAST(t.t AS integer DEFAULT -1 ON ERROR) AS foo FROM t;
foo
-----
-1
1
-1
2
(4 rows)
Along the way, I made a few design decisions, each of which is up for
debate:
First, I created OidInputFunctionCallSafe, which is to OidInputFunctionCall
what InputFunctionCallSafe is to InputFunctionCall. Given that the only
place I ended up using it was stringTypeDatumSafe(), it may be possible to
just move that code inside stringTypeDatumSafe.
Next, I had a need for FuncExpr, CoerceViaIO, and ArrayCoerce to all report
if their expr argument failed, and if not, just past the evaluation of
expr2. Rather than duplicate this logic in several places, I chose instead
to modify CoalesceExpr to allow for an error-test mode in addition to its
default null-test mode, and then to provide this altered node with two
expressions, the first being the error-safe typecast of expr and the second
being the non-error-safe typecast of expr2.
I still don't have array-to-array casts working, as the changed I would
likely need to make to ArrayCoerce get somewhat invasive, so this seemed
like a good time to post my work so far and solicit some feedback beyond
what I've already been getting from Jeff Davis and Michael Paquier.
I've sidestepped domains as well for the time being as well as avoiding JIT
issues entirely.
No documentation is currently prepared. All but one of the regression test
queries work, the one that is currently failing is:
SELECT CAST('{234,def,567}'::text[] AS integer[] DEFAULT '{-1011}' ON
ERROR) as array_test2;
Other quirks:
- an unaliased CAST ON DEFAULT will return the column name of "coalesce",
which internally is true, but obviously would be quite confusing to a user.
As a side observation, I noticed that the optimizer already tries to
resolve expressions based on constants and to collapse expression trees
where possible, which makes me wonder if the work done to do the same in
transformTypeCast/ and coerce_to_target_type and coerce_type isn't also
wasted.
Attachments:
0003-CAST-ON-DEFAULT-work-in-progress.patchtext/x-patch; charset=US-ASCII; name=0003-CAST-ON-DEFAULT-work-in-progress.patchDownload
From 74aaf068a2d071f6fefab27309d7a0252f28ccee Mon Sep 17 00:00:00 2001
From: coreyhuinker <corey.huinker@gmail.com>
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 <partboundspec> PartitionBoundSpec
%type <list> hash_partbound
%type <defelt> hash_partbound_elem
+%type <node> cast_on_error_clause
+%type <node> 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
0001-add-OidInputFunctionCall.patchtext/x-patch; charset=US-ASCII; name=0001-add-OidInputFunctionCall.patchDownload
From 897c9c68a29ad0fa57f28734df0c77553e026d80 Mon Sep 17 00:00:00 2001
From: coreyhuinker <corey.huinker@gmail.com>
Date: Sun, 18 Dec 2022 16:20:01 -0500
Subject: [PATCH 1/3] add OidInputFunctionCall
---
src/backend/utils/fmgr/fmgr.c | 13 +++++++++++++
src/include/fmgr.h | 4 ++++
2 files changed, 17 insertions(+)
diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index 0d37f69298..e9a19ce653 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -1701,6 +1701,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/fmgr.h b/src/include/fmgr.h
index b7832d0aa2..b835ef72b5 100644
--- a/src/include/fmgr.h
+++ b/src/include/fmgr.h
@@ -706,6 +706,10 @@ extern bool InputFunctionCallSafe(FmgrInfo *flinfo, 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,
--
2.38.1
0002-add-stringTypeDatumSafe.patchtext/x-patch; charset=US-ASCII; name=0002-add-stringTypeDatumSafe.patchDownload
From 1153c977ba153f116170ca2922e0785f9d2604c8 Mon Sep 17 00:00:00 2001
From: coreyhuinker <corey.huinker@gmail.com>
Date: Mon, 19 Dec 2022 13:39:16 -0500
Subject: [PATCH 2/3] add stringTypeDatumSafe
---
src/backend/parser/parse_type.c | 13 +++++++++++++
src/include/parser/parse_type.h | 2 ++
2 files changed, 15 insertions(+)
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index f7ad689459..0e6dfe3b49 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/include/parser/parse_type.h b/src/include/parser/parse_type.h
index 4e5624d721..7468f75823 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);
--
2.38.1
Corey Huinker <corey.huinker@gmail.com> writes:
The proposed changes are as follows:
CAST(expr AS typename)
continues to behave as before.
CAST(expr AS typename ERROR ON ERROR)
has the identical behavior as the unadorned CAST() above.
CAST(expr AS typename NULL ON ERROR)
will use error-safe functions to do the cast of expr, and will return
NULL if the cast fails.
CAST(expr AS typename DEFAULT expr2 ON ERROR)
will use error-safe functions to do the cast of expr, and will return
expr2 if the cast fails.
While I approve of trying to get some functionality in this area,
I'm not sure that extending CAST is a great idea, because I'm afraid
that the SQL committee will do something that conflicts with it.
If we know that they are about to standardize exactly this syntax,
where is that information available? If we don't know that,
I'd prefer to invent some kind of function or other instead of
extending the grammar.
regards, tom lane
On Tue, 20 Dec 2022 at 04:27, Corey Huinker <corey.huinker@gmail.com> wrote:
Attached is my work in progress to implement the changes to the CAST() function as proposed by Vik Fearing.
This work builds upon the Error-safe User Functions work currently ongoing.
The proposed changes are as follows:
CAST(expr AS typename)
continues to behave as before.CAST(expr AS typename ERROR ON ERROR)
has the identical behavior as the unadorned CAST() above.CAST(expr AS typename NULL ON ERROR)
will use error-safe functions to do the cast of expr, and will return NULL if the cast fails.CAST(expr AS typename DEFAULT expr2 ON ERROR)
will use error-safe functions to do the cast of expr, and will return expr2 if the cast fails.There is an additional FORMAT parameter that I have not yet implemented, my understanding is that it is largely intended for DATE/TIME field conversions, but others are certainly possible.
CAST(expr AS typename FORMAT fmt DEFAULT expr2 ON ERROR)What is currently working:
- Any scalar expression that can be evaluated at parse time. These tests from cast.sql all currently work: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;
- Scalar values evaluated at runtime.
CREATE TEMPORARY TABLE t(t text);
INSERT INTO t VALUES ('a'), ('1'), ('b'), (2);
SELECT CAST(t.t AS integer DEFAULT -1 ON ERROR) AS foo FROM t;
foo
-----
-1
1
-1
2
(4 rows)Along the way, I made a few design decisions, each of which is up for debate:
First, I created OidInputFunctionCallSafe, which is to OidInputFunctionCall what InputFunctionCallSafe is to InputFunctionCall. Given that the only place I ended up using it was stringTypeDatumSafe(), it may be possible to just move that code inside stringTypeDatumSafe.
Next, I had a need for FuncExpr, CoerceViaIO, and ArrayCoerce to all report if their expr argument failed, and if not, just past the evaluation of expr2. Rather than duplicate this logic in several places, I chose instead to modify CoalesceExpr to allow for an error-test mode in addition to its default null-test mode, and then to provide this altered node with two expressions, the first being the error-safe typecast of expr and the second being the non-error-safe typecast of expr2.
I still don't have array-to-array casts working, as the changed I would likely need to make to ArrayCoerce get somewhat invasive, so this seemed like a good time to post my work so far and solicit some feedback beyond what I've already been getting from Jeff Davis and Michael Paquier.
I've sidestepped domains as well for the time being as well as avoiding JIT issues entirely.
No documentation is currently prepared. All but one of the regression test queries work, the one that is currently failing is:
SELECT CAST('{234,def,567}'::text[] AS integer[] DEFAULT '{-1011}' ON ERROR) as array_test2;
Other quirks:
- an unaliased CAST ON DEFAULT will return the column name of "coalesce", which internally is true, but obviously would be quite confusing to a user.As a side observation, I noticed that the optimizer already tries to resolve expressions based on constants and to collapse expression trees where possible, which makes me wonder if the work done to do the same in transformTypeCast/ and coerce_to_target_type and coerce_type isn't also wasted.
CFBot shows some compilation errors as in [1]https://cirrus-ci.com/task/6687753371385856?logs=gcc_warning#L448, please post an updated
version for the same:
[02:53:44.829] time make -s -j${BUILD_JOBS} world-bin
[02:55:41.164] llvmjit_expr.c: In function ‘llvm_compile_expr’:
[02:55:41.164] llvmjit_expr.c:928:6: error: ‘v_resnull’ undeclared
(first use in this function); did you mean ‘v_resnullp’?
[02:55:41.164] 928 | v_resnull = LLVMBuildLoad(b, v_reserrorp, "");
[02:55:41.164] | ^~~~~~~~~
[02:55:41.164] | v_resnullp
[02:55:41.164] llvmjit_expr.c:928:6: note: each undeclared identifier
is reported only once for each function it appears in
[02:55:41.164] llvmjit_expr.c:928:35: error: ‘v_reserrorp’ undeclared
(first use in this function); did you mean ‘v_reserror’?
[02:55:41.164] 928 | v_resnull = LLVMBuildLoad(b, v_reserrorp, "");
[02:55:41.164] | ^~~~~~~~~~~
[02:55:41.164] | v_reserror
[02:55:41.165] make[2]: *** [<builtin>: llvmjit_expr.o] Error 1
[02:55:41.165] make[2]: *** Waiting for unfinished jobs....
[02:55:45.495] make[1]https://cirrus-ci.com/task/6687753371385856?logs=gcc_warning#L448: *** [Makefile:42: all-backend/jit/llvm-recurse] Error 2
[02:55:45.495] make: *** [GNUmakefile:21: world-bin-src-recurse] Error 2
[1]: https://cirrus-ci.com/task/6687753371385856?logs=gcc_warning#L448
Regards,
Vignesh
On 2023-01-02 Mo 10:57, Tom Lane wrote:
Corey Huinker <corey.huinker@gmail.com> writes:
The proposed changes are as follows:
CAST(expr AS typename)
continues to behave as before.
CAST(expr AS typename ERROR ON ERROR)
has the identical behavior as the unadorned CAST() above.
CAST(expr AS typename NULL ON ERROR)
will use error-safe functions to do the cast of expr, and will return
NULL if the cast fails.
CAST(expr AS typename DEFAULT expr2 ON ERROR)
will use error-safe functions to do the cast of expr, and will return
expr2 if the cast fails.While I approve of trying to get some functionality in this area,
I'm not sure that extending CAST is a great idea, because I'm afraid
that the SQL committee will do something that conflicts with it.
If we know that they are about to standardize exactly this syntax,
where is that information available? If we don't know that,
I'd prefer to invent some kind of function or other instead of
extending the grammar.
+1
cheers
andrew
--
Andrew Dunstan
EDB: https://www.enterprisedb.com
On Mon, Jan 2, 2023 at 10:57 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Corey Huinker <corey.huinker@gmail.com> writes:
The proposed changes are as follows:
CAST(expr AS typename)
continues to behave as before.
CAST(expr AS typename ERROR ON ERROR)
has the identical behavior as the unadorned CAST() above.
CAST(expr AS typename NULL ON ERROR)
will use error-safe functions to do the cast of expr, and will return
NULL if the cast fails.
CAST(expr AS typename DEFAULT expr2 ON ERROR)
will use error-safe functions to do the cast of expr, and will return
expr2 if the cast fails.While I approve of trying to get some functionality in this area,
I'm not sure that extending CAST is a great idea, because I'm afraid
that the SQL committee will do something that conflicts with it.
If we know that they are about to standardize exactly this syntax,
where is that information available? If we don't know that,
I'd prefer to invent some kind of function or other instead of
extending the grammar.regards, tom lane
I'm going off the spec that Vik presented in
/messages/by-id/f8600a3b-f697-2577-8fea-f40d3e18bea8@postgresfriends.org
which is his effort to get it through the SQL committee. I was
alreading thinking about how to get the SQLServer TRY_CAST() function into
postgres, so this seemed like the logical next step.
While the syntax may change, the underlying infrastructure would remain
basically the same: we would need the ability to detect that a typecast had
failed, and replace it with the default value, and handle that at parse
time, or executor time, and handle array casts where the array has the
default but the underlying elements can't.
It would be simple to move the grammar changes to their own patch if that
removes a barrier for people.
Corey Huinker <corey.huinker@gmail.com> writes:
On Mon, Jan 2, 2023 at 10:57 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
While I approve of trying to get some functionality in this area,
I'm not sure that extending CAST is a great idea, because I'm afraid
that the SQL committee will do something that conflicts with it.
I'm going off the spec that Vik presented in
/messages/by-id/f8600a3b-f697-2577-8fea-f40d3e18bea8@postgresfriends.org
which is his effort to get it through the SQL committee.
I'm pretty certain that sending something to pgsql-hackers will have
exactly zero impact on the SQL committee. Is there anything actually
submitted to the committee, and if so what's its status?
regards, tom lane
On 1/3/23 19:14, Tom Lane wrote:
Corey Huinker <corey.huinker@gmail.com> writes:
On Mon, Jan 2, 2023 at 10:57 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
While I approve of trying to get some functionality in this area,
I'm not sure that extending CAST is a great idea, because I'm afraid
that the SQL committee will do something that conflicts with it.I'm going off the spec that Vik presented in
/messages/by-id/f8600a3b-f697-2577-8fea-f40d3e18bea8@postgresfriends.org
which is his effort to get it through the SQL committee.I'm pretty certain that sending something to pgsql-hackers will have
exactly zero impact on the SQL committee. Is there anything actually
submitted to the committee, and if so what's its status?
I have not posted my paper to the committee yet, but I plan to do so
before the working group's meeting early February. Just like with
posting patches here, I cannot guarantee that it will get accepted but I
will be arguing for it.
I don't think we should add that syntax until I do get it through the
committee, just in case they change something.
--
Vik Fearing
Vik Fearing <vik@postgresfriends.org> writes:
I have not posted my paper to the committee yet, but I plan to do so
before the working group's meeting early February. Just like with
posting patches here, I cannot guarantee that it will get accepted but I
will be arguing for it.
I don't think we should add that syntax until I do get it through the
committee, just in case they change something.
Agreed. So this is something we won't be able to put into v16;
it'll have to wait till there's something solid from the committee.
regards, tom lane
On Tue, 3 Jan 2023 at 14:16, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Vik Fearing <vik@postgresfriends.org> writes:
I don't think we should add that syntax until I do get it through the
committee, just in case they change something.Agreed. So this is something we won't be able to put into v16;
it'll have to wait till there's something solid from the committee.
I guess I'll mark this Rejected in the CF then. Who knows when the SQL
committee will look at this...
--
Gregory Stark
As Commitfest Manager
On Mon, 19 Dec 2022 at 17:57, Corey Huinker <corey.huinker@gmail.com> wrote:
Attached is my work in progress to implement the changes to the CAST()
function as proposed by Vik Fearing.CAST(expr AS typename NULL ON ERROR)
will use error-safe functions to do the cast of expr, and will return
NULL if the cast fails.CAST(expr AS typename DEFAULT expr2 ON ERROR)
will use error-safe functions to do the cast of expr, and will return
expr2 if the cast fails.
Is there any difference between NULL and DEFAULT NULL?
On Tue, Mar 28, 2023 at 2:53 PM Gregory Stark (as CFM) <stark.cfm@gmail.com>
wrote:
On Tue, 3 Jan 2023 at 14:16, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Vik Fearing <vik@postgresfriends.org> writes:
I don't think we should add that syntax until I do get it through the
committee, just in case they change something.Agreed. So this is something we won't be able to put into v16;
it'll have to wait till there's something solid from the committee.I guess I'll mark this Rejected in the CF then. Who knows when the SQL
committee will look at this...--
Gregory Stark
As Commitfest Manager
Yes, for now. I'm in touch with the pg-people on the committee and will
resume work when there's something to act upon.
On Tue, Mar 28, 2023 at 3:25 PM Isaac Morland <isaac.morland@gmail.com>
wrote:
On Mon, 19 Dec 2022 at 17:57, Corey Huinker <corey.huinker@gmail.com>
wrote:Attached is my work in progress to implement the changes to the CAST()
function as proposed by Vik Fearing.CAST(expr AS typename NULL ON ERROR)
will use error-safe functions to do the cast of expr, and will return
NULL if the cast fails.CAST(expr AS typename DEFAULT expr2 ON ERROR)
will use error-safe functions to do the cast of expr, and will return
expr2 if the cast fails.Is there any difference between NULL and DEFAULT NULL?
What I think you're asking is: is there a difference between these two
statements:
SELECT CAST(my_string AS integer NULL ON ERROR) FROM my_table;
SELECT CAST(my_string AS integer DEFAULT NULL ON ERROR) FROM my_table;
And as I understand it, the answer would be no, there is no practical
difference. The first case is just a convenient shorthand, whereas the
second case tees you up for a potentially complex expression. Before you
ask, I believe the ON ERROR syntax could be made optional. As I implemented
it, both cases create a default expression which then typecast to integer,
and in both cases that expression would be a const-null, so the optimizer
steps would very quickly collapse those steps into a plain old constant.
hi.
more preparation work has been committed.
1. SQL/JSON patch [1]https://git.postgresql.org/cgit/postgresql.git/diff/src/backend/parser/gram.y?id=6185c9737cf48c9540782d88f12bd2912d6ca1cc added keyword ERROR
2. CoerceViaIo, CoerceToDomain can be evaluated error safe. see commit [2]https://git.postgresql.org/cgit/postgresql.git/commit/?id=aaaf9449ec6be62cb0d30ed3588dc384f56274bf.
3. ExprState added ErrorSaveContext point, so before calling ExecInitExprRec
set valid ErrorSaveContext for ExprState->escontext we should evaluate
expression error softly.
see commit [2]https://git.postgresql.org/cgit/postgresql.git/commit/?id=aaaf9449ec6be62cb0d30ed3588dc384f56274bf also.
I only found oracle implement, [3]https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/CAST.html.
Based on my reading of [4]https://peter.eisentraut.org/blog/2023/04/04/sql-2023-is-finished-here-is-whats-new, it seems CAST(EXPRESSION AS TYPE DEFAULT
def_expr ON ERROR)
is not included in SQL:2023.
anyway, just share my POC based on the previous patch in this thread.
it will work for domain over composite, composite over domain.
example:
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('(,42)' AS comp_domain_with_typmod DEFAULT NULL ON ERROR);
--return NULL
[1]: https://git.postgresql.org/cgit/postgresql.git/diff/src/backend/parser/gram.y?id=6185c9737cf48c9540782d88f12bd2912d6ca1cc
[2]: https://git.postgresql.org/cgit/postgresql.git/commit/?id=aaaf9449ec6be62cb0d30ed3588dc384f56274bf
[3]: https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/CAST.html
[4]: https://peter.eisentraut.org/blog/2023/04/04/sql-2023-is-finished-here-is-whats-new
Attachments:
v1-0001-make-ArrayCoerceExpr-error-safe.patchtext/x-patch; charset=US-ASCII; name=v1-0001-make-ArrayCoerceExpr-error-safe.patchDownload
From 47c181eee593468c3d7b7cb57aec3a1ea8cb3c1d Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Fri, 18 Jul 2025 13:00:19 +0800
Subject: [PATCH v1 1/2] 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 f1569879b52..1f3f899874f 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 1a37737d4a2..81e46cff725 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 c8f53c6fbe7..b5f98bf22f9 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
v1-0002-CAST-expr-AS-newtype-DEFAULT-ON-ERROR.patchtext/x-patch; charset=US-ASCII; name=v1-0002-CAST-expr-AS-newtype-DEFAULT-ON-ERROR.patchDownload
From be7e2bb7cecd1a24ebcce9266759a385dfdf55a1 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sun, 20 Jul 2025 22:11:59 +0800
Subject: [PATCH v1 2/2] CAST(expr AS newtype DEFAULT ON ERROR)
Type cast nodes are generally one of: FuncExpr, CoerceViaIO, or ArrayCoerceExpr;
see build_coercion_expression. We've already made CoerceViaIO error safe[0]; we
just need to teach it to evaluate default expression after coercion failures.
ArrayCoerceExpr is also handling errors safely.
However, FuncExpr can't be made error-safe directly (see example like int84), so
instead, we construct a CoerceViaIO node using the source expression as its
argument.
[0]: https://git.postgresql.org/cgit/postgresql.git/commit/?id=aaaf9449ec6be62cb0d30ed3588dc384f56274bf
context: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
discussion: https://postgr.es/m/
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 | 110 ++++++++++++++
src/backend/executor/execExprInterp.c | 30 ++++
src/backend/nodes/nodeFuncs.c | 57 +++++++
src/backend/optimizer/util/clauses.c | 24 +++
src/backend/parser/gram.y | 31 +++-
src/backend/parser/parse_expr.c | 205 ++++++++++++++++++++++++++
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 | 17 +++
src/include/nodes/parsenodes.h | 6 +
src/include/nodes/primnodes.h | 33 +++++
src/include/parser/parse_type.h | 2 +
src/test/regress/expected/misc.out | 127 ++++++++++++++++
src/test/regress/sql/misc.sql | 54 +++++++
src/tools/pgindent/typedefs.list | 3 +
20 files changed, 769 insertions(+), 2 deletions(-)
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 1f3f899874f..5085821c016 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,
@@ -1702,6 +1705,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;
+
if (state->escontext != NULL)
elemstate->escontext = state->escontext;
@@ -2180,6 +2186,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;
@@ -4744,6 +4758,102 @@ ExecBuildParamSetEqual(TupleDesc desc,
return state;
}
+/*
+ * Push steps to evaluate a SafeTypeCastExpr and its various subsidiary expressions.
+ * We already handle CoerceViaIO, CoerceToDomain, and ArrayCoerceExpr error
+ * softly. However, FuncExpr (e.g., int84) cannot be made error-safe.
+ * In such cases, we wrap the source expression and target type information into
+ * a CoerceViaIO node instead.
+ */
+static void
+ExecInitSafeTypeCastExpr(SafeTypeCastExpr *stcexpr , ExprState *state,
+ Datum *resv, bool *resnull,
+ ExprEvalStep *scratch)
+{
+ /*
+ * If coercion to the target type fails, fall back to the default expression
+ * specified in the ON ERROR clause.
+ */
+ if (stcexpr->cast_expr == NULL)
+ {
+ ExecInitExprRec((Expr *) stcexpr->default_expr,
+ state, resv, resnull);
+ return;
+ }
+ else if (stcexpr->binarycoercible)
+ {
+ /*
+ * source type binary coerceable with target type, just evaulate the
+ * source expression
+ */
+ ExecInitExprRec((Expr *) stcexpr->source_expr,
+ state, resv, resnull);
+ return;
+ }
+ else
+ {
+ CoerceViaIO *newexpr;
+ SafeTypeCastState *stcstate;
+ ErrorSaveContext *escontext;
+ ErrorSaveContext *saved_escontext;
+ List *jumps_to_end = NIL;
+
+ stcstate = palloc0(sizeof(SafeTypeCastState));
+ stcstate->stcexpr = stcexpr;
+ stcstate->escontext.type = T_ErrorSaveContext;
+ escontext = &stcstate->escontext;
+ state->escontext = escontext;
+
+ Assert(stcexpr->cast_expr);
+ /* evaluate argument expression into step's result area */
+ if (IsA(stcexpr->cast_expr, CoerceViaIO) ||
+ IsA(stcexpr->cast_expr, CoerceToDomain) ||
+ IsA(stcexpr->cast_expr, ArrayCoerceExpr))
+ 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 81e46cff725..f31b67a47e0 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
+ * JsonBehavior 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/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 7bc823507f1..5da7bd5d88f 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,18 @@ raw_expression_tree_walker_impl(Node *node,
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/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index f45131c34c5..df781c444fd 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -2938,6 +2938,30 @@ 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;
+
+ /*
+ * We can not fold cast_expr to a constant now, it may error
+ * out. E.g. CAST(1 AS DATE DEFAULT NULL ON ERROR).
+ */
+ 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->binarycoercible = stc->binarycoercible;
+ 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 73345bb3c70..7e16b5fc9f3 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
@@ -15943,8 +15945,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"),
@@ -16330,6 +16349,14 @@ 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
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index d66276801c6..92998087e67 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -37,6 +37,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"
@@ -76,6 +77,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);
@@ -170,6 +172,10 @@ transformExprRecurse(ParseState *pstate, Node *expr)
result = transformTypeCast(pstate, (TypeCast *) expr);
break;
+ case T_SafeTypeCast:
+ result = transformTypeSafeCast(pstate, (SafeTypeCast *) expr);
+ break;
+
case T_CollateClause:
result = transformCollateClause(pstate, (CollateClause *) expr);
break;
@@ -2779,6 +2785,205 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
return result;
}
+
+/*
+ * Recursively checks if the given expression, or its sub-node in some cases,
+ * is valid for using as an ON ERROR / ON EMPTY DEFAULT expression.
+ */
+static bool
+ValidCastDefaultExpr(Node *expr, void *context)
+{
+ if (expr == NULL)
+ return false;
+
+ switch (nodeTag(expr))
+ {
+ /* Acceptable expression nodes */
+ case T_Const:
+ case T_FuncExpr:
+ case T_OpExpr:
+ return true;
+
+ /* Acceptable iff arg of the following nodes is one of the above */
+ case T_CoerceViaIO:
+ case T_CoerceToDomain:
+ case T_ArrayCoerceExpr:
+ case T_ConvertRowtypeExpr:
+ case T_RelabelType:
+ return expression_tree_walker(expr, ValidCastDefaultExpr,
+ context);
+ default:
+ break;
+ }
+
+ return false;
+}
+
+
+/*
+ * Handle an explicit CAST construct.
+ *
+ * Transform the argument, look up the type name, and apply any necessary
+ * coercion function(s).
+ * must do via coerce via IO.
+ * See transformJsonFuncExpr, ExecInitJsonExpr
+ * maybe we also need a JsonExpr Node represent
+ */
+static Node *
+transformTypeSafeCast(ParseState *pstate, SafeTypeCast *tc)
+{
+ SafeTypeCastExpr *result;
+ Node *def_expr;
+ Node *cast_expr = NULL;
+ Oid inputType;
+ Oid targetType;
+ int32 targetTypmod;
+ int location;
+ Node *source_expr = NULL;
+ 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 (IsA(def_expr, CollateExpr))
+ ereport(ERROR,
+ errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("DEFAULT expression can not as COLLATE clause"),
+ parser_errposition(pstate, def_expr_loc));
+ if (!ValidCastDefaultExpr(def_expr, NULL))
+ ereport(ERROR,
+ errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("can only specify a constant, non-aggregate function, or operator expression for DEFAULT"),
+ parser_errposition(pstate, def_expr_loc));
+ if (contain_var_clause(def_expr))
+ ereport(ERROR,
+ errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("DEFAULT expression must not contain column references"),
+ parser_errposition(pstate, def_expr_loc));
+ if (expression_returns_set(def_expr))
+ ereport(ERROR,
+ errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("DEFAULT expression must not return a set"),
+ parser_errposition(pstate, def_expr_loc));
+
+ 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))
+ {
+ source_expr = transformArrayExpr(pstate,
+ (A_ArrayExpr *) tc_arg,
+ targetBaseType,
+ elementType,
+ targetBaseTypmod);
+ }
+ else
+ source_expr = transformExprRecurse(pstate, tc_arg);
+ }
+ else
+ source_expr = transformExprRecurse(pstate, tc_arg);
+
+ inputType = exprType(source_expr);
+ if (inputType == InvalidOid)
+ return (Node *) result; /* do nothing if NULL input */
+
+ /*
+ * 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 (exprType(source_expr) == UNKNOWNOID && IsA(source_expr, Const))
+ {
+ Const *con = (Const *) source_expr;
+ int32 inputTypeMod;
+ bool converted;
+ Datum datum;
+ Type baseType;
+ baseType = typeidType(targetType);
+
+ if (targetType == INTERVALOID)
+ inputTypeMod = targetTypmod;
+ else
+ inputTypeMod = -1;
+
+ if (!con->constisnull)
+ converted = stringTypeDatumSafe(baseType,
+ DatumGetCString(con->constvalue),
+ inputTypeMod,
+ &datum);
+ else
+ converted = stringTypeDatumSafe(baseType,
+ NULL,
+ inputTypeMod,
+ &datum);
+ if(!converted)
+ can_coerce = false;
+
+ ReleaseSysCache(baseType);
+ }
+
+ if (can_coerce)
+ 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;
+ result->binarycoercible = IsBinaryCoercible(inputType, targetType);
+
+ 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 4aba0d9d4d5..812ed18c162 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 7713bdc6af0..02e5f9c92d7 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 b5f98bf22f9..6bd8a989dbd 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 3d6e6bdbfd2..eb47bc336b2 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 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 782291d9998..9de895e682f 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 75366203706..0afcf09c086 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 0fe7b4ebc77..299d4eef4ed 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 e107d6e5f81..fd26e1c98b6 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1058,6 +1058,23 @@ typedef struct DomainConstraintState
ExprState *check_exprstate; /* check_expr's eval state, or NULL */
} DomainConstraintState;
+typedef struct SafeTypeCastState
+{
+ NodeTag type;
+
+ SafeTypeCastExpr *stcexpr;
+
+ /* Set to true if source_expr evaluation cause an error. */
+ NullableDatum error;
+
+ int jump_error;
+
+ int jump_end;
+
+ 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 86a236bd58b..95174e0feef 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 6dfca3cb35b..a02eef27800 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -756,6 +756,39 @@ 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
+{
+ Expr xpr;
+
+ /* transformed expression being casted */
+ Node *source_expr;
+
+ /* transformed cast expression, it maybe NULL! */
+ Node *cast_expr;
+
+ /* in case cast expression evaulation failed, fallback default expression */
+ Node *default_expr;
+
+ /* does source type binary coerceable with target type */
+ bool binarycoercible;
+
+ /* 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 0d919d8bfa2..12381aed64c 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/misc.out b/src/test/regress/expected/misc.out
index 6e816c57f1f..a23e847f268 100644
--- a/src/test/regress/expected/misc.out
+++ b/src/test/regress/expected/misc.out
@@ -396,3 +396,130 @@ SELECT *, (equipment(CAST((h.*) AS hobbies_r))).name FROM hobbies_r h;
--
-- rewrite rules
--
+-- CAST DEFAULT ON ERROR
+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(1 AS int8 DEFAULT NULL ON ERROR);
+ int8
+------
+ 1
+(1 row)
+
+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 ERROR); --error
+ERROR: integer out of range
+SELECT CAST('a' as date DEFAULT ret_int8() ON ERROR); --error
+ERROR: cannot cast on_error default expression to type date
+LINE 1: SELECT CAST('a' as date DEFAULT ret_int8() ON ERROR);
+ ^
+-- test valid DEFAULT expression for CAST = ON 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;
+SELECT CAST('a' as int DEFAULT ret_setint() ON ERROR) FROM hobbies_r; --error
+ERROR: DEFAULT expression must not return a set
+LINE 1: SELECT CAST('a' as int DEFAULT ret_setint() ON ERROR) FROM h...
+ ^
+SELECT CAST('1' as text DEFAULT '2' collate "C" ON ERROR); --error
+ERROR: DEFAULT expression can not as COLLATE clause
+LINE 1: SELECT CAST('1' as text DEFAULT '2' collate "C" ON ERROR);
+ ^
+SELECT CAST('a' as int DEFAULT sum(1) ON ERROR); --error
+ERROR: can only specify a constant, non-aggregate function, or operator expression for DEFAULT
+LINE 1: SELECT CAST('a' as int DEFAULT sum(1) ON ERROR);
+ ^
+SELECT CAST('a' as int DEFAULT 'b' ON ERROR); --error
+ERROR: invalid input syntax for type integer: "b"
+LINE 1: SELECT CAST('a' as int DEFAULT 'b' ON ERROR);
+ ^
+SELECT CAST('a' as text DEFAULT name || 'j' ON ERROR) FROM hobbies_r; --error
+ERROR: DEFAULT expression must not contain column references
+LINE 1: SELECT CAST('a' as text DEFAULT name || 'j' ON ERROR) FROM h...
+ ^
+-- test array coerce
+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)
+
+-- 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 ERROR); --error
+ERROR: value for domain d_int42 violates check constraint "d_int42_check"
+SELECT CAST(11 AS d_int42 DEFAULT 42 ON ERROR); --ok
+ d_int42
+---------
+ 42
+(1 row)
+
+SELECT CAST(NULL AS d_int42 DEFAULT NULL ON ERROR); --error
+ERROR: domain d_int42 does not allow null values
+SELECT CAST(NULL AS d_int42 DEFAULT 42 ON ERROR); --ok
+ d_int42
+---------
+ 42
+(1 row)
+
+SELECT CAST('(,42)' AS comp_domain_with_typmod DEFAULT NULL ON ERROR);
+ comp_domain_with_typmod
+-------------------------
+
+(1 row)
+
+SELECT CAST('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1,2)' ON ERROR);
+ comp_domain_with_typmod
+-------------------------
+ ("1 ",2)
+(1 row)
+
+SELECT CAST('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1234,2)' ON ERROR); --error
+ERROR: value too long for type character(3)
+LINE 1: ...ST('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1234,2)'...
+ ^
+-- test deparse
+CREATE VIEW safecastview AS
+SELECT CAST('1234' as char(3) DEFAULT -1111 ON ERROR),
+ CAST('a' as int DEFAULT random(min=>1, max=>1::int) ON ERROR) as safecast;
+\sv safecastview
+CREATE OR REPLACE VIEW public.safecastview AS
+ SELECT CAST('1234' AS character(3) DEFAULT '-1111'::integer::character(3) ON ERROR) AS bpchar,
+ CAST('a' AS integer DEFAULT random(min => 1, max => 1) ON ERROR) AS safecast
+CREATE INdEX cast_error_idx ON hobbies_r((cast(name as int DEFAULT random(min=>1, max=>1) ON ERROR))); --error
+ERROR: functions in index expression must be marked IMMUTABLE
diff --git a/src/test/regress/sql/misc.sql b/src/test/regress/sql/misc.sql
index 165a2e175fb..d9b3368b099 100644
--- a/src/test/regress/sql/misc.sql
+++ b/src/test/regress/sql/misc.sql
@@ -273,3 +273,57 @@ SELECT *, (equipment(CAST((h.*) AS hobbies_r))).name FROM hobbies_r h;
--
-- rewrite rules
--
+
+-- CAST DEFAULT ON ERROR
+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(1 AS int8 DEFAULT NULL ON 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 ERROR); --error
+SELECT CAST('a' as date DEFAULT ret_int8() ON ERROR); --error
+
+-- test valid DEFAULT expression for CAST = ON 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;
+
+SELECT CAST('a' as int DEFAULT ret_setint() ON ERROR) FROM hobbies_r; --error
+
+SELECT CAST('1' as text DEFAULT '2' collate "C" ON ERROR); --error
+SELECT CAST('a' as int DEFAULT sum(1) ON ERROR); --error
+SELECT CAST('a' as int DEFAULT 'b' ON ERROR); --error
+SELECT CAST('a' as text DEFAULT name || 'j' ON ERROR) FROM hobbies_r; --error
+
+-- test array coerce
+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);
+
+-- 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 ERROR); --error
+SELECT CAST(11 AS d_int42 DEFAULT 42 ON ERROR); --ok
+SELECT CAST(NULL AS d_int42 DEFAULT NULL ON ERROR); --error
+SELECT CAST(NULL AS d_int42 DEFAULT 42 ON ERROR); --ok
+SELECT CAST('(,42)' AS comp_domain_with_typmod DEFAULT NULL ON ERROR);
+SELECT CAST('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1,2)' ON ERROR);
+SELECT CAST('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1234,2)' ON ERROR); --error
+
+-- test deparse
+CREATE VIEW safecastview AS
+SELECT CAST('1234' as char(3) DEFAULT -1111 ON ERROR),
+ CAST('a' as int DEFAULT random(min=>1, max=>1::int) ON ERROR) as safecast;
+\sv safecastview
+
+CREATE INdEX cast_error_idx ON hobbies_r((cast(name as int DEFAULT random(min=>1, max=>1) ON ERROR))); --error
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index ff050e93a50..2e79046d83b 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
On 22/07/2025 03:59, jian he wrote:
Based on my reading of [4], it seems CAST(EXPRESSION AS TYPE DEFAULT
def_expr ON ERROR)
is not included in SQL:2023.[4]https://peter.eisentraut.org/blog/2023/04/04/sql-2023-is-finished-here-is-whats-new
It was accepted into the standard after 2023 was released. I am the
author of this change in the standard, so feel free to ask me anything
you're unsure about.
--
Vik Fearing
On Tue, Jul 22, 2025 at 2:45 PM Vik Fearing <vik@postgresfriends.org> wrote:
It was accepted into the standard after 2023 was released. I am the
author of this change in the standard, so feel free to ask me anything
you're unsure about.
is the generally syntax as mentioned in this thread:
CAST(source_expression AS target_type DEFAULT default_expression ON ERROR)
if so, what's the restriction of default_expression?
On 22/07/2025 12:19, jian he wrote:
On Tue, Jul 22, 2025 at 2:45 PM Vik Fearing <vik@postgresfriends.org> wrote:
It was accepted into the standard after 2023 was released. I am the
author of this change in the standard, so feel free to ask me anything
you're unsure about.is the generally syntax as mentioned in this thread:
CAST(source_expression AS target_type DEFAULT default_expression ON ERROR)if so, what's the restriction of default_expression?
The actual syntax is:
<cast specification> ::=
CAST <left paren>
<cast operand> AS <cast target>
[ FORMAT <cast template> ]
[ <cast error behavior> ON CONVERSION ERROR ]
<right paren>
"CONVERSION" is probably a noise word, but it is there because A) Oracle
wanted it there, and B) it makes sense because if the <cast error
behavior> fails, that is still a failure of the entire CAST.
The <cast error behavior> is:
<cast error behavior> ::=
ERROR
| NULL
| DEFAULT <value expression>
but I am planning on removing the NULL variant in favor of having the
<value expression> be a <contextually typed value specification>. So it
would be either ERROR ON CONVERSION ERROR (postgres's current behavior),
or DEFAULT NULL ON CONVERSION ERROR.
An example of B) above would be: CAST('five' AS INTEGER DEFAULT 'six' ON
CONVERSION ERROR). 'six' is no more an integer than 'five' is, so that
would error out because the conversion error does not happen on the
operand but on the default clause. CAST('five' AS INTEGER DEFAULT 6 ON
CONVERSION ERROR) would work.
--
Vik Fearing
On 22/07/2025 14:26, Vik Fearing wrote:
The <cast error behavior> is:
<cast error behavior> ::=
ERROR
| NULL
| DEFAULT <value expression>but I am planning on removing the NULL variant in favor of having the
<value expression> be a <contextually typed value specification>. So
it would be either ERROR ON CONVERSION ERROR (postgres's current
behavior), or DEFAULT NULL ON CONVERSION ERROR.
Sorry, I meant <implicitly typed value specification>.
The point being that CAST(ARRAY['1', '2', 'three'] AS INTEGER ARRAY
DEFAULT NULL ON CONVERSION ERROR) will give you (CAST NULL AS INTEGER
ARRAY) and *not* ARRAY[1, 2, NULL].
--
Vik Fearing
On Tue, Jul 22, 2025 at 2:45 AM Vik Fearing <vik@postgresfriends.org> wrote:
On 22/07/2025 03:59, jian he wrote:
Based on my reading of [4], it seems CAST(EXPRESSION AS TYPE DEFAULT
def_expr ON ERROR)
is not included in SQL:2023.[4]
https://peter.eisentraut.org/blog/2023/04/04/sql-2023-is-finished-here-is-whats-new
It was accepted into the standard after 2023 was released. I am the
author of this change in the standard, so feel free to ask me anything
you're unsure about.
That's excellent news. I was already planning on retrying this for v19, but
I'll try sooner now.
On Tue, Jul 22, 2025 at 8:26 PM Vik Fearing <vik@postgresfriends.org> wrote:
The actual syntax is:
<cast specification> ::=
CAST <left paren>
<cast operand> AS <cast target>
[ FORMAT <cast template> ]
[ <cast error behavior> ON CONVERSION ERROR ]
<right paren>"CONVERSION" is probably a noise word, but it is there because A) Oracle
wanted it there, and B) it makes sense because if the <cast error
behavior> fails, that is still a failure of the entire CAST.The <cast error behavior> is:
<cast error behavior> ::=
ERROR
| NULL
| DEFAULT <value expression>but I am planning on removing the NULL variant in favor of having the
<value expression> be a <contextually typed value specification>. So it
would be either ERROR ON CONVERSION ERROR (postgres's current behavior),
or DEFAULT NULL ON CONVERSION ERROR.An example of B) above would be: CAST('five' AS INTEGER DEFAULT 'six' ON
CONVERSION ERROR). 'six' is no more an integer than 'five' is, so that
would error out because the conversion error does not happen on the
operand but on the default clause. CAST('five' AS INTEGER DEFAULT 6 ON
CONVERSION ERROR) would work.
hi.
<cast error behavior> ::=
ERROR
| NULL
| DEFAULT <value expression>
for <value expression>
I disallow it from returning a set, or using aggregate or window functions.
For example, the following three cases will fail:
+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 ret_setint() ON CONVERSION ERROR) --error
(ret_setint function is warped as (select 1 union all select 2))
for array coerce, which you already mentioned, i think the following
is what we expected.
+SELECT CAST('{234,def,567}'::text[] AS integer[] DEFAULT '{-1011}' ON
CONVERSION ERROR);
+ int4
+---------
+ {-1011}
+(1 row)
I didn't implement the [ FORMAT <cast template> ] part for now.
please check the attached regress test and tests expected result.
Attachments:
v2-0001-make-ArrayCoerceExpr-error-safe.patchapplication/x-patch; name=v2-0001-make-ArrayCoerceExpr-error-safe.patchDownload
From 47c181eee593468c3d7b7cb57aec3a1ea8cb3c1d Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Fri, 18 Jul 2025 13:00:19 +0800
Subject: [PATCH v2 1/2] 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 f1569879b52..1f3f899874f 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 1a37737d4a2..81e46cff725 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 c8f53c6fbe7..b5f98bf22f9 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
v2-0002-CAST-expr-AS-newtype-DEFAULT-ON-ERROR.patchapplication/x-patch; name=v2-0002-CAST-expr-AS-newtype-DEFAULT-ON-ERROR.patchDownload
From 7ec9361ba5d6225d6adbda28ae218d82b4a74b7a Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 24 Jul 2025 09:10:35 +0800
Subject: [PATCH v2 2/2] CAST(expr AS newtype DEFAULT ON ERROR)
Type cast nodes are generally one of: FuncExpr, CoerceViaIO, or ArrayCoerceExpr;
see build_coercion_expression. We've already made CoerceViaIO error safe[0]; we
just need to teach it to evaluate default expression after coercion failures.
ArrayCoerceExpr is also handling errors safely.
However, FuncExpr can't be made error-safe directly (see example like int84), so
instead, we construct a CoerceViaIO node using the source expression as its
argument.
[0]: https://git.postgresql.org/cgit/postgresql.git/commit/?id=aaaf9449ec6be62cb0d30ed3588dc384f56274bf
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@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 | 98 +++++++++
src/backend/executor/execExprInterp.c | 30 +++
src/backend/nodes/nodeFuncs.c | 57 +++++
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 | 296 +++++++++++++++++++++++++-
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 | 17 ++
src/include/nodes/parsenodes.h | 6 +
src/include/nodes/primnodes.h | 33 +++
src/include/parser/parse_type.h | 2 +
src/test/regress/expected/misc.out | 151 +++++++++++++
src/test/regress/sql/misc.sql | 60 ++++++
src/tools/pgindent/typedefs.list | 3 +
21 files changed, 881 insertions(+), 8 deletions(-)
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 1f3f899874f..94f04265ba7 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;
@@ -4744,6 +4755,93 @@ ExecBuildParamSetEqual(TupleDesc desc,
return state;
}
+/*
+ * Push steps to evaluate a SafeTypeCastExpr and its various subsidiary expressions.
+ * We already handle CoerceViaIO, CoerceToDomain, and ArrayCoerceExpr error
+ * softly. However, FuncExpr (e.g., int84) cannot be made error-safe.
+ * In such cases, we wrap the source expression and target type information into
+ * a CoerceViaIO node instead.
+ */
+static void
+ExecInitSafeTypeCastExpr(SafeTypeCastExpr *stcexpr , ExprState *state,
+ Datum *resv, bool *resnull,
+ ExprEvalStep *scratch)
+{
+ /*
+ * If coercion to the target type fails, fall back to the default expression
+ * specified in the ON 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;
+
+ stcstate = palloc0(sizeof(SafeTypeCastState));
+ stcstate->stcexpr = stcexpr;
+ stcstate->escontext.type = T_ErrorSaveContext;
+ escontext = &stcstate->escontext;
+ state->escontext = escontext;
+
+ Assert(stcexpr->cast_expr);
+ /* 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))
+ 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 81e46cff725..98e8a9bde62 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/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 7bc823507f1..5da7bd5d88f 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,18 @@ raw_expression_tree_walker_impl(Node *node,
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 31f97151977..76426c88e9a 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 f45131c34c5..a7b4e809716 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 73345bb3c70..66f05113a5c 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
@@ -15943,8 +15945,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"),
@@ -16330,6 +16349,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 d66276801c6..57dff4b7760 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 targetTypeId, 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,6 +2016,106 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
return result;
}
+static bool CovertUnknownConstSafe(ParseState *pstate, Node *node,
+ Oid targetTypeId, 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(targetTypeId, &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;
+}
+
+/*
+ * See transformArrayExpr.
+*/
+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
*
@@ -2013,7 +2125,7 @@ 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)
{
ArrayExpr *newa = makeNode(ArrayExpr);
List *newelems = NIL;
@@ -2044,9 +2156,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 +2185,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 +2255,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 +2869,8 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
(A_ArrayExpr *) arg,
targetBaseType,
elementType,
- targetBaseTypmod);
+ targetBaseTypmod,
+ NULL);
}
else
expr = transformExprRecurse(pstate, arg);
@@ -2779,6 +2907,162 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
return result;
}
+
+/*
+ * Handle an explicit CAST construct.
+ *
+ * Transform the argument, look up the type name, and apply any necessary
+ * coercion function(s).
+ * must do via coerce via IO.
+ * See transformJsonFuncExpr, ExecInitJsonExpr
+ * maybe we also need a JsonExpr Node represent
+ */
+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)
+ {
+ 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 4aba0d9d4d5..812ed18c162 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 7713bdc6af0..02e5f9c92d7 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 b5f98bf22f9..6bd8a989dbd 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 3d6e6bdbfd2..ee868de2c64 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 782291d9998..9de895e682f 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 75366203706..0afcf09c086 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 0fe7b4ebc77..299d4eef4ed 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 e107d6e5f81..fd26e1c98b6 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1058,6 +1058,23 @@ typedef struct DomainConstraintState
ExprState *check_exprstate; /* check_expr's eval state, or NULL */
} DomainConstraintState;
+typedef struct SafeTypeCastState
+{
+ NodeTag type;
+
+ SafeTypeCastExpr *stcexpr;
+
+ /* Set to true if source_expr evaluation cause an error. */
+ NullableDatum error;
+
+ int jump_error;
+
+ int jump_end;
+
+ 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 86a236bd58b..95174e0feef 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 6dfca3cb35b..c66c9aaf556 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -756,6 +756,39 @@ 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;
+
+ /* transformed cast expression, it maybe NULL! */
+ Node *cast_expr;
+
+ /* in case cast expression evaulation failed, fallback default expression */
+ 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 0d919d8bfa2..12381aed64c 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/misc.out b/src/test/regress/expected/misc.out
index 6e816c57f1f..515e761ddcf 100644
--- a/src/test/regress/expected/misc.out
+++ b/src/test/regress/expected/misc.out
@@ -396,3 +396,154 @@ SELECT *, (equipment(CAST((h.*) AS hobbies_r))).name FROM hobbies_r h;
--
-- rewrite rules
--
+-- 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)
+
+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 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;
+SELECT CAST('a' as int DEFAULT ret_setint() ON CONVERSION ERROR) FROM hobbies_r; --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);
+ ^
+-- 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)
+
+CREATE TABLE tcast(a text[], b int GENERATED BY DEFAULT AS IDENTITY);
+INSERT INTO tcast VALUES ('{12}'), ('{1,a, b}'), ('{{1,2}, {c,d}}'), ('{13}');
+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 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 hobbies_r((cast(name as int DEFAULT random(min=>1, max=>1) ON CONVERSION ERROR))); --error
+ERROR: functions in index expression must be marked IMMUTABLE
diff --git a/src/test/regress/sql/misc.sql b/src/test/regress/sql/misc.sql
index 165a2e175fb..3d0b59313ce 100644
--- a/src/test/regress/sql/misc.sql
+++ b/src/test/regress/sql/misc.sql
@@ -273,3 +273,63 @@ SELECT *, (equipment(CAST((h.*) AS hobbies_r))).name FROM hobbies_r h;
--
-- rewrite rules
--
+
+-- 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);
+
+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 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;
+
+SELECT CAST('a' as int DEFAULT ret_setint() ON CONVERSION ERROR) FROM hobbies_r; --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
+
+-- 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);
+
+CREATE TABLE tcast(a text[], b int GENERATED BY DEFAULT AS IDENTITY);
+INSERT INTO tcast VALUES ('{12}'), ('{1,a, b}'), ('{{1,2}, {c,d}}'), ('{13}');
+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 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 hobbies_r((cast(name as int DEFAULT random(min=>1, max=>1) ON CONVERSION ERROR))); --error
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index ff050e93a50..2e79046d83b 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
On Tue, Jul 22, 2025 at 8:26 PM Vik Fearing <vik@postgresfriends.org> wrote:
On 22/07/2025 12:19, jian he wrote:
On Tue, Jul 22, 2025 at 2:45 PM Vik Fearing <vik@postgresfriends.org> wrote:
It was accepted into the standard after 2023 was released. I am the
author of this change in the standard, so feel free to ask me anything
you're unsure about.is the generally syntax as mentioned in this thread:
CAST(source_expression AS target_type DEFAULT default_expression ON ERROR)if so, what's the restriction of default_expression?
The actual syntax is:
<cast specification> ::=
CAST <left paren>
<cast operand> AS <cast target>
[ FORMAT <cast template> ]
[ <cast error behavior> ON CONVERSION ERROR ]
<right paren>"CONVERSION" is probably a noise word, but it is there because A) Oracle
wanted it there, and B) it makes sense because if the <cast error
behavior> fails, that is still a failure of the entire CAST.The <cast error behavior> is:
<cast error behavior> ::=
ERROR
| NULL
| DEFAULT <value expression>
hi.
just want to confirm my understanding of ``[ FORMAT <cast template> ]``.
SELECT CAST('2022-13-32' AS DATE FORMAT 'YYYY-MM-DD' DEFAULT NULL ON
CONVERSION ERROR);
will return NULL.
because ``SELECT to_date('2022-13-32', 'YYYY-MM-DD');``
will error out, so the above query will fall back to the DEFAULT
expression evaluation.
On 24/07/2025 03:22, jian he wrote:
+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
This seems like an arbitrary restriction. Can you explain why this is
necessary? Those same expressions are allowed as the <cast operand>.
+SELECT CAST('a' as int DEFAULT ret_setint() ON CONVERSION ERROR) --error
(ret_setint function is warped as (select 1 union all select 2))
This makes sense to me.
for array coerce, which you already mentioned, i think the following is what we expected. +SELECT CAST('{234,def,567}'::text[] AS integer[] DEFAULT '{-1011}' ON CONVERSION ERROR); + int4 +--------- + {-1011} +(1 row)
Yes, that looks correct to me.
I didn't implement the [ FORMAT <cast template> ] part for now.
That is fine, since it's separate feature
please check the attached regress test and tests expected result.
Except for the weird restriction on the default value, this all looks
good to me (with the usual caveat that I am not an expert in C).
Are you planning to also implement the <castable predicate>?
--
Vik Fearing
On 24/07/2025 15:44, jian he wrote:
just want to confirm my understanding of ``[ FORMAT <cast template> ]``.
SELECT CAST('2022-13-32' AS DATE FORMAT 'YYYY-MM-DD' DEFAULT NULL ON
CONVERSION ERROR);
will return NULL.
because ``SELECT to_date('2022-13-32', 'YYYY-MM-DD');``
will error out, so the above query will fall back to the DEFAULT
expression evaluation.
That is correct. Any error produced during typecasting will fall back
to the DEFAULT value. If not supplied, the behavior is ERROR ON ERROR
as it currently is.
Any error produced while converting the DEFAULT value to the requested
type is raised as an error.
--
Vik Fearing
I didn't implement the [ FORMAT <cast template> ] part for now.
please check the attached regress test and tests expected result.
Question about this:
+/*
+ * Push steps to evaluate a SafeTypeCastExpr and its various subsidiary
expressions.
+ * We already handle CoerceViaIO, CoerceToDomain, and ArrayCoerceExpr error
+ * softly. However, FuncExpr (e.g., int84) cannot be made error-safe.
+ * In such cases, we wrap the source expression and target type
information into
+ * a CoerceViaIO node instead.
+ */
I'm not sure we _can_ just fall back to the CoerceViaIO if there is a
defined cast from TypeA -> TypeB. I seem to recall there was some reason we
couldn't do that, possibly to do with how it handled rounding, but I have
no clear memory of it.
Aside from that, I like what you've done with making SafeTypeCastExpr be
its own node type and not saddling regular typecasts with the overhead.
On Thu, Jul 31, 2025 at 3:15 AM Corey Huinker <corey.huinker@gmail.com> wrote:
Question about this:
+/* + * Push steps to evaluate a SafeTypeCastExpr and its various subsidiary expressions. + * We already handle CoerceViaIO, CoerceToDomain, and ArrayCoerceExpr error + * softly. However, FuncExpr (e.g., int84) cannot be made error-safe. + * In such cases, we wrap the source expression and target type information into + * a CoerceViaIO node instead. + */I'm not sure we _can_ just fall back to the CoerceViaIO if there is a defined cast from TypeA -> TypeB. I seem to recall there was some reason we couldn't do that, possibly to do with how it handled rounding, but I have no clear memory of it.
indeed.
select ('11.1'::numeric::int);
return 11, but '11.1' string can not coerce to int 11. So in this
case, we can not use CoerceViaIO.
so we need to handle numeric source types with fractional points with
special care.
currently, this applies only to numeric, float4, and float8.
(hope this is all the corner case we need to catch...)
select castsource::regtype, casttarget::regtype, castfunc::regproc, castcontext
from pg_cast pc
where castsource::regtype = ANY('{numeric, float4, float8}'::regtype[])
and castmethod = 'f';
only return 17 rows. one row is cast numreic to money, function numeric_cash.
numeric_cash seems more trickly to be error safe, because it will call
numeric_mul.
so I made these 16 function errors safe.
see v3-0001-make-some-numeric-cast-function-error-safe.patch
Attachments:
v3-0002-make-ArrayCoerceExpr-error-safe.patchtext/x-patch; charset=US-ASCII; name=v3-0002-make-ArrayCoerceExpr-error-safe.patchDownload
From 0fa2361b7d7692a9c032d8a8a7f2b04ab4302849 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Fri, 18 Jul 2025 13:00:19 +0800
Subject: [PATCH v3 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 f1569879b52..1f3f899874f 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 1a37737d4a2..81e46cff725 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 c8f53c6fbe7..b5f98bf22f9 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
v3-0001-make-some-numeric-cast-function-error-safe.patchtext/x-patch; charset=US-ASCII; name=v3-0001-make-some-numeric-cast-function-error-safe.patchDownload
From 798db5f9a05a5398c36641af0e33505311939330 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Fri, 1 Aug 2025 13:22:59 +0800
Subject: [PATCH v3 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_4hQ@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 7b97d2be6ca..6461e9c94b1 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 9dd5889f34c..4b9a7620671 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 c9233565d57..cf498627a9a 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
v3-0003-CAST-expr-AS-newtype-DEFAULT-ON-ERROR.patchtext/x-patch; charset=US-ASCII; name=v3-0003-CAST-expr-AS-newtype-DEFAULT-ON-ERROR.patchDownload
From 6741e1ca2473f10d9d0f2bb3ec81eed6886f2787 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Fri, 1 Aug 2025 13:42:55 +0800
Subject: [PATCH v3 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_4hQ@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/nodes/nodeFuncs.c | 57 +++++
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 +
22 files changed, 1063 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 1f3f899874f..03cafad525d 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 81e46cff725..98e8a9bde62 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/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 7bc823507f1..5da7bd5d88f 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,18 @@ raw_expression_tree_walker_impl(Node *node,
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 31f97151977..76426c88e9a 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 6f0b338d2cd..c04ca88b0fb 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -2942,6 +2942,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 db43034b9db..20293d75524 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
@@ -15931,8 +15933,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"),
@@ -16318,6 +16337,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 d66276801c6..2f5a3059aca 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 4aba0d9d4d5..812ed18c162 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 7713bdc6af0..02e5f9c92d7 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 b5f98bf22f9..6bd8a989dbd 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 3d6e6bdbfd2..ee868de2c64 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 782291d9998..9de895e682f 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 75366203706..0afcf09c086 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 0fe7b4ebc77..299d4eef4ed 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 e107d6e5f81..282bfa770ef 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 86a236bd58b..95174e0feef 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 6dfca3cb35b..d2df7c28932 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 0d919d8bfa2..12381aed64c 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 00000000000..2915e6d7a3a
--- /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 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;
+SELECT CAST('a' as int DEFAULT ret_setint() ON CONVERSION ERROR) FROM hobbies_r; --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);
+ ^
+-- 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)
+
+CREATE TABLE tcast(a text[], b int GENERATED BY DEFAULT AS IDENTITY);
+INSERT INTO tcast VALUES ('{12}'), ('{1,a, b}'), ('{{1,2}, {c,d}}'), ('{13}');
+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 hobbies_r((cast(name 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 fbffc67ae60..ebbd454c450 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 00000000000..657db393996
--- /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 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;
+
+SELECT CAST('a' as int DEFAULT ret_setint() ON CONVERSION ERROR) FROM hobbies_r; --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
+
+-- 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);
+
+CREATE TABLE tcast(a text[], b int GENERATED BY DEFAULT AS IDENTITY);
+INSERT INTO tcast VALUES ('{12}'), ('{1,a, b}'), ('{{1,2}, {c,d}}'), ('{13}');
+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 hobbies_r((cast(name 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 e6f2e93b2d6..5b32df3a2db 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2652,6 +2652,9 @@ STRLEN
SV
SYNCHRONIZATION_BARRIER
SYSTEM_INFO
+SafeTypeCast
+SafeTypeCastExpr
+SafeTypeCastState
SampleScan
SampleScanGetSampleSize_function
SampleScanState
--
2.34.1
hi.
fix the regress tests failure in
https://api.cirrus-ci.com/v1/artifact/task/5894868779663360/testrun/build/testrun/regress/regress/regression.diffs
Attachments:
v4-0002-make-ArrayCoerceExpr-error-safe.patchtext/x-patch; charset=US-ASCII; name=v4-0002-make-ArrayCoerceExpr-error-safe.patchDownload
From e60e5190511326568eba8e6748062adb47f1134c Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@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
v4-0001-make-some-numeric-cast-function-error-safe.patchtext/x-patch; charset=US-ASCII; name=v4-0001-make-some-numeric-cast-function-error-safe.patchDownload
From e3854ee1d362646ea97a8baa4e262cc3a9e95913 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@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_4hQ@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
v4-0003-CAST-expr-AS-newtype-DEFAULT-ON-ERROR.patchtext/x-patch; charset=US-ASCII; name=v4-0003-CAST-expr-AS-newtype-DEFAULT-ON-ERROR.patchDownload
From 138c6db6386d5c937fce6d903b3608f50950ee3a Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@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_4hQ@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
so we need to handle numeric source types with fractional points with
special care.
currently, this applies only to numeric, float4, and float8.
(hope this is all the corner case we need to catch...)
I'm fairly certain that the committers won't like us special-casing the
internal cast functions, as we would have to maintain these special cases
as new core types are added, and it still bypasses the defined cast
function for user-defined types, which could have similar issues similar to
the rounding issue.
I think the way forward here is either to:
1. add a second function definition to CAST. The potential syntax forr a
second function gets clumsy, but might look something like this:
CREATE CAST (source_type AS target_type)
WITH FUNCTION function_name [ (argument_type [, ...]) ]
[ AS ASSIGNMENT | AS IMPLICIT ]
[
WITH SAFE FUNCTION function_name [ (argument_type [, ...]) ]
[ AS ASSIGNMENT | AS IMPLICIT ]
]
That doesn't seem right to me, it seems easier to:
2. Modify the CAST definition to indicate whether the existing cast
function has the regular function signature or a -Safe one. In cases where
a CAST has a defined function but the safe flag is turned off, we would
have to fail the query with an error like "Defined CAST function from
srctype to desttype is not error-safe".
This would involve changing the syntax of CREATE CAST by adding an option
SAFE, or ERROR SAFE, or similar:
CREATE CAST (source_type AS target_type)
WITH [SAFE] FUNCTION function_name [ (argument_type [, ...]) ]
[ AS ASSIGNMENT | AS IMPLICIT ]
We would add a new value to pg_cast.castmethod, 's' for "safe".
We could refactor all the numeric types to use the modified functions, so
no special-case code there anymore, and it gives extension writers an
incentive to (eventually) make their own cast functions error-safe.
While method 2 seems a lot cleaner, there may be a performance regression
in the now error-safe typecast functions. If so, that might tip the balance
to having two functions defined.
On Mon, Aug 4, 2025 at 1:09 PM Corey Huinker <corey.huinker@gmail.com> wrote:
so we need to handle numeric source types with fractional points with
special care.
currently, this applies only to numeric, float4, and float8.
(hope this is all the corner case we need to catch...)I'm fairly certain that the committers won't like us special-casing the internal cast functions, as we would have to maintain these special cases as new core types are added, and it still bypasses the defined cast function for user-defined types, which could have similar issues similar to the rounding issue.
It's not special-casing the internal cast functions.
It's how the cast being evaluated.
There are two ways: CoerceViaIO, FuncExpr.
generally if there is a pg_cast entry, postgres will use FuncExpr. but to safely
cast evaluation (DEFAULT ON CONVERSION ERROR) we can not use FuncExpr in some
cases. Because the FuncExpr associate function is not error safe.
So in v4, we try to use CoerceViaIO to evaluate the case, but it turns
out CoerceViaIO results are
not the same as FuncExpr.
one of the example is:
select ('11.1'::numeric::int);
In the end, it seems we need to make all these functions in the below
query error safe.
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
order by castsource::regtype;
It's a lot of work, but seems doable, after playing around with it.
I don't think we need to change the pg_cast catalog entry,
we just need to make these function (pg_cast.castmethod) errors safe.
In the end, it seems we need to make all these functions in the below
query error safe.
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc
pp on
pp.oid = pc.castfunc and pc.castfunc > 0
order by castsource::regtype;
It's a lot of work, but seems doable, after playing around with it.
It is do-able. But that's just the cast functions that are part of core
postgres.
I don't think we need to change the pg_cast catalog entry,
we just need to make these function (pg_cast.castmethod) errors safe.
That would break any user-defined cast functions that were not also error
safe, which is to say all of them.
We need a way for user-defined cast functions to indicate whether or not
they are error safe, and handle both situations accordingly (i.e. fail a
CAST ON DEFAULT when the user-defined cast is not error-safe).
On Tue, Aug 5, 2025 at 12:10 PM Corey Huinker <corey.huinker@gmail.com> wrote:
In the end, it seems we need to make all these functions in the below
query error safe.
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
order by castsource::regtype;
It's a lot of work, but seems doable, after playing around with it.It is do-able. But that's just the cast functions that are part of core postgres.
hi.
it's doable for most of the data types.
but I found geometric type related cast function refactoring to error
safe is quite challenging,
so I skip that part refactoring.
I don't think we need to change the pg_cast catalog entry,
we just need to make these function (pg_cast.castmethod) errors safe.That would break any user-defined cast functions that were not also error safe, which is to say all of them.
We need a way for user-defined cast functions to indicate whether or not they are error safe, and handle both situations accordingly (i.e. fail a CAST ON DEFAULT when the user-defined cast is not error-safe).
I understand what you mean now.
CREATE CAST can use built-in functions too, we have no way to check
whether these CREATE CAST
associated functions are error safe or not.
for example:
CREATE CAST (jsonpath AS bytea) WITH FUNCTION jsonpath_send (jsonpath
) AS ASSIGNMENT;
select '$'::jsonpath::bytea;
To avoid this situation, we have to add a new column to pg_cast to
indicate whether a cast function is error-safe.
It's unlikely a pg_cast entry has two functions, one is error safe, one is not,
adding pg_cast.casterrorsafefunc would not be that appropriate.
so I choose pg_cast.casterrorsafe would be fine.
pg_cast.casterrorsafe true means castfunc function is error safe, we
can use it as safe cast evaluation
(CAST... DEFAULT defexpr ON CONVERSION ERROR)
please check the attached V6 script:
v6-0001 to v6-0016 is about making existing pg_cast.castfunc function
error safe.
(commit message have associated query to explain the refactoring, as mentioned
above, geometric and money associated type not refactored yet)
v6-0017-make-ArrayCoerceExpr-error-safe.patch
v6-0018-CAST-expr-AS-newtype-DEFAULT-ON-ERROR.patch
is about (CAST... DEFAULT defexpr ON CONVERSION ERROR).
Attachments:
v6-0016-error-safe-for-casting-character-varying-to-other-types-p.patchtext/x-patch; charset=US-ASCII; name=v6-0016-error-safe-for-casting-character-varying-to-other-types-p.patchDownload
From 8efc16c4b2e1c6fa14a4851325956c10232e75e9 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sun, 10 Aug 2025 01:36:17 +0800
Subject: [PATCH v6 16/18] error safe for casting character varying to other
types per pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc and pc.castfunc > 0 and
(castsource::regtype = 'character varying'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
-------------------+-------------------+----------+-------------+------------+---------------+----------
character varying | regclass | 1079 | i | f | text_regclass | regclass
character varying | "char" | 944 | a | f | text_char | char
character varying | name | 1400 | i | f | text_name | name
character varying | xml | 2896 | e | f | texttoxml | xml
character varying | character varying | 669 | i | f | varchar | varchar
(5 rows)
texttoxml, text_regclass was refactored as error safe in prior patch.
text_char, text_name is already error safe.
---
src/backend/utils/adt/varchar.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c
index 5cb5c8c46f9..08f1bf5a24d 100644
--- a/src/backend/utils/adt/varchar.c
+++ b/src/backend/utils/adt/varchar.c
@@ -634,7 +634,7 @@ varchar(PG_FUNCTION_ARGS)
{
for (i = maxmblen; i < len; i++)
if (s_data[i] != ' ')
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
errmsg("value too long for type character varying(%d)",
maxlen)));
--
2.34.1
v6-0015-error-safe-for-casting-timestamp-to-other-types-per-pg_ca.patchtext/x-patch; charset=US-ASCII; name=v6-0015-error-safe-for-casting-timestamp-to-other-types-per-pg_ca.patchDownload
From b1cd2195beefa7a4995dcb2ec8a390ff99ca7c4d Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sun, 10 Aug 2025 01:33:36 +0800
Subject: [PATCH v6 15/18] error safe for casting timestamp to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
and pc.castfunc > 0 and (castsource::regtype ='timestamp'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
-----------------------------+-----------------------------+----------+-------------+------------+-----------------------+-------------
timestamp without time zone | date | 2029 | a | f | timestamp_date | date
timestamp without time zone | time without time zone | 1316 | a | f | timestamp_time | time
timestamp without time zone | timestamp with time zone | 2028 | i | f | timestamp_timestamptz | timestamptz
timestamp without time zone | timestamp without time zone | 1961 | i | f | timestamp_scale | timestamp
(4 rows)
---
src/backend/utils/adt/date.c | 13 +++++++++++--
src/backend/utils/adt/timestamp.c | 17 +++++++++++++++--
2 files changed, 26 insertions(+), 4 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 111bfd8f519..c5562b563e5 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -1373,7 +1373,16 @@ timestamp_date(PG_FUNCTION_ARGS)
Timestamp timestamp = PG_GETARG_TIMESTAMP(0);
DateADT result;
- result = timestamp2date_opt_overflow(timestamp, NULL);
+ if (likely(!fcinfo->context))
+ result = timestamptz2date_opt_overflow(timestamp, NULL);
+ else
+ {
+ int overflow;
+ result = timestamp2date_opt_overflow(timestamp, &overflow);
+
+ if (overflow != 0)
+ PG_RETURN_NULL();
+ }
PG_RETURN_DATEADT(result);
}
@@ -2089,7 +2098,7 @@ timestamp_time(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 8648a16db7f..b40dfd224d2 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -352,7 +352,8 @@ timestamp_scale(PG_FUNCTION_ARGS)
result = timestamp;
- AdjustTimestampForTypmod(&result, typmod, NULL);
+ if (!AdjustTimestampForTypmod(&result, typmod, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_TIMESTAMP(result);
}
@@ -6451,7 +6452,19 @@ timestamp_timestamptz(PG_FUNCTION_ARGS)
{
Timestamp timestamp = PG_GETARG_TIMESTAMP(0);
- PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(timestamp));
+ if (likely(!fcinfo->context))
+ PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(timestamp));
+ else
+ {
+ TimestampTz result;
+ int overflow;
+
+ result = timestamp2timestamptz_opt_overflow(timestamp, &overflow);
+ if (overflow != 0)
+ PG_RETURN_NULL();
+ else
+ PG_RETURN_TIMESTAMPTZ(result);
+ }
}
/*
--
2.34.1
v6-0014-error-safe-for-casting-timestamptz-to-other-types-per-pg_.patchtext/x-patch; charset=US-ASCII; name=v6-0014-error-safe-for-casting-timestamptz-to-other-types-per-pg_.patchDownload
From a6e538d1ffdd0530879b7ad681180944c7390eea Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sun, 10 Aug 2025 01:31:09 +0800
Subject: [PATCH v6 14/18] error safe for casting timestamptz to other types
per pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
and pc.castfunc > 0 and (castsource::regtype ='timestamptz'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
--------------------------+-----------------------------+----------+-------------+------------+-----------------------+-------------
timestamp with time zone | date | 1178 | a | f | timestamptz_date | date
timestamp with time zone | time without time zone | 2019 | a | f | timestamptz_time | time
timestamp with time zone | timestamp without time zone | 2027 | a | f | timestamptz_timestamp | timestamp
timestamp with time zone | time with time zone | 1388 | a | f | timestamptz_timetz | timetz
timestamp with time zone | timestamp with time zone | 1967 | i | f | timestamptz_scale | timestamptz
(5 rows)
---
src/backend/utils/adt/date.c | 16 +++++++++++++---
src/backend/utils/adt/timestamp.c | 17 +++++++++++++++--
2 files changed, 28 insertions(+), 5 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 4f0f3d26989..111bfd8f519 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -1468,7 +1468,17 @@ timestamptz_date(PG_FUNCTION_ARGS)
TimestampTz timestamp = PG_GETARG_TIMESTAMP(0);
DateADT result;
- result = timestamptz2date_opt_overflow(timestamp, NULL);
+ if (likely(!fcinfo->context))
+ result = timestamptz2date_opt_overflow(timestamp, NULL);
+ else
+ {
+ int overflow;
+ result = timestamptz2date_opt_overflow(timestamp, &overflow);
+
+ if (overflow != 0)
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_DATEADT(result);
}
@@ -2110,7 +2120,7 @@ timestamptz_time(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
@@ -3029,7 +3039,7 @@ timestamptz_timetz(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index f1e96b84a6f..8648a16db7f 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -875,7 +875,8 @@ timestamptz_scale(PG_FUNCTION_ARGS)
result = timestamp;
- AdjustTimestampForTypmod(&result, typmod, NULL);
+ if (!AdjustTimestampForTypmod(&result, typmod, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_TIMESTAMPTZ(result);
}
@@ -6528,7 +6529,19 @@ timestamptz_timestamp(PG_FUNCTION_ARGS)
{
TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(0);
- PG_RETURN_TIMESTAMP(timestamptz2timestamp(timestamp));
+ if (likely(!fcinfo->context))
+ PG_RETURN_TIMESTAMP(timestamptz2timestamp(timestamp));
+ else
+ {
+ int overflow;
+ Timestamp result;
+
+ result = timestamptz2timestamp_opt_overflow(timestamp, &overflow);
+ if (overflow != 0)
+ PG_RETURN_NULL();
+ else
+ PG_RETURN_TIMESTAMP(result);
+ }
}
/*
--
2.34.1
v6-0017-make-ArrayCoerceExpr-error-safe.patchtext/x-patch; charset=US-ASCII; name=v6-0017-make-ArrayCoerceExpr-error-safe.patchDownload
From 2d6daf36a294f5ff64b30b453bb319e09114c1d3 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sun, 10 Aug 2025 01:41:14 +0800
Subject: [PATCH v6 17/18] make ArrayCoerceExpr error safe
similar to https://git.postgresql.org/cgit/postgresql.git/commit/?id=aaaf9449ec6be62cb0d30ed3588dc384f56274bf
---
src/backend/executor/execExpr.c | 1 +
src/backend/executor/execExprInterp.c | 7 +++++++
src/backend/utils/adt/arrayfuncs.c | 7 +++++++
3 files changed, 15 insertions(+)
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index f1569879b52..921ec4e0bc1 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -1701,6 +1701,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
elemstate->innermost_caseval = (Datum *) palloc(sizeof(Datum));
elemstate->innermost_casenull = (bool *) palloc(sizeof(bool));
+ 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 67f4e00eac4..998674c180d 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -3644,6 +3644,13 @@ 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 c8f53c6fbe7..b5f98bf22f9 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
v6-0018-CAST-expr-AS-newtype-DEFAULT-ON-ERROR.patchtext/x-patch; charset=US-ASCII; name=v6-0018-CAST-expr-AS-newtype-DEFAULT-ON-ERROR.patchDownload
From 51bef18b4cc1025848ef7600737449b86015509b Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Mon, 11 Aug 2025 14:11:42 +0800
Subject: [PATCH v6 18/18] CAST(expr AS newtype DEFAULT ON ERROR)
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.
draft doc also added.
We cannot simply prohibit user-defined functions in pg_cast for safe cast
evaluation because CREATE CAST can also utilize built-in functions. So, to
completely disallow custom casts created via CREATE CAST used in safe cast
evaluation, a new field in pg_cast would unfortunately be necessary.
[0]: https://git.postgresql.org/cgit/postgresql.git/commit/?id=aaaf9449ec6be62cb0d30ed3588dc384f56274bf
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@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}
---
doc/src/sgml/catalogs.sgml | 11 +
doc/src/sgml/syntax.sgml | 15 +
src/backend/catalog/pg_cast.c | 1 +
src/backend/executor/execExpr.c | 83 +++-
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 | 345 +++++++++++++-
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/catalog/pg_cast.dat | 302 ++++++------
src/include/catalog/pg_cast.h | 4 +
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 | 556 ++++++++++++++++++++++
src/test/regress/expected/create_cast.out | 5 +
src/test/regress/expected/opr_sanity.out | 24 +-
src/test/regress/parallel_schedule | 2 +-
src/test/regress/sql/cast.sql | 259 ++++++++++
src/test/regress/sql/create_cast.sql | 1 +
src/tools/pgindent/typedefs.list | 3 +
31 files changed, 1793 insertions(+), 173 deletions(-)
create mode 100644 src/test/regress/expected/cast.out
create mode 100644 src/test/regress/sql/cast.sql
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index da8a7882580..6398a6a0339 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1846,6 +1846,17 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<literal>b</literal> means that the types are binary-coercible, thus no conversion is required.
</para></entry>
</row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>casterrorsafe</structfield> <type>bool</type>
+ </para>
+ <para>
+ This indicates whether the <structfield>castfunc</structfield> function is error-safe.
+ If true, the <structfield>castfunc</structfield> function is error-safe; if false, it is not.
+ </para></entry>
+ </row>
+
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml
index 916189a7d68..4854b78966e 100644
--- a/doc/src/sgml/syntax.sgml
+++ b/doc/src/sgml/syntax.sgml
@@ -2104,6 +2104,21 @@ CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable>
The <literal>CAST</literal> syntax conforms to SQL; the syntax with
<literal>::</literal> is historical <productname>PostgreSQL</productname>
usage.
+ The alternative syntax is
+<synopsis>
+CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable> ERROR ON CONVERSION ERROR )
+</synopsis>
+ </para>
+
+ <para>
+<synopsis>
+CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable> DEFAULT <replaceable>expression</replaceable> ON CONVERSION ERROR )
+</synopsis>
+ For example, the following query will evaluate the default expression and return 42.
+ TODO: more explanation
+<programlisting>
+SELECT (CAST('error' AS integer DEFAULT 42 ON CONVERSION ERROR));
+</programlisting>
</para>
<para>
diff --git a/src/backend/catalog/pg_cast.c b/src/backend/catalog/pg_cast.c
index 1773c9c5491..6fe65d24d31 100644
--- a/src/backend/catalog/pg_cast.c
+++ b/src/backend/catalog/pg_cast.c
@@ -84,6 +84,7 @@ CastCreate(Oid sourcetypeid, Oid targettypeid,
values[Anum_pg_cast_castfunc - 1] = ObjectIdGetDatum(funcid);
values[Anum_pg_cast_castcontext - 1] = CharGetDatum(castcontext);
values[Anum_pg_cast_castmethod - 1] = CharGetDatum(castmethod);
+ values[Anum_pg_cast_casterrorsafe - 1] = BoolGetDatum(false);
tuple = heap_form_tuple(RelationGetDescr(relation), values, nulls);
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 921ec4e0bc1..eaed2d2be67 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,
@@ -2178,6 +2181,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;
@@ -2744,7 +2755,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;
@@ -4742,6 +4753,76 @@ 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
+ {
+ SafeTypeCastState *stcstate;
+ ErrorSaveContext *escontext;
+ ErrorSaveContext *saved_escontext;
+ List *jumps_to_end = NIL;
+
+ stcstate = palloc0(sizeof(SafeTypeCastState));
+ stcstate->stcexpr = stcexpr;
+ stcstate->escontext.type = T_ErrorSaveContext;
+ escontext = &stcstate->escontext;
+ state->escontext = escontext;
+
+ /* evaluate argument expression into step's result area */
+ ExecInitExprRec((Expr *) stcexpr->cast_expr,
+ 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 998674c180d..351923d7ec4 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 */
@@ -5190,6 +5196,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 890bcb0b0a7..a2dfb823932 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 7bc823507f1..212f3e3c50c 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 31f97151977..76426c88e9a 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 6f0b338d2cd..c04ca88b0fb 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -2942,6 +2942,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 db43034b9db..20293d75524 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
@@ -15931,8 +15933,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"),
@@ -16318,6 +16337,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 d66276801c6..f837699d4b7 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -16,6 +16,8 @@
#include "postgres.h"
#include "catalog/pg_aggregate.h"
+#include "catalog/pg_cast.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
#include "miscadmin.h"
@@ -37,6 +39,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 +63,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 +82,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 +113,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 +172,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 +2017,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 +2168,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 +2197,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 +2267,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 +2881,8 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
(A_ArrayExpr *) arg,
targetBaseType,
elementType,
- targetBaseTypmod);
+ targetBaseTypmod,
+ NULL);
}
else
expr = transformExprRecurse(pstate, arg);
@@ -2779,6 +2919,199 @@ 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;
+ TypeCast *tcast = (TypeCast *) tc->cast;
+ Node *tc_arg = tcast->arg;
+ Node *def_expr;
+ Node *cast_expr = NULL;
+ Node *source_expr = NULL;
+ Node *array_expr = NULL;
+ Oid inputType = InvalidOid;
+ Oid targetType;
+ Oid targetBaseType;
+ int32 targetTypmod;
+ int32 targetBaseTypmod;
+ bool can_coerce = true;
+ int def_expr_loc = -1;
+ int location;
+
+ result = makeNode(SafeTypeCastExpr);
+
+ /* Look up the type name first */
+ typenameTypeIdAndMod(pstate, tcast->typeName, &targetType, &targetTypmod);
+ targetBaseTypmod = targetTypmod;
+ targetBaseType = getBaseTypeAndTypmod(targetType, &targetBaseTypmod);
+
+ 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 DEFAULT clause for CAST ... ON CONVERSION ERROR 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 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.
+ */
+ 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)
+ {
+ Assert(source_expr == NULL);
+ 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)
+ {
+ Node *origexpr;
+ cast_expr = coerce_to_target_type(pstate, source_expr, inputType,
+ targetType, targetTypmod,
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ location);
+ origexpr = cast_expr;
+ while (cast_expr && IsA(cast_expr, CollateExpr))
+ cast_expr = (Node *) ((CollateExpr *) cast_expr)->arg;
+
+ if (cast_expr && IsA(cast_expr, FuncExpr))
+ {
+ HeapTuple tuple;
+ ListCell *lc;
+ Node *sexpr;
+ FuncExpr *fexpr = (FuncExpr *) cast_expr;
+
+ lc = list_head(fexpr->args);
+ sexpr = (Node *) lfirst(lc);
+
+ /* Look in pg_cast */
+ tuple = SearchSysCache2(CASTSOURCETARGET,
+ ObjectIdGetDatum(exprType(sexpr)),
+ ObjectIdGetDatum(targetType));
+
+ if (HeapTupleIsValid(tuple))
+ {
+ Form_pg_cast castForm = (Form_pg_cast) GETSTRUCT(tuple);
+ if (!castForm->casterrorsafe)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot cast type %s to %s when %s clause is specified for %s",
+ format_type_be(inputType),
+ format_type_be(targetType),
+ "DEFAULT",
+ "CAST ... ON CONVERSION ERROR"),
+ errhint("Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast"),
+ parser_errposition(pstate, exprLocation(source_expr)));
+ }
+ else
+ elog(ERROR, "cache lookup failed for pg_cast entry (%s cast to %s)",
+ format_type_be(inputType),
+ format_type_be(targetType));
+ ReleaseSysCache(tuple);
+ }
+ cast_expr = origexpr;
+ }
+
+ 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 4aba0d9d4d5..812ed18c162 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 7713bdc6af0..02e5f9c92d7 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 b5f98bf22f9..6bd8a989dbd 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 3d6e6bdbfd2..ee868de2c64 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 782291d9998..9de895e682f 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/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat
index fbfd669587f..3adef6b4faf 100644
--- a/src/include/catalog/pg_cast.dat
+++ b/src/include/catalog/pg_cast.dat
@@ -19,65 +19,65 @@
# int2->int4->int8->numeric->float4->float8, while casts in the
# reverse direction are assignment-only.
{ castsource => 'int8', casttarget => 'int2', castfunc => 'int2(int8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'int4', castfunc => 'int4(int8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'float4', castfunc => 'float4(int8)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'float8', castfunc => 'float8(int8)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'numeric', castfunc => 'numeric(int8)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'int8', castfunc => 'int8(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'int4', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'float4', castfunc => 'float4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'float8', castfunc => 'float8(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'numeric', castfunc => 'numeric(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'int8', castfunc => 'int8(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'int2', castfunc => 'int2(int4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'float4', castfunc => 'float4(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'float8', castfunc => 'float8(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'numeric', castfunc => 'numeric(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'int8', castfunc => 'int8(float4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'int2', castfunc => 'int2(float4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'int4', castfunc => 'int4(float4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'float8', castfunc => 'float8(float4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'numeric',
- castfunc => 'numeric(float4)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'numeric(float4)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'int8', castfunc => 'int8(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'int2', castfunc => 'int2(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'int4', castfunc => 'int4(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'float4', castfunc => 'float4(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'numeric',
- castfunc => 'numeric(float8)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'numeric(float8)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'int8', castfunc => 'int8(numeric)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'int2', castfunc => 'int2(numeric)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'int4', castfunc => 'int4(numeric)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'float4',
- castfunc => 'float4(numeric)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'float4(numeric)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'float8',
- castfunc => 'float8(numeric)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'float8(numeric)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'money', casttarget => 'numeric', castfunc => 'numeric(money)',
castcontext => 'a', castmethod => 'f' },
{ castsource => 'numeric', casttarget => 'money', castfunc => 'money(numeric)',
@@ -89,13 +89,13 @@
# Allow explicit coercions between int4 and bool
{ castsource => 'int4', casttarget => 'bool', castfunc => 'bool(int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'int4', castfunc => 'int4(bool)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Allow explicit coercions between xid8 and xid
{ castsource => 'xid8', casttarget => 'xid', castfunc => 'xid(xid8)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# OID category: allow implicit conversion from any integral type (including
# int8, to support OID literals > 2G) to OID, as well as assignment coercion
@@ -106,13 +106,13 @@
# casts from text and varchar to regclass, which exist mainly to support
# legacy forms of nextval() and related functions.
{ castsource => 'int8', casttarget => 'oid', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'oid', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'oid', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regproc', castfunc => '0',
@@ -120,13 +120,13 @@
{ castsource => 'regproc', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regproc', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regproc', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regproc', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regproc', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regproc', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'regproc', casttarget => 'regprocedure', castfunc => '0',
@@ -138,13 +138,13 @@
{ castsource => 'regprocedure', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regprocedure', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regprocedure', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regprocedure', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regprocedure', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regprocedure', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regoper', castfunc => '0',
@@ -152,13 +152,13 @@
{ castsource => 'regoper', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regoper', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regoper', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regoper', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regoper', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regoper', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'regoper', casttarget => 'regoperator', castfunc => '0',
@@ -170,13 +170,13 @@
{ castsource => 'regoperator', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regoperator', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regoperator', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regoperator', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regoperator', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regoperator', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regclass', castfunc => '0',
@@ -184,13 +184,13 @@
{ castsource => 'regclass', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regclass', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regclass', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regclass', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regclass', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regclass', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regcollation', castfunc => '0',
@@ -198,13 +198,13 @@
{ castsource => 'regcollation', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regcollation', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regcollation', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regcollation', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regcollation', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regcollation', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regtype', castfunc => '0',
@@ -212,13 +212,13 @@
{ castsource => 'regtype', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regtype', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regtype', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regtype', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regtype', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regtype', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regconfig', castfunc => '0',
@@ -226,13 +226,13 @@
{ castsource => 'regconfig', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regconfig', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regconfig', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regconfig', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regconfig', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regconfig', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regdictionary', castfunc => '0',
@@ -240,31 +240,31 @@
{ castsource => 'regdictionary', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regdictionary', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regdictionary', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regdictionary', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regdictionary', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regdictionary', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'text', casttarget => 'regclass', castfunc => 'regclass',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'regclass', castfunc => 'regclass',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'oid', casttarget => 'regrole', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regrole', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regrole', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regrole', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regrole', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regrole', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regrole', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regnamespace', castfunc => '0',
@@ -272,13 +272,13 @@
{ castsource => 'regnamespace', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regnamespace', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regnamespace', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regnamespace', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regnamespace', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regnamespace', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regdatabase', castfunc => '0',
@@ -286,13 +286,13 @@
{ castsource => 'regdatabase', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regdatabase', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regdatabase', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regdatabase', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regdatabase', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regdatabase', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
@@ -302,57 +302,57 @@
{ castsource => 'text', casttarget => 'varchar', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'bpchar', casttarget => 'text', castfunc => 'text(bpchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bpchar', casttarget => 'varchar', castfunc => 'text(bpchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'text', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'varchar', casttarget => 'bpchar', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'char', casttarget => 'text', castfunc => 'text(char)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'char', casttarget => 'bpchar', castfunc => 'bpchar(char)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'char', casttarget => 'varchar', castfunc => 'text(char)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'name', casttarget => 'text', castfunc => 'text(name)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'name', casttarget => 'bpchar', castfunc => 'bpchar(name)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'name', casttarget => 'varchar', castfunc => 'varchar(name)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'text', casttarget => 'char', castfunc => 'char(text)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bpchar', casttarget => 'char', castfunc => 'char(text)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'char', castfunc => 'char(text)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'text', casttarget => 'name', castfunc => 'name(text)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bpchar', casttarget => 'name', castfunc => 'name(bpchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'name', castfunc => 'name(varchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
# Allow explicit coercions between bytea and integer types
{ castsource => 'int2', casttarget => 'bytea', castfunc => 'bytea(int2)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'bytea', castfunc => 'bytea(int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'bytea', castfunc => 'bytea(int8)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bytea', casttarget => 'int2', castfunc => 'int2(bytea)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bytea', casttarget => 'int4', castfunc => 'int4(bytea)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bytea', casttarget => 'int8', castfunc => 'int8(bytea)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Allow explicit coercions between int4 and "char"
{ castsource => 'char', casttarget => 'int4', castfunc => 'int4(char)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'char', castfunc => 'char(int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# pg_node_tree can be coerced to, but not from, text
{ castsource => 'pg_node_tree', casttarget => 'text', castfunc => '0',
@@ -378,31 +378,31 @@
# Datetime category
{ castsource => 'date', casttarget => 'timestamp',
- castfunc => 'timestamp(date)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timestamp(date)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'date', casttarget => 'timestamptz',
- castfunc => 'timestamptz(date)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timestamptz(date)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'time', casttarget => 'interval', castfunc => 'interval(time)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'time', casttarget => 'timetz', castfunc => 'timetz(time)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'date',
- castfunc => 'date(timestamp)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'date(timestamp)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'time',
- castfunc => 'time(timestamp)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'time(timestamp)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'timestamptz',
- castfunc => 'timestamptz(timestamp)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timestamptz(timestamp)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'date',
- castfunc => 'date(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'date(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'time',
- castfunc => 'time(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'time(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'timestamp',
- castfunc => 'timestamp(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'timestamp(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'timetz',
- castfunc => 'timetz(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'timetz(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'interval', casttarget => 'time', castfunc => 'time(interval)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timetz', casttarget => 'time', castfunc => 'time(timetz)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
# Geometric category
{ castsource => 'point', casttarget => 'box', castfunc => 'box(point)',
@@ -436,15 +436,15 @@
# MAC address category
{ castsource => 'macaddr', casttarget => 'macaddr8', castfunc => 'macaddr8',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'macaddr8', casttarget => 'macaddr', castfunc => 'macaddr',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
# INET category
{ castsource => 'cidr', casttarget => 'inet', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'inet', casttarget => 'cidr', castfunc => 'cidr',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
# BitString category
{ castsource => 'bit', casttarget => 'varbit', castfunc => '0',
@@ -454,13 +454,13 @@
# Cross-category casts between bit and int4, int8
{ castsource => 'int8', casttarget => 'bit', castfunc => 'bit(int8,int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'bit', castfunc => 'bit(int4,int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bit', casttarget => 'int8', castfunc => 'int8(bit)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bit', casttarget => 'int4', castfunc => 'int4(bit)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Cross-category casts to and from TEXT
# We need entries here only for a few specialized cases where the behavior
@@ -471,68 +471,68 @@
# behavior will ensue when the automatic cast is applied instead of the
# pg_cast entry!
{ castsource => 'cidr', casttarget => 'text', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'inet', casttarget => 'text', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'text', castfunc => 'text(bool)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'xml', casttarget => 'text', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'text', casttarget => 'xml', castfunc => 'xml',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Cross-category casts to and from VARCHAR
# We support all the same casts as for TEXT.
{ castsource => 'cidr', casttarget => 'varchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'inet', casttarget => 'varchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'varchar', castfunc => 'text(bool)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'xml', casttarget => 'varchar', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'varchar', casttarget => 'xml', castfunc => 'xml',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Cross-category casts to and from BPCHAR
# We support all the same casts as for TEXT.
{ castsource => 'cidr', casttarget => 'bpchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'inet', casttarget => 'bpchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'bpchar', castfunc => 'text(bool)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'xml', casttarget => 'bpchar', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'bpchar', casttarget => 'xml', castfunc => 'xml',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Length-coercion functions
{ castsource => 'bpchar', casttarget => 'bpchar',
castfunc => 'bpchar(bpchar,int4,bool)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'varchar',
castfunc => 'varchar(varchar,int4,bool)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'time', casttarget => 'time', castfunc => 'time(time,int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'timestamp',
castfunc => 'timestamp(timestamp,int4)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'timestamptz',
castfunc => 'timestamptz(timestamptz,int4)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'interval', casttarget => 'interval',
castfunc => 'interval(interval,int4)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timetz', casttarget => 'timetz',
- castfunc => 'timetz(timetz,int4)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timetz(timetz,int4)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bit', casttarget => 'bit', castfunc => 'bit(bit,int4,bool)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varbit', casttarget => 'varbit', castfunc => 'varbit',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'numeric',
- castfunc => 'numeric(numeric,int4)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'numeric(numeric,int4)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
# json to/from jsonb
{ castsource => 'json', casttarget => 'jsonb', castfunc => '0',
@@ -542,36 +542,36 @@
# jsonb to numeric and bool types
{ castsource => 'jsonb', casttarget => 'bool', castfunc => 'bool(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'numeric', castfunc => 'numeric(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'int2', castfunc => 'int2(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'int4', castfunc => 'int4(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'int8', castfunc => 'int8(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'float4', castfunc => 'float4(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'float8', castfunc => 'float8(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# range to multirange
{ castsource => 'int4range', casttarget => 'int4multirange',
castfunc => 'int4multirange(int4range)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8range', casttarget => 'int8multirange',
castfunc => 'int8multirange(int8range)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numrange', casttarget => 'nummultirange',
castfunc => 'nummultirange(numrange)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'daterange', casttarget => 'datemultirange',
castfunc => 'datemultirange(daterange)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'tsrange', casttarget => 'tsmultirange',
- castfunc => 'tsmultirange(tsrange)', castcontext => 'e', castmethod => 'f' },
+ castfunc => 'tsmultirange(tsrange)', castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'tstzrange', casttarget => 'tstzmultirange',
castfunc => 'tstzmultirange(tstzrange)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
]
diff --git a/src/include/catalog/pg_cast.h b/src/include/catalog/pg_cast.h
index 6a0ca337153..218d81d535a 100644
--- a/src/include/catalog/pg_cast.h
+++ b/src/include/catalog/pg_cast.h
@@ -47,6 +47,10 @@ CATALOG(pg_cast,2605,CastRelationId)
/* cast method */
char castmethod;
+
+ /* cast function error safe */
+ bool casterrorsafe BKI_DEFAULT(f);
+
} FormData_pg_cast;
/* ----------------
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 75366203706..0afcf09c086 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 0fe7b4ebc77..299d4eef4ed 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 e107d6e5f81..282bfa770ef 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 86a236bd58b..95174e0feef 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 6dfca3cb35b..d2df7c28932 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 0d919d8bfa2..12381aed64c 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 00000000000..bf62e9348f1
--- /dev/null
+++ b/src/test/regress/expected/cast.out
@@ -0,0 +1,556 @@
+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 DEFAULT clause is specified for CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(1::numeric AS money DEFAULT NULL ON CONVERSION E...
+ ^
+HINT: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(1111 AS "char" DEFAULT NULL ON CONVERSION ERROR);
+ char
+------
+
+(1 row)
+
+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 DEFAULT clause for CAST ... ON CONVERSION ERROR 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)'...
+ ^
+-----safe cast from bytea type to other type
+SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT NULL ON CONVERSION ERROR);
+ int8
+------
+
+(1 row)
+
+SELECT CAST('\x123456789A'::bytea AS int4 DEFAULT NULL ON CONVERSION ERROR);
+ int4
+------
+
+(1 row)
+
+SELECT CAST('\x123456'::bytea AS int2 DEFAULT NULL ON CONVERSION ERROR);
+ int2
+------
+
+(1 row)
+
+-----safe cast from range type to other type
+SELECT CAST('[1,2]'::int4range AS int4multirange DEFAULT NULL ON CONVERSION ERROR);
+ int4multirange
+----------------
+ {[1,3)}
+(1 row)
+
+SELECT CAST('[1,2]'::int8range AS int8multirange DEFAULT NULL ON CONVERSION ERROR);
+ int8multirange
+----------------
+ {[1,3)}
+(1 row)
+
+SELECT CAST('[1,2]'::numrange AS nummultirange DEFAULT NULL ON CONVERSION ERROR);
+ nummultirange
+---------------
+ {[1,2]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::daterange AS datemultirange DEFAULT NULL ON CONVERSION ERROR);
+ datemultirange
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::tsrange AS tsmultirange DEFAULT NULL ON CONVERSION ERROR);
+ tsmultirange
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::tstzrange AS tstzmultirange DEFAULT NULL ON CONVERSION ERROR);
+ tstzmultirange
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+-----safe cast from geometry to other geometry is not supported
+SELECT CAST(NULL::point AS box DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR: cannot cast type point to box when DEFAULT clause is specified for CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::point AS box DEFAULT NULL ON CONVERSION ER...
+ ^
+HINT: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(NULL::lseg AS point DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR: cannot cast type lseg to point when DEFAULT clause is specified for CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::lseg AS point DEFAULT NULL ON CONVERSION E...
+ ^
+HINT: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(NULL::path AS polygon DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR: cannot cast type path to polygon when DEFAULT clause is specified for CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::path AS polygon DEFAULT NULL ON CONVERSION...
+ ^
+HINT: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(NULL::box AS point DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR: cannot cast type box to point when DEFAULT clause is specified for CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::box AS point DEFAULT NULL ON CONVERSION ER...
+ ^
+HINT: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(NULL::box AS lseg DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR: cannot cast type box to lseg when DEFAULT clause is specified for CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::box AS lseg DEFAULT NULL ON CONVERSION ERR...
+ ^
+HINT: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(NULL::box AS polygon DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR: cannot cast type box to polygon when DEFAULT clause is specified for CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::box AS polygon DEFAULT NULL ON CONVERSION ...
+ ^
+HINT: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(NULL::box AS circle DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR: cannot cast type box to circle when DEFAULT clause is specified for CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::box AS circle DEFAULT NULL ON CONVERSION E...
+ ^
+HINT: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(NULL::polygon AS point DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR: cannot cast type polygon to point when DEFAULT clause is specified for CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::polygon AS point DEFAULT NULL ON CONVERSIO...
+ ^
+HINT: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(NULL::polygon AS path DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR: cannot cast type polygon to path when DEFAULT clause is specified for CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::polygon AS path DEFAULT NULL ON CONVERSION...
+ ^
+HINT: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(NULL::polygon AS box DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR: cannot cast type polygon to box when DEFAULT clause is specified for CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::polygon AS box DEFAULT NULL ON CONVERSION ...
+ ^
+HINT: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(NULL::polygon AS circle DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR: cannot cast type polygon to circle when DEFAULT clause is specified for CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::polygon AS circle DEFAULT NULL ON CONVERSI...
+ ^
+HINT: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(NULL::circle AS point DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR: cannot cast type circle to point when DEFAULT clause is specified for CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::circle AS point DEFAULT NULL ON CONVERSION...
+ ^
+HINT: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(NULL::circle AS box DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR: cannot cast type circle to box when DEFAULT clause is specified for CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::circle AS box DEFAULT NULL ON CONVERSION E...
+ ^
+HINT: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(NULL::circle AS polygon DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR: cannot cast type circle to polygon when DEFAULT clause is specified for CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::circle AS polygon DEFAULT NULL ON CONVERSI...
+ ^
+HINT: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+-----safe cast from money or money cast to other type is not supported
+SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR: cannot cast type bigint to money when DEFAULT clause is specified for CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION E...
+ ^
+HINT: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR: cannot cast type integer to money when DEFAULT clause is specified for CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION E...
+ ^
+HINT: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR: cannot cast type numeric to money when DEFAULT clause is specified for CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSIO...
+ ^
+HINT: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR: cannot cast type money to numeric when DEFAULT clause is specified for CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSIO...
+ ^
+HINT: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+--test cast numeric value with fraction to another numeric value
+CREATE TABLE safecast(col1 float4, col2 float8, col3 numeric, col4 numeric[],
+ col5 int2 default 32767,
+ col6 int4 default 32768,
+ col7 int8 default 4294967296);
+INSERT INTO safecast VALUES('11.1234', '11.1234', '9223372036854775808', '{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 oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col1 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col1 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col1 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col1 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col1 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM safecast;
+ float4 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-----------+---------+---------+--------+---------+-----------+------------------+------------+------------------
+ 11.1234 | 11 | 11 | | 11 | 11.1234 | 11.1233997344971 | 11.1234 | 11.1
+ Infinity | | | | | Infinity | Infinity | Infinity |
+ -Infinity | | | | | -Infinity | -Infinity | -Infinity |
+ NaN | | | | | NaN | NaN | NaN | NaN
+(4 rows)
+
+SELECT col2 as float8,
+ CAST(col2 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col2 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col2 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col2 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col2 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col2 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col2 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col2 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM safecast;
+ float8 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-----------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 11.1234 | 11 | 11 | | 11 | 11.1234 | 11.1234 | 11.1234 | 11.1
+ Infinity | | | | | Infinity | Infinity | Infinity |
+ -Infinity | | | | | -Infinity | -Infinity | -Infinity |
+ NaN | | | | | NaN | NaN | NaN | NaN
+(4 rows)
+
+SELECT col3 as numeric,
+ CAST(col3 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col3 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col3 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col3 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col3 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col3 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col3 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col3 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM safecast;
+ numeric | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+---------------------+---------+---------+--------+---------+-------------+----------------------+---------------------+------------------
+ 9223372036854775808 | | | | | 9.22337e+18 | 9.22337203685478e+18 | 9223372036854775808 |
+ Infinity | | | | | Infinity | Infinity | Infinity |
+ -Infinity | | | | | -Infinity | -Infinity | -Infinity |
+ NaN | | | | | NaN | NaN | NaN | NaN
+(4 rows)
+
+SELECT col4 as num_arr,
+ CAST(col4 AS int2[] DEFAULT NULL ON CONVERSION ERROR) as int2arr,
+ CAST(col4 AS int4[] DEFAULT NULL ON CONVERSION ERROR) as int4arr,
+ CAST(col4 as int8[] DEFAULT NULL ON CONVERSION ERROR) as int8arr,
+ CAST(col4 as float4[] DEFAULT NULL ON CONVERSION ERROR) as f4arr,
+ CAST(col4 as float8[] DEFAULT NULL ON CONVERSION ERROR) as f8arr,
+ CAST(col4 as numeric(10,1)[] DEFAULT NULL ON CONVERSION ERROR) as numarr
+FROM 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)
+
+SELECT col5 as int2,
+ CAST(col5 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col5 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col5 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col5 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col5 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col5 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col5 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col5 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM safecast;
+ int2 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+(4 rows)
+
+SELECT col6 as int4,
+ CAST(col6 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col6 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col6 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col6 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col6 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col6 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col6 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col6 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM safecast;
+ int4 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+(4 rows)
+
+SELECT col7 as int8,
+ CAST(col7 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col7 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col7 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col7 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col7 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col7 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col7 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col7 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM safecast;
+ int8 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+------------+---------+---------+--------+------------+-------------+------------+------------+------------------
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+(4 rows)
+
+--test date/timestamp/interval realted cast
+CREATE TABLE safecast1(col0 date, col1 timestamp, col2 timestamptz, col3 interval, col4 time, col5 timetz);
+insert into safecast1 VALUES
+('-infinity', '-infinity', '-infinity', '-infinity', '2003-03-07 15:36:39 America/New_York', '2003-07-07 15:36:39 America/New_York'),
+('-infinity', 'infinity', 'infinity', 'infinity','11:59:59.99 PM', '11:59:59.99 PM PDT');
+SELECT col0 as date,
+ CAST(col0 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col0 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col0 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col0 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col0 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM safecast1;
+ date | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale
+-----------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity | -infinity | | | -infinity
+ -infinity | -infinity | -infinity | | | -infinity
+(2 rows)
+
+SELECT col1 as timestamp,
+ CAST(col1 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col1 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col1 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col1 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col1 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM safecast1;
+ timestamp | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale
+-----------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity | -infinity | | | -infinity
+ infinity | infinity | infinity | | | infinity
+(2 rows)
+
+SELECT col2 as timestamptz,
+ CAST(col2 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col2 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col2 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col2 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col2 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM safecast1;
+ timestamptz | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale
+-------------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity | -infinity | | | -infinity
+ infinity | infinity | infinity | | | infinity
+(2 rows)
+
+SELECT col3 as interval,
+ CAST(col3 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col3 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col3 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col3 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col3 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale,
+ CAST(col3 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM safecast1;
+ interval | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale | to_interval_scale
+-----------+----------------+---------+----------+-----------+--------------------+-------------------
+ -infinity | | | | | | -infinity
+ infinity | | | | | | infinity
+(2 rows)
+
+SELECT col4 as time,
+ CAST(col4 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col4 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM safecast1;
+ time | to_times | to_timetz | to_interval | to_interval_scale
+-------------+-------------+----------------+-------------------------------+-------------------------------
+ 15:36:39 | 15:36:39 | 15:36:39-07 | @ 15 hours 36 mins 39 secs | @ 15 hours 36 mins 39 secs
+ 23:59:59.99 | 23:59:59.99 | 23:59:59.99-07 | @ 23 hours 59 mins 59.99 secs | @ 23 hours 59 mins 59.99 secs
+(2 rows)
+
+SELECT col5 as timetz,
+ CAST(col5 AS time DEFAULT NULL ON CONVERSION ERROR) as to_time,
+ CAST(col5 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_time_scale,
+ CAST(col5 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col5 AS timetz(6) DEFAULT NULL ON CONVERSION ERROR) as to_timetz_scale,
+ CAST(col5 AS interval DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col5 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM safecast1;
+ timetz | to_time | to_time_scale | to_timetz | to_timetz_scale | to_interval | to_interval_scale
+----------------+-------------+---------------+----------------+-----------------+-------------+-------------------
+ 15:36:39-04 | 15:36:39 | 15:36:39 | 15:36:39-04 | 15:36:39-04 | |
+ 23:59:59.99-07 | 23:59:59.99 | 23:59:59.99 | 23:59:59.99-07 | 23:59:59.99-07 | |
+(2 rows)
+
+CREATE TABLE safecast2(col0 jsonb, col1 text );
+INSERT INTO safecast2 VALUES ('"test"', '<value>one</value');
+SELECT col1 as text,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) as to_regclass,
+ CAST(col0 AS "char" DEFAULT NULL ON CONVERSION ERROR) as to_char,
+ CAST(col0 AS name DEFAULT NULL ON CONVERSION ERROR) as to_name,
+ CAST(col0 AS xml DEFAULT NULL ON CONVERSION ERROR) as to_xml
+FROM safecast2;
+ text | to_regclass | to_char | to_name | to_xml
+-------------------+-------------+---------+---------+--------
+ <value>one</value | | | "test" |
+(1 row)
+
+SELECT col0 as jsonb,
+ CAST(col0 AS integer DEFAULT NULL ON CONVERSION ERROR) as to_integer,
+ CAST(col0 AS numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col0 AS bigint DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col0 AS float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col0 AS float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col0 AS boolean DEFAULT NULL ON CONVERSION ERROR) as to_bool,
+ CAST(col0 AS smallint DEFAULT NULL ON CONVERSION ERROR) as to_smallint
+FROM safecast2;
+ jsonb | to_integer | to_numeric | to_int8 | to_float4 | to_float8 | to_bool | to_smallint
+--------+------------+------------+---------+-----------+-----------+---------+-------------
+ "test" | | | | | | |
+(1 row)
+
+-- 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/expected/create_cast.out b/src/test/regress/expected/create_cast.out
index fd4871d94db..1ec99eef670 100644
--- a/src/test/regress/expected/create_cast.out
+++ b/src/test/regress/expected/create_cast.out
@@ -86,6 +86,11 @@ SELECT 1234::int4::casttesttype; -- Should work now
bar1234
(1 row)
+SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- error
+ERROR: cannot cast type integer to casttesttype when DEFAULT clause is specified for CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVE...
+ ^
+HINT: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
-- check dependencies generated for that
SELECT pg_describe_object(classid, objid, objsubid) as obj,
pg_describe_object(refclassid, refobjid, refobjsubid) as objref,
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 20bf9ea9cdf..25478364b7c 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -943,8 +943,8 @@ SELECT *
FROM pg_cast c
WHERE castsource = 0 OR casttarget = 0 OR castcontext NOT IN ('e', 'a', 'i')
OR castmethod NOT IN ('f', 'b' ,'i');
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Check that castfunc is nonzero only for cast methods that need a function,
@@ -953,8 +953,8 @@ SELECT *
FROM pg_cast c
WHERE (castmethod = 'f' AND castfunc = 0)
OR (castmethod IN ('b', 'i') AND castfunc <> 0);
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Look for casts to/from the same type that aren't length coercion functions.
@@ -963,15 +963,15 @@ WHERE (castmethod = 'f' AND castfunc = 0)
SELECT *
FROM pg_cast c
WHERE castsource = casttarget AND castfunc = 0;
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
SELECT c.*
FROM pg_cast c, pg_proc p
WHERE c.castfunc = p.oid AND p.pronargs < 2 AND castsource = casttarget;
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Look for cast functions that don't have the right signature. The
@@ -989,8 +989,8 @@ WHERE c.castfunc = p.oid AND
OR (c.castsource = 'character'::regtype AND
p.proargtypes[0] = 'text'::regtype))
OR NOT binary_coercible(p.prorettype, c.casttarget));
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
SELECT c.*
@@ -998,8 +998,8 @@ FROM pg_cast c, pg_proc p
WHERE c.castfunc = p.oid AND
((p.pronargs > 1 AND p.proargtypes[1] != 'int4'::regtype) OR
(p.pronargs > 2 AND p.proargtypes[2] != 'bool'::regtype));
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Look for binary compatible casts that do not have the reverse
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index fbffc67ae60..ebbd454c450 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 00000000000..27334212c8c
--- /dev/null
+++ b/src/test/regress/sql/cast.sql
@@ -0,0 +1,259 @@
+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);
+SELECT CAST(1111 AS "char" 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
+
+-----safe cast from bytea type to other type
+SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('\x123456789A'::bytea AS int4 DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('\x123456'::bytea AS int2 DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast from range type to other type
+SELECT CAST('[1,2]'::int4range AS int4multirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[1,2]'::int8range AS int8multirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[1,2]'::numrange AS nummultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::daterange AS datemultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::tsrange AS tsmultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::tstzrange AS tstzmultirange DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast from geometry to other geometry is not supported
+SELECT CAST(NULL::point AS box DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(NULL::lseg AS point DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(NULL::path AS polygon DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(NULL::box AS point DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(NULL::box AS lseg DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(NULL::box AS polygon DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(NULL::box AS circle DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(NULL::polygon AS point DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(NULL::polygon AS path DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(NULL::polygon AS box DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(NULL::polygon AS circle DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(NULL::circle AS point DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(NULL::circle AS box DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(NULL::circle AS polygon DEFAULT NULL ON CONVERSION ERROR); --error
+
+-----safe cast from money or money cast to other type is not supported
+SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(NULL::money AS numeric DEFAULT NULL 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[],
+ col5 int2 default 32767,
+ col6 int4 default 32768,
+ col7 int8 default 4294967296);
+INSERT INTO safecast VALUES('11.1234', '11.1234', '9223372036854775808', '{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 oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col1 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col1 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col1 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col1 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col1 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM 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 oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col2 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col2 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col2 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col2 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col2 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM 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 oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col3 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col3 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col3 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col3 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col3 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM 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;
+
+SELECT col5 as int2,
+ CAST(col5 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col5 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col5 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col5 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col5 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col5 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col5 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col5 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM safecast;
+
+SELECT col6 as int4,
+ CAST(col6 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col6 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col6 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col6 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col6 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col6 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col6 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col6 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM safecast;
+
+SELECT col7 as int8,
+ CAST(col7 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col7 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col7 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col7 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col7 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col7 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col7 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col7 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM safecast;
+
+--test date/timestamp/interval realted cast
+CREATE TABLE safecast1(col0 date, col1 timestamp, col2 timestamptz, col3 interval, col4 time, col5 timetz);
+insert into safecast1 VALUES
+('-infinity', '-infinity', '-infinity', '-infinity', '2003-03-07 15:36:39 America/New_York', '2003-07-07 15:36:39 America/New_York'),
+('-infinity', 'infinity', 'infinity', 'infinity','11:59:59.99 PM', '11:59:59.99 PM PDT');
+
+SELECT col0 as date,
+ CAST(col0 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col0 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col0 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col0 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col0 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM safecast1;
+
+SELECT col1 as timestamp,
+ CAST(col1 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col1 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col1 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col1 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col1 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM safecast1;
+
+SELECT col2 as timestamptz,
+ CAST(col2 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col2 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col2 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col2 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col2 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM safecast1;
+
+SELECT col3 as interval,
+ CAST(col3 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col3 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col3 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col3 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col3 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale,
+ CAST(col3 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM safecast1;
+
+SELECT col4 as time,
+ CAST(col4 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col4 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM safecast1;
+
+SELECT col5 as timetz,
+ CAST(col5 AS time DEFAULT NULL ON CONVERSION ERROR) as to_time,
+ CAST(col5 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_time_scale,
+ CAST(col5 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col5 AS timetz(6) DEFAULT NULL ON CONVERSION ERROR) as to_timetz_scale,
+ CAST(col5 AS interval DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col5 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM safecast1;
+
+CREATE TABLE safecast2(col0 jsonb, col1 text );
+INSERT INTO safecast2 VALUES ('"test"', '<value>one</value');
+
+SELECT col1 as text,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) as to_regclass,
+ CAST(col0 AS "char" DEFAULT NULL ON CONVERSION ERROR) as to_char,
+ CAST(col0 AS name DEFAULT NULL ON CONVERSION ERROR) as to_name,
+ CAST(col0 AS xml DEFAULT NULL ON CONVERSION ERROR) as to_xml
+FROM safecast2;
+
+SELECT col0 as jsonb,
+ CAST(col0 AS integer DEFAULT NULL ON CONVERSION ERROR) as to_integer,
+ CAST(col0 AS numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col0 AS bigint DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col0 AS float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col0 AS float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col0 AS boolean DEFAULT NULL ON CONVERSION ERROR) as to_bool,
+ CAST(col0 AS smallint DEFAULT NULL ON CONVERSION ERROR) as to_smallint
+FROM safecast2;
+
+-- 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/test/regress/sql/create_cast.sql b/src/test/regress/sql/create_cast.sql
index 32187853cc7..0a15a795d87 100644
--- a/src/test/regress/sql/create_cast.sql
+++ b/src/test/regress/sql/create_cast.sql
@@ -62,6 +62,7 @@ $$ SELECT ('bar'::text || $1::text); $$;
CREATE CAST (int4 AS casttesttype) WITH FUNCTION bar_int4_text(int4) AS IMPLICIT;
SELECT 1234::int4::casttesttype; -- Should work now
+SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- error
-- check dependencies generated for that
SELECT pg_describe_object(classid, objid, objsubid) as obj,
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index e6f2e93b2d6..5b32df3a2db 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2652,6 +2652,9 @@ STRLEN
SV
SYNCHRONIZATION_BARRIER
SYSTEM_INFO
+SafeTypeCast
+SafeTypeCastExpr
+SafeTypeCastState
SampleScan
SampleScanGetSampleSize_function
SampleScanState
--
2.34.1
v6-0010-error-safe-for-casting-text-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v6-0010-error-safe-for-casting-text-to-other-types-per-pg_cast.patchDownload
From 2efaf3b582b15d62b3250535dfbc0840a7444dcb Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sun, 10 Aug 2025 01:56:03 +0800
Subject: [PATCH v6 10/18] error safe for casting text to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'text'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------+----------+-------------+------------+---------------+----------
text | regclass | 1079 | i | f | text_regclass | regclass
text | "char" | 944 | a | f | text_char | char
text | name | 407 | i | f | text_name | name
text | xml | 2896 | e | f | texttoxml | xml
(4 rows)
already error safe: text_name, text_char.
texttoxml is refactored in character type error safe patch.
---
src/backend/catalog/namespace.c | 167 ++++++++++++++++++++++++++++++++
src/backend/utils/adt/regproc.c | 22 ++++-
src/backend/utils/adt/varlena.c | 42 ++++++++
src/include/catalog/namespace.h | 4 +
src/include/utils/varlena.h | 1 +
5 files changed, 233 insertions(+), 3 deletions(-)
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index d97d632a7ef..4a5506eeac5 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -641,6 +641,137 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
return relId;
}
+/* see RangeVarGetRelidExtended also */
+Oid
+RangeVarGetRelidExtendedSafe(const RangeVar *relation, LOCKMODE lockmode,
+ uint32 flags, RangeVarGetRelidCallback callback, void *callback_arg,
+ Node *escontext)
+{
+ uint64 inval_count;
+ Oid relId;
+ Oid oldRelId = InvalidOid;
+ bool retry = false;
+ bool missing_ok = (flags & RVR_MISSING_OK) != 0;
+
+ /* verify that flags do no conflict */
+ Assert(!((flags & RVR_NOWAIT) && (flags & RVR_SKIP_LOCKED)));
+
+ /*
+ * We check the catalog name and then ignore it.
+ */
+ if (relation->catalogname)
+ {
+ if (strcmp(relation->catalogname, get_database_name(MyDatabaseId)) != 0)
+ ereturn(escontext, InvalidOid,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cross-database references are not implemented: \"%s.%s.%s\"",
+ relation->catalogname, relation->schemaname,
+ relation->relname));
+ }
+
+ for (;;)
+ {
+ inval_count = SharedInvalidMessageCounter;
+
+ if (relation->relpersistence == RELPERSISTENCE_TEMP)
+ {
+ if (!OidIsValid(myTempNamespace))
+ relId = InvalidOid;
+ else
+ {
+ if (relation->schemaname)
+ {
+ Oid namespaceId;
+
+ namespaceId = LookupExplicitNamespace(relation->schemaname, missing_ok);
+
+ /*
+ * For missing_ok, allow a non-existent schema name to
+ * return InvalidOid.
+ */
+ if (namespaceId != myTempNamespace)
+ ereturn(escontext, InvalidOid,
+ errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("temporary tables cannot specify a schema name"));
+ }
+
+ relId = get_relname_relid(relation->relname, myTempNamespace);
+ }
+ }
+ else if (relation->schemaname)
+ {
+ Oid namespaceId;
+
+ /* use exact schema given */
+ namespaceId = LookupExplicitNamespace(relation->schemaname, missing_ok);
+ if (missing_ok && !OidIsValid(namespaceId))
+ relId = InvalidOid;
+ else
+ relId = get_relname_relid(relation->relname, namespaceId);
+ }
+ else
+ {
+ /* search the namespace path */
+ relId = RelnameGetRelid(relation->relname);
+ }
+
+ if (callback)
+ callback(relation, relId, oldRelId, callback_arg);
+
+ if (lockmode == NoLock)
+ break;
+
+ if (retry)
+ {
+ if (relId == oldRelId)
+ break;
+ if (OidIsValid(oldRelId))
+ UnlockRelationOid(oldRelId, lockmode);
+ }
+
+ if (!OidIsValid(relId))
+ AcceptInvalidationMessages();
+ else if (!(flags & (RVR_NOWAIT | RVR_SKIP_LOCKED)))
+ LockRelationOid(relId, lockmode);
+ else if (!ConditionalLockRelationOid(relId, lockmode))
+ {
+ if (relation->schemaname)
+ ereport(DEBUG1,
+ errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s.%s\"",
+ relation->schemaname, relation->relname));
+ else
+ ereport(DEBUG1,
+ errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s\"",
+ relation->relname));
+
+ return InvalidOid;
+ }
+
+ if (inval_count == SharedInvalidMessageCounter)
+ break;
+
+ retry = true;
+ oldRelId = relId;
+ }
+
+ if (!OidIsValid(relId))
+ {
+ if (relation->schemaname)
+ ereport(DEBUG1,
+ errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s.%s\" does not exist",
+ relation->schemaname, relation->relname));
+ else
+ ereport(DEBUG1,
+ errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" does not exist",
+ relation->relname));
+ }
+ return relId;
+}
+
/*
* RangeVarGetCreationNamespace
* Given a RangeVar describing a to-be-created relation,
@@ -3580,6 +3711,42 @@ makeRangeVarFromNameList(const List *names)
return rel;
}
+/*
+ * makeRangeVarFromNameListSafe
+ * Utility routine to convert a qualified-name list into RangeVar form.
+ * The result maybe NULL.
+ */
+RangeVar *
+makeRangeVarFromNameListSafe(const List *names, Node *escontext)
+{
+ RangeVar *rel = makeRangeVar(NULL, NULL, -1);
+
+ switch (list_length(names))
+ {
+ case 1:
+ rel->relname = strVal(linitial(names));
+ break;
+ case 2:
+ rel->schemaname = strVal(linitial(names));
+ rel->relname = strVal(lsecond(names));
+ break;
+ case 3:
+ rel->catalogname = strVal(linitial(names));
+ rel->schemaname = strVal(lsecond(names));
+ rel->relname = strVal(lthird(names));
+ break;
+ default:
+ errsave(escontext,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("improper relation name (too many dotted names): %s",
+ NameListToString(names)));
+ rel = NULL;
+ break;
+ }
+
+ return rel;
+}
+
/*
* NameListToString
* Utility routine to convert a qualified-name list into a string.
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index b8bbe95e82e..e7f259e8a0a 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -1895,10 +1895,26 @@ text_regclass(PG_FUNCTION_ARGS)
Oid result;
RangeVar *rv;
- rv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
+ if (likely(!fcinfo->context))
+ {
+ rv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
- /* We might not even have permissions on this relation; don't lock it. */
- result = RangeVarGetRelid(rv, NoLock, false);
+ /* We might not even have permissions on this relation; don't lock it. */
+ result = RangeVarGetRelid(rv, NoLock, false);
+ }
+ else
+ {
+ List *rvnames;
+
+ rvnames = textToQualifiedNameListSafe(relname, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
+
+ rv = makeRangeVarFromNameList(rvnames);
+ result = RangeVarGetRelidExtendedSafe(rv, NoLock, 0, NULL, NULL, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
+ }
PG_RETURN_OID(result);
}
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index 11b442a5941..80852d5e922 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -2724,6 +2724,48 @@ textToQualifiedNameList(text *textval)
return result;
}
+/* see textToQualifiedNameList also */
+List *
+textToQualifiedNameListSafe(text *textval, Node *escontext)
+{
+ char *rawname;
+ List *result = NIL;
+ List *namelist;
+ ListCell *l;
+
+ /* Convert to C string (handles possible detoasting). */
+ /* Note we rely on being able to modify rawname below. */
+ rawname = text_to_cstring(textval);
+
+ if (!SplitIdentifierString(rawname, '.', &namelist))
+ {
+ errsave(escontext,
+ errcode(ERRCODE_INVALID_NAME),
+ errmsg("invalid name syntax"));
+ return NIL;
+ }
+
+ if (namelist == NIL)
+ {
+ errsave(escontext,
+ errcode(ERRCODE_INVALID_NAME),
+ errmsg("invalid name syntax"));
+ return NIL;
+ }
+
+ foreach(l, namelist)
+ {
+ char *curname = (char *) lfirst(l);
+
+ result = lappend(result, makeString(pstrdup(curname)));
+ }
+
+ pfree(rawname);
+ list_free(namelist);
+
+ return result;
+}
+
/*
* SplitIdentifierString --- parse a string containing identifiers
*
diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h
index 8c7ccc69a3c..e095cbe70fa 100644
--- a/src/include/catalog/namespace.h
+++ b/src/include/catalog/namespace.h
@@ -85,6 +85,9 @@ extern Oid RangeVarGetRelidExtended(const RangeVar *relation,
LOCKMODE lockmode, uint32 flags,
RangeVarGetRelidCallback callback,
void *callback_arg);
+extern Oid RangeVarGetRelidExtendedSafe(const RangeVar *relation, LOCKMODE lockmode,
+ uint32 flags, RangeVarGetRelidCallback callback, void *callback_arg,
+ Node *escontext);
extern Oid RangeVarGetCreationNamespace(const RangeVar *newRelation);
extern Oid RangeVarGetAndCheckCreationNamespace(RangeVar *relation,
LOCKMODE lockmode,
@@ -148,6 +151,7 @@ extern Oid LookupCreationNamespace(const char *nspname);
extern void CheckSetNamespace(Oid oldNspOid, Oid nspOid);
extern Oid QualifiedNameGetCreationNamespace(const List *names, char **objname_p);
extern RangeVar *makeRangeVarFromNameList(const List *names);
+extern RangeVar *makeRangeVarFromNameListSafe(const List *names, Node *escontext);
extern char *NameListToString(const List *names);
extern char *NameListToQuotedString(const List *names);
diff --git a/src/include/utils/varlena.h b/src/include/utils/varlena.h
index db9fdf72941..0cf01ae5281 100644
--- a/src/include/utils/varlena.h
+++ b/src/include/utils/varlena.h
@@ -27,6 +27,7 @@ extern int varstr_levenshtein_less_equal(const char *source, int slen,
int ins_c, int del_c, int sub_c,
int max_d, bool trusted);
extern List *textToQualifiedNameList(text *textval);
+extern List *textToQualifiedNameListSafe(text *textval, Node *escontext);
extern bool SplitIdentifierString(char *rawstring, char separator,
List **namelist);
extern bool SplitDirectoriesString(char *rawstring, char separator,
--
2.34.1
v6-0012-error-safe-for-casting-interval-to-other-types-per-pg_cas.patchtext/x-patch; charset=US-ASCII; name=v6-0012-error-safe-for-casting-interval-to-other-types-per-pg_cas.patchDownload
From f118cff3a42d350e0e2a7274ac54f6f9a8771ed9 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sun, 10 Aug 2025 01:17:27 +0800
Subject: [PATCH v6 12/18] error safe for casting interval to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'interval'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------------+----------+-------------+------------+----------------+----------
interval | time without time zone | 1419 | a | f | interval_time | time
interval | interval | 1200 | i | f | interval_scale | interval
(2 rows)
---
src/backend/utils/adt/date.c | 2 +-
src/backend/utils/adt/timestamp.c | 3 ++-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index c7a3cde2d81..4f0f3d26989 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2180,7 +2180,7 @@ interval_time(PG_FUNCTION_ARGS)
TimeADT result;
if (INTERVAL_NOT_FINITE(span))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("cannot convert infinite interval to time")));
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index e640b48205b..f1e96b84a6f 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1334,7 +1334,8 @@ interval_scale(PG_FUNCTION_ARGS)
result = palloc(sizeof(Interval));
*result = *interval;
- AdjustIntervalForTypmod(result, typmod, NULL);
+ if (!AdjustIntervalForTypmod(result, typmod, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_INTERVAL_P(result);
}
--
2.34.1
v6-0013-error-safe-for-casting-macaddr8-to-other-types-per-pg_cas.patchtext/x-patch; charset=US-ASCII; name=v6-0013-error-safe-for-casting-macaddr8-to-other-types-per-pg_cas.patchDownload
From 44d59f188fb4dfad880d9f5d25cd3e5625529c8e Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Tue, 5 Aug 2025 16:55:44 +0800
Subject: [PATCH v6 13/18] error safe for casting macaddr8 to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
and pc.castfunc > 0 and castsource::regtype ='macaddr8'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------+----------+-------------+------------+-------------------+---------
macaddr8 | macaddr | 4124 | i | f | macaddr8tomacaddr | macaddr
(1 row)
---
src/backend/utils/adt/mac8.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/backend/utils/adt/mac8.c b/src/backend/utils/adt/mac8.c
index 08e41ba4eea..1c903f152de 100644
--- a/src/backend/utils/adt/mac8.c
+++ b/src/backend/utils/adt/mac8.c
@@ -550,7 +550,7 @@ macaddr8tomacaddr(PG_FUNCTION_ARGS)
result = (macaddr *) palloc0(sizeof(macaddr));
if ((addr->d != 0xFF) || (addr->e != 0xFE))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("macaddr8 data out of range to convert to macaddr"),
errhint("Only addresses that have FF and FE as values in the "
--
2.34.1
v6-0009-error-safe-for-casting-date-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v6-0009-error-safe-for-casting-date-to-other-types-per-pg_cast.patchDownload
From 5e9ced90d27245b90b80294dde7c64405abd6f5d Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 9 Aug 2025 22:35:57 +0800
Subject: [PATCH v6 09/18] error safe for casting date to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'date'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+-----------------------------+----------+-------------+------------+------------------+-------------
date | timestamp without time zone | 2024 | i | f | date_timestamp | timestamp
date | timestamp with time zone | 1174 | i | f | date_timestamptz | timestamptz
(2 rows)
---
src/backend/utils/adt/date.c | 22 ++++++++++++++++++++--
1 file changed, 20 insertions(+), 2 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 344f58b92f7..c7a3cde2d81 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -1350,7 +1350,16 @@ date_timestamp(PG_FUNCTION_ARGS)
DateADT dateVal = PG_GETARG_DATEADT(0);
Timestamp result;
- result = date2timestamp(dateVal);
+ if (likely(!fcinfo->context))
+ result = date2timestamp(dateVal);
+ else
+ {
+ int overflow;
+
+ result = date2timestamp_opt_overflow(dateVal, &overflow);
+ if (overflow != 0)
+ PG_RETURN_NULL();
+ }
PG_RETURN_TIMESTAMP(result);
}
@@ -1435,7 +1444,16 @@ date_timestamptz(PG_FUNCTION_ARGS)
DateADT dateVal = PG_GETARG_DATEADT(0);
TimestampTz result;
- result = date2timestamptz(dateVal);
+ if (likely(!fcinfo->context))
+ result = date2timestamptz(dateVal);
+ else
+ {
+ int overflow;
+ result = date2timestamptz_opt_overflow(dateVal, &overflow);
+
+ if (overflow != 0)
+ PG_RETURN_NULL();
+ }
PG_RETURN_TIMESTAMP(result);
}
--
2.34.1
v6-0011-error-safe-for-casting-inet-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v6-0011-error-safe-for-casting-inet-to-other-types-per-pg_cast.patchDownload
From fe3598c63639bc81051b0127c15b1aef3e2115f5 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Tue, 5 Aug 2025 15:42:58 +0800
Subject: [PATCH v6 11/18] error safe for casting inet to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'inet'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+-------------------+----------+-------------+------------+--------------+---------
inet | cidr | 1715 | a | f | inet_to_cidr | cidr
inet | text | 730 | a | f | network_show | text
inet | character varying | 730 | a | f | network_show | text
inet | character | 730 | a | f | network_show | text
(4 rows)
---
src/backend/utils/adt/network.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/backend/utils/adt/network.c b/src/backend/utils/adt/network.c
index 9fd211b2d45..0e2021bcc3c 100644
--- a/src/backend/utils/adt/network.c
+++ b/src/backend/utils/adt/network.c
@@ -1167,7 +1167,7 @@ network_show(PG_FUNCTION_ARGS)
if (pg_inet_net_ntop(ip_family(ip), ip_addr(ip), ip_maxbits(ip),
tmp, sizeof(tmp)) == NULL)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
errmsg("could not format inet value: %m")));
--
2.34.1
v6-0008-error-safe-for-casting-jsonb-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v6-0008-error-safe-for-casting-jsonb-to-other-types-per-pg_cast.patchDownload
From 6a409bdb5c41ed3b0e2b8b51530bd6d6be0dc9f0 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Wed, 6 Aug 2025 12:12:31 +0800
Subject: [PATCH v6 08/18] error safe for casting jsonb to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'jsonb'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+---------------+---------
jsonb | boolean | 3556 | e | f | jsonb_bool | bool
jsonb | numeric | 3449 | e | f | jsonb_numeric | numeric
jsonb | smallint | 3450 | e | f | jsonb_int2 | int2
jsonb | integer | 3451 | e | f | jsonb_int4 | int4
jsonb | bigint | 3452 | e | f | jsonb_int8 | int8
jsonb | real | 3453 | e | f | jsonb_float4 | float4
jsonb | double precision | 2580 | e | f | jsonb_float8 | float8
(7 rows)
---
src/backend/utils/adt/jsonb.c | 35 ++++++++++++++++++-----------------
1 file changed, 18 insertions(+), 17 deletions(-)
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index da94d424d61..7f3715b1684 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -2004,8 +2004,8 @@ JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res)
/*
* Emit correct, translatable cast error message
*/
-static void
-cannotCastJsonbValue(enum jbvType type, const char *sqltype)
+static Datum
+cannotCastJsonbValue(enum jbvType type, const char *sqltype, Node *escontext)
{
static const struct
{
@@ -2026,12 +2026,13 @@ cannotCastJsonbValue(enum jbvType type, const char *sqltype)
for (i = 0; i < lengthof(messages); i++)
if (messages[i].type == type)
- ereport(ERROR,
+ ereturn(escontext, (Datum) 0,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(messages[i].msg, sqltype)));
/* should be unreachable */
elog(ERROR, "unknown jsonb type: %d", (int) type);
+ return (Datum) 0;
}
Datum
@@ -2041,7 +2042,7 @@ jsonb_bool(PG_FUNCTION_ARGS)
JsonbValue v;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "boolean");
+ return cannotCastJsonbValue(v.type, "boolean", fcinfo->context);
if (v.type == jbvNull)
{
@@ -2050,7 +2051,7 @@ jsonb_bool(PG_FUNCTION_ARGS)
}
if (v.type != jbvBool)
- cannotCastJsonbValue(v.type, "boolean");
+ return cannotCastJsonbValue(v.type, "boolean", fcinfo->context);
PG_FREE_IF_COPY(in, 0);
@@ -2065,7 +2066,7 @@ jsonb_numeric(PG_FUNCTION_ARGS)
Numeric retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "numeric");
+ return cannotCastJsonbValue(v.type, "numeric", fcinfo->context);
if (v.type == jbvNull)
{
@@ -2074,7 +2075,7 @@ jsonb_numeric(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "numeric");
+ return cannotCastJsonbValue(v.type, "numeric", fcinfo->context);
/*
* v.val.numeric points into jsonb body, so we need to make a copy to
@@ -2095,7 +2096,7 @@ jsonb_int2(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "smallint");
+ return cannotCastJsonbValue(v.type, "smallint", fcinfo->context);
if (v.type == jbvNull)
{
@@ -2104,7 +2105,7 @@ jsonb_int2(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "smallint");
+ return cannotCastJsonbValue(v.type, "smallint", fcinfo->context);
retValue = DirectFunctionCall1(numeric_int2,
NumericGetDatum(v.val.numeric));
@@ -2122,7 +2123,7 @@ jsonb_int4(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "integer");
+ return cannotCastJsonbValue(v.type, "integer", fcinfo->context);
if (v.type == jbvNull)
{
@@ -2131,7 +2132,7 @@ jsonb_int4(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "integer");
+ return cannotCastJsonbValue(v.type, "integer", fcinfo->context);
retValue = DirectFunctionCall1(numeric_int4,
NumericGetDatum(v.val.numeric));
@@ -2149,7 +2150,7 @@ jsonb_int8(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "bigint");
+ return cannotCastJsonbValue(v.type, "bigint", fcinfo->context);
if (v.type == jbvNull)
{
@@ -2158,7 +2159,7 @@ jsonb_int8(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "bigint");
+ return cannotCastJsonbValue(v.type, "bigint", fcinfo->context);
retValue = DirectFunctionCall1(numeric_int8,
NumericGetDatum(v.val.numeric));
@@ -2176,7 +2177,7 @@ jsonb_float4(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "real");
+ return cannotCastJsonbValue(v.type, "real", fcinfo->context);
if (v.type == jbvNull)
{
@@ -2185,7 +2186,7 @@ jsonb_float4(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "real");
+ return cannotCastJsonbValue(v.type, "real", fcinfo->context);
retValue = DirectFunctionCall1(numeric_float4,
NumericGetDatum(v.val.numeric));
@@ -2203,7 +2204,7 @@ jsonb_float8(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "double precision");
+ return cannotCastJsonbValue(v.type, "double precision", fcinfo->context);
if (v.type == jbvNull)
{
@@ -2212,7 +2213,7 @@ jsonb_float8(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "double precision");
+ return cannotCastJsonbValue(v.type, "double precision", fcinfo->context);
retValue = DirectFunctionCall1(numeric_float8,
NumericGetDatum(v.val.numeric));
--
2.34.1
v6-0006-error-safe-for-casting-float4-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v6-0006-error-safe-for-casting-float4-to-other-types-per-pg_cast.patchDownload
From 60c9c8cac8cd24803ec7319eb19ff4cdd838cb72 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 9 Aug 2025 22:17:48 +0800
Subject: [PATCH v6 06/18] error safe for casting float4 to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'float4'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+----------------+---------
real | bigint | 653 | a | f | ftoi8 | int8
real | smallint | 238 | a | f | ftoi2 | int2
real | integer | 319 | a | f | ftoi4 | int4
real | double precision | 311 | i | f | ftod | float8
real | numeric | 1742 | a | f | float4_numeric | numeric
(5 rows)
float4 to float8, numeric is error safe, so no need refactor ftod,
float4_numeric.
---
src/backend/utils/adt/float.c | 4 ++--
src/backend/utils/adt/int8.c | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index 7b97d2be6ca..ab5c38d723d 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -1298,7 +1298,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 +1323,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 96a6737a95c..8c38d02d011 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -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")));
--
2.34.1
v6-0007-error-safe-for-casting-float8-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v6-0007-error-safe-for-casting-float8-to-other-types-per-pg_cast.patchDownload
From 5b4d86921d852efda26e3483c85e92ca77581f9c Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 9 Aug 2025 22:26:13 +0800
Subject: [PATCH v6 07/18] error safe for casting float8 to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'float8'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------------+------------+----------+-------------+------------+----------------+---------
double precision | bigint | 483 | a | f | dtoi8 | int8
double precision | smallint | 237 | a | f | dtoi2 | int2
double precision | integer | 317 | a | f | dtoi4 | int4
double precision | real | 312 | a | f | dtof | float4
double precision | numeric | 1743 | a | f | float8_numeric | numeric
(5 rows)
---
src/backend/utils/adt/float.c | 12 ++++++++----
src/backend/utils/adt/int8.c | 2 +-
2 files changed, 9 insertions(+), 5 deletions(-)
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index ab5c38d723d..6461e9c94b1 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")));
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 8c38d02d011..5bddcccf378 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")));
--
2.34.1
v6-0005-error-safe-for-casting-numeric-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v6-0005-error-safe-for-casting-numeric-to-other-types-per-pg_cast.patchDownload
From 3738b36237e21a9341abea8a599e7ed7fa899307 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 9 Aug 2025 21:33:56 +0800
Subject: [PATCH v6 05/18] error safe for casting numeric to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'numeric'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+----------------+---------
numeric | bigint | 1779 | a | f | numeric_int8 | int8
numeric | smallint | 1783 | a | f | numeric_int2 | int2
numeric | integer | 1744 | a | f | numeric_int4 | int4
numeric | real | 1745 | i | f | numeric_float4 | float4
numeric | double precision | 1746 | i | f | numeric_float8 | float8
numeric | money | 3824 | a | f | numeric_cash | money
numeric | numeric | 1703 | i | f | numeric | numeric
(7 rows)
current safe cast for money data type is not supported, so no realted function
refactoring.
---
src/backend/utils/adt/numeric.c | 69 ++++++++++++++++++++++++++-------
1 file changed, 56 insertions(+), 13 deletions(-)
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 122f2efab8b..2ab26d59e41 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -1259,7 +1259,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));
}
@@ -1310,7 +1311,8 @@ numeric (PG_FUNCTION_ARGS)
init_var(&var);
set_var_from_num(num, &var);
- (void) apply_typmod(&var, typmod, NULL);
+ if (!apply_typmod(&var, typmod, fcinfo->context))
+ PG_RETURN_NULL();
new = make_result(&var);
free_var(&var);
@@ -4550,7 +4552,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))
+ 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);
+ }
}
/*
@@ -4638,7 +4655,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))
+ 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);
+ }
}
@@ -4662,11 +4694,11 @@ numeric_int2(PG_FUNCTION_ARGS)
if (NUMERIC_IS_SPECIAL(num))
{
if (NUMERIC_IS_NAN(num))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert NaN to %s", "smallint")));
else
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert infinity to %s", "smallint")));
}
@@ -4675,12 +4707,12 @@ numeric_int2(PG_FUNCTION_ARGS)
init_var_from_num(num, &x);
if (!numericvar_to_int64(&x, &val))
- ereport(ERROR,
+ ereturn(fcinfo->context, (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(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
@@ -4745,10 +4777,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);
}
@@ -4840,7 +4876,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
v6-0004-error-safe-for-casting-bigint-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v6-0004-error-safe-for-casting-bigint-to-other-types-per-pg_cast.patchDownload
From 9ca7cb44e80f642f1e602ce55012bbbb6cca80f4 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 9 Aug 2025 21:25:38 +0800
Subject: [PATCH v6 04/18] error safe for casting bigint to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'bigint'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+--------------+---------
bigint | smallint | 714 | a | f | int82 | int2
bigint | integer | 480 | a | f | int84 | int4
bigint | real | 652 | i | f | i8tof | float4
bigint | double precision | 482 | i | f | i8tod | float8
bigint | numeric | 1781 | i | f | int8_numeric | numeric
bigint | money | 3812 | a | f | int8_cash | money
bigint | oid | 1287 | i | f | i8tooid | oid
bigint | regproc | 1287 | i | f | i8tooid | oid
bigint | regprocedure | 1287 | i | f | i8tooid | oid
bigint | regoper | 1287 | i | f | i8tooid | oid
bigint | regoperator | 1287 | i | f | i8tooid | oid
bigint | regclass | 1287 | i | f | i8tooid | oid
bigint | regcollation | 1287 | i | f | i8tooid | oid
bigint | regtype | 1287 | i | f | i8tooid | oid
bigint | regconfig | 1287 | i | f | i8tooid | oid
bigint | regdictionary | 1287 | i | f | i8tooid | oid
bigint | regrole | 1287 | i | f | i8tooid | oid
bigint | regnamespace | 1287 | i | f | i8tooid | oid
bigint | regdatabase | 1287 | i | f | i8tooid | oid
bigint | bytea | 6369 | e | f | int8_bytea | bytea
bigint | bit | 2075 | e | f | bitfromint8 | bit
(21 rows)
already error safe: i8tof, i8tod, int8_numeric, int8_bytea, bitfromint8
---
src/backend/utils/adt/int8.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 9dd5889f34c..96a6737a95c 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -1251,7 +1251,7 @@ int84(PG_FUNCTION_ARGS)
int64 arg = PG_GETARG_INT64(0);
if (unlikely(arg < PG_INT32_MIN) || unlikely(arg > PG_INT32_MAX))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
@@ -1272,7 +1272,7 @@ int82(PG_FUNCTION_ARGS)
int64 arg = PG_GETARG_INT64(0);
if (unlikely(arg < PG_INT16_MIN) || unlikely(arg > PG_INT16_MAX))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
@@ -1355,7 +1355,7 @@ i8tooid(PG_FUNCTION_ARGS)
int64 arg = PG_GETARG_INT64(0);
if (unlikely(arg < 0) || unlikely(arg > PG_UINT32_MAX))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("OID out of range")));
--
2.34.1
v6-0003-error-safe-for-casting-integer-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v6-0003-error-safe-for-casting-integer-to-other-types-per-pg_cast.patchDownload
From bb6577b6491b0cd78760c068fc26521437662513 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 9 Aug 2025 21:20:32 +0800
Subject: [PATCH v6 03/18] error safe for casting integer to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'integer'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+--------------+---------
integer | bigint | 481 | i | f | int48 | int8
integer | smallint | 314 | a | f | i4toi2 | int2
integer | real | 318 | i | f | i4tof | float4
integer | double precision | 316 | i | f | i4tod | float8
integer | numeric | 1740 | i | f | int4_numeric | numeric
integer | money | 3811 | a | f | int4_cash | money
integer | boolean | 2557 | e | f | int4_bool | bool
integer | bytea | 6368 | e | f | int4_bytea | bytea
integer | "char" | 78 | e | f | i4tochar | char
integer | bit | 1683 | e | f | bitfromint4 | bit
(10 rows)
only int4_cash, i4toi2, i4tochar need take care of error handling. but support
for cash data type is not easy, so only i4toi2, i4tochar function refactoring.
---
src/backend/utils/adt/char.c | 2 +-
src/backend/utils/adt/int.c | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/backend/utils/adt/char.c b/src/backend/utils/adt/char.c
index 22dbfc950b1..e90844a29f0 100644
--- a/src/backend/utils/adt/char.c
+++ b/src/backend/utils/adt/char.c
@@ -192,7 +192,7 @@ i4tochar(PG_FUNCTION_ARGS)
int32 arg1 = PG_GETARG_INT32(0);
if (arg1 < SCHAR_MIN || arg1 > SCHAR_MAX)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("\"char\" out of range")));
diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c
index b5781989a64..b45599d402d 100644
--- a/src/backend/utils/adt/int.c
+++ b/src/backend/utils/adt/int.c
@@ -350,7 +350,7 @@ i4toi2(PG_FUNCTION_ARGS)
int32 arg1 = PG_GETARG_INT32(0);
if (unlikely(arg1 < SHRT_MIN) || unlikely(arg1 > SHRT_MAX))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
--
2.34.1
v6-0001-error-safe-for-casting-bytea-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v6-0001-error-safe-for-casting-bytea-to-other-types-per-pg_cast.patchDownload
From efc37e956c0af2d0a255b8f047f68d8b67784761 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Mon, 4 Aug 2025 20:11:53 +0800
Subject: [PATCH v6 01/18] error safe for casting bytea to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc and pc.castfunc > 0 and castsource::regtype ='bytea'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------+----------+-------------+------------+------------+---------
bytea | smallint | 6370 | e | f | bytea_int2 | int2
bytea | integer | 6371 | e | f | bytea_int4 | int4
bytea | bigint | 6372 | e | f | bytea_int8 | int8
(3 rows)
---
src/backend/utils/adt/bytea.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/backend/utils/adt/bytea.c b/src/backend/utils/adt/bytea.c
index 6e7b914c563..f43ad6b4aff 100644
--- a/src/backend/utils/adt/bytea.c
+++ b/src/backend/utils/adt/bytea.c
@@ -1027,7 +1027,7 @@ bytea_int2(PG_FUNCTION_ARGS)
/* Check that the byte array is not too long */
if (len > sizeof(result))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range"));
@@ -1052,7 +1052,7 @@ bytea_int4(PG_FUNCTION_ARGS)
/* Check that the byte array is not too long */
if (len > sizeof(result))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range"));
@@ -1077,7 +1077,7 @@ bytea_int8(PG_FUNCTION_ARGS)
/* Check that the byte array is not too long */
if (len > sizeof(result))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("bigint out of range"));
--
2.34.1
v6-0002-error-safe-for-casting-character-to-other-types-per-pg_ca.patchtext/x-patch; charset=US-ASCII; name=v6-0002-error-safe-for-casting-character-to-other-types-per-pg_ca.patchDownload
From 4d0fc95b1fc9690b7a631b98d3d0f392a2a293bd Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 9 Aug 2025 17:49:04 +0800
Subject: [PATCH v6 02/18] error safe for casting character to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype ='character'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+-------------------+----------+-------------+------------+-------------+---------
character | text | 401 | i | f | rtrim1 | text
character | character varying | 401 | i | f | rtrim1 | text
character | "char" | 944 | a | f | text_char | char
character | name | 409 | i | f | bpchar_name | name
character | xml | 2896 | e | f | texttoxml | xml
character | character | 668 | i | f | bpchar | bpchar
(6 rows)
only texttoxml, bpchar(PG_FUNCTION_ARGS) need take care of error handling.
other already error safe.
---
src/backend/executor/execExprInterp.c | 2 +-
src/backend/utils/adt/varchar.c | 2 +-
src/backend/utils/adt/xml.c | 12 ++++++++----
src/include/utils/xml.h | 2 +-
4 files changed, 11 insertions(+), 7 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 0e1a74976f7..67f4e00eac4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -4542,7 +4542,7 @@ ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op)
*op->resvalue = PointerGetDatum(xmlparse(data,
xexpr->xmloption,
- preserve_whitespace));
+ preserve_whitespace, NULL));
*op->resnull = false;
}
break;
diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c
index 3f40c9da1a0..5cb5c8c46f9 100644
--- a/src/backend/utils/adt/varchar.c
+++ b/src/backend/utils/adt/varchar.c
@@ -307,7 +307,7 @@ bpchar(PG_FUNCTION_ARGS)
{
for (i = maxmblen; i < len; i++)
if (s[i] != ' ')
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
errmsg("value too long for type character(%d)",
maxlen)));
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 182e8f75db7..1d423e0f9a3 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -660,7 +660,7 @@ texttoxml(PG_FUNCTION_ARGS)
{
text *data = PG_GETARG_TEXT_PP(0);
- PG_RETURN_XML_P(xmlparse(data, xmloption, true));
+ PG_RETURN_XML_P(xmlparse(data, xmloption, true, fcinfo->context));
}
@@ -1029,14 +1029,18 @@ xmlelement(XmlExpr *xexpr,
xmltype *
-xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace)
+xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, Node *escontext)
{
#ifdef USE_LIBXML
xmlDocPtr doc;
doc = xml_parse(data, xmloption_arg, preserve_whitespace,
- GetDatabaseEncoding(), NULL, NULL, NULL);
- xmlFreeDoc(doc);
+ GetDatabaseEncoding(), NULL, NULL, escontext);
+ if (doc)
+ xmlFreeDoc(doc);
+
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return NULL;
return (xmltype *) data;
#else
diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h
index 0d7a816b9f9..b3d3f11fac4 100644
--- a/src/include/utils/xml.h
+++ b/src/include/utils/xml.h
@@ -73,7 +73,7 @@ extern xmltype *xmlconcat(List *args);
extern xmltype *xmlelement(XmlExpr *xexpr,
Datum *named_argvalue, bool *named_argnull,
Datum *argvalue, bool *argnull);
-extern xmltype *xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace);
+extern xmltype *xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, Node *escontext);
extern xmltype *xmlpi(const char *target, text *arg, bool arg_is_null, bool *result_is_null);
extern xmltype *xmlroot(xmltype *data, text *version, int standalone);
extern bool xml_is_document(xmltype *arg);
--
2.34.1
hi.
First of all, rebase v6.
select distinct castsource::regtype, casttarget::regtype, pp.prosrc
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
join pg_type pt on pt.oid = castsource
join pg_type pt1 on pt1.oid = casttarget
and pc.castfunc > 0 and pt.typarray <> 0 and pt.typnamespace =
'pg_catalog'::regnamespace
and pt1.typnamespace = 'pg_catalog'::regnamespace
order by castsource::regtype, casttarget::regtype;
The above query will list all cast functions in PG_CATALOG schema that
need to be
refactored to be error-safe. looking around, I found that the below source data
type, the cast function is already error safe:
boolean
name
"char" ("char" to integer was handled in integer cast part)
regprocedure
regoper
regoperator
regclass
regtype
regconfig
regdictionary
regproc
oid
regnamespace
regrole
regcollation
regdatabase
int4range
numrange
tsrange
tstzrange
daterange
int8range
xid8
time without time zone
time with time zone
After applying the attached v7 patch patchset,
the below are cast functions that I didn't refactor to error safe yet.
select pc.castsource::regtype,
pc.casttarget::regtype, castfunc::regproc, pp.prosrc
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
join pg_type pt on pt.oid = castsource
join pg_type pt1 on pt1.oid = casttarget
and pc.castfunc > 0 and pt.typnamespace = 'pg_catalog'::regnamespace
and pt1.typnamespace = 'pg_catalog'::regnamespace and not pc.casterrorsafe;
castsource | casttarget | castfunc | prosrc
------------+------------+----------------------+--------------
bigint | money | pg_catalog.money | int8_cash
integer | money | pg_catalog.money | int4_cash
numeric | money | pg_catalog.money | numeric_cash
money | numeric | pg_catalog."numeric" | cash_numeric
(4 rows)
The reason I don't refactor these cast functions related to money data
type is because
1. I am not sure if the PGLC_localeconv() function is error safe or not.
2. refactoring such ``DirectFunctionCall1(numeric_int8, amount));``
requires more effort.
please check the attached v7 patch set:
01-error-safe-for-casting-bytea-to-other-types
02-error-safe-for-casting-bit-varbit-to-other-types
03-error-safe-for-casting-character-to-other-types
04-error-safe-for-casting-text-to-other-types
05-error-safe-for-casting-character-varying-to-other-types
06-error-safe-for-casting-inet-to-other-types
07-error-safe-for-casting-macaddr8-to-other-types
08-error-safe-for-casting-integer-to-other-types
09-error-safe-for-casting-bigint-to-other-types
10-error-safe-for-casting-numeric-to-other-types
11-error-safe-for-casting-float4-to-other-types
12-error-safe-for-casting-float8-to-other-types
13-error-safe-for-casting-date-to-other-types
14-error-safe-for-casting-interval-to-other-types
15-error-safe-for-casting-timestamptz-to-other-types
16-error-safe-for-casting-timestamp-to-other-types
17-error-safe-for-casting-jsonb-to-other-types
18-error-safe-for-casting-geometry-data-types
19-invent_some_error_safe_function.patch
20-CAST-expr-AS-newtype-DEFAULT-ON-ERROR.patch
Each patch includes a commit message explaining which function is being
refactored to be error-safe.
I also made some changes for
"20-CAST-expr-AS-newtype-DEFAULT-ON-ERROR.patch"
If the source expression is Const, it can be unknown type or other type.
* for UNKNOWN type, example:
SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT NULL ON
CONVERSION ERROR);
We use CoerceUnknownConstSafe to verify earlier that such Const can be
coerced to target data type or not.
* for other data type, example:
SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT NULL ON
CONVERSION ERROR);
we use evaluate_expr_safe to evaluate it error safe way earlier in
transformTypeSafeCast.
similar to transformPartitionBoundValue call evaluate_expr
The geometry cast functions ``(poly_circle(PG_FUNCTION_ARGS)`` have
problems making it error safe,
working on it.
Attachments:
v7-0019-invent-some-error-safe-functions.patchtext/x-patch; charset=US-ASCII; name=v7-0019-invent-some-error-safe-functions.patchDownload
From a70d65ce8183eef768eba2e340b208743539ddd5 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Fri, 10 Oct 2025 18:57:21 +0800
Subject: [PATCH v7 19/20] invent some error safe functions
stringTypeDatumSafe: error safe version of stringTypeDatum
evaluate_expr_safe: error safe version of evaluate_expr
ExecInitExprSafe: soft error variant of ExecInitExpr
OidInputFunctionCallSafe: soft error variant of OidInputFunctionCall
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/executor/execExpr.c | 41 +++++++++++++++++
src/backend/optimizer/util/clauses.c | 69 ++++++++++++++++++++++++++++
src/backend/parser/parse_type.c | 14 ++++++
src/backend/utils/fmgr/fmgr.c | 13 ++++++
src/include/executor/executor.h | 1 +
src/include/fmgr.h | 3 ++
src/include/optimizer/optimizer.h | 2 +
src/include/parser/parse_type.h | 2 +
8 files changed, 145 insertions(+)
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index f1569879b52..b302be36f73 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -170,6 +170,47 @@ ExecInitExpr(Expr *node, PlanState *parent)
return state;
}
+/*
+ * ExecInitExprSafe: soft error variant of ExecInitExpr.
+ *
+ * use it only for expression nodes support soft errors, not all expression
+ * nodes support it.
+*/
+ExprState *
+ExecInitExprSafe(Expr *node, PlanState *parent)
+{
+ ExprState *state;
+ ExprEvalStep scratch = {0};
+
+ /* Special case: NULL expression produces a NULL ExprState pointer */
+ if (node == NULL)
+ return NULL;
+
+ /* Initialize ExprState with empty step list */
+ state = makeNode(ExprState);
+ state->expr = node;
+ state->parent = parent;
+ state->ext_params = NULL;
+ state->escontext = makeNode(ErrorSaveContext);
+ state->escontext->type = T_ErrorSaveContext;
+ state->escontext->error_occurred = false;
+ state->escontext->details_wanted = false;
+
+ /* Insert setup steps as needed */
+ ExecCreateExprSetupSteps(state, (Node *) node);
+
+ /* Compile the expression proper */
+ ExecInitExprRec(node, state, &state->resvalue, &state->resnull);
+
+ /* Finally, append a DONE step */
+ scratch.opcode = EEOP_DONE_RETURN;
+ ExprEvalPushStep(state, &scratch);
+
+ ExecReadyExpr(state);
+
+ return state;
+}
+
/*
* ExecInitExprWithParams: prepare a standalone expression tree for execution
*
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 81d768ff2a2..3f9ff455ba6 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -5147,6 +5147,75 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
resultTypByVal);
}
+/*
+ * evaluate_expr_safe: error safe version of evaluate_expr
+ *
+ * We use the executor's routine ExecEvalExpr() to avoid duplication of
+ * code and ensure we get the same result as the executor would get.
+ *
+ * return NULL when evaulation failed. Ensure the expression expr can be evaulated
+ * in a soft error way.
+ *
+ * See comments on evaluate_expr too.
+ */
+Expr *
+evaluate_expr_safe(Expr *expr, Oid result_type, int32 result_typmod,
+ Oid result_collation)
+{
+ EState *estate;
+ ExprState *exprstate;
+ MemoryContext oldcontext;
+ Datum const_val;
+ bool const_is_null;
+ int16 resultTypLen;
+ bool resultTypByVal;
+
+ estate = CreateExecutorState();
+
+ /* We can use the estate's working context to avoid memory leaks. */
+ oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
+
+ /* Make sure any opfuncids are filled in. */
+ fix_opfuncids((Node *) expr);
+
+ /*
+ * Prepare expr for execution. (Note: we can't use ExecPrepareExpr
+ * because it'd result in recursively invoking eval_const_expressions.)
+ */
+ exprstate = ExecInitExprSafe(expr, NULL);
+
+ const_val = ExecEvalExprSwitchContext(exprstate,
+ GetPerTupleExprContext(estate),
+ &const_is_null);
+
+ /* Get info needed about result datatype */
+ get_typlenbyval(result_type, &resultTypLen, &resultTypByVal);
+
+ /* Get back to outer memory context */
+ MemoryContextSwitchTo(oldcontext);
+
+ if (SOFT_ERROR_OCCURRED(exprstate->escontext))
+ {
+ FreeExecutorState(estate);
+ return NULL;
+ }
+
+ if (!const_is_null)
+ {
+ if (resultTypLen == -1)
+ const_val = PointerGetDatum(PG_DETOAST_DATUM_COPY(const_val));
+ else
+ const_val = datumCopy(const_val, resultTypByVal, resultTypLen);
+ }
+
+ FreeExecutorState(estate);
+
+ return (Expr *) makeConst(result_type, result_typmod, result_collation,
+ resultTypLen,
+ const_val, const_is_null,
+ resultTypByVal);
+}
+
/*
* inline_set_returning_function
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index 7713bdc6af0..d260aeec5dc 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,19 @@ stringTypeDatum(Type tp, char *string, int32 atttypmod)
return OidInputFunctionCall(typinput, string, typioparam, atttypmod);
}
+/* error safe version of stringTypeDatum */
+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,
+ (Node *) &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/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index 0fe63c6bb83..aaa4a42b1ea 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, Node *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/executor.h b/src/include/executor/executor.h
index 3248e78cd28..ff9997e1cc1 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -321,6 +321,7 @@ ExecProcNode(PlanState *node)
* prototypes from functions in execExpr.c
*/
extern ExprState *ExecInitExpr(Expr *node, PlanState *parent);
+extern ExprState *ExecInitExprSafe(Expr *node, PlanState *parent);
extern ExprState *ExecInitExprWithParams(Expr *node, ParamListInfo ext_params);
extern ExprState *ExecInitQual(List *qual, PlanState *parent);
extern ExprState *ExecInitCheck(List *qual, PlanState *parent);
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index 74fe3ea0575..991e14034d3 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, Node *escontext,
+ Datum *result);
extern char *OutputFunctionCall(FmgrInfo *flinfo, Datum val);
extern char *OidOutputFunctionCall(Oid functionId, Datum val);
extern Datum ReceiveFunctionCall(FmgrInfo *flinfo, StringInfo buf,
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index a34113903c0..f1b6381d084 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -146,6 +146,8 @@ extern Node *estimate_expression_value(PlannerInfo *root, Node *node);
extern Expr *evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
Oid result_collation);
+extern Expr *evaluate_expr_safe(Expr *expr, Oid result_type, int32 result_typmod,
+ Oid result_collation);
extern bool var_is_nonnullable(PlannerInfo *root, Var *var, bool use_rel_info);
extern List *expand_function_arguments(List *args, bool include_out_arguments,
diff --git a/src/include/parser/parse_type.h b/src/include/parser/parse_type.h
index 0d919d8bfa2..12381aed64c 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);
--
2.34.1
v7-0020-CAST-expr-AS-newtype-DEFAULT-ON-ERROR.patchtext/x-patch; charset=UTF-8; name=v7-0020-CAST-expr-AS-newtype-DEFAULT-ON-ERROR.patchDownload
From 3e350ca387117f05388e340e048c14a95ce724e1 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Fri, 10 Oct 2025 19:06:14 +0800
Subject: [PATCH v7 20/20] CAST(expr AS newtype DEFAULT ON ERROR)
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.
draft doc also added.
We cannot simply prohibit user-defined functions in pg_cast for safe cast
evaluation because CREATE CAST can also utilize built-in functions. So, to
completely disallow custom casts created via CREATE CAST used in safe cast
evaluation, a new field in pg_cast would unfortunately be necessary.
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}
[0]: https://git.postgresql.org/cgit/postgresql.git/commit/?id=aaaf9449ec6be62cb0d30ed3588dc384f56274b
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
.../pg_stat_statements/expected/select.out | 23 +-
contrib/pg_stat_statements/sql/select.sql | 5 +
doc/src/sgml/catalogs.sgml | 12 +
doc/src/sgml/syntax.sgml | 16 +
src/backend/catalog/pg_cast.c | 1 +
src/backend/executor/execExpr.c | 76 +-
src/backend/executor/execExprInterp.c | 37 +
src/backend/jit/llvm/llvmjit_expr.c | 48 ++
src/backend/nodes/nodeFuncs.c | 67 ++
src/backend/optimizer/util/clauses.c | 24 +
src/backend/parser/gram.y | 24 +-
src/backend/parser/parse_agg.c | 9 +
src/backend/parser/parse_expr.c | 383 +++++++++-
src/backend/parser/parse_func.c | 3 +
src/backend/parser/parse_target.c | 14 +
src/backend/utils/adt/arrayfuncs.c | 13 +
src/backend/utils/adt/ruleutils.c | 16 +
src/include/catalog/pg_cast.dat | 330 ++++-----
src/include/catalog/pg_cast.h | 4 +
src/include/executor/execExpr.h | 8 +
src/include/nodes/execnodes.h | 30 +
src/include/nodes/parsenodes.h | 6 +
src/include/nodes/primnodes.h | 33 +
src/include/parser/parse_node.h | 2 +
src/test/regress/expected/cast.out | 681 ++++++++++++++++++
src/test/regress/expected/create_cast.out | 5 +
src/test/regress/expected/opr_sanity.out | 24 +-
src/test/regress/parallel_schedule | 2 +-
src/test/regress/sql/cast.sql | 301 ++++++++
src/test/regress/sql/create_cast.sql | 1 +
src/tools/pgindent/typedefs.list | 3 +
31 files changed, 2011 insertions(+), 190 deletions(-)
create mode 100644 src/test/regress/expected/cast.out
create mode 100644 src/test/regress/sql/cast.sql
diff --git a/contrib/pg_stat_statements/expected/select.out b/contrib/pg_stat_statements/expected/select.out
index 75c896f3885..6e67997f0a2 100644
--- a/contrib/pg_stat_statements/expected/select.out
+++ b/contrib/pg_stat_statements/expected/select.out
@@ -73,6 +73,25 @@ SELECT 1 AS "int" OFFSET 2 FETCH FIRST 3 ROW ONLY;
-----
(0 rows)
+--error safe type cast
+SELECT CAST('a' AS int DEFAULT 2 ON CONVERSION ERROR);
+ int4
+------
+ 2
+(1 row)
+
+SELECT CAST('11' AS int DEFAULT 2 ON CONVERSION ERROR);
+ int4
+------
+ 11
+(1 row)
+
+SELECT CAST('12' AS numeric DEFAULT 2 ON CONVERSION ERROR);
+ numeric
+---------
+ 12
+(1 row)
+
-- DISTINCT and ORDER BY patterns
-- Try some query permutations which once produced identical query IDs
SELECT DISTINCT 1 AS "int";
@@ -222,6 +241,8 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
2 | 2 | SELECT $1 AS "int" ORDER BY 1
1 | 2 | SELECT $1 AS i UNION SELECT $2 ORDER BY i
1 | 1 | SELECT $1 || $2
+ 2 | 2 | SELECT CAST($1 AS int DEFAULT $2 ON CONVERSION ERROR)
+ 1 | 1 | SELECT CAST($1 AS numeric DEFAULT $2 ON CONVERSION ERROR)
2 | 2 | SELECT DISTINCT $1 AS "int"
0 | 0 | SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"
1 | 1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
@@ -230,7 +251,7 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
| | ) +
| | SELECT f FROM t ORDER BY f
1 | 1 | select $1::jsonb ? $2
-(17 rows)
+(19 rows)
SELECT pg_stat_statements_reset() IS NOT NULL AS t;
t
diff --git a/contrib/pg_stat_statements/sql/select.sql b/contrib/pg_stat_statements/sql/select.sql
index 11662cde08c..7ee8160fd84 100644
--- a/contrib/pg_stat_statements/sql/select.sql
+++ b/contrib/pg_stat_statements/sql/select.sql
@@ -25,6 +25,11 @@ SELECT 1 AS "int" LIMIT 3 OFFSET 3;
SELECT 1 AS "int" OFFSET 1 FETCH FIRST 2 ROW ONLY;
SELECT 1 AS "int" OFFSET 2 FETCH FIRST 3 ROW ONLY;
+--error safe type cast
+SELECT CAST('a' AS int DEFAULT 2 ON CONVERSION ERROR);
+SELECT CAST('11' AS int DEFAULT 2 ON CONVERSION ERROR);
+SELECT CAST('12' AS numeric DEFAULT 2 ON CONVERSION ERROR);
+
-- DISTINCT and ORDER BY patterns
-- Try some query permutations which once produced identical query IDs
SELECT DISTINCT 1 AS "int";
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 9b3aae8603b..957041add7b 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1849,6 +1849,18 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<literal>b</literal> means that the types are binary-coercible, thus no conversion is required.
</para></entry>
</row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>casterrorsafe</structfield> <type>bool</type>
+ </para>
+ <para>
+ This indicates whether the <structfield>castfunc</structfield> function is error safe.
+ If the <structfield>castfunc</structfield> function is error safe, it can be used in
+ For further details see <xref linkend="sql-syntax-type-casts"/>.
+ </para></entry>
+ </row>
+
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml
index 237d7306fe8..9acb7d69645 100644
--- a/doc/src/sgml/syntax.sgml
+++ b/doc/src/sgml/syntax.sgml
@@ -2106,6 +2106,22 @@ CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable>
The <literal>CAST</literal> syntax conforms to SQL; the syntax with
<literal>::</literal> is historical <productname>PostgreSQL</productname>
usage.
+ It can also be written as:
+<synopsis>
+CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable> ERROR ON CONVERSION ERROR )
+</synopsis>
+ </para>
+
+ <para>
+Type cast can also be error safe.
+<synopsis>
+CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable> DEFAULT <replaceable>defexpression</replaceable> ON CONVERSION ERROR )
+</synopsis>
+ For example, the following query will evaluate the default expression and return 42.
+ TODO: more explanation
+<programlisting>
+SELECT CAST('error' AS integer DEFAULT 42 ON CONVERSION ERROR);
+</programlisting>
</para>
<para>
diff --git a/src/backend/catalog/pg_cast.c b/src/backend/catalog/pg_cast.c
index 1773c9c5491..6fe65d24d31 100644
--- a/src/backend/catalog/pg_cast.c
+++ b/src/backend/catalog/pg_cast.c
@@ -84,6 +84,7 @@ CastCreate(Oid sourcetypeid, Oid targettypeid,
values[Anum_pg_cast_castfunc - 1] = ObjectIdGetDatum(funcid);
values[Anum_pg_cast_castcontext - 1] = CharGetDatum(castcontext);
values[Anum_pg_cast_castmethod - 1] = CharGetDatum(castmethod);
+ values[Anum_pg_cast_casterrorsafe - 1] = BoolGetDatum(false);
tuple = heap_form_tuple(RelationGetDescr(relation), values, nulls);
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index b302be36f73..88a647e44fa 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,
@@ -1742,6 +1745,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
elemstate->innermost_caseval = (Datum *) palloc(sizeof(Datum));
elemstate->innermost_casenull = (bool *) palloc(sizeof(bool));
+ elemstate->escontext = state->escontext;
ExecInitExprRec(acoerce->elemexpr, elemstate,
&elemstate->resvalue, &elemstate->resnull);
@@ -2218,6 +2222,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;
@@ -2784,7 +2796,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;
@@ -4782,6 +4794,68 @@ ExecBuildParamSetEqual(TupleDesc desc,
return state;
}
+/*
+ * Push steps to evaluate a SafeTypeCastExpr and its various subsidiary
+ * expressions.
+ */
+static void
+ExecInitSafeTypeCastExpr(SafeTypeCastExpr *stcexpr , ExprState *state,
+ Datum *resv, bool *resnull,
+ ExprEvalStep *scratch)
+{
+ /*
+ * 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
+ {
+ ExprEvalStep *as;
+ SafeTypeCastState *stcstate;
+ ErrorSaveContext *saved_escontext;
+ int jumps_to_end;
+
+ stcstate = palloc0(sizeof(SafeTypeCastState));
+ stcstate->stcexpr = stcexpr;
+ stcstate->escontext.type = T_ErrorSaveContext;
+ state->escontext = &stcstate->escontext;
+
+ /* evaluate argument expression into step's result area */
+ ExecInitExprRec((Expr *) stcexpr->cast_expr,
+ 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 = 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;
+
+ as = &state->steps[jumps_to_end];
+ 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 67f4e00eac4..49da2905810 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,12 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
EEO_NEXT();
}
+ EEO_CASE(EEOP_SAFETYPE_CAST)
+ {
+ /* too complex for an inline implementation */
+ EEO_JUMP(ExecEvalSafeTypeCast(state, op));
+ }
+
EEO_CASE(EEOP_JSONEXPR_PATH)
{
/* too complex for an inline implementation */
@@ -3644,6 +3651,12 @@ 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;
+ }
}
/*
@@ -5183,6 +5196,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 712b35df7e5..ebea7c4445f 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -2256,6 +2256,54 @@ 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 ede838cd40c..533e21120a6 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;
@@ -1550,6 +1561,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;
@@ -2321,6 +2341,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:
@@ -3330,6 +3362,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;
@@ -4464,6 +4509,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->def_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/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 3f9ff455ba6..14e0df3bee2 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -2945,6 +2945,30 @@ 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);
+
+ /*
+ * We must not reduce any recognizably constant subexpressions
+ * in cast_expr here, since we don’t want it to fail
+ * prematurely.
+ */
+ 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 21caf2d43bf..c62e9ac8edf 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -649,6 +649,7 @@ 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_opt
%type <node> json_format_clause
json_format_clause_opt
@@ -15982,8 +15983,20 @@ 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_opt ')'
+ {
+ TypeCast *cast = (TypeCast *) makeTypeCast($3, $5, @1);
+ if ($6 == NULL)
+ $$ = (Node *) cast;
+ else
+ {
+ SafeTypeCast *safecast = makeNode(SafeTypeCast);
+ safecast->cast = (Node *) cast;
+ safecast->def_expr = $6;
+
+ $$ = (Node *) safecast;
+ }
+ }
| EXTRACT '(' extract_list ')'
{
$$ = (Node *) makeFuncCall(SystemFuncName("extract"),
@@ -16370,6 +16383,13 @@ func_expr_common_subexpr:
;
+cast_on_error_clause_opt:
+ DEFAULT a_expr ON CONVERSION_P ERROR_P { $$ = $2; }
+ | ERROR_P ON CONVERSION_P ERROR_P { $$ = NULL; }
+ | NULL_P ON CONVERSION_P ERROR_P { $$ = makeNullAConst(-1); }
+ | /* EMPTY */ { $$ = NULL; }
+ ;
+
/*
* SQL/XML support
*/
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 3254c83cc6c..6573e2a93ea 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -486,6 +486,12 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
err = _("grouping operations are not allowed in check constraints");
break;
+ case EXPR_KIND_CAST_DEFAULT:
+ if (isAgg)
+ err = _("aggregate functions are not allowed in CAST DEFAULT expressions");
+ else
+ err = _("grouping operations are not allowed in CAST DEFAULT expressions");
+ break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
@@ -956,6 +962,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_DOMAIN_CHECK:
err = _("window functions are not allowed in check constraints");
break;
+ case EXPR_KIND_CAST_DEFAULT:
+ err = _("window functions are not allowed in CAST DEFAULT expressions");
+ break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
err = _("window functions are not allowed in DEFAULT expressions");
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 12119f147fc..ac60b74dceb 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -17,6 +17,8 @@
#include "access/htup_details.h"
#include "catalog/pg_aggregate.h"
+#include "catalog/pg_cast.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -37,6 +39,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 +63,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 *can_coerce);
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 +80,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);
@@ -107,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 CoerceUnknownConstSafe(ParseState *pstate, Node *node,
+ Oid targetType, int32 targetTypeMod);
/*
@@ -164,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;
@@ -576,6 +587,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
case EXPR_KIND_COPY_WHERE:
case EXPR_KIND_GENERATED_COLUMN:
case EXPR_KIND_CYCLE_MARK:
+ case EXPR_KIND_CAST_DEFAULT:
/* okay */
break;
@@ -1824,6 +1836,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_DOMAIN_CHECK:
err = _("cannot use subquery in check constraint");
break;
+ case EXPR_KIND_CAST_DEFAULT:
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
err = _("cannot use subquery in DEFAULT expression");
@@ -2005,16 +2018,73 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
return result;
}
+/*
+ * Return true if successfuly coerced a Unknown Const to targetType
+*/
+static bool
+CoerceUnknownConstSafe(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;
+}
+
+
/*
* 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().
+ *
+ * Most of the time, the caller will pass can_coerce as NULL.
+ * can_coerce is not NULL only when performing parse analysis for expressions
+ * like CAST(DEFAULT ... ON CONVERSION ERROR).
+ * When can_coerce is not NULL, it should be assumed to default to true. If
+ * coerce array elements to the target type fails, can_coerce will be set to
+ * 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;
@@ -2022,6 +2092,7 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
ListCell *element;
Oid coerce_type;
bool coerce_hard;
+ bool expr_is_const = false;
/*
* Transform the element expressions
@@ -2045,9 +2116,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
@@ -2088,6 +2160,9 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
}
else
{
+ /* We obviously know the target type when performing type-safe cast */
+ Assert(can_coerce == NULL);
+
/* Can't handle an empty array without a target type */
if (newelems == NIL)
ereport(ERROR,
@@ -2140,7 +2215,36 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
Node *e = (Node *) lfirst(element);
Node *newe;
- if (coerce_hard)
+ /*
+ * For error-safe type casting, we need to safely check whether the
+ * UNKNOWN constant can be coerced to the target type. If it cannot, set
+ * can_coerce to false and coerce source element to TEXT data type.
+ * see coerce_type function also.
+ */
+ if (can_coerce != NULL && IsA(e, Const))
+ {
+ if (exprType(e) == UNKNOWNOID)
+ {
+ if (!CoerceUnknownConstSafe(pstate, e, coerce_type, typmod))
+ {
+ *can_coerce = false;
+
+ /* see resolveTargetListUnknowns also */
+ e = coerce_to_target_type(pstate, e,
+ UNKNOWNOID,
+ TEXTOID,
+ -1,
+ COERCION_IMPLICIT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ }
+ }
+ expr_is_const = true;
+ }
+
+ if (can_coerce != NULL && (!*can_coerce))
+ newe = e;
+ else if (coerce_hard)
{
newe = coerce_to_target_type(pstate, e,
exprType(e),
@@ -2149,13 +2253,51 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
COERCION_EXPLICIT,
COERCE_EXPLICIT_CAST,
-1);
- if (newe == NULL)
+ if (newe == NULL && can_coerce == NULL)
ereport(ERROR,
(errcode(ERRCODE_CANNOT_COERCE),
errmsg("cannot cast type %s to %s",
format_type_be(exprType(e)),
format_type_be(coerce_type)),
parser_errposition(pstate, exprLocation(e))));
+
+ if (can_coerce != NULL)
+ {
+ if (newe == NULL)
+ {
+ newe = e;
+ *can_coerce = false;
+ }
+ else if (expr_is_const && !IsA(newe, Const))
+ {
+ /*
+ * Use evaluate_expr_safe to pre-evaluate simple constant
+ * cast expressions early, in a way that tolter errors .
+ *
+ * Rationale:
+ * 1. When deparsing safe cast expressions (or in other
+ * cases), eval_const_expressions might be invoked, but
+ * it cannot handle errors gracefully.
+ * 2. If the cast expression involves only simple constants,
+ * we can safely evaluate it ahead of time. If the
+ * evaluation fails, it indicates that such a cast is not
+ * possible, and we can then fall back to the CAST
+ * DEFAULT expression.
+ */
+ Expr *newe_copy = (Expr *) copyObject(newe);
+
+ newe = (Node *) evaluate_expr_safe(newe_copy,
+ coerce_type,
+ typmod,
+ exprCollation(newe));
+ if (newe == NULL)
+ {
+ newe = e;
+ *can_coerce = false;
+ }
+ }
+ expr_is_const = false;
+ }
}
else
newe = coerce_to_common_type(pstate, e,
@@ -2743,7 +2885,8 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
(A_ArrayExpr *) arg,
targetBaseType,
elementType,
- targetBaseTypmod);
+ targetBaseTypmod,
+ NULL);
}
else
expr = transformExprRecurse(pstate, arg);
@@ -2780,6 +2923,228 @@ 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;
+ TypeCast *tcast = (TypeCast *) tc->cast;
+ Node *def_expr = NULL;
+ Node *cast_expr = NULL;
+ Node *source_expr = NULL;
+ Oid inputType = InvalidOid;
+ Oid targetType;
+ Oid targetBaseType;
+ Oid typeColl;
+ int32 targetTypmod;
+ int32 targetBaseTypmod;
+ bool can_coerce = true;
+ bool expr_is_const = false;
+ int location;
+
+ /* Look up the type name first */
+ typenameTypeIdAndMod(pstate, tcast->typeName, &targetType, &targetTypmod);
+ targetBaseTypmod = targetTypmod;
+
+ typeColl = get_typcollation(targetType);
+ targetBaseType = getBaseTypeAndTypmod(targetType, &targetBaseTypmod);
+
+ /* now looking at cast default expression */
+ def_expr = transformExpr(pstate, tc->def_expr, EXPR_KIND_CAST_DEFAULT);
+
+ def_expr = coerce_to_target_type(pstate, def_expr, exprType(def_expr),
+ targetType, targetTypmod,
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ exprLocation(def_expr));
+
+ assign_expr_collations(pstate, def_expr);
+
+ if (def_expr == NULL)
+ ereport(ERROR,
+ errcode(ERRCODE_CANNOT_COERCE),
+ errmsg("cannot coerce %s expression to type %s",
+ "CAST DEFAULT",
+ format_type_be(targetType)),
+ parser_coercion_errposition(pstate, exprLocation(tc->def_expr), def_expr));
+
+ /*
+ * The collation of DEFAULT expression must match the collation of the
+ * target type.
+ */
+ if (OidIsValid(typeColl))
+ {
+ Oid defColl;
+
+ defColl = exprCollation(def_expr);
+
+ if (OidIsValid(defColl) && typeColl != defColl)
+ ereport(ERROR,
+ errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("the collation of CAST DEFAULT expression conflicts with target type collation"),
+ errdetail("\"%s\" versus \"%s\"",
+ get_collation_name(typeColl),
+ get_collation_name(defColl)),
+ parser_errposition(pstate, exprLocation(def_expr)));
+ }
+
+ /*
+ * If the type cast 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(tcast->arg, A_ArrayExpr))
+ {
+ 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.
+ */
+ elementType = get_element_type(targetBaseType);
+
+ if (OidIsValid(elementType))
+ {
+ source_expr = transformArrayExpr(pstate,
+ (A_ArrayExpr *) tcast->arg,
+ targetBaseType,
+ elementType,
+ targetBaseTypmod,
+ &can_coerce);
+ }
+ else
+ source_expr = transformExprRecurse(pstate, tcast->arg);
+ }
+ else
+ source_expr = transformExprRecurse(pstate, tcast->arg);
+
+ inputType = exprType(source_expr);
+ if (inputType == InvalidOid)
+ return (Node *) NULL; /* return NULL if NULL input */
+
+ if (can_coerce && IsA(source_expr, Const))
+ {
+ if (exprType(source_expr) == UNKNOWNOID)
+ can_coerce = CoerceUnknownConstSafe(pstate,
+ source_expr,
+ targetType,
+ targetTypmod);
+ expr_is_const = true;
+ }
+
+ /*
+ * Location of the coercion is preferentially the location of 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)
+ {
+ Node *origexpr;
+
+ cast_expr = coerce_to_target_type(pstate, source_expr, inputType,
+ targetType, targetTypmod,
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ location);
+ origexpr = cast_expr;
+
+ while (cast_expr && IsA(cast_expr, CollateExpr))
+ cast_expr = (Node *) ((CollateExpr *) cast_expr)->arg;
+
+ if (cast_expr != NULL && IsA(cast_expr, FuncExpr))
+ {
+ HeapTuple tuple;
+ ListCell *lc;
+ Node *sexpr;
+ FuncExpr *fexpr = (FuncExpr *) cast_expr;
+
+ lc = list_head(fexpr->args);
+ sexpr = (Node *) lfirst(lc);
+
+ /* Look in pg_cast */
+ tuple = SearchSysCache2(CASTSOURCETARGET,
+ ObjectIdGetDatum(exprType(sexpr)),
+ ObjectIdGetDatum(targetType));
+
+ if (HeapTupleIsValid(tuple))
+ {
+ Form_pg_cast castForm = (Form_pg_cast) GETSTRUCT(tuple);
+ if (!castForm->casterrorsafe)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot cast type %s to %s when %s expression is specified in %s",
+ format_type_be(inputType),
+ format_type_be(targetType),
+ "DEFAULT",
+ "CAST ... ON CONVERSION ERROR"),
+ errhint("Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast"),
+ parser_errposition(pstate, exprLocation(source_expr)));
+ }
+ else
+ elog(ERROR, "cache lookup failed for pg_cast entry (%s cast to %s)",
+ format_type_be(inputType),
+ format_type_be(targetType));
+ ReleaseSysCache(tuple);
+
+ if (expr_is_const)
+ {
+ /*
+ * Use evaluate_expr_safe to pre-evaluate simple constant cast
+ * expressions early, in a way that tolter errors .
+ *
+ * Rationale:
+ * 1. When deparsing safe cast expressions (or in other cases),
+ * eval_const_expressions might be invoked, but it cannot
+ * handle errors gracefully.
+ * 2. If the cast expression involves only simple constants, we
+ * can safely evaluate it ahead of time. If the evaluation
+ * fails, it indicates that such a cast is not possible, and
+ * we can then fall back to the CAST DEFAULT expression.
+ */
+ FuncExpr *newexpr = (FuncExpr *) copyObject(cast_expr);
+
+ assign_expr_collations(pstate, (Node *) newexpr);
+
+ cast_expr = (Node *) evaluate_expr_safe((Expr *) newexpr,
+ targetType,
+ targetTypmod,
+ typeColl);
+ }
+ }
+
+ /*
+ * set cast_expr back as is, cast_expr can be NULL after
+ * evaluate_expr_safe
+ */
+ if (cast_expr != NULL)
+ cast_expr = origexpr;
+ }
+
+ Assert(can_coerce || cast_expr == NULL);
+
+ result = makeNode(SafeTypeCastExpr);
+ result->source_expr = source_expr;
+ result->cast_expr = cast_expr;
+ result->default_expr = def_expr;
+ result->resulttype = targetType;
+ result->resulttypmod = targetTypmod;
+ result->resultcollid = typeColl;
+
+ return (Node *) result;
+}
+
/*
* Handle an explicit COLLATE clause.
*
@@ -3193,6 +3558,8 @@ ParseExprKindName(ParseExprKind exprKind)
case EXPR_KIND_CHECK_CONSTRAINT:
case EXPR_KIND_DOMAIN_CHECK:
return "CHECK";
+ case EXPR_KIND_CAST_DEFAULT:
+ return "CAST DEFAULT";
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
return "DEFAULT";
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 778d69c6f3c..a90705b9847 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2743,6 +2743,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
case EXPR_KIND_DOMAIN_CHECK:
err = _("set-returning functions are not allowed in check constraints");
break;
+ case EXPR_KIND_CAST_DEFAULT:
+ err = _("set-returning functions are not allowed in CAST DEFAULT expressions");
+ break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
err = _("set-returning functions are not allowed in DEFAULT expressions");
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 905c975d83b..d67e5f8a5b7 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1822,6 +1822,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/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index a8951f55b93..31cb36a56e6 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -3289,6 +3289,19 @@ 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 (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 050eef97a4c..4278e44b4a9 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -10558,6 +10558,22 @@ 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/include/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat
index fbfd669587f..1ef59a982af 100644
--- a/src/include/catalog/pg_cast.dat
+++ b/src/include/catalog/pg_cast.dat
@@ -19,65 +19,65 @@
# int2->int4->int8->numeric->float4->float8, while casts in the
# reverse direction are assignment-only.
{ castsource => 'int8', casttarget => 'int2', castfunc => 'int2(int8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'int4', castfunc => 'int4(int8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'float4', castfunc => 'float4(int8)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'float8', castfunc => 'float8(int8)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'numeric', castfunc => 'numeric(int8)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'int8', castfunc => 'int8(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'int4', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'float4', castfunc => 'float4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'float8', castfunc => 'float8(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'numeric', castfunc => 'numeric(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'int8', castfunc => 'int8(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'int2', castfunc => 'int2(int4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'float4', castfunc => 'float4(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'float8', castfunc => 'float8(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'numeric', castfunc => 'numeric(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'int8', castfunc => 'int8(float4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'int2', castfunc => 'int2(float4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'int4', castfunc => 'int4(float4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'float8', castfunc => 'float8(float4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'numeric',
- castfunc => 'numeric(float4)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'numeric(float4)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'int8', castfunc => 'int8(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'int2', castfunc => 'int2(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'int4', castfunc => 'int4(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'float4', castfunc => 'float4(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'numeric',
- castfunc => 'numeric(float8)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'numeric(float8)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'int8', castfunc => 'int8(numeric)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'int2', castfunc => 'int2(numeric)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'int4', castfunc => 'int4(numeric)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'float4',
- castfunc => 'float4(numeric)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'float4(numeric)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'float8',
- castfunc => 'float8(numeric)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'float8(numeric)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'money', casttarget => 'numeric', castfunc => 'numeric(money)',
castcontext => 'a', castmethod => 'f' },
{ castsource => 'numeric', casttarget => 'money', castfunc => 'money(numeric)',
@@ -89,13 +89,13 @@
# Allow explicit coercions between int4 and bool
{ castsource => 'int4', casttarget => 'bool', castfunc => 'bool(int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'int4', castfunc => 'int4(bool)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Allow explicit coercions between xid8 and xid
{ castsource => 'xid8', casttarget => 'xid', castfunc => 'xid(xid8)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# OID category: allow implicit conversion from any integral type (including
# int8, to support OID literals > 2G) to OID, as well as assignment coercion
@@ -106,13 +106,13 @@
# casts from text and varchar to regclass, which exist mainly to support
# legacy forms of nextval() and related functions.
{ castsource => 'int8', casttarget => 'oid', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'oid', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'oid', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regproc', castfunc => '0',
@@ -120,13 +120,13 @@
{ castsource => 'regproc', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regproc', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regproc', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regproc', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regproc', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regproc', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'regproc', casttarget => 'regprocedure', castfunc => '0',
@@ -138,13 +138,13 @@
{ castsource => 'regprocedure', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regprocedure', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regprocedure', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regprocedure', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regprocedure', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regprocedure', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regoper', castfunc => '0',
@@ -152,13 +152,13 @@
{ castsource => 'regoper', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regoper', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regoper', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regoper', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regoper', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regoper', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'regoper', casttarget => 'regoperator', castfunc => '0',
@@ -170,13 +170,13 @@
{ castsource => 'regoperator', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regoperator', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regoperator', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regoperator', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regoperator', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regoperator', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regclass', castfunc => '0',
@@ -184,13 +184,13 @@
{ castsource => 'regclass', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regclass', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regclass', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regclass', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regclass', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regclass', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regcollation', castfunc => '0',
@@ -198,13 +198,13 @@
{ castsource => 'regcollation', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regcollation', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regcollation', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regcollation', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regcollation', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regcollation', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regtype', castfunc => '0',
@@ -212,13 +212,13 @@
{ castsource => 'regtype', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regtype', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regtype', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regtype', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regtype', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regtype', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regconfig', castfunc => '0',
@@ -226,13 +226,13 @@
{ castsource => 'regconfig', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regconfig', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regconfig', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regconfig', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regconfig', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regconfig', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regdictionary', castfunc => '0',
@@ -240,31 +240,31 @@
{ castsource => 'regdictionary', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regdictionary', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regdictionary', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regdictionary', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regdictionary', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regdictionary', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'text', casttarget => 'regclass', castfunc => 'regclass',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'regclass', castfunc => 'regclass',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'oid', casttarget => 'regrole', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regrole', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regrole', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regrole', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regrole', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regrole', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regrole', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regnamespace', castfunc => '0',
@@ -272,13 +272,13 @@
{ castsource => 'regnamespace', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regnamespace', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regnamespace', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regnamespace', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regnamespace', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regnamespace', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regdatabase', castfunc => '0',
@@ -286,13 +286,13 @@
{ castsource => 'regdatabase', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regdatabase', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regdatabase', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regdatabase', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regdatabase', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regdatabase', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
@@ -302,57 +302,57 @@
{ castsource => 'text', casttarget => 'varchar', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'bpchar', casttarget => 'text', castfunc => 'text(bpchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bpchar', casttarget => 'varchar', castfunc => 'text(bpchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'text', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'varchar', casttarget => 'bpchar', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'char', casttarget => 'text', castfunc => 'text(char)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'char', casttarget => 'bpchar', castfunc => 'bpchar(char)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'char', casttarget => 'varchar', castfunc => 'text(char)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'name', casttarget => 'text', castfunc => 'text(name)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'name', casttarget => 'bpchar', castfunc => 'bpchar(name)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'name', casttarget => 'varchar', castfunc => 'varchar(name)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'text', casttarget => 'char', castfunc => 'char(text)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bpchar', casttarget => 'char', castfunc => 'char(text)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'char', castfunc => 'char(text)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'text', casttarget => 'name', castfunc => 'name(text)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bpchar', casttarget => 'name', castfunc => 'name(bpchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'name', castfunc => 'name(varchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
# Allow explicit coercions between bytea and integer types
{ castsource => 'int2', casttarget => 'bytea', castfunc => 'bytea(int2)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'bytea', castfunc => 'bytea(int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'bytea', castfunc => 'bytea(int8)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bytea', casttarget => 'int2', castfunc => 'int2(bytea)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bytea', casttarget => 'int4', castfunc => 'int4(bytea)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bytea', casttarget => 'int8', castfunc => 'int8(bytea)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Allow explicit coercions between int4 and "char"
{ castsource => 'char', casttarget => 'int4', castfunc => 'int4(char)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'char', castfunc => 'char(int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# pg_node_tree can be coerced to, but not from, text
{ castsource => 'pg_node_tree', casttarget => 'text', castfunc => '0',
@@ -378,73 +378,73 @@
# Datetime category
{ castsource => 'date', casttarget => 'timestamp',
- castfunc => 'timestamp(date)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timestamp(date)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'date', casttarget => 'timestamptz',
- castfunc => 'timestamptz(date)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timestamptz(date)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'time', casttarget => 'interval', castfunc => 'interval(time)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'time', casttarget => 'timetz', castfunc => 'timetz(time)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'date',
- castfunc => 'date(timestamp)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'date(timestamp)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'time',
- castfunc => 'time(timestamp)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'time(timestamp)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'timestamptz',
- castfunc => 'timestamptz(timestamp)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timestamptz(timestamp)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'date',
- castfunc => 'date(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'date(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'time',
- castfunc => 'time(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'time(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'timestamp',
- castfunc => 'timestamp(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'timestamp(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'timetz',
- castfunc => 'timetz(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'timetz(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'interval', casttarget => 'time', castfunc => 'time(interval)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timetz', casttarget => 'time', castfunc => 'time(timetz)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
# Geometric category
{ castsource => 'point', casttarget => 'box', castfunc => 'box(point)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'lseg', casttarget => 'point', castfunc => 'point(lseg)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'path', casttarget => 'polygon', castfunc => 'polygon(path)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'box', casttarget => 'point', castfunc => 'point(box)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'box', casttarget => 'lseg', castfunc => 'lseg(box)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'box', casttarget => 'polygon', castfunc => 'polygon(box)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'box', casttarget => 'circle', castfunc => 'circle(box)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'polygon', casttarget => 'point', castfunc => 'point(polygon)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'polygon', casttarget => 'path', castfunc => 'path',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'polygon', casttarget => 'box', castfunc => 'box(polygon)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'polygon', casttarget => 'circle',
- castfunc => 'circle(polygon)', castcontext => 'e', castmethod => 'f' },
+ castfunc => 'circle(polygon)', castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'circle', casttarget => 'point', castfunc => 'point(circle)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'circle', casttarget => 'box', castfunc => 'box(circle)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'circle', casttarget => 'polygon',
- castfunc => 'polygon(circle)', castcontext => 'e', castmethod => 'f' },
+ castfunc => 'polygon(circle)', castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# MAC address category
{ castsource => 'macaddr', casttarget => 'macaddr8', castfunc => 'macaddr8',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'macaddr8', casttarget => 'macaddr', castfunc => 'macaddr',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
# INET category
{ castsource => 'cidr', casttarget => 'inet', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'inet', casttarget => 'cidr', castfunc => 'cidr',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
# BitString category
{ castsource => 'bit', casttarget => 'varbit', castfunc => '0',
@@ -454,13 +454,13 @@
# Cross-category casts between bit and int4, int8
{ castsource => 'int8', casttarget => 'bit', castfunc => 'bit(int8,int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'bit', castfunc => 'bit(int4,int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bit', casttarget => 'int8', castfunc => 'int8(bit)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bit', casttarget => 'int4', castfunc => 'int4(bit)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Cross-category casts to and from TEXT
# We need entries here only for a few specialized cases where the behavior
@@ -471,68 +471,68 @@
# behavior will ensue when the automatic cast is applied instead of the
# pg_cast entry!
{ castsource => 'cidr', casttarget => 'text', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'inet', casttarget => 'text', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'text', castfunc => 'text(bool)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'xml', casttarget => 'text', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'text', casttarget => 'xml', castfunc => 'xml',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Cross-category casts to and from VARCHAR
# We support all the same casts as for TEXT.
{ castsource => 'cidr', casttarget => 'varchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'inet', casttarget => 'varchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'varchar', castfunc => 'text(bool)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'xml', casttarget => 'varchar', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'varchar', casttarget => 'xml', castfunc => 'xml',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Cross-category casts to and from BPCHAR
# We support all the same casts as for TEXT.
{ castsource => 'cidr', casttarget => 'bpchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'inet', casttarget => 'bpchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'bpchar', castfunc => 'text(bool)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'xml', casttarget => 'bpchar', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'bpchar', casttarget => 'xml', castfunc => 'xml',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Length-coercion functions
{ castsource => 'bpchar', casttarget => 'bpchar',
castfunc => 'bpchar(bpchar,int4,bool)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'varchar',
castfunc => 'varchar(varchar,int4,bool)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'time', casttarget => 'time', castfunc => 'time(time,int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'timestamp',
castfunc => 'timestamp(timestamp,int4)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'timestamptz',
castfunc => 'timestamptz(timestamptz,int4)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'interval', casttarget => 'interval',
castfunc => 'interval(interval,int4)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timetz', casttarget => 'timetz',
- castfunc => 'timetz(timetz,int4)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timetz(timetz,int4)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bit', casttarget => 'bit', castfunc => 'bit(bit,int4,bool)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varbit', casttarget => 'varbit', castfunc => 'varbit',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'numeric',
- castfunc => 'numeric(numeric,int4)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'numeric(numeric,int4)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
# json to/from jsonb
{ castsource => 'json', casttarget => 'jsonb', castfunc => '0',
@@ -542,36 +542,36 @@
# jsonb to numeric and bool types
{ castsource => 'jsonb', casttarget => 'bool', castfunc => 'bool(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'numeric', castfunc => 'numeric(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'int2', castfunc => 'int2(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'int4', castfunc => 'int4(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'int8', castfunc => 'int8(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'float4', castfunc => 'float4(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'float8', castfunc => 'float8(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# range to multirange
{ castsource => 'int4range', casttarget => 'int4multirange',
castfunc => 'int4multirange(int4range)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8range', casttarget => 'int8multirange',
castfunc => 'int8multirange(int8range)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numrange', casttarget => 'nummultirange',
castfunc => 'nummultirange(numrange)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'daterange', casttarget => 'datemultirange',
castfunc => 'datemultirange(daterange)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'tsrange', casttarget => 'tsmultirange',
- castfunc => 'tsmultirange(tsrange)', castcontext => 'e', castmethod => 'f' },
+ castfunc => 'tsmultirange(tsrange)', castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'tstzrange', casttarget => 'tstzmultirange',
castfunc => 'tstzmultirange(tstzrange)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
]
diff --git a/src/include/catalog/pg_cast.h b/src/include/catalog/pg_cast.h
index 6a0ca337153..218d81d535a 100644
--- a/src/include/catalog/pg_cast.h
+++ b/src/include/catalog/pg_cast.h
@@ -47,6 +47,10 @@ CATALOG(pg_cast,2605,CastRelationId)
/* cast method */
char castmethod;
+
+ /* cast function error safe */
+ bool casterrorsafe BKI_DEFAULT(f);
+
} FormData_pg_cast;
/* ----------------
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 75366203706..34a884e3196 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_SAFETYPE_CAST */
+ 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);
+extern 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/nodes/execnodes.h b/src/include/nodes/execnodes.h
index a36653c37f9..015cd9f47e7 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1053,6 +1053,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 dc09d1a3f03..83549151bfb 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -400,6 +400,12 @@ typedef struct TypeCast
ParseLoc location; /* token location, or -1 if unknown */
} TypeCast;
+typedef struct SafeTypeCast
+{
+ NodeTag type;
+ Node *cast;
+ Node *def_expr; /* default expr */
+} SafeTypeCast;
/*
* CollateClause - a COLLATE expression
*/
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 1b4436f2ff6..70b72ed2f40 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -769,6 +769,39 @@ 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
+{
+ Expr xpr;
+
+ /* transformed expression being casted */
+ Node *source_expr;
+
+ /*
+ * The transformed cast expression; It will NULL if can not cast source type
+ * to target type
+ */
+ Node *cast_expr pg_node_attr(query_jumble_ignore);
+
+ /* Fall back to the default expression if cast evaluation fails */
+ Node *default_expr;
+
+ /* cast result data type */
+ Oid resulttype;
+
+ /* cast result data type typmod (usually -1) */
+ int32 resulttypmod;
+
+ /* cast result data type collation */
+ Oid resultcollid;
+
+} SafeTypeCastExpr;
+
/*
* FuncExpr - expression node for a function call
*
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index f7d07c84542..9f5b32e0360 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -67,6 +67,8 @@ typedef enum ParseExprKind
EXPR_KIND_VALUES_SINGLE, /* single-row VALUES (in INSERT only) */
EXPR_KIND_CHECK_CONSTRAINT, /* CHECK constraint for a table */
EXPR_KIND_DOMAIN_CHECK, /* CHECK constraint for a domain */
+ EXPR_KIND_CAST_DEFAULT, /* default expression in
+ CAST DEFAULT ON CONVERSION ERROR */
EXPR_KIND_COLUMN_DEFAULT, /* default value for a table column */
EXPR_KIND_FUNCTION_DEFAULT, /* default parameter value for function */
EXPR_KIND_INDEX_EXPRESSION, /* index expression */
diff --git a/src/test/regress/expected/cast.out b/src/test/regress/expected/cast.out
new file mode 100644
index 00000000000..6614ba3370c
--- /dev/null
+++ b/src/test/regress/expected/cast.out
@@ -0,0 +1,681 @@
+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 DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(1::numeric AS money DEFAULT NULL ON CONVERSION E...
+ ^
+HINT: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(1111 AS "char" DEFAULT NULL ON CONVERSION ERROR);
+ char
+------
+
+(1 row)
+
+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 coerce CAST DEFAULT expression to type date
+LINE 1: SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERR...
+ ^
+-- test array coerce
+SELECT CAST(array[11.12] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+ {11}
+(1 row)
+
+SELECT CAST(array[11.12] AS date[] DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+SELECT CAST(array[11111111111111111] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+SELECT CAST(array[['abc'],[456]] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+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: set-returning functions are not allowed in CAST DEFAULT expressions
+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: aggregate functions are not allowed in CAST DEFAULT expressions
+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: window functions are not allowed in CAST DEFAULT expressions
+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 DOMAIN d_varchar as varchar(3) NOT NULL;
+CREATE TYPE comp_domain_with_typmod AS (a d_char3_not_null, b int);
+CREATE TYPE comp2 AS (a d_varchar);
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION ERROR); --error
+ERROR: value too long for type character varying(3)
+LINE 1: SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION...
+ ^
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(123)' ON CONVERSION ERROR); --ok
+ comp2
+-------
+ (123)
+(1 row)
+
+SELECT CAST(11 AS d_int42 DEFAULT 41 ON CONVERSION ERROR); --error
+ERROR: value for domain d_int42 violates check constraint "d_int42_check"
+SELECT CAST(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)'...
+ ^
+-----safe cast with geometry data type
+SELECT CAST('(1,2)'::point AS box DEFAULT NULL ON CONVERSION ERROR);
+ box
+-------------
+ (1,2),(1,2)
+(1 row)
+
+SELECT CAST('[(NaN,1),(NaN,infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+----------------
+ (NaN,Infinity)
+(1 row)
+
+SELECT CAST('[(1e+300,Infinity),(1e+300,Infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+-------------------
+ (1e+300,Infinity)
+(1 row)
+
+SELECT CAST('[(1,2),(3,4)]'::path as polygon DEFAULT NULL ON CONVERSION ERROR);
+ polygon
+---------
+
+(1 row)
+
+SELECT CAST('(NaN,1.0,NaN,infinity)'::box AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+----------------
+ (NaN,Infinity)
+(1 row)
+
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS lseg DEFAULT NULL ON CONVERSION ERROR);
+ lseg
+---------------
+ [(2,2),(0,0)]
+(1 row)
+
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS polygon DEFAULT NULL ON CONVERSION ERROR);
+ polygon
+---------------------------
+ ((0,0),(0,2),(2,2),(2,0))
+(1 row)
+
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS path DEFAULT NULL ON CONVERSION ERROR);
+ path
+------
+
+(1 row)
+
+SELECT CAST('(2.0,infinity,NaN,infinity)'::box AS circle DEFAULT NULL ON CONVERSION ERROR);
+ circle
+----------------------
+ <(NaN,Infinity),NaN>
+(1 row)
+
+SELECT CAST('(NaN,0.0),(2.0,4.0),(0.0,infinity)'::polygon AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+----------------
+ (NaN,Infinity)
+(1 row)
+
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS path DEFAULT NULL ON CONVERSION ERROR);
+ path
+---------------------
+ ((2,0),(2,4),(0,0))
+(1 row)
+
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS box DEFAULT NULL ON CONVERSION ERROR);
+ box
+-------------
+ (2,4),(0,0)
+(1 row)
+
+SELECT CAST('(NaN,infinity),(2.0,4.0),(0.0,infinity)'::polygon AS circle DEFAULT NULL ON CONVERSION ERROR);
+ circle
+----------------------
+ <(NaN,Infinity),NaN>
+(1 row)
+
+SELECT CAST('<(5,1),3>'::circle AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+-------
+ (5,1)
+(1 row)
+
+SELECT CAST('<(3,5),0>'::circle as box DEFAULT NULL ON CONVERSION ERROR);
+ box
+-------------
+ (3,5),(3,5)
+(1 row)
+
+SELECT CAST('<(3,5),0>'::circle as polygon DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot convert circle with radius zero to polygon
+CONTEXT: SQL function "polygon" statement 1
+-----safe cast from/to money type is not supported, all the following would result error
+SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type bigint to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION E...
+ ^
+HINT: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type integer to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION E...
+ ^
+HINT: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type numeric to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSIO...
+ ^
+HINT: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type money to numeric when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSIO...
+ ^
+HINT: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+/*
+the following are safe cast test with type
+{
+ bytea, bit character, text, character-varying,
+ inet, macaddr8
+ int2, integer, bigint, numeric, float4, float8,
+ date, interval, timestamptz, timestamp
+ jsonb
+}
+*/
+-----safe cast from bytea type to other type
+SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT NULL ON CONVERSION ERROR);
+ int8
+------
+
+(1 row)
+
+SELECT CAST('\x123456789A'::bytea AS int4 DEFAULT NULL ON CONVERSION ERROR);
+ int4
+------
+
+(1 row)
+
+SELECT CAST('\x123456'::bytea AS int2 DEFAULT NULL ON CONVERSION ERROR);
+ int2
+------
+
+(1 row)
+
+SELECT CAST('111111111100001'::bit(100) AS INT DEFAULT NULL ON CONVERSION ERROR);
+ int4
+------
+
+(1 row)
+
+SELECT CAST ('111111111100001'::bit(100) AS INT8 DEFAULT NULL ON CONVERSION ERROR);
+ int8
+------
+
+(1 row)
+
+select CAST('a.b.c.d'::text as regclass default NULL on conversion error);
+ regclass
+----------
+
+(1 row)
+
+CREATE TABLE test_safecast(col0 text);
+INSERT INTO test_safecast(col0) VALUES ('<value>one</value');
+SELECT col0 as text,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) as to_regclass,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) IS NULL as expect_true,
+ CAST(col0 AS "char" DEFAULT NULL ON CONVERSION ERROR) as to_char,
+ CAST(col0 AS name DEFAULT NULL ON CONVERSION ERROR) as to_name,
+ CAST(col0 AS xml DEFAULT NULL ON CONVERSION ERROR) as to_xml
+FROM test_safecast;
+ text | to_regclass | expect_true | to_char | to_name | to_xml
+-------------------+-------------+-------------+---------+-------------------+--------
+ <value>one</value | | t | < | <value>one</value |
+(1 row)
+
+-----safe cast from other type to inet/cidr
+SELECT CAST('192.168.1.x' as inet DEFAULT NULL ON CONVERSION ERROR);
+ inet
+------
+
+(1 row)
+
+SELECT CAST('192.168.1.2/30' as cidr DEFAULT NULL ON CONVERSION ERROR);
+ cidr
+------
+
+(1 row)
+
+SELECT CAST('22:00:5c:08:55:08:01:02'::macaddr8 as macaddr DEFAULT NULL ON CONVERSION ERROR);
+ macaddr
+---------
+
+(1 row)
+
+-----safe cast from range type to other type
+SELECT CAST('[1,2]'::int4range AS int4multirange DEFAULT NULL ON CONVERSION ERROR);
+ int4multirange
+----------------
+ {[1,3)}
+(1 row)
+
+SELECT CAST('[1,2]'::int8range AS int8multirange DEFAULT NULL ON CONVERSION ERROR);
+ int8multirange
+----------------
+ {[1,3)}
+(1 row)
+
+SELECT CAST('[1,2]'::numrange AS nummultirange DEFAULT NULL ON CONVERSION ERROR);
+ nummultirange
+---------------
+ {[1,2]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::daterange AS datemultirange DEFAULT NULL ON CONVERSION ERROR);
+ datemultirange
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::tsrange AS tsmultirange DEFAULT NULL ON CONVERSION ERROR);
+ tsmultirange
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::tstzrange AS tstzmultirange DEFAULT NULL ON CONVERSION ERROR);
+ tstzmultirange
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+--test cast numeric value with fraction to another numeric value
+CREATE TABLE safecast(col1 float4, col2 float8, col3 numeric, col4 numeric[],
+ col5 int2 default 32767,
+ col6 int4 default 32768,
+ col7 int8 default 4294967296);
+INSERT INTO safecast VALUES('11.1234', '11.1234', '9223372036854775808', '{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 col5 as int2,
+ CAST(col5 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col5 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col5 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col5 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col5 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col5 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col5 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col5 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM safecast;
+ int2 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+(4 rows)
+
+SELECT col6 as int4,
+ CAST(col6 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col6 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col6 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col6 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col6 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col6 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col6 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col6 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM safecast;
+ int4 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+(4 rows)
+
+SELECT col7 as int8,
+ CAST(col7 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col7 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col7 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col7 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col7 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col7 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col7 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col7 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM safecast;
+ int8 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+------------+---------+---------+--------+------------+-------------+------------+------------+------------------
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+(4 rows)
+
+SELECT col3 as numeric,
+ CAST(col3 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col3 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col3 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col3 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col3 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col3 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col3 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col3 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM safecast;
+ numeric | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+---------------------+---------+---------+--------+---------+-------------+----------------------+---------------------+------------------
+ 9223372036854775808 | | | | | 9.22337e+18 | 9.22337203685478e+18 | 9223372036854775808 |
+ Infinity | | | | | Infinity | Infinity | Infinity |
+ -Infinity | | | | | -Infinity | -Infinity | -Infinity |
+ NaN | | | | | NaN | NaN | NaN | NaN
+(4 rows)
+
+SELECT col4 as num_arr,
+ CAST(col4 AS int2[] DEFAULT NULL ON CONVERSION ERROR) as int2arr,
+ CAST(col4 AS int4[] DEFAULT NULL ON CONVERSION ERROR) as int4arr,
+ CAST(col4 as int8[] DEFAULT NULL ON CONVERSION ERROR) as int8arr,
+ CAST(col4 as float4[] DEFAULT NULL ON CONVERSION ERROR) as f4arr,
+ CAST(col4 as float8[] DEFAULT NULL ON CONVERSION ERROR) as f8arr,
+ CAST(col4 as numeric(10,1)[] DEFAULT NULL ON CONVERSION ERROR) as numarr
+FROM 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)
+
+SELECT col1 as float4,
+ CAST(col1 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col1 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col1 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col1 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col1 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col1 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col1 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col1 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM safecast;
+ float4 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-----------+---------+---------+--------+---------+-----------+------------------+------------+------------------
+ 11.1234 | 11 | 11 | | 11 | 11.1234 | 11.1233997344971 | 11.1234 | 11.1
+ Infinity | | | | | Infinity | Infinity | Infinity |
+ -Infinity | | | | | -Infinity | -Infinity | -Infinity |
+ NaN | | | | | NaN | NaN | NaN | NaN
+(4 rows)
+
+SELECT col2 as float8,
+ CAST(col2 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col2 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col2 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col2 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col2 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col2 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col2 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col2 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM safecast;
+ float8 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-----------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 11.1234 | 11 | 11 | | 11 | 11.1234 | 11.1234 | 11.1234 | 11.1
+ Infinity | | | | | Infinity | Infinity | Infinity |
+ -Infinity | | | | | -Infinity | -Infinity | -Infinity |
+ NaN | | | | | NaN | NaN | NaN | NaN
+(4 rows)
+
+--test date/timestamp/interval realted cast
+CREATE TABLE safecast1(col0 date, col1 timestamp, col2 timestamptz, col3 interval, col4 time, col5 timetz);
+insert into safecast1 VALUES
+('-infinity', '-infinity', '-infinity', '-infinity',
+ '2003-03-07 15:36:39 America/New_York', '2003-07-07 15:36:39 America/New_York'),
+('-infinity', 'infinity', 'infinity', 'infinity',
+ '11:59:59.99 PM', '11:59:59.99 PM PDT');
+SELECT col0 as date,
+ CAST(col0 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col0 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col0 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col0 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col0 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM safecast1;
+ date | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale
+-----------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity | -infinity | | | -infinity
+ -infinity | -infinity | -infinity | | | -infinity
+(2 rows)
+
+SELECT col1 as timestamp,
+ CAST(col1 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col1 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col1 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col1 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col1 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM safecast1;
+ timestamp | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale
+-----------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity | -infinity | | | -infinity
+ infinity | infinity | infinity | | | infinity
+(2 rows)
+
+SELECT col2 as timestamptz,
+ CAST(col2 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col2 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col2 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col2 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col2 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM safecast1;
+ timestamptz | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale
+-------------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity | -infinity | | | -infinity
+ infinity | infinity | infinity | | | infinity
+(2 rows)
+
+SELECT col3 as interval,
+ CAST(col3 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col3 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col3 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col3 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col3 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale,
+ CAST(col3 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM safecast1;
+ interval | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale | to_interval_scale
+-----------+----------------+---------+----------+-----------+--------------------+-------------------
+ -infinity | | | | | | -infinity
+ infinity | | | | | | infinity
+(2 rows)
+
+SELECT col4 as time,
+ CAST(col4 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col4 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM safecast1;
+ time | to_times | to_timetz | to_interval | to_interval_scale
+-------------+-------------+----------------+-------------------------------+-------------------------------
+ 15:36:39 | 15:36:39 | 15:36:39-07 | @ 15 hours 36 mins 39 secs | @ 15 hours 36 mins 39 secs
+ 23:59:59.99 | 23:59:59.99 | 23:59:59.99-07 | @ 23 hours 59 mins 59.99 secs | @ 23 hours 59 mins 59.99 secs
+(2 rows)
+
+SELECT col5 as timetz,
+ CAST(col5 AS time DEFAULT NULL ON CONVERSION ERROR) as to_time,
+ CAST(col5 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_time_scale,
+ CAST(col5 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col5 AS timetz(6) DEFAULT NULL ON CONVERSION ERROR) as to_timetz_scale,
+ CAST(col5 AS interval DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col5 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM safecast1;
+ timetz | to_time | to_time_scale | to_timetz | to_timetz_scale | to_interval | to_interval_scale
+----------------+-------------+---------------+----------------+-----------------+-------------+-------------------
+ 15:36:39-04 | 15:36:39 | 15:36:39 | 15:36:39-04 | 15:36:39-04 | |
+ 23:59:59.99-07 | 23:59:59.99 | 23:59:59.99 | 23:59:59.99-07 | 23:59:59.99-07 | |
+(2 rows)
+
+CREATE TEMP TABLE test_safecast1(col0 jsonb);
+INSERT INTO test_safecast1(col0) VALUES ('"test"');
+SELECT col0 as jsonb,
+ CAST(col0 AS integer DEFAULT NULL ON CONVERSION ERROR) as to_integer,
+ CAST(col0 AS numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col0 AS bigint DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col0 AS float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col0 AS float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col0 AS boolean DEFAULT NULL ON CONVERSION ERROR) as to_bool,
+ CAST(col0 AS smallint DEFAULT NULL ON CONVERSION ERROR) as to_smallint
+FROM test_safecast1;
+ jsonb | to_integer | to_numeric | to_int8 | to_float4 | to_float8 | to_bool | to_smallint
+--------+------------+------------+---------+-----------+-----------+---------+-------------
+ "test" | | | | | | |
+(1 row)
+
+-- test deparse
+CREATE DOMAIN d_int_arr as int[];
+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'::text], ARRAY['a'::text]] 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 VIEW safecastview1 AS
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS d_int_arr DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1,
+ CAST(ARRAY[['1', '2'], ['three', 'a']] AS d_int_arr DEFAULT '{21,22}' ON CONVERSION ERROR) as cast2;
+\sv safecastview1
+CREATE OR REPLACE VIEW public.safecastview1 AS
+ SELECT CAST(ARRAY[ARRAY[1], ARRAY['three'::text], ARRAY['a'::text]] AS d_int_arr DEFAULT '{1,2}'::integer[]::d_int_arr ON CONVERSION ERROR) AS cast1,
+ CAST(ARRAY[ARRAY[1, 2], ARRAY['three'::text, 'a'::text]] AS d_int_arr DEFAULT '{21,22}'::integer[]::d_int_arr ON CONVERSION ERROR) AS cast2
+SELECT * FROM safecastview1;
+ cast1 | cast2
+-------+---------
+ {1,2} | {21,22}
+(1 row)
+
+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/expected/create_cast.out b/src/test/regress/expected/create_cast.out
index 0e69644bca2..38eab453344 100644
--- a/src/test/regress/expected/create_cast.out
+++ b/src/test/regress/expected/create_cast.out
@@ -88,6 +88,11 @@ SELECT 1234::int4::casttesttype; -- Should work now
bar1234
(1 row)
+SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- error
+ERROR: cannot cast type integer to casttesttype when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVE...
+ ^
+HINT: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
-- check dependencies generated for that
SELECT pg_describe_object(classid, objid, objsubid) as obj,
pg_describe_object(refclassid, refobjid, refobjsubid) as objref,
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index a357e1d0c0e..81ea244859f 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -943,8 +943,8 @@ SELECT *
FROM pg_cast c
WHERE castsource = 0 OR casttarget = 0 OR castcontext NOT IN ('e', 'a', 'i')
OR castmethod NOT IN ('f', 'b' ,'i');
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Check that castfunc is nonzero only for cast methods that need a function,
@@ -953,8 +953,8 @@ SELECT *
FROM pg_cast c
WHERE (castmethod = 'f' AND castfunc = 0)
OR (castmethod IN ('b', 'i') AND castfunc <> 0);
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Look for casts to/from the same type that aren't length coercion functions.
@@ -963,15 +963,15 @@ WHERE (castmethod = 'f' AND castfunc = 0)
SELECT *
FROM pg_cast c
WHERE castsource = casttarget AND castfunc = 0;
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
SELECT c.*
FROM pg_cast c, pg_proc p
WHERE c.castfunc = p.oid AND p.pronargs < 2 AND castsource = casttarget;
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Look for cast functions that don't have the right signature. The
@@ -989,8 +989,8 @@ WHERE c.castfunc = p.oid AND
OR (c.castsource = 'character'::regtype AND
p.proargtypes[0] = 'text'::regtype))
OR NOT binary_coercible(p.prorettype, c.casttarget));
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
SELECT c.*
@@ -998,8 +998,8 @@ FROM pg_cast c, pg_proc p
WHERE c.castfunc = p.oid AND
((p.pronargs > 1 AND p.proargtypes[1] != 'int4'::regtype) OR
(p.pronargs > 2 AND p.proargtypes[2] != 'bool'::regtype));
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Look for binary compatible casts that do not have the reverse
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index f9450cdc477..573a6699a63 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 00000000000..eb68ec21859
--- /dev/null
+++ b/src/test/regress/sql/cast.sql
@@ -0,0 +1,301 @@
+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);
+SELECT CAST(1111 AS "char" 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(array[11.12] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(array[11.12] AS date[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(array[11111111111111111] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(array[['abc'],[456]] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+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 DOMAIN d_varchar as varchar(3) NOT NULL;
+CREATE TYPE comp_domain_with_typmod AS (a d_char3_not_null, b int);
+CREATE TYPE comp2 AS (a d_varchar);
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION ERROR); --error
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(123)' ON CONVERSION ERROR); --ok
+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
+
+-----safe cast with geometry data type
+SELECT CAST('(1,2)'::point AS box DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[(NaN,1),(NaN,infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[(1e+300,Infinity),(1e+300,Infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[(1,2),(3,4)]'::path as polygon DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NaN,1.0,NaN,infinity)'::box AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS lseg DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS polygon DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS path DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,infinity,NaN,infinity)'::box AS circle DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NaN,0.0),(2.0,4.0),(0.0,infinity)'::polygon AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS path DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS box DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NaN,infinity),(2.0,4.0),(0.0,infinity)'::polygon AS circle DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('<(5,1),3>'::circle AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('<(3,5),0>'::circle as box DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('<(3,5),0>'::circle as polygon DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast from/to money type is not supported, all the following would result error
+SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSION ERROR);
+
+/*
+the following are safe cast test with type
+{
+ bytea, bit character, text, character-varying,
+ inet, macaddr8
+ int2, integer, bigint, numeric, float4, float8,
+ date, interval, timestamptz, timestamp
+ jsonb
+}
+*/
+-----safe cast from bytea type to other type
+SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('\x123456789A'::bytea AS int4 DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('\x123456'::bytea AS int2 DEFAULT NULL ON CONVERSION ERROR);
+
+SELECT CAST('111111111100001'::bit(100) AS INT DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST ('111111111100001'::bit(100) AS INT8 DEFAULT NULL ON CONVERSION ERROR);
+
+select CAST('a.b.c.d'::text as regclass default NULL on conversion error);
+
+CREATE TABLE test_safecast(col0 text);
+INSERT INTO test_safecast(col0) VALUES ('<value>one</value');
+
+SELECT col0 as text,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) as to_regclass,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) IS NULL as expect_true,
+ CAST(col0 AS "char" DEFAULT NULL ON CONVERSION ERROR) as to_char,
+ CAST(col0 AS name DEFAULT NULL ON CONVERSION ERROR) as to_name,
+ CAST(col0 AS xml DEFAULT NULL ON CONVERSION ERROR) as to_xml
+FROM test_safecast;
+
+-----safe cast from other type to inet/cidr
+SELECT CAST('192.168.1.x' as inet DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('192.168.1.2/30' as cidr DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('22:00:5c:08:55:08:01:02'::macaddr8 as macaddr DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast from range type to other type
+SELECT CAST('[1,2]'::int4range AS int4multirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[1,2]'::int8range AS int8multirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[1,2]'::numrange AS nummultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::daterange AS datemultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::tsrange AS tsmultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::tstzrange AS tstzmultirange DEFAULT NULL ON CONVERSION ERROR);
+
+--test cast numeric value with fraction to another numeric value
+CREATE TABLE safecast(col1 float4, col2 float8, col3 numeric, col4 numeric[],
+ col5 int2 default 32767,
+ col6 int4 default 32768,
+ col7 int8 default 4294967296);
+INSERT INTO safecast VALUES('11.1234', '11.1234', '9223372036854775808', '{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 col5 as int2,
+ CAST(col5 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col5 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col5 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col5 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col5 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col5 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col5 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col5 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM safecast;
+
+SELECT col6 as int4,
+ CAST(col6 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col6 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col6 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col6 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col6 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col6 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col6 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col6 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM safecast;
+
+SELECT col7 as int8,
+ CAST(col7 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col7 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col7 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col7 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col7 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col7 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col7 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col7 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM 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 oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col3 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col3 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col3 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col3 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col3 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM 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;
+
+SELECT col1 as float4,
+ CAST(col1 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col1 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col1 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col1 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col1 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col1 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col1 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col1 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM 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 oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col2 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col2 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col2 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col2 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col2 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM safecast;
+
+--test date/timestamp/interval realted cast
+CREATE TABLE safecast1(col0 date, col1 timestamp, col2 timestamptz, col3 interval, col4 time, col5 timetz);
+insert into safecast1 VALUES
+('-infinity', '-infinity', '-infinity', '-infinity',
+ '2003-03-07 15:36:39 America/New_York', '2003-07-07 15:36:39 America/New_York'),
+('-infinity', 'infinity', 'infinity', 'infinity',
+ '11:59:59.99 PM', '11:59:59.99 PM PDT');
+
+SELECT col0 as date,
+ CAST(col0 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col0 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col0 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col0 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col0 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM safecast1;
+
+SELECT col1 as timestamp,
+ CAST(col1 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col1 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col1 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col1 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col1 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM safecast1;
+
+SELECT col2 as timestamptz,
+ CAST(col2 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col2 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col2 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col2 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col2 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM safecast1;
+
+SELECT col3 as interval,
+ CAST(col3 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col3 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col3 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col3 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col3 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale,
+ CAST(col3 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM safecast1;
+
+SELECT col4 as time,
+ CAST(col4 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col4 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM safecast1;
+
+SELECT col5 as timetz,
+ CAST(col5 AS time DEFAULT NULL ON CONVERSION ERROR) as to_time,
+ CAST(col5 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_time_scale,
+ CAST(col5 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col5 AS timetz(6) DEFAULT NULL ON CONVERSION ERROR) as to_timetz_scale,
+ CAST(col5 AS interval DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col5 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM safecast1;
+
+CREATE TEMP TABLE test_safecast1(col0 jsonb);
+INSERT INTO test_safecast1(col0) VALUES ('"test"');
+SELECT col0 as jsonb,
+ CAST(col0 AS integer DEFAULT NULL ON CONVERSION ERROR) as to_integer,
+ CAST(col0 AS numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col0 AS bigint DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col0 AS float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col0 AS float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col0 AS boolean DEFAULT NULL ON CONVERSION ERROR) as to_bool,
+ CAST(col0 AS smallint DEFAULT NULL ON CONVERSION ERROR) as to_smallint
+FROM test_safecast1;
+
+-- test deparse
+CREATE DOMAIN d_int_arr as int[];
+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 VIEW safecastview1 AS
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS d_int_arr DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1,
+ CAST(ARRAY[['1', '2'], ['three', 'a']] AS d_int_arr DEFAULT '{21,22}' ON CONVERSION ERROR) as cast2;
+\sv safecastview1
+SELECT * FROM safecastview1;
+
+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/test/regress/sql/create_cast.sql b/src/test/regress/sql/create_cast.sql
index 32187853cc7..0a15a795d87 100644
--- a/src/test/regress/sql/create_cast.sql
+++ b/src/test/regress/sql/create_cast.sql
@@ -62,6 +62,7 @@ $$ SELECT ('bar'::text || $1::text); $$;
CREATE CAST (int4 AS casttesttype) WITH FUNCTION bar_int4_text(int4) AS IMPLICIT;
SELECT 1234::int4::casttesttype; -- Should work now
+SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- error
-- check dependencies generated for that
SELECT pg_describe_object(classid, objid, objsubid) as obj,
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 5290b91e83e..7996854b3a5 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2660,6 +2660,9 @@ STRLEN
SV
SYNCHRONIZATION_BARRIER
SYSTEM_INFO
+SafeTypeCast
+SafeTypeCastExpr
+SafeTypeCastState
SampleScan
SampleScanGetSampleSize_function
SampleScanState
--
2.34.1
v7-0016-error-safe-for-casting-timestamp-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v7-0016-error-safe-for-casting-timestamp-to-other-types-per-pg_cast.patchDownload
From 2f2168688b59e2d3d0568636f32190afe67e969e Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 18:44:35 +0800
Subject: [PATCH v7 16/20] error safe for casting timestamp to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
and pc.castfunc > 0 and (castsource::regtype ='timestamp'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
-----------------------------+-----------------------------+----------+-------------+------------+-----------------------+-------------
timestamp without time zone | date | 2029 | a | f | timestamp_date | date
timestamp without time zone | time without time zone | 1316 | a | f | timestamp_time | time
timestamp without time zone | timestamp with time zone | 2028 | i | f | timestamp_timestamptz | timestamptz
timestamp without time zone | timestamp without time zone | 1961 | i | f | timestamp_scale | timestamp
(4 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 13 +++++++++++--
src/backend/utils/adt/timestamp.c | 17 +++++++++++++++--
2 files changed, 26 insertions(+), 4 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 111bfd8f519..c5562b563e5 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -1373,7 +1373,16 @@ timestamp_date(PG_FUNCTION_ARGS)
Timestamp timestamp = PG_GETARG_TIMESTAMP(0);
DateADT result;
- result = timestamp2date_opt_overflow(timestamp, NULL);
+ if (likely(!fcinfo->context))
+ result = timestamptz2date_opt_overflow(timestamp, NULL);
+ else
+ {
+ int overflow;
+ result = timestamp2date_opt_overflow(timestamp, &overflow);
+
+ if (overflow != 0)
+ PG_RETURN_NULL();
+ }
PG_RETURN_DATEADT(result);
}
@@ -2089,7 +2098,7 @@ timestamp_time(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 116e3ef28fc..7a57ac3eaf6 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -352,7 +352,8 @@ timestamp_scale(PG_FUNCTION_ARGS)
result = timestamp;
- AdjustTimestampForTypmod(&result, typmod, NULL);
+ if (!AdjustTimestampForTypmod(&result, typmod, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_TIMESTAMP(result);
}
@@ -6430,7 +6431,19 @@ timestamp_timestamptz(PG_FUNCTION_ARGS)
{
Timestamp timestamp = PG_GETARG_TIMESTAMP(0);
- PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(timestamp));
+ if (likely(!fcinfo->context))
+ PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(timestamp));
+ else
+ {
+ TimestampTz result;
+ int overflow;
+
+ result = timestamp2timestamptz_opt_overflow(timestamp, &overflow);
+ if (overflow != 0)
+ PG_RETURN_NULL();
+ else
+ PG_RETURN_TIMESTAMPTZ(result);
+ }
}
/*
--
2.34.1
v7-0004-error-safe-for-casting-text-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v7-0004-error-safe-for-casting-text-to-other-types-per-pg_cast.patchDownload
From a402051d73e3b3ed86f2972639bd3910ee7b7cff Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 15:49:46 +0800
Subject: [PATCH v7 04/20] error safe for casting text to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'text'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------+----------+-------------+------------+---------------+----------
text | regclass | 1079 | i | f | text_regclass | regclass
text | "char" | 944 | a | f | text_char | char
text | name | 407 | i | f | text_name | name
text | xml | 2896 | e | f | texttoxml | xml
(4 rows)
already error safe: text_name, text_char.
texttoxml is refactored in character type error safe patch.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/catalog/namespace.c | 167 ++++++++++++++++++++++++++++++++
src/backend/utils/adt/regproc.c | 33 ++++++-
src/backend/utils/adt/varlena.c | 42 ++++++++
src/include/catalog/namespace.h | 6 ++
src/include/utils/varlena.h | 1 +
5 files changed, 246 insertions(+), 3 deletions(-)
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index d23474da4fb..a04d179c7fb 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -640,6 +640,137 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
return relId;
}
+/* safe version of RangeVarGetRelidExtended */
+Oid
+RangeVarGetRelidExtendedSafe(const RangeVar *relation, LOCKMODE lockmode,
+ uint32 flags, RangeVarGetRelidCallback callback, void *callback_arg,
+ Node *escontext)
+{
+ uint64 inval_count;
+ Oid relId;
+ Oid oldRelId = InvalidOid;
+ bool retry = false;
+ bool missing_ok = (flags & RVR_MISSING_OK) != 0;
+
+ /* verify that flags do no conflict */
+ Assert(!((flags & RVR_NOWAIT) && (flags & RVR_SKIP_LOCKED)));
+
+ /*
+ * We check the catalog name and then ignore it.
+ */
+ if (relation->catalogname)
+ {
+ if (strcmp(relation->catalogname, get_database_name(MyDatabaseId)) != 0)
+ ereturn(escontext, InvalidOid,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cross-database references are not implemented: \"%s.%s.%s\"",
+ relation->catalogname, relation->schemaname,
+ relation->relname));
+ }
+
+ for (;;)
+ {
+ inval_count = SharedInvalidMessageCounter;
+
+ if (relation->relpersistence == RELPERSISTENCE_TEMP)
+ {
+ if (!OidIsValid(myTempNamespace))
+ relId = InvalidOid;
+ else
+ {
+ if (relation->schemaname)
+ {
+ Oid namespaceId;
+
+ namespaceId = LookupExplicitNamespace(relation->schemaname, missing_ok);
+
+ /*
+ * For missing_ok, allow a non-existent schema name to
+ * return InvalidOid.
+ */
+ if (namespaceId != myTempNamespace)
+ ereturn(escontext, InvalidOid,
+ errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("temporary tables cannot specify a schema name"));
+ }
+
+ relId = get_relname_relid(relation->relname, myTempNamespace);
+ }
+ }
+ else if (relation->schemaname)
+ {
+ Oid namespaceId;
+
+ /* use exact schema given */
+ namespaceId = LookupExplicitNamespace(relation->schemaname, missing_ok);
+ if (missing_ok && !OidIsValid(namespaceId))
+ relId = InvalidOid;
+ else
+ relId = get_relname_relid(relation->relname, namespaceId);
+ }
+ else
+ {
+ /* search the namespace path */
+ relId = RelnameGetRelid(relation->relname);
+ }
+
+ if (callback)
+ callback(relation, relId, oldRelId, callback_arg);
+
+ if (lockmode == NoLock)
+ break;
+
+ if (retry)
+ {
+ if (relId == oldRelId)
+ break;
+ if (OidIsValid(oldRelId))
+ UnlockRelationOid(oldRelId, lockmode);
+ }
+
+ if (!OidIsValid(relId))
+ AcceptInvalidationMessages();
+ else if (!(flags & (RVR_NOWAIT | RVR_SKIP_LOCKED)))
+ LockRelationOid(relId, lockmode);
+ else if (!ConditionalLockRelationOid(relId, lockmode))
+ {
+ if (relation->schemaname)
+ ereport(DEBUG1,
+ errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s.%s\"",
+ relation->schemaname, relation->relname));
+ else
+ ereport(DEBUG1,
+ errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s\"",
+ relation->relname));
+
+ return InvalidOid;
+ }
+
+ if (inval_count == SharedInvalidMessageCounter)
+ break;
+
+ retry = true;
+ oldRelId = relId;
+ }
+
+ if (!OidIsValid(relId))
+ {
+ if (relation->schemaname)
+ ereturn(escontext, InvalidOid,
+ errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s.%s\" does not exist",
+ relation->schemaname, relation->relname));
+ else
+ ereturn(escontext, InvalidOid,
+ errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" does not exist",
+ relation->relname));
+ }
+ return relId;
+}
+
/*
* RangeVarGetCreationNamespace
* Given a RangeVar describing a to-be-created relation,
@@ -3650,6 +3781,42 @@ makeRangeVarFromNameList(const List *names)
return rel;
}
+/*
+ * makeRangeVarFromNameListSafe
+ * Utility routine to convert a qualified-name list into RangeVar form.
+ * The result maybe NULL.
+ */
+RangeVar *
+makeRangeVarFromNameListSafe(const List *names, Node *escontext)
+{
+ RangeVar *rel = makeRangeVar(NULL, NULL, -1);
+
+ switch (list_length(names))
+ {
+ case 1:
+ rel->relname = strVal(linitial(names));
+ break;
+ case 2:
+ rel->schemaname = strVal(linitial(names));
+ rel->relname = strVal(lsecond(names));
+ break;
+ case 3:
+ rel->catalogname = strVal(linitial(names));
+ rel->schemaname = strVal(lsecond(names));
+ rel->relname = strVal(lthird(names));
+ break;
+ default:
+ errsave(escontext,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("improper relation name (too many dotted names): %s",
+ NameListToString(names)));
+ rel = NULL;
+ break;
+ }
+
+ return rel;
+}
+
/*
* NameListToString
* Utility routine to convert a qualified-name list into a string.
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index e5c2246f2c9..059d0154335 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -1902,10 +1902,37 @@ text_regclass(PG_FUNCTION_ARGS)
Oid result;
RangeVar *rv;
- rv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
+ if (likely(!fcinfo->context))
+ {
+ rv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
- /* We might not even have permissions on this relation; don't lock it. */
- result = RangeVarGetRelid(rv, NoLock, false);
+ /*
+ * We might not even have permissions on this relation; don't lock it.
+ */
+ result = RangeVarGetRelid(rv, NoLock, false);
+ }
+ else
+ {
+ List *rvnames;
+
+ rvnames = textToQualifiedNameListSafe(relname, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
+
+ rv = makeRangeVarFromNameListSafe(rvnames, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
+
+ result = RangeVarGetRelidExtendedSafe(rv,
+ NoLock,
+ 0,
+ NULL,
+ NULL,
+ fcinfo->context);
+
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
+ }
PG_RETURN_OID(result);
}
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index 2c398cd9e5c..1117939430a 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -2717,6 +2717,48 @@ textToQualifiedNameList(text *textval)
return result;
}
+/* error safe version of textToQualifiedNameList */
+List *
+textToQualifiedNameListSafe(text *textval, Node *escontext)
+{
+ char *rawname;
+ List *result = NIL;
+ List *namelist;
+ ListCell *l;
+
+ /* Convert to C string (handles possible detoasting). */
+ /* Note we rely on being able to modify rawname below. */
+ rawname = text_to_cstring(textval);
+
+ if (!SplitIdentifierString(rawname, '.', &namelist))
+ {
+ errsave(escontext,
+ errcode(ERRCODE_INVALID_NAME),
+ errmsg("invalid name syntax"));
+ return NIL;
+ }
+
+ if (namelist == NIL)
+ {
+ errsave(escontext,
+ errcode(ERRCODE_INVALID_NAME),
+ errmsg("invalid name syntax"));
+ return NIL;
+ }
+
+ foreach(l, namelist)
+ {
+ char *curname = (char *) lfirst(l);
+
+ result = lappend(result, makeString(pstrdup(curname)));
+ }
+
+ pfree(rawname);
+ list_free(namelist);
+
+ return result;
+}
+
/*
* SplitIdentifierString --- parse a string containing identifiers
*
diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h
index f1423f28c32..ab61af55ddc 100644
--- a/src/include/catalog/namespace.h
+++ b/src/include/catalog/namespace.h
@@ -103,6 +103,11 @@ extern Oid RangeVarGetRelidExtended(const RangeVar *relation,
LOCKMODE lockmode, uint32 flags,
RangeVarGetRelidCallback callback,
void *callback_arg);
+extern Oid RangeVarGetRelidExtendedSafe(const RangeVar *relation,
+ LOCKMODE lockmode, uint32 flags,
+ RangeVarGetRelidCallback callback,
+ void *callback_arg,
+ Node *escontext);
extern Oid RangeVarGetCreationNamespace(const RangeVar *newRelation);
extern Oid RangeVarGetAndCheckCreationNamespace(RangeVar *relation,
LOCKMODE lockmode,
@@ -168,6 +173,7 @@ extern Oid LookupCreationNamespace(const char *nspname);
extern void CheckSetNamespace(Oid oldNspOid, Oid nspOid);
extern Oid QualifiedNameGetCreationNamespace(const List *names, char **objname_p);
extern RangeVar *makeRangeVarFromNameList(const List *names);
+extern RangeVar *makeRangeVarFromNameListSafe(const List *names, Node *escontext);
extern char *NameListToString(const List *names);
extern char *NameListToQuotedString(const List *names);
diff --git a/src/include/utils/varlena.h b/src/include/utils/varlena.h
index db9fdf72941..0cf01ae5281 100644
--- a/src/include/utils/varlena.h
+++ b/src/include/utils/varlena.h
@@ -27,6 +27,7 @@ extern int varstr_levenshtein_less_equal(const char *source, int slen,
int ins_c, int del_c, int sub_c,
int max_d, bool trusted);
extern List *textToQualifiedNameList(text *textval);
+extern List *textToQualifiedNameListSafe(text *textval, Node *escontext);
extern bool SplitIdentifierString(char *rawstring, char separator,
List **namelist);
extern bool SplitDirectoriesString(char *rawstring, char separator,
--
2.34.1
v7-0017-error-safe-for-casting-jsonb-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v7-0017-error-safe-for-casting-jsonb-to-other-types-per-pg_cast.patchDownload
From 8f462532bbcc356181f212eeb9f8f1a070b88efe Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 18:58:40 +0800
Subject: [PATCH v7 17/20] error safe for casting jsonb to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'jsonb'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+---------------+---------
jsonb | boolean | 3556 | e | f | jsonb_bool | bool
jsonb | numeric | 3449 | e | f | jsonb_numeric | numeric
jsonb | smallint | 3450 | e | f | jsonb_int2 | int2
jsonb | integer | 3451 | e | f | jsonb_int4 | int4
jsonb | bigint | 3452 | e | f | jsonb_int8 | int8
jsonb | real | 3453 | e | f | jsonb_float4 | float4
jsonb | double precision | 2580 | e | f | jsonb_float8 | float8
(7 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/jsonb.c | 91 +++++++++++++++++++++++++++++------
1 file changed, 75 insertions(+), 16 deletions(-)
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index da94d424d61..1ff00f72f78 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -2005,7 +2005,7 @@ JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res)
* Emit correct, translatable cast error message
*/
static void
-cannotCastJsonbValue(enum jbvType type, const char *sqltype)
+cannotCastJsonbValue(enum jbvType type, const char *sqltype, Node *escontext)
{
static const struct
{
@@ -2026,9 +2026,12 @@ cannotCastJsonbValue(enum jbvType type, const char *sqltype)
for (i = 0; i < lengthof(messages); i++)
if (messages[i].type == type)
- ereport(ERROR,
+ {
+ errsave(escontext,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(messages[i].msg, sqltype)));
+ return;
+ }
/* should be unreachable */
elog(ERROR, "unknown jsonb type: %d", (int) type);
@@ -2041,7 +2044,11 @@ jsonb_bool(PG_FUNCTION_ARGS)
JsonbValue v;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "boolean");
+ {
+ cannotCastJsonbValue(v.type, "boolean", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2050,7 +2057,11 @@ jsonb_bool(PG_FUNCTION_ARGS)
}
if (v.type != jbvBool)
- cannotCastJsonbValue(v.type, "boolean");
+ {
+ cannotCastJsonbValue(v.type, "boolean", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
PG_FREE_IF_COPY(in, 0);
@@ -2065,7 +2076,11 @@ jsonb_numeric(PG_FUNCTION_ARGS)
Numeric retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "numeric");
+ {
+ cannotCastJsonbValue(v.type, "numeric", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2074,7 +2089,11 @@ jsonb_numeric(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "numeric");
+ {
+ cannotCastJsonbValue(v.type, "numeric", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
/*
* v.val.numeric points into jsonb body, so we need to make a copy to
@@ -2095,7 +2114,11 @@ jsonb_int2(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "smallint");
+ {
+ cannotCastJsonbValue(v.type, "smallint", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2104,7 +2127,11 @@ jsonb_int2(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "smallint");
+ {
+ cannotCastJsonbValue(v.type, "smallint", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_int2,
NumericGetDatum(v.val.numeric));
@@ -2122,7 +2149,11 @@ jsonb_int4(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "integer");
+ {
+ cannotCastJsonbValue(v.type, "integer", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2131,7 +2162,11 @@ jsonb_int4(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "integer");
+ {
+ cannotCastJsonbValue(v.type, "integer", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_int4,
NumericGetDatum(v.val.numeric));
@@ -2149,7 +2184,11 @@ jsonb_int8(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "bigint");
+ {
+ cannotCastJsonbValue(v.type, "bigint", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2158,7 +2197,11 @@ jsonb_int8(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "bigint");
+ {
+ cannotCastJsonbValue(v.type, "bigint", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_int8,
NumericGetDatum(v.val.numeric));
@@ -2176,7 +2219,11 @@ jsonb_float4(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "real");
+ {
+ cannotCastJsonbValue(v.type, "real", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2185,7 +2232,11 @@ jsonb_float4(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "real");
+ {
+ cannotCastJsonbValue(v.type, "real", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_float4,
NumericGetDatum(v.val.numeric));
@@ -2203,7 +2254,11 @@ jsonb_float8(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "double precision");
+ {
+ cannotCastJsonbValue(v.type, "double precision", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2212,7 +2267,11 @@ jsonb_float8(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "double precision");
+ {
+ cannotCastJsonbValue(v.type, "double precision", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_float8,
NumericGetDatum(v.val.numeric));
--
2.34.1
v7-0013-error-safe-for-casting-date-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v7-0013-error-safe-for-casting-date-to-other-types-per-pg_cast.patchDownload
From d5c577a6ce3e69417940dbba049e2abde9bd934e Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 18:42:50 +0800
Subject: [PATCH v7 13/20] error safe for casting date to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'date'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+-----------------------------+----------+-------------+------------+------------------+-------------
date | timestamp without time zone | 2024 | i | f | date_timestamp | timestamp
date | timestamp with time zone | 1174 | i | f | date_timestamptz | timestamptz
(2 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 22 ++++++++++++++++++++--
1 file changed, 20 insertions(+), 2 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 344f58b92f7..c7a3cde2d81 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -1350,7 +1350,16 @@ date_timestamp(PG_FUNCTION_ARGS)
DateADT dateVal = PG_GETARG_DATEADT(0);
Timestamp result;
- result = date2timestamp(dateVal);
+ if (likely(!fcinfo->context))
+ result = date2timestamp(dateVal);
+ else
+ {
+ int overflow;
+
+ result = date2timestamp_opt_overflow(dateVal, &overflow);
+ if (overflow != 0)
+ PG_RETURN_NULL();
+ }
PG_RETURN_TIMESTAMP(result);
}
@@ -1435,7 +1444,16 @@ date_timestamptz(PG_FUNCTION_ARGS)
DateADT dateVal = PG_GETARG_DATEADT(0);
TimestampTz result;
- result = date2timestamptz(dateVal);
+ if (likely(!fcinfo->context))
+ result = date2timestamptz(dateVal);
+ else
+ {
+ int overflow;
+ result = date2timestamptz_opt_overflow(dateVal, &overflow);
+
+ if (overflow != 0)
+ PG_RETURN_NULL();
+ }
PG_RETURN_TIMESTAMP(result);
}
--
2.34.1
v7-0014-error-safe-for-casting-interval-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v7-0014-error-safe-for-casting-interval-to-other-types-per-pg_cast.patchDownload
From 14337c6ba6e656d85bcf95aa4c18c078fbbfb740 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 18:43:29 +0800
Subject: [PATCH v7 14/20] error safe for casting interval to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'interval'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------------+----------+-------------+------------+----------------+----------
interval | time without time zone | 1419 | a | f | interval_time | time
interval | interval | 1200 | i | f | interval_scale | interval
(2 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 2 +-
src/backend/utils/adt/timestamp.c | 3 ++-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index c7a3cde2d81..4f0f3d26989 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2180,7 +2180,7 @@ interval_time(PG_FUNCTION_ARGS)
TimeADT result;
if (INTERVAL_NOT_FINITE(span))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("cannot convert infinite interval to time")));
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 156a4830ffd..7b565cc6d66 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1334,7 +1334,8 @@ interval_scale(PG_FUNCTION_ARGS)
result = palloc(sizeof(Interval));
*result = *interval;
- AdjustIntervalForTypmod(result, typmod, NULL);
+ if (!AdjustIntervalForTypmod(result, typmod, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_INTERVAL_P(result);
}
--
2.34.1
v7-0015-error-safe-for-casting-timestamptz-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v7-0015-error-safe-for-casting-timestamptz-to-other-types-per-pg_cast.patchDownload
From e56515b37e3a5601d7bd315ba8816a7d569c7824 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Mon, 6 Oct 2025 12:39:22 +0800
Subject: [PATCH v7 15/20] error safe for casting timestamptz to other types
per pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
and pc.castfunc > 0 and (castsource::regtype ='timestamptz'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
--------------------------+-----------------------------+----------+-------------+------------+-----------------------+-------------
timestamp with time zone | date | 1178 | a | f | timestamptz_date | date
timestamp with time zone | time without time zone | 2019 | a | f | timestamptz_time | time
timestamp with time zone | timestamp without time zone | 2027 | a | f | timestamptz_timestamp | timestamp
timestamp with time zone | time with time zone | 1388 | a | f | timestamptz_timetz | timetz
timestamp with time zone | timestamp with time zone | 1967 | i | f | timestamptz_scale | timestamptz
(5 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 16 +++++++++++++---
src/backend/utils/adt/timestamp.c | 17 +++++++++++++++--
2 files changed, 28 insertions(+), 5 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 4f0f3d26989..111bfd8f519 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -1468,7 +1468,17 @@ timestamptz_date(PG_FUNCTION_ARGS)
TimestampTz timestamp = PG_GETARG_TIMESTAMP(0);
DateADT result;
- result = timestamptz2date_opt_overflow(timestamp, NULL);
+ if (likely(!fcinfo->context))
+ result = timestamptz2date_opt_overflow(timestamp, NULL);
+ else
+ {
+ int overflow;
+ result = timestamptz2date_opt_overflow(timestamp, &overflow);
+
+ if (overflow != 0)
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_DATEADT(result);
}
@@ -2110,7 +2120,7 @@ timestamptz_time(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
@@ -3029,7 +3039,7 @@ timestamptz_timetz(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 7b565cc6d66..116e3ef28fc 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -875,7 +875,8 @@ timestamptz_scale(PG_FUNCTION_ARGS)
result = timestamp;
- AdjustTimestampForTypmod(&result, typmod, NULL);
+ if (!AdjustTimestampForTypmod(&result, typmod, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_TIMESTAMPTZ(result);
}
@@ -6507,7 +6508,19 @@ timestamptz_timestamp(PG_FUNCTION_ARGS)
{
TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(0);
- PG_RETURN_TIMESTAMP(timestamptz2timestamp(timestamp));
+ if (likely(!fcinfo->context))
+ PG_RETURN_TIMESTAMP(timestamptz2timestamp(timestamp));
+ else
+ {
+ int overflow;
+ Timestamp result;
+
+ result = timestamptz2timestamp_opt_overflow(timestamp, &overflow);
+ if (overflow != 0)
+ PG_RETURN_NULL();
+ else
+ PG_RETURN_TIMESTAMP(result);
+ }
}
/*
--
2.34.1
v7-0012-error-safe-for-casting-float8-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v7-0012-error-safe-for-casting-float8-to-other-types-per-pg_cast.patchDownload
From 5558c75ac6876a83e739a4a219f4f166f3fa8a06 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 18:41:55 +0800
Subject: [PATCH v7 12/20] error safe for casting float8 to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'float8'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------------+------------+----------+-------------+------------+----------------+---------
double precision | bigint | 483 | a | f | dtoi8 | int8
double precision | smallint | 237 | a | f | dtoi2 | int2
double precision | integer | 317 | a | f | dtoi4 | int4
double precision | real | 312 | a | f | dtof | float4
double precision | numeric | 1743 | a | f | float8_numeric | numeric
(5 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/float.c | 29 +++++++++++++++++++++++++----
src/backend/utils/adt/int8.c | 6 +++++-
src/backend/utils/adt/numeric.c | 3 ++-
3 files changed, 32 insertions(+), 6 deletions(-)
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index dd8a2ff378b..6747544b679 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -1199,9 +1199,22 @@ dtof(PG_FUNCTION_ARGS)
result = (float4) num;
if (unlikely(isinf(result)) && !isinf(num))
- float_overflow_error();
+ {
+ errsave(fcinfo->context,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: overflow"));
+
+ PG_RETURN_NULL();
+ }
+
if (unlikely(result == 0.0f) && num != 0.0)
- float_underflow_error();
+ {
+ errsave(fcinfo->context,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: underflow"));
+
+ PG_RETURN_NULL();
+ }
PG_RETURN_FLOAT4(result);
}
@@ -1224,10 +1237,14 @@ dtoi4(PG_FUNCTION_ARGS)
/* Range check */
if (unlikely(isnan(num) || !FLOAT8_FITS_IN_INT32(num)))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_INT32((int32) num);
}
@@ -1249,10 +1266,14 @@ dtoi2(PG_FUNCTION_ARGS)
/* Range check */
if (unlikely(isnan(num) || !FLOAT8_FITS_IN_INT16(num)))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_INT16((int16) num);
}
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index bcdb020a91c..437dbbccd4a 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -1268,10 +1268,14 @@ dtoi8(PG_FUNCTION_ARGS)
/* Range check */
if (unlikely(isnan(num) || !FLOAT8_FITS_IN_INT64(num)))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("bigint out of range")));
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_INT64((int64) num);
}
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 8839b095f60..76cd9800c2a 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -4570,7 +4570,8 @@ float8_numeric(PG_FUNCTION_ARGS)
init_var(&result);
/* Assume we need not worry about leading/trailing spaces */
- (void) set_var_from_str(buf, buf, &result, &endptr, NULL);
+ if (!set_var_from_str(buf, buf, &result, &endptr, fcinfo->context))
+ PG_RETURN_NULL();
res = make_result(&result);
--
2.34.1
v7-0011-error-safe-for-casting-float4-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v7-0011-error-safe-for-casting-float4-to-other-types-per-pg_cast.patchDownload
From ab4382ec5ad71348279f6fd1be3d04d66bb3399e Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 18:33:57 +0800
Subject: [PATCH v7 11/20] error safe for casting float4 to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'float4'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+----------------+---------
real | bigint | 653 | a | f | ftoi8 | int8
real | smallint | 238 | a | f | ftoi2 | int2
real | integer | 319 | a | f | ftoi4 | int4
real | double precision | 311 | i | f | ftod | float8
real | numeric | 1742 | a | f | float4_numeric | numeric
(5 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/float.c | 12 ++++++++++--
src/backend/utils/adt/int8.c | 6 +++++-
src/backend/utils/adt/numeric.c | 3 ++-
3 files changed, 17 insertions(+), 4 deletions(-)
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index 7b97d2be6ca..dd8a2ff378b 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -1298,10 +1298,14 @@ ftoi4(PG_FUNCTION_ARGS)
/* Range check */
if (unlikely(isnan(num) || !FLOAT4_FITS_IN_INT32(num)))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_INT32((int32) num);
}
@@ -1323,10 +1327,14 @@ ftoi2(PG_FUNCTION_ARGS)
/* Range check */
if (unlikely(isnan(num) || !FLOAT4_FITS_IN_INT16(num)))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_INT16((int16) num);
}
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 3b2d100be92..bcdb020a91c 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -1303,10 +1303,14 @@ ftoi8(PG_FUNCTION_ARGS)
/* Range check */
if (unlikely(isnan(num) || !FLOAT4_FITS_IN_INT64(num)))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("bigint out of range")));
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_INT64((int64) num);
}
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index ce5f71109e7..8839b095f60 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -4668,7 +4668,8 @@ float4_numeric(PG_FUNCTION_ARGS)
init_var(&result);
/* Assume we need not worry about leading/trailing spaces */
- (void) set_var_from_str(buf, buf, &result, &endptr, NULL);
+ if (!set_var_from_str(buf, buf, &result, &endptr, fcinfo->context))
+ PG_RETURN_NULL();
res = make_result(&result);
--
2.34.1
v7-0010-error-safe-for-casting-numeric-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v7-0010-error-safe-for-casting-numeric-to-other-types-per-pg_cast.patchDownload
From f9af62970b23dfee506ee8fd46fdbee80170e6fd Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 21:57:31 +0800
Subject: [PATCH v7 10/20] error safe for casting numeric to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'numeric'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+----------------+---------
numeric | bigint | 1779 | a | f | numeric_int8 | int8
numeric | smallint | 1783 | a | f | numeric_int2 | int2
numeric | integer | 1744 | a | f | numeric_int4 | int4
numeric | real | 1745 | i | f | numeric_float4 | float4
numeric | double precision | 1746 | i | f | numeric_float8 | float8
numeric | money | 3824 | a | f | numeric_cash | money
numeric | numeric | 1703 | i | f | numeric | numeric
(7 rows)
discussion: https://postgr.es/m/CACJufxHCMzrHOW=wRe8L30rMhB3sjwAv1LE928Fa7sxMu1Tx-g@mail.gmail.com
---
src/backend/utils/adt/numeric.c | 68 +++++++++++++++++++++++++--------
1 file changed, 53 insertions(+), 15 deletions(-)
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 2501007d981..ce5f71109e7 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -1244,7 +1244,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));
}
@@ -1295,8 +1296,9 @@ numeric (PG_FUNCTION_ARGS)
init_var(&var);
set_var_from_num(num, &var);
- (void) apply_typmod(&var, typmod, NULL);
- new = make_result(&var);
+ if (!apply_typmod(&var, typmod, fcinfo->context))
+ PG_RETURN_NULL();
+ new = make_result_safe(&var, fcinfo->context);
free_var(&var);
@@ -3019,7 +3021,10 @@ numeric_mul(PG_FUNCTION_ARGS)
Numeric num2 = PG_GETARG_NUMERIC(1);
Numeric res;
- res = numeric_mul_safe(num1, num2, NULL);
+ res = numeric_mul_safe(num1, num2, fcinfo->context);
+
+ if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
PG_RETURN_NUMERIC(res);
}
@@ -4393,9 +4398,15 @@ numeric_int4_safe(Numeric num, Node *escontext)
Datum
numeric_int4(PG_FUNCTION_ARGS)
{
+ int32 result;
Numeric num = PG_GETARG_NUMERIC(0);
- PG_RETURN_INT32(numeric_int4_safe(num, NULL));
+ result = numeric_int4_safe(num, fcinfo->context);
+
+ if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
+
+ PG_RETURN_INT32(result);
}
/*
@@ -4463,9 +4474,15 @@ numeric_int8_safe(Numeric num, Node *escontext)
Datum
numeric_int8(PG_FUNCTION_ARGS)
{
+ int64 result;
Numeric num = PG_GETARG_NUMERIC(0);
- PG_RETURN_INT64(numeric_int8_safe(num, NULL));
+ result = numeric_int8_safe(num, fcinfo->context);
+
+ if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
+
+ PG_RETURN_INT64(result);
}
@@ -4489,28 +4506,38 @@ numeric_int2(PG_FUNCTION_ARGS)
if (NUMERIC_IS_SPECIAL(num))
{
if (NUMERIC_IS_NAN(num))
- ereport(ERROR,
+ errsave(fcinfo->context,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert NaN to %s", "smallint")));
else
- ereport(ERROR,
+ errsave(fcinfo->context,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert infinity to %s", "smallint")));
+
+ PG_RETURN_NULL();
}
/* Convert to variable format and thence to int8 */
init_var_from_num(num, &x);
if (!numericvar_to_int64(&x, &val))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
+ PG_RETURN_NULL();
+ }
+
if (unlikely(val < PG_INT16_MIN) || unlikely(val > PG_INT16_MAX))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
+ PG_RETURN_NULL();
+ }
+
/* Down-convert to int2 */
result = (int16) val;
@@ -4572,10 +4599,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);
}
@@ -4667,7 +4698,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
v7-0008-error-safe-for-casting-integer-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v7-0008-error-safe-for-casting-integer-to-other-types-per-pg_cast.patchDownload
From afa97fc4300711baf573799a7f363644fee03b4c Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 16:04:07 +0800
Subject: [PATCH v7 08/20] error safe for casting integer to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'integer'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+--------------+---------
integer | bigint | 481 | i | f | int48 | int8
integer | smallint | 314 | a | f | i4toi2 | int2
integer | real | 318 | i | f | i4tof | float4
integer | double precision | 316 | i | f | i4tod | float8
integer | numeric | 1740 | i | f | int4_numeric | numeric
integer | money | 3811 | a | f | int4_cash | money
integer | boolean | 2557 | e | f | int4_bool | bool
integer | bytea | 6368 | e | f | int4_bytea | bytea
integer | "char" | 78 | e | f | i4tochar | char
integer | bit | 1683 | e | f | bitfromint4 | bit
(10 rows)
only int4_cash, i4toi2, i4tochar need take care of error handling. but support
for cash data type is not easy, so only i4toi2, i4tochar function refactoring.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/char.c | 6 +++++-
src/backend/utils/adt/int.c | 5 ++++-
2 files changed, 9 insertions(+), 2 deletions(-)
diff --git a/src/backend/utils/adt/char.c b/src/backend/utils/adt/char.c
index 22dbfc950b1..7cf09d954e7 100644
--- a/src/backend/utils/adt/char.c
+++ b/src/backend/utils/adt/char.c
@@ -192,10 +192,14 @@ i4tochar(PG_FUNCTION_ARGS)
int32 arg1 = PG_GETARG_INT32(0);
if (arg1 < SCHAR_MIN || arg1 > SCHAR_MAX)
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("\"char\" out of range")));
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_CHAR((int8) arg1);
}
diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c
index b5781989a64..4d3ce23a4af 100644
--- a/src/backend/utils/adt/int.c
+++ b/src/backend/utils/adt/int.c
@@ -350,10 +350,13 @@ i4toi2(PG_FUNCTION_ARGS)
int32 arg1 = PG_GETARG_INT32(0);
if (unlikely(arg1 < SHRT_MIN) || unlikely(arg1 > SHRT_MAX))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
+ PG_RETURN_NULL();
+ }
PG_RETURN_INT16((int16) arg1);
}
--
2.34.1
v7-0009-error-safe-for-casting-bigint-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v7-0009-error-safe-for-casting-bigint-to-other-types-per-pg_cast.patchDownload
From ecce9850b5793174fb67699b95106c363820888c Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 16:38:37 +0800
Subject: [PATCH v7 09/20] error safe for casting bigint to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'bigint'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+--------------+---------
bigint | smallint | 714 | a | f | int82 | int2
bigint | integer | 480 | a | f | int84 | int4
bigint | real | 652 | i | f | i8tof | float4
bigint | double precision | 482 | i | f | i8tod | float8
bigint | numeric | 1781 | i | f | int8_numeric | numeric
bigint | money | 3812 | a | f | int8_cash | money
bigint | oid | 1287 | i | f | i8tooid | oid
bigint | regproc | 1287 | i | f | i8tooid | oid
bigint | regprocedure | 1287 | i | f | i8tooid | oid
bigint | regoper | 1287 | i | f | i8tooid | oid
bigint | regoperator | 1287 | i | f | i8tooid | oid
bigint | regclass | 1287 | i | f | i8tooid | oid
bigint | regcollation | 1287 | i | f | i8tooid | oid
bigint | regtype | 1287 | i | f | i8tooid | oid
bigint | regconfig | 1287 | i | f | i8tooid | oid
bigint | regdictionary | 1287 | i | f | i8tooid | oid
bigint | regrole | 1287 | i | f | i8tooid | oid
bigint | regnamespace | 1287 | i | f | i8tooid | oid
bigint | regdatabase | 1287 | i | f | i8tooid | oid
bigint | bytea | 6369 | e | f | int8_bytea | bytea
bigint | bit | 2075 | e | f | bitfromint8 | bit
(21 rows)
already error safe: i8tof, i8tod, int8_numeric, int8_bytea, bitfromint8
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/int8.c | 18 +++++++++++++++---
1 file changed, 15 insertions(+), 3 deletions(-)
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index bdea490202a..3b2d100be92 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -1204,10 +1204,14 @@ int84(PG_FUNCTION_ARGS)
int64 arg = PG_GETARG_INT64(0);
if (unlikely(arg < PG_INT32_MIN) || unlikely(arg > PG_INT32_MAX))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_INT32((int32) arg);
}
@@ -1225,10 +1229,14 @@ int82(PG_FUNCTION_ARGS)
int64 arg = PG_GETARG_INT64(0);
if (unlikely(arg < PG_INT16_MIN) || unlikely(arg > PG_INT16_MAX))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_INT16((int16) arg);
}
@@ -1308,10 +1316,14 @@ i8tooid(PG_FUNCTION_ARGS)
int64 arg = PG_GETARG_INT64(0);
if (unlikely(arg < 0) || unlikely(arg > PG_UINT32_MAX))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("OID out of range")));
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_OID((Oid) arg);
}
--
2.34.1
v7-0006-error-safe-for-casting-inet-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v7-0006-error-safe-for-casting-inet-to-other-types-per-pg_cast.patchDownload
From 101651e9a37fa394113006f3f5ba30218d1fb60a Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 15:55:55 +0800
Subject: [PATCH v7 06/20] error safe for casting inet to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'inet'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+-------------------+----------+-------------+------------+--------------+---------
inet | cidr | 1715 | a | f | inet_to_cidr | cidr
inet | text | 730 | a | f | network_show | text
inet | character varying | 730 | a | f | network_show | text
inet | character | 730 | a | f | network_show | text
(4 rows)
inet_to_cidr is already error safe.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/network.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/backend/utils/adt/network.c b/src/backend/utils/adt/network.c
index 3cb0ab6829a..f34228763da 100644
--- a/src/backend/utils/adt/network.c
+++ b/src/backend/utils/adt/network.c
@@ -1137,10 +1137,14 @@ network_show(PG_FUNCTION_ARGS)
if (pg_inet_net_ntop(ip_family(ip), ip_addr(ip), ip_maxbits(ip),
tmp, sizeof(tmp)) == NULL)
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
errmsg("could not format inet value: %m")));
+ PG_RETURN_NULL();
+ }
+
/* Add /n if not present (which it won't be) */
if (strchr(tmp, '/') == NULL)
{
--
2.34.1
v7-0007-error-safe-for-casting-macaddr8-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v7-0007-error-safe-for-casting-macaddr8-to-other-types-per-pg_cast.patchDownload
From 7d5152e88cb8506811f61f83ff2bb9a7f3cfacbe Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 19:12:12 +0800
Subject: [PATCH v7 07/20] error safe for casting macaddr8 to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
and pc.castfunc > 0 and castsource::regtype ='macaddr8'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------+----------+-------------+------------+-------------------+---------
macaddr8 | macaddr | 4124 | i | f | macaddr8tomacaddr | macaddr
(1 row)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/mac8.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/backend/utils/adt/mac8.c b/src/backend/utils/adt/mac8.c
index 08e41ba4eea..e2a7a90e42c 100644
--- a/src/backend/utils/adt/mac8.c
+++ b/src/backend/utils/adt/mac8.c
@@ -550,7 +550,8 @@ macaddr8tomacaddr(PG_FUNCTION_ARGS)
result = (macaddr *) palloc0(sizeof(macaddr));
if ((addr->d != 0xFF) || (addr->e != 0xFE))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("macaddr8 data out of range to convert to macaddr"),
errhint("Only addresses that have FF and FE as values in the "
@@ -558,6 +559,9 @@ macaddr8tomacaddr(PG_FUNCTION_ARGS)
"xx:xx:xx:ff:fe:xx:xx:xx, are eligible to be converted "
"from macaddr8 to macaddr.")));
+ PG_RETURN_NULL();
+ }
+
result->a = addr->a;
result->b = addr->b;
result->c = addr->c;
--
2.34.1
v7-0003-error-safe-for-casting-character-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v7-0003-error-safe-for-casting-character-to-other-types-per-pg_cast.patchDownload
From a6c47c4c351f2d69e5a6be2e77c5701a73e6a24e Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 15:46:16 +0800
Subject: [PATCH v7 03/20] error safe for casting character to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype ='character'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+-------------------+----------+-------------+------------+-------------+---------
character | text | 401 | i | f | rtrim1 | text
character | character varying | 401 | i | f | rtrim1 | text
character | "char" | 944 | a | f | text_char | char
character | name | 409 | i | f | bpchar_name | name
character | xml | 2896 | e | f | texttoxml | xml
character | character | 668 | i | f | bpchar | bpchar
(6 rows)
only texttoxml, bpchar(PG_FUNCTION_ARGS) need take care of error handling.
other functions already error safe.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/executor/execExprInterp.c | 2 +-
src/backend/utils/adt/varchar.c | 6 +++++-
src/backend/utils/adt/xml.c | 12 ++++++++----
src/include/utils/xml.h | 2 +-
4 files changed, 15 insertions(+), 7 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 0e1a74976f7..67f4e00eac4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -4542,7 +4542,7 @@ ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op)
*op->resvalue = PointerGetDatum(xmlparse(data,
xexpr->xmloption,
- preserve_whitespace));
+ preserve_whitespace, NULL));
*op->resnull = false;
}
break;
diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c
index 3f40c9da1a0..a95e4bd094e 100644
--- a/src/backend/utils/adt/varchar.c
+++ b/src/backend/utils/adt/varchar.c
@@ -307,10 +307,14 @@ bpchar(PG_FUNCTION_ARGS)
{
for (i = maxmblen; i < len; i++)
if (s[i] != ' ')
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
errmsg("value too long for type character(%d)",
maxlen)));
+
+ PG_RETURN_NULL();
+ }
}
len = maxmblen;
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 66b44183695..e6a5be8d9cc 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -659,7 +659,7 @@ texttoxml(PG_FUNCTION_ARGS)
{
text *data = PG_GETARG_TEXT_PP(0);
- PG_RETURN_XML_P(xmlparse(data, xmloption, true));
+ PG_RETURN_XML_P(xmlparse(data, xmloption, true, fcinfo->context));
}
@@ -1028,14 +1028,18 @@ xmlelement(XmlExpr *xexpr,
xmltype *
-xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace)
+xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, Node *escontext)
{
#ifdef USE_LIBXML
xmlDocPtr doc;
doc = xml_parse(data, xmloption_arg, preserve_whitespace,
- GetDatabaseEncoding(), NULL, NULL, NULL);
- xmlFreeDoc(doc);
+ GetDatabaseEncoding(), NULL, NULL, escontext);
+ if (doc)
+ xmlFreeDoc(doc);
+
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return NULL;
return (xmltype *) data;
#else
diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h
index 0d7a816b9f9..b3d3f11fac4 100644
--- a/src/include/utils/xml.h
+++ b/src/include/utils/xml.h
@@ -73,7 +73,7 @@ extern xmltype *xmlconcat(List *args);
extern xmltype *xmlelement(XmlExpr *xexpr,
Datum *named_argvalue, bool *named_argnull,
Datum *argvalue, bool *argnull);
-extern xmltype *xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace);
+extern xmltype *xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, Node *escontext);
extern xmltype *xmlpi(const char *target, text *arg, bool arg_is_null, bool *result_is_null);
extern xmltype *xmlroot(xmltype *data, text *version, int standalone);
extern bool xml_is_document(xmltype *arg);
--
2.34.1
v7-0005-error-safe-for-casting-character-varying-to-other-types-per-pg_ca.patchtext/x-patch; charset=US-ASCII; name=v7-0005-error-safe-for-casting-character-varying-to-other-types-per-pg_ca.patchDownload
From 0849ffe6c62fccf18ce03cbd8ef593a601daf50f Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 19:17:00 +0800
Subject: [PATCH v7 05/20] error safe for casting character varying to other
types per pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc and pc.castfunc > 0 and
(castsource::regtype = 'character varying'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
-------------------+-------------------+----------+-------------+------------+---------------+----------
character varying | regclass | 1079 | i | f | text_regclass | regclass
character varying | "char" | 944 | a | f | text_char | char
character varying | name | 1400 | i | f | text_name | name
character varying | xml | 2896 | e | f | texttoxml | xml
character varying | character varying | 669 | i | f | varchar | varchar
(5 rows)
texttoxml, text_regclass was refactored as error safe in prior patch.
text_char, text_name is already error safe.
so here we only need handle function "varchar".
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/varchar.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c
index a95e4bd094e..fdbef272c39 100644
--- a/src/backend/utils/adt/varchar.c
+++ b/src/backend/utils/adt/varchar.c
@@ -638,10 +638,14 @@ varchar(PG_FUNCTION_ARGS)
{
for (i = maxmblen; i < len; i++)
if (s_data[i] != ' ')
- ereport(ERROR,
+ {
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
errmsg("value too long for type character varying(%d)",
maxlen)));
+
+ PG_RETURN_NULL();
+ }
}
PG_RETURN_VARCHAR_P((VarChar *) cstring_to_text_with_len(s_data,
--
2.34.1
v7-0002-error-safe-for-casting-bit-varbit-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v7-0002-error-safe-for-casting-bit-varbit-to-other-types-per-pg_cast.patchDownload
From fed4f75d0a2a77c99eebcd6351ff099d385587bf Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 19:09:53 +0800
Subject: [PATCH v7 02/20] error safe for casting bit/varbit to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
where pc.castfunc > 0 and (castsource::regtype ='bit'::regtype or
castsource::regtype ='varbit'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
-------------+-------------+----------+-------------+------------+-----------+---------
bit | bigint | 2076 | e | f | bittoint8 | int8
bit | integer | 1684 | e | f | bittoint4 | int4
bit | bit | 1685 | i | f | bit | bit
bit varying | bit varying | 1687 | i | f | varbit | varbit
(4 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/varbit.c | 24 ++++++++++++++++++++----
1 file changed, 20 insertions(+), 4 deletions(-)
diff --git a/src/backend/utils/adt/varbit.c b/src/backend/utils/adt/varbit.c
index 205a67dafc5..bfcea18f4b2 100644
--- a/src/backend/utils/adt/varbit.c
+++ b/src/backend/utils/adt/varbit.c
@@ -401,11 +401,15 @@ bit(PG_FUNCTION_ARGS)
PG_RETURN_VARBIT_P(arg);
if (!isExplicit)
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_STRING_DATA_LENGTH_MISMATCH),
errmsg("bit string length %d does not match type bit(%d)",
VARBITLEN(arg), len)));
+ PG_RETURN_NULL();
+ }
+
rlen = VARBITTOTALLEN(len);
/* set to 0 so that string is zero-padded */
result = (VarBit *) palloc0(rlen);
@@ -752,11 +756,15 @@ varbit(PG_FUNCTION_ARGS)
PG_RETURN_VARBIT_P(arg);
if (!isExplicit)
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
errmsg("bit string too long for type bit varying(%d)",
len)));
+ PG_RETURN_NULL();
+ }
+
rlen = VARBITTOTALLEN(len);
result = (VarBit *) palloc(rlen);
SET_VARSIZE(result, rlen);
@@ -1591,10 +1599,14 @@ bittoint4(PG_FUNCTION_ARGS)
/* Check that the bit string is not too long */
if (VARBITLEN(arg) > sizeof(result) * BITS_PER_BYTE)
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
+ PG_RETURN_NULL();
+ }
+
result = 0;
for (r = VARBITS(arg); r < VARBITEND(arg); r++)
{
@@ -1671,10 +1683,14 @@ bittoint8(PG_FUNCTION_ARGS)
/* Check that the bit string is not too long */
if (VARBITLEN(arg) > sizeof(result) * BITS_PER_BYTE)
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("bigint out of range")));
+ PG_RETURN_NULL();
+ }
+
result = 0;
for (r = VARBITS(arg); r < VARBITEND(arg); r++)
{
--
2.34.1
v7-0018-error-safe-for-casting-geometry-data-type.patchtext/x-patch; charset=US-ASCII; name=v7-0018-error-safe-for-casting-geometry-data-type.patchDownload
From cedd16bb3bc7df4870f6689cb90e137e83dcc66a Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 19:19:04 +0800
Subject: [PATCH v7 18/20] error safe for casting geometry data type
select castsource::regtype, casttarget::regtype, pp.prosrc
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
join pg_type pt on pt.oid = castsource
join pg_type pt1 on pt1.oid = casttarget
and pc.castfunc > 0 and pt.typarray <> 0
and pt.typnamespace = 'pg_catalog'::regnamespace
and pt1.typnamespace = 'pg_catalog'::regnamespace
and (pt.typcategory = 'G' or pt1.typcategory = 'G')
order by castsource::regtype, casttarget::regtype;
castsource | casttarget | prosrc
------------+------------+---------------
point | box | point_box
lseg | point | lseg_center
path | polygon | path_poly
box | point | box_center
box | lseg | box_diagonal
box | polygon | box_poly
box | circle | box_circle
polygon | point | poly_center
polygon | path | poly_path
polygon | box | poly_box
polygon | circle | poly_circle
circle | point | circle_center
circle | box | circle_box
circle | polygon |
(14 rows)
already error safe: point_box, box_diagonal, box_poly, poly_path, poly_box, circle_center
almost error safe: path_poly
discussion: https://postgr.es/m/CACJufxHCMzrHOW=wRe8L30rMhB3sjwAv1LE928Fa7sxMu1Tx-g@mail.gmail.com
---
src/backend/access/spgist/spgproc.c | 2 +-
src/backend/utils/adt/float.c | 8 +-
src/backend/utils/adt/geo_ops.c | 282 ++++++++++++++++++++++++----
src/backend/utils/adt/geo_spgist.c | 2 +-
src/include/utils/float.h | 107 +++++++++++
src/include/utils/geo_decls.h | 4 +-
6 files changed, 358 insertions(+), 47 deletions(-)
diff --git a/src/backend/access/spgist/spgproc.c b/src/backend/access/spgist/spgproc.c
index 660009291da..b3e0fbc59ba 100644
--- a/src/backend/access/spgist/spgproc.c
+++ b/src/backend/access/spgist/spgproc.c
@@ -51,7 +51,7 @@ point_box_distance(Point *point, BOX *box)
else
dy = 0.0;
- return HYPOT(dx, dy);
+ return HYPOT(dx, dy, NULL);
}
/*
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index 6747544b679..06b2b6ae295 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -772,7 +772,7 @@ float8pl(PG_FUNCTION_ARGS)
float8 arg1 = PG_GETARG_FLOAT8(0);
float8 arg2 = PG_GETARG_FLOAT8(1);
- PG_RETURN_FLOAT8(float8_pl(arg1, arg2));
+ PG_RETURN_FLOAT8(float8_pl_safe(arg1, arg2, fcinfo->context));
}
Datum
@@ -781,7 +781,7 @@ float8mi(PG_FUNCTION_ARGS)
float8 arg1 = PG_GETARG_FLOAT8(0);
float8 arg2 = PG_GETARG_FLOAT8(1);
- PG_RETURN_FLOAT8(float8_mi(arg1, arg2));
+ PG_RETURN_FLOAT8(float8_mi_safe(arg1, arg2, fcinfo->context));
}
Datum
@@ -790,7 +790,7 @@ float8mul(PG_FUNCTION_ARGS)
float8 arg1 = PG_GETARG_FLOAT8(0);
float8 arg2 = PG_GETARG_FLOAT8(1);
- PG_RETURN_FLOAT8(float8_mul(arg1, arg2));
+ PG_RETURN_FLOAT8(float8_mul_safe(arg1, arg2, fcinfo->context));
}
Datum
@@ -799,7 +799,7 @@ float8div(PG_FUNCTION_ARGS)
float8 arg1 = PG_GETARG_FLOAT8(0);
float8 arg2 = PG_GETARG_FLOAT8(1);
- PG_RETURN_FLOAT8(float8_div(arg1, arg2));
+ PG_RETURN_FLOAT8(float8_div_safe(arg1, arg2, fcinfo->context));
}
diff --git a/src/backend/utils/adt/geo_ops.c b/src/backend/utils/adt/geo_ops.c
index 377a1b3f3ad..a5f2537da25 100644
--- a/src/backend/utils/adt/geo_ops.c
+++ b/src/backend/utils/adt/geo_ops.c
@@ -78,11 +78,14 @@ enum path_delim
/* Routines for points */
static inline void point_construct(Point *result, float8 x, float8 y);
static inline void point_add_point(Point *result, Point *pt1, Point *pt2);
+static inline bool point_add_point_safe(Point *result, Point *pt1, Point *pt2,
+ Node *escontext);
static inline void point_sub_point(Point *result, Point *pt1, Point *pt2);
static inline void point_mul_point(Point *result, Point *pt1, Point *pt2);
static inline void point_div_point(Point *result, Point *pt1, Point *pt2);
static inline bool point_eq_point(Point *pt1, Point *pt2);
static inline float8 point_dt(Point *pt1, Point *pt2);
+static inline bool point_dt_safe(Point *pt1, Point *pt2, Node *escontext, float8 *result);
static inline float8 point_sl(Point *pt1, Point *pt2);
static int point_inside(Point *p, int npts, Point *plist);
@@ -109,6 +112,7 @@ static float8 lseg_closept_lseg(Point *result, LSEG *on_lseg, LSEG *to_lseg);
/* Routines for boxes */
static inline void box_construct(BOX *result, Point *pt1, Point *pt2);
static void box_cn(Point *center, BOX *box);
+static bool box_cn_safe(Point *center, BOX *box, Node* escontext);
static bool box_ov(BOX *box1, BOX *box2);
static float8 box_ar(BOX *box);
static float8 box_ht(BOX *box);
@@ -125,7 +129,7 @@ static float8 circle_ar(CIRCLE *circle);
/* Routines for polygons */
static void make_bound_box(POLYGON *poly);
-static void poly_to_circle(CIRCLE *result, POLYGON *poly);
+static bool poly_to_circle_safe(CIRCLE *result, POLYGON *poly, Node *escontext);
static bool lseg_inside_poly(Point *a, Point *b, POLYGON *poly, int start);
static bool poly_contain_poly(POLYGON *contains_poly, POLYGON *contained_poly);
static bool plist_same(int npts, Point *p1, Point *p2);
@@ -851,7 +855,8 @@ box_center(PG_FUNCTION_ARGS)
BOX *box = PG_GETARG_BOX_P(0);
Point *result = (Point *) palloc(sizeof(Point));
- box_cn(result, box);
+ if (!box_cn_safe(result, box, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_POINT_P(result);
}
@@ -875,6 +880,31 @@ box_cn(Point *center, BOX *box)
center->y = float8_div(float8_pl(box->high.y, box->low.y), 2.0);
}
+/* safe version of box_cn */
+static bool
+box_cn_safe(Point *center, BOX *box, Node* escontext)
+{
+ float8 x;
+ float8 y;
+
+ x = float8_pl_safe(box->high.x, box->low.x, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ center->x = float8_div_safe(x, 2.0, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ y = float8_pl_safe(box->high.y, box->low.y, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ center->y = float8_div_safe(y, 2.0, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ return true;
+}
/* box_wd - returns the width (length) of the box
* (horizontal magnitude).
@@ -1276,7 +1306,7 @@ line_distance(PG_FUNCTION_ARGS)
PG_RETURN_FLOAT8(float8_div(fabs(float8_mi(l1->C,
float8_mul(ratio, l2->C))),
- HYPOT(l1->A, l1->B)));
+ HYPOT(l1->A, l1->B, NULL)));
}
/* line_interpt()
@@ -2001,9 +2031,34 @@ point_distance(PG_FUNCTION_ARGS)
static inline float8
point_dt(Point *pt1, Point *pt2)
{
- return HYPOT(float8_mi(pt1->x, pt2->x), float8_mi(pt1->y, pt2->y));
+ return HYPOT(float8_mi(pt1->x, pt2->x), float8_mi(pt1->y, pt2->y), NULL);
}
+/* errror safe version of point_dt */
+static inline bool
+point_dt_safe(Point *pt1, Point *pt2, Node *escontext, float8 *result)
+{
+ float8 x;
+ float8 y;
+
+ Assert(result != NULL);
+
+ x = float8_mi_safe(pt1->x, pt2->x, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ y = float8_mi_safe(pt1->y, pt2->y, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ *result = HYPOT(x, y, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ return true;
+}
+
+
Datum
point_slope(PG_FUNCTION_ARGS)
{
@@ -2317,13 +2372,31 @@ lseg_center(PG_FUNCTION_ARGS)
{
LSEG *lseg = PG_GETARG_LSEG_P(0);
Point *result;
+ float8 x;
+ float8 y;
result = (Point *) palloc(sizeof(Point));
- result->x = float8_div(float8_pl(lseg->p[0].x, lseg->p[1].x), 2.0);
- result->y = float8_div(float8_pl(lseg->p[0].y, lseg->p[1].y), 2.0);
+ x = float8_pl_safe(lseg->p[0].x, lseg->p[1].x, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ result->x = float8_div_safe(x, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ y = float8_pl_safe(lseg->p[0].y, lseg->p[1].y, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ result->y = float8_div_safe(y, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
PG_RETURN_POINT_P(result);
+
+fail:
+ PG_RETURN_NULL();
}
@@ -4115,6 +4188,27 @@ point_add_point(Point *result, Point *pt1, Point *pt2)
float8_pl(pt1->y, pt2->y));
}
+/* error safe version of point_add_point */
+static inline bool
+point_add_point_safe(Point *result, Point *pt1, Point *pt2,
+ Node *escontext)
+{
+ float8 x;
+ float8 y;
+
+ x = float8_pl_safe(pt1->x, pt2->x, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ y = float8_pl_safe(pt1->y, pt2->y, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ point_construct(result, x, y);
+
+ return true;
+}
+
Datum
point_add(PG_FUNCTION_ARGS)
{
@@ -4458,10 +4552,14 @@ path_poly(PG_FUNCTION_ARGS)
/* This is not very consistent --- other similar cases return NULL ... */
if (!path->closed)
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("open path cannot be converted to polygon")));
+ PG_RETURN_NULL();
+ }
+
/*
* Never overflows: the old size fit in MaxAllocSize, and the new size is
* just a small constant larger.
@@ -4508,7 +4606,9 @@ poly_center(PG_FUNCTION_ARGS)
result = (Point *) palloc(sizeof(Point));
- poly_to_circle(&circle, poly);
+ if (!poly_to_circle_safe(&circle, poly, fcinfo->context))
+ PG_RETURN_NULL();
+
*result = circle.center;
PG_RETURN_POINT_P(result);
@@ -5005,7 +5105,7 @@ circle_mul_pt(PG_FUNCTION_ARGS)
result = (CIRCLE *) palloc(sizeof(CIRCLE));
point_mul_point(&result->center, &circle->center, point);
- result->radius = float8_mul(circle->radius, HYPOT(point->x, point->y));
+ result->radius = float8_mul(circle->radius, HYPOT(point->x, point->y, NULL));
PG_RETURN_CIRCLE_P(result);
}
@@ -5020,7 +5120,7 @@ circle_div_pt(PG_FUNCTION_ARGS)
result = (CIRCLE *) palloc(sizeof(CIRCLE));
point_div_point(&result->center, &circle->center, point);
- result->radius = float8_div(circle->radius, HYPOT(point->x, point->y));
+ result->radius = float8_div(circle->radius, HYPOT(point->x, point->y, NULL));
PG_RETURN_CIRCLE_P(result);
}
@@ -5191,14 +5291,30 @@ circle_box(PG_FUNCTION_ARGS)
box = (BOX *) palloc(sizeof(BOX));
- delta = float8_div(circle->radius, sqrt(2.0));
+ delta = float8_div_safe(circle->radius, sqrt(2.0), fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
- box->high.x = float8_pl(circle->center.x, delta);
- box->low.x = float8_mi(circle->center.x, delta);
- box->high.y = float8_pl(circle->center.y, delta);
- box->low.y = float8_mi(circle->center.y, delta);
+ box->high.x = float8_pl_safe(circle->center.x, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ box->low.x = float8_mi_safe(circle->center.x, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ box->high.y = float8_pl_safe(circle->center.y, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ box->low.y = float8_mi_safe(circle->center.y, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
PG_RETURN_BOX_P(box);
+
+fail:
+ PG_RETURN_NULL();
}
/* box_circle()
@@ -5209,15 +5325,35 @@ box_circle(PG_FUNCTION_ARGS)
{
BOX *box = PG_GETARG_BOX_P(0);
CIRCLE *circle;
+ float8 x;
+ float8 y;
circle = (CIRCLE *) palloc(sizeof(CIRCLE));
- circle->center.x = float8_div(float8_pl(box->high.x, box->low.x), 2.0);
- circle->center.y = float8_div(float8_pl(box->high.y, box->low.y), 2.0);
+ x = float8_pl_safe(box->high.x, box->low.x, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
- circle->radius = point_dt(&circle->center, &box->high);
+ circle->center.x = float8_div_safe(x, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ y = float8_pl_safe(box->high.y, box->low.y, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ circle->center.y = float8_div_safe(y, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ if (!point_dt_safe(&circle->center, &box->high, fcinfo->context,
+ &circle->radius))
+ goto fail;
PG_RETURN_CIRCLE_P(circle);
+
+fail:
+ PG_RETURN_NULL();
}
@@ -5230,28 +5366,38 @@ circle_poly(PG_FUNCTION_ARGS)
int base_size,
size;
int i;
+ float8 x;
+ float8 y;
float8 angle;
float8 anglestep;
if (FPzero(circle->radius))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert circle with radius zero to polygon")));
+ goto fail;
+ }
if (npts < 2)
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("must request at least 2 points")));
+ goto fail;
+ }
base_size = sizeof(poly->p[0]) * npts;
size = offsetof(POLYGON, p) + base_size;
/* Check for integer overflow */
if (base_size / npts != sizeof(poly->p[0]) || size <= base_size)
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("too many points requested")));
-
+ goto fail;
+ }
poly = (POLYGON *) palloc0(size); /* zero any holes */
SET_VARSIZE(poly, size);
poly->npts = npts;
@@ -5260,17 +5406,33 @@ circle_poly(PG_FUNCTION_ARGS)
for (i = 0; i < npts; i++)
{
- angle = float8_mul(anglestep, i);
+ angle = float8_mul_safe(anglestep, i, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
- poly->p[i].x = float8_mi(circle->center.x,
- float8_mul(circle->radius, cos(angle)));
- poly->p[i].y = float8_pl(circle->center.y,
- float8_mul(circle->radius, sin(angle)));
+ x = float8_mul_safe(circle->radius, cos(angle), fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ poly->p[i].x = float8_mi_safe(circle->center.x, x, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ y = float8_mul_safe(circle->radius, sin(angle),fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ poly->p[i].y = float8_pl_safe(circle->center.y, y, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
}
make_bound_box(poly);
PG_RETURN_POLYGON_P(poly);
+
+fail:
+ PG_RETURN_NULL();
}
/*
@@ -5281,10 +5443,11 @@ circle_poly(PG_FUNCTION_ARGS)
* XXX This algorithm should use weighted means of line segments
* rather than straight average values of points - tgl 97/01/21.
*/
-static void
-poly_to_circle(CIRCLE *result, POLYGON *poly)
+static bool
+poly_to_circle_safe(CIRCLE *result, POLYGON *poly, Node *escontext)
{
int i;
+ float8 x;
Assert(poly->npts > 0);
@@ -5293,14 +5456,41 @@ poly_to_circle(CIRCLE *result, POLYGON *poly)
result->radius = 0;
for (i = 0; i < poly->npts; i++)
- point_add_point(&result->center, &result->center, &poly->p[i]);
- result->center.x = float8_div(result->center.x, poly->npts);
- result->center.y = float8_div(result->center.y, poly->npts);
+ {
+ if (!point_add_point_safe(&result->center,
+ &result->center,
+ &poly->p[i],
+ escontext))
+ return false;
+ }
+
+ result->center.x = float8_div_safe(result->center.x,
+ poly->npts,
+ escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ result->center.y = float8_div_safe(result->center.y,
+ poly->npts,
+ escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
for (i = 0; i < poly->npts; i++)
- result->radius = float8_pl(result->radius,
- point_dt(&poly->p[i], &result->center));
- result->radius = float8_div(result->radius, poly->npts);
+ {
+ if (!point_dt_safe(&poly->p[i], &result->center, escontext, &x))
+ return false;
+
+ result->radius = float8_pl_safe(result->radius, x, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+ }
+
+ result->radius = float8_div_safe(result->radius, poly->npts, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ return true;
}
Datum
@@ -5311,7 +5501,8 @@ poly_circle(PG_FUNCTION_ARGS)
result = (CIRCLE *) palloc(sizeof(CIRCLE));
- poly_to_circle(result, poly);
+ if (!poly_to_circle_safe(result, poly, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_CIRCLE_P(result);
}
@@ -5516,7 +5707,7 @@ plist_same(int npts, Point *p1, Point *p2)
*-----------------------------------------------------------------------
*/
float8
-pg_hypot(float8 x, float8 y)
+pg_hypot(float8 x, float8 y, Node *escontext)
{
float8 yx,
result;
@@ -5554,9 +5745,22 @@ pg_hypot(float8 x, float8 y)
result = x * sqrt(1.0 + (yx * yx));
if (unlikely(isinf(result)))
- float_overflow_error();
+ {
+ errsave(escontext,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: overflow"));
+
+ result = 0.0;
+ }
+
if (unlikely(result == 0.0))
- float_underflow_error();
+ {
+ errsave(escontext,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: underflow"));
+
+ result = 0.0;
+ }
return result;
}
diff --git a/src/backend/utils/adt/geo_spgist.c b/src/backend/utils/adt/geo_spgist.c
index fec33e95372..ffffd3cd2de 100644
--- a/src/backend/utils/adt/geo_spgist.c
+++ b/src/backend/utils/adt/geo_spgist.c
@@ -390,7 +390,7 @@ pointToRectBoxDistance(Point *point, RectBox *rect_box)
else
dy = 0;
- return HYPOT(dx, dy);
+ return HYPOT(dx, dy, NULL);
}
diff --git a/src/include/utils/float.h b/src/include/utils/float.h
index fc2a9cf6475..5a7033f2a60 100644
--- a/src/include/utils/float.h
+++ b/src/include/utils/float.h
@@ -109,6 +109,24 @@ float4_pl(const float4 val1, const float4 val2)
return result;
}
+/* error safe version of float8_pl */
+static inline float8
+float8_pl_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+ float8 result;
+
+ result = val1 + val2;
+ if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
+ {
+ errsave(escontext,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: overflow"));
+ result = 0.0f;
+ }
+
+ return result;
+}
+
static inline float8
float8_pl(const float8 val1, const float8 val2)
{
@@ -133,6 +151,24 @@ float4_mi(const float4 val1, const float4 val2)
return result;
}
+/* error safe version of float8_mi */
+static inline float8
+float8_mi_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+ float8 result;
+
+ result = val1 - val2;
+ if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
+ {
+ errsave(escontext,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: overflow"));
+ result = 0.0f;
+ }
+
+ return result;
+}
+
static inline float8
float8_mi(const float8 val1, const float8 val2)
{
@@ -159,6 +195,36 @@ float4_mul(const float4 val1, const float4 val2)
return result;
}
+/* error safe version of float8_mul */
+static inline float8
+float8_mul_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+ float8 result;
+
+ result = val1 * val2;
+ if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
+ {
+ errsave(escontext,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: overflow"));
+
+ result = 0.0;
+ return result;
+ }
+
+ if (unlikely(result == 0.0) && val1 != 0.0 && val2 != 0.0)
+ {
+ errsave(escontext,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: underflow"));
+
+ result = 0.0;
+ return result;
+ }
+
+ return result;
+}
+
static inline float8
float8_mul(const float8 val1, const float8 val2)
{
@@ -189,6 +255,47 @@ float4_div(const float4 val1, const float4 val2)
return result;
}
+/* error safe version of float8_div */
+static inline float8
+float8_div_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+ float8 result;
+
+ if (unlikely(val2 == 0.0) && !isnan(val1))
+ {
+ errsave(escontext,
+ errcode(ERRCODE_DIVISION_BY_ZERO),
+ errmsg("division by zero"));
+
+ result = 0.0;
+ return result;
+ }
+
+ result = val1 / val2;
+
+ if (unlikely(isinf(result)) && !isinf(val1))
+ {
+ errsave(escontext,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: overflow"));
+
+ result = 0.0;
+ return result;
+ }
+
+ if (unlikely(result == 0.0) && val1 != 0.0 && !isinf(val2))
+ {
+ errsave(escontext,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: underflow"));
+
+ result = 0.0;
+ return result;
+ }
+
+ return result;
+}
+
static inline float8
float8_div(const float8 val1, const float8 val2)
{
diff --git a/src/include/utils/geo_decls.h b/src/include/utils/geo_decls.h
index 8a9df75c93c..0056a26639f 100644
--- a/src/include/utils/geo_decls.h
+++ b/src/include/utils/geo_decls.h
@@ -88,7 +88,7 @@ FPge(double A, double B)
#define FPge(A,B) ((A) >= (B))
#endif
-#define HYPOT(A, B) pg_hypot(A, B)
+#define HYPOT(A, B, escontext) pg_hypot(A, B, escontext)
/*---------------------------------------------------------------------
* Point - (x,y)
@@ -280,6 +280,6 @@ CirclePGetDatum(const CIRCLE *X)
* in geo_ops.c
*/
-extern float8 pg_hypot(float8 x, float8 y);
+extern float8 pg_hypot(float8 x, float8 y, Node *escontext);
#endif /* GEO_DECLS_H */
--
2.34.1
v7-0001-error-safe-for-casting-bytea-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v7-0001-error-safe-for-casting-bytea-to-other-types-per-pg_cast.patchDownload
From 1338cc5c2f867ffad457fb5624eb9d0e0627e67c Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 15:36:35 +0800
Subject: [PATCH v7 01/20] error safe for casting bytea to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc and pc.castfunc > 0 and castsource::regtype ='bytea'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------+----------+-------------+------------+------------+---------
bytea | smallint | 6370 | e | f | bytea_int2 | int2
bytea | integer | 6371 | e | f | bytea_int4 | int4
bytea | bigint | 6372 | e | f | bytea_int8 | int8
(3 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/bytea.c | 18 +++++++++++++++---
1 file changed, 15 insertions(+), 3 deletions(-)
diff --git a/src/backend/utils/adt/bytea.c b/src/backend/utils/adt/bytea.c
index 6e7b914c563..4a8adcb8204 100644
--- a/src/backend/utils/adt/bytea.c
+++ b/src/backend/utils/adt/bytea.c
@@ -1027,10 +1027,14 @@ bytea_int2(PG_FUNCTION_ARGS)
/* Check that the byte array is not too long */
if (len > sizeof(result))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range"));
+ PG_RETURN_NULL();
+ }
+
/* Convert it to an integer; most significant bytes come first */
result = 0;
for (int i = 0; i < len; i++)
@@ -1052,10 +1056,14 @@ bytea_int4(PG_FUNCTION_ARGS)
/* Check that the byte array is not too long */
if (len > sizeof(result))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range"));
+ PG_RETURN_NULL();
+ }
+
/* Convert it to an integer; most significant bytes come first */
result = 0;
for (int i = 0; i < len; i++)
@@ -1077,10 +1085,14 @@ bytea_int8(PG_FUNCTION_ARGS)
/* Check that the byte array is not too long */
if (len > sizeof(result))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("bigint out of range"));
+ PG_RETURN_NULL();
+ }
+
/* Convert it to an integer; most significant bytes come first */
result = 0;
for (int i = 0; i < len; i++)
--
2.34.1
On Fri, Oct 10, 2025 at 8:23 PM jian he <jian.universality@gmail.com> wrote:
After applying the attached v7 patch patchset,
the below are cast functions that I didn't refactor to error safe yet.select pc.castsource::regtype,
pc.casttarget::regtype, castfunc::regproc, pp.prosrc
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
join pg_type pt on pt.oid = castsource
join pg_type pt1 on pt1.oid = casttarget
and pc.castfunc > 0 and pt.typnamespace = 'pg_catalog'::regnamespace
and pt1.typnamespace = 'pg_catalog'::regnamespace and not pc.casterrorsafe;castsource | casttarget | castfunc | prosrc
------------+------------+----------------------+--------------
bigint | money | pg_catalog.money | int8_cash
integer | money | pg_catalog.money | int4_cash
numeric | money | pg_catalog.money | numeric_cash
money | numeric | pg_catalog."numeric" | cash_numeric
(4 rows)The reason I don't refactor these cast functions related to money data
type is because
1. I am not sure if the PGLC_localeconv() function is error safe or not.
2. refactoring such ``DirectFunctionCall1(numeric_int8, amount));``
requires more effort.please check the attached v7 patch set:
01-error-safe-for-casting-bytea-to-other-types
02-error-safe-for-casting-bit-varbit-to-other-types
03-error-safe-for-casting-character-to-other-types
04-error-safe-for-casting-text-to-other-types
05-error-safe-for-casting-character-varying-to-other-types
06-error-safe-for-casting-inet-to-other-types
07-error-safe-for-casting-macaddr8-to-other-types
08-error-safe-for-casting-integer-to-other-types
09-error-safe-for-casting-bigint-to-other-types
10-error-safe-for-casting-numeric-to-other-types
11-error-safe-for-casting-float4-to-other-types
12-error-safe-for-casting-float8-to-other-types
13-error-safe-for-casting-date-to-other-types
14-error-safe-for-casting-interval-to-other-types
15-error-safe-for-casting-timestamptz-to-other-types
16-error-safe-for-casting-timestamp-to-other-types
17-error-safe-for-casting-jsonb-to-other-types
18-error-safe-for-casting-geometry-data-types
19-invent_some_error_safe_function.patch
20-CAST-expr-AS-newtype-DEFAULT-ON-ERROR.patchEach patch includes a commit message explaining which function is being
refactored to be error-safe.
Summary of what this patch set is doing:
To implement the syntax:
CAST(source_expr AS target_type DEFAULT def_expr ON CONVERSION ERROR)
we need to ensure that the transformed cast expression is error-safe.
A transformed cast expression can be either a CoerceViaIO or a FuncExpr.
Since CoerceViaIO is already error-safe but FuncExpr cannot be
rewritten as CoerceViaIO
one of the example: SELECT ('11.1'::numeric::int)
So we need to refactor how pg_cast.castfunc works to make it error-safe.
We also need to query pg_cast to determine whether a given pg_cast.castfunc is
error-safe. For this, a new field casterrorsafe is added to pg_cast.
Patches 01–18: Refactor all pg_cast.castfunc entries (except those with the
money data type) to be error-safe.
Patches 19–20: Implement support for CAST(... AS ... DEFAULT def_expr ON
CONVERSION ERROR)
Please check the attached v8.
It includes minor changes compared to v7:
mainly a small mistake fix in src/backend/jit/llvm/llvmjit_expr.c.
Additionally, one occurrence of NO_XML_SUPPORT(); was replaced with
errsave(escontext, …).
Attachments:
v8-0017-error-safe-for-casting-jsonb-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v8-0017-error-safe-for-casting-jsonb-to-other-types-per-pg_cast.patchDownload
From e015c06c2b620864c4203393d388a6b1091cbbf3 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 18:58:40 +0800
Subject: [PATCH v8 17/20] error safe for casting jsonb to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'jsonb'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+---------------+---------
jsonb | boolean | 3556 | e | f | jsonb_bool | bool
jsonb | numeric | 3449 | e | f | jsonb_numeric | numeric
jsonb | smallint | 3450 | e | f | jsonb_int2 | int2
jsonb | integer | 3451 | e | f | jsonb_int4 | int4
jsonb | bigint | 3452 | e | f | jsonb_int8 | int8
jsonb | real | 3453 | e | f | jsonb_float4 | float4
jsonb | double precision | 2580 | e | f | jsonb_float8 | float8
(7 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/jsonb.c | 91 +++++++++++++++++++++++++++++------
1 file changed, 75 insertions(+), 16 deletions(-)
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index da94d424d61..1ff00f72f78 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -2005,7 +2005,7 @@ JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res)
* Emit correct, translatable cast error message
*/
static void
-cannotCastJsonbValue(enum jbvType type, const char *sqltype)
+cannotCastJsonbValue(enum jbvType type, const char *sqltype, Node *escontext)
{
static const struct
{
@@ -2026,9 +2026,12 @@ cannotCastJsonbValue(enum jbvType type, const char *sqltype)
for (i = 0; i < lengthof(messages); i++)
if (messages[i].type == type)
- ereport(ERROR,
+ {
+ errsave(escontext,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(messages[i].msg, sqltype)));
+ return;
+ }
/* should be unreachable */
elog(ERROR, "unknown jsonb type: %d", (int) type);
@@ -2041,7 +2044,11 @@ jsonb_bool(PG_FUNCTION_ARGS)
JsonbValue v;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "boolean");
+ {
+ cannotCastJsonbValue(v.type, "boolean", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2050,7 +2057,11 @@ jsonb_bool(PG_FUNCTION_ARGS)
}
if (v.type != jbvBool)
- cannotCastJsonbValue(v.type, "boolean");
+ {
+ cannotCastJsonbValue(v.type, "boolean", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
PG_FREE_IF_COPY(in, 0);
@@ -2065,7 +2076,11 @@ jsonb_numeric(PG_FUNCTION_ARGS)
Numeric retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "numeric");
+ {
+ cannotCastJsonbValue(v.type, "numeric", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2074,7 +2089,11 @@ jsonb_numeric(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "numeric");
+ {
+ cannotCastJsonbValue(v.type, "numeric", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
/*
* v.val.numeric points into jsonb body, so we need to make a copy to
@@ -2095,7 +2114,11 @@ jsonb_int2(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "smallint");
+ {
+ cannotCastJsonbValue(v.type, "smallint", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2104,7 +2127,11 @@ jsonb_int2(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "smallint");
+ {
+ cannotCastJsonbValue(v.type, "smallint", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_int2,
NumericGetDatum(v.val.numeric));
@@ -2122,7 +2149,11 @@ jsonb_int4(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "integer");
+ {
+ cannotCastJsonbValue(v.type, "integer", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2131,7 +2162,11 @@ jsonb_int4(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "integer");
+ {
+ cannotCastJsonbValue(v.type, "integer", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_int4,
NumericGetDatum(v.val.numeric));
@@ -2149,7 +2184,11 @@ jsonb_int8(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "bigint");
+ {
+ cannotCastJsonbValue(v.type, "bigint", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2158,7 +2197,11 @@ jsonb_int8(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "bigint");
+ {
+ cannotCastJsonbValue(v.type, "bigint", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_int8,
NumericGetDatum(v.val.numeric));
@@ -2176,7 +2219,11 @@ jsonb_float4(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "real");
+ {
+ cannotCastJsonbValue(v.type, "real", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2185,7 +2232,11 @@ jsonb_float4(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "real");
+ {
+ cannotCastJsonbValue(v.type, "real", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_float4,
NumericGetDatum(v.val.numeric));
@@ -2203,7 +2254,11 @@ jsonb_float8(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "double precision");
+ {
+ cannotCastJsonbValue(v.type, "double precision", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2212,7 +2267,11 @@ jsonb_float8(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "double precision");
+ {
+ cannotCastJsonbValue(v.type, "double precision", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_float8,
NumericGetDatum(v.val.numeric));
--
2.34.1
v8-0019-invent-some-error-safe-functions.patchtext/x-patch; charset=US-ASCII; name=v8-0019-invent-some-error-safe-functions.patchDownload
From f020e840b5cf648f3ef8ac726ad733ff8b4983c6 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Fri, 10 Oct 2025 18:57:21 +0800
Subject: [PATCH v8 19/20] invent some error safe functions
stringTypeDatumSafe: error safe version of stringTypeDatum
evaluate_expr_safe: error safe version of evaluate_expr
ExecInitExprSafe: soft error variant of ExecInitExpr
OidInputFunctionCallSafe: soft error variant of OidInputFunctionCall
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/executor/execExpr.c | 41 +++++++++++++++++
src/backend/optimizer/util/clauses.c | 69 ++++++++++++++++++++++++++++
src/backend/parser/parse_type.c | 14 ++++++
src/backend/utils/fmgr/fmgr.c | 13 ++++++
src/include/executor/executor.h | 1 +
src/include/fmgr.h | 3 ++
src/include/optimizer/optimizer.h | 2 +
src/include/parser/parse_type.h | 2 +
8 files changed, 145 insertions(+)
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index f1569879b52..b302be36f73 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -170,6 +170,47 @@ ExecInitExpr(Expr *node, PlanState *parent)
return state;
}
+/*
+ * ExecInitExprSafe: soft error variant of ExecInitExpr.
+ *
+ * use it only for expression nodes support soft errors, not all expression
+ * nodes support it.
+*/
+ExprState *
+ExecInitExprSafe(Expr *node, PlanState *parent)
+{
+ ExprState *state;
+ ExprEvalStep scratch = {0};
+
+ /* Special case: NULL expression produces a NULL ExprState pointer */
+ if (node == NULL)
+ return NULL;
+
+ /* Initialize ExprState with empty step list */
+ state = makeNode(ExprState);
+ state->expr = node;
+ state->parent = parent;
+ state->ext_params = NULL;
+ state->escontext = makeNode(ErrorSaveContext);
+ state->escontext->type = T_ErrorSaveContext;
+ state->escontext->error_occurred = false;
+ state->escontext->details_wanted = false;
+
+ /* Insert setup steps as needed */
+ ExecCreateExprSetupSteps(state, (Node *) node);
+
+ /* Compile the expression proper */
+ ExecInitExprRec(node, state, &state->resvalue, &state->resnull);
+
+ /* Finally, append a DONE step */
+ scratch.opcode = EEOP_DONE_RETURN;
+ ExprEvalPushStep(state, &scratch);
+
+ ExecReadyExpr(state);
+
+ return state;
+}
+
/*
* ExecInitExprWithParams: prepare a standalone expression tree for execution
*
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 81d768ff2a2..3f9ff455ba6 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -5147,6 +5147,75 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
resultTypByVal);
}
+/*
+ * evaluate_expr_safe: error safe version of evaluate_expr
+ *
+ * We use the executor's routine ExecEvalExpr() to avoid duplication of
+ * code and ensure we get the same result as the executor would get.
+ *
+ * return NULL when evaulation failed. Ensure the expression expr can be evaulated
+ * in a soft error way.
+ *
+ * See comments on evaluate_expr too.
+ */
+Expr *
+evaluate_expr_safe(Expr *expr, Oid result_type, int32 result_typmod,
+ Oid result_collation)
+{
+ EState *estate;
+ ExprState *exprstate;
+ MemoryContext oldcontext;
+ Datum const_val;
+ bool const_is_null;
+ int16 resultTypLen;
+ bool resultTypByVal;
+
+ estate = CreateExecutorState();
+
+ /* We can use the estate's working context to avoid memory leaks. */
+ oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
+
+ /* Make sure any opfuncids are filled in. */
+ fix_opfuncids((Node *) expr);
+
+ /*
+ * Prepare expr for execution. (Note: we can't use ExecPrepareExpr
+ * because it'd result in recursively invoking eval_const_expressions.)
+ */
+ exprstate = ExecInitExprSafe(expr, NULL);
+
+ const_val = ExecEvalExprSwitchContext(exprstate,
+ GetPerTupleExprContext(estate),
+ &const_is_null);
+
+ /* Get info needed about result datatype */
+ get_typlenbyval(result_type, &resultTypLen, &resultTypByVal);
+
+ /* Get back to outer memory context */
+ MemoryContextSwitchTo(oldcontext);
+
+ if (SOFT_ERROR_OCCURRED(exprstate->escontext))
+ {
+ FreeExecutorState(estate);
+ return NULL;
+ }
+
+ if (!const_is_null)
+ {
+ if (resultTypLen == -1)
+ const_val = PointerGetDatum(PG_DETOAST_DATUM_COPY(const_val));
+ else
+ const_val = datumCopy(const_val, resultTypByVal, resultTypLen);
+ }
+
+ FreeExecutorState(estate);
+
+ return (Expr *) makeConst(result_type, result_typmod, result_collation,
+ resultTypLen,
+ const_val, const_is_null,
+ resultTypByVal);
+}
+
/*
* inline_set_returning_function
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index 7713bdc6af0..d260aeec5dc 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,19 @@ stringTypeDatum(Type tp, char *string, int32 atttypmod)
return OidInputFunctionCall(typinput, string, typioparam, atttypmod);
}
+/* error safe version of stringTypeDatum */
+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,
+ (Node *) &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/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index 0fe63c6bb83..aaa4a42b1ea 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, Node *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/executor.h b/src/include/executor/executor.h
index 3248e78cd28..ff9997e1cc1 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -321,6 +321,7 @@ ExecProcNode(PlanState *node)
* prototypes from functions in execExpr.c
*/
extern ExprState *ExecInitExpr(Expr *node, PlanState *parent);
+extern ExprState *ExecInitExprSafe(Expr *node, PlanState *parent);
extern ExprState *ExecInitExprWithParams(Expr *node, ParamListInfo ext_params);
extern ExprState *ExecInitQual(List *qual, PlanState *parent);
extern ExprState *ExecInitCheck(List *qual, PlanState *parent);
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index 74fe3ea0575..991e14034d3 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, Node *escontext,
+ Datum *result);
extern char *OutputFunctionCall(FmgrInfo *flinfo, Datum val);
extern char *OidOutputFunctionCall(Oid functionId, Datum val);
extern Datum ReceiveFunctionCall(FmgrInfo *flinfo, StringInfo buf,
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index a34113903c0..f1b6381d084 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -146,6 +146,8 @@ extern Node *estimate_expression_value(PlannerInfo *root, Node *node);
extern Expr *evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
Oid result_collation);
+extern Expr *evaluate_expr_safe(Expr *expr, Oid result_type, int32 result_typmod,
+ Oid result_collation);
extern bool var_is_nonnullable(PlannerInfo *root, Var *var, bool use_rel_info);
extern List *expand_function_arguments(List *args, bool include_out_arguments,
diff --git a/src/include/parser/parse_type.h b/src/include/parser/parse_type.h
index 0d919d8bfa2..12381aed64c 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);
--
2.34.1
v8-0020-CAST-expr-AS-newtype-DEFAULT-ON-ERROR.patchtext/x-patch; charset=UTF-8; name=v8-0020-CAST-expr-AS-newtype-DEFAULT-ON-ERROR.patchDownload
From 7e302066156bfbb9fe2159139259ddd2027aaeca Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 11 Oct 2025 09:35:33 +0800
Subject: [PATCH v8 20/20] CAST(expr AS newtype DEFAULT ON ERROR)
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.
draft doc also added.
We cannot simply prohibit user-defined functions in pg_cast for safe cast
evaluation because CREATE CAST can also utilize built-in functions. So, to
completely disallow custom casts created via CREATE CAST used in safe cast
evaluation, a new field in pg_cast would unfortunately be necessary.
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}
[0]: https://git.postgresql.org/cgit/postgresql.git/commit/?id=aaaf9449ec6be62cb0d30ed3588dc384f56274b
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
.../pg_stat_statements/expected/select.out | 23 +-
contrib/pg_stat_statements/sql/select.sql | 5 +
doc/src/sgml/catalogs.sgml | 12 +
doc/src/sgml/syntax.sgml | 16 +
src/backend/catalog/pg_cast.c | 1 +
src/backend/executor/execExpr.c | 76 +-
src/backend/executor/execExprInterp.c | 37 +
src/backend/jit/llvm/llvmjit_expr.c | 49 ++
src/backend/nodes/nodeFuncs.c | 67 ++
src/backend/optimizer/util/clauses.c | 24 +
src/backend/parser/gram.y | 24 +-
src/backend/parser/parse_agg.c | 9 +
src/backend/parser/parse_expr.c | 383 +++++++++-
src/backend/parser/parse_func.c | 3 +
src/backend/parser/parse_target.c | 14 +
src/backend/utils/adt/arrayfuncs.c | 13 +
src/backend/utils/adt/ruleutils.c | 16 +
src/include/catalog/pg_cast.dat | 330 ++++-----
src/include/catalog/pg_cast.h | 4 +
src/include/executor/execExpr.h | 8 +
src/include/nodes/execnodes.h | 30 +
src/include/nodes/parsenodes.h | 6 +
src/include/nodes/primnodes.h | 33 +
src/include/parser/parse_node.h | 2 +
src/test/regress/expected/cast.out | 681 ++++++++++++++++++
src/test/regress/expected/create_cast.out | 5 +
src/test/regress/expected/opr_sanity.out | 24 +-
src/test/regress/parallel_schedule | 2 +-
src/test/regress/sql/cast.sql | 301 ++++++++
src/test/regress/sql/create_cast.sql | 1 +
src/tools/pgindent/typedefs.list | 3 +
31 files changed, 2012 insertions(+), 190 deletions(-)
create mode 100644 src/test/regress/expected/cast.out
create mode 100644 src/test/regress/sql/cast.sql
diff --git a/contrib/pg_stat_statements/expected/select.out b/contrib/pg_stat_statements/expected/select.out
index 75c896f3885..6e67997f0a2 100644
--- a/contrib/pg_stat_statements/expected/select.out
+++ b/contrib/pg_stat_statements/expected/select.out
@@ -73,6 +73,25 @@ SELECT 1 AS "int" OFFSET 2 FETCH FIRST 3 ROW ONLY;
-----
(0 rows)
+--error safe type cast
+SELECT CAST('a' AS int DEFAULT 2 ON CONVERSION ERROR);
+ int4
+------
+ 2
+(1 row)
+
+SELECT CAST('11' AS int DEFAULT 2 ON CONVERSION ERROR);
+ int4
+------
+ 11
+(1 row)
+
+SELECT CAST('12' AS numeric DEFAULT 2 ON CONVERSION ERROR);
+ numeric
+---------
+ 12
+(1 row)
+
-- DISTINCT and ORDER BY patterns
-- Try some query permutations which once produced identical query IDs
SELECT DISTINCT 1 AS "int";
@@ -222,6 +241,8 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
2 | 2 | SELECT $1 AS "int" ORDER BY 1
1 | 2 | SELECT $1 AS i UNION SELECT $2 ORDER BY i
1 | 1 | SELECT $1 || $2
+ 2 | 2 | SELECT CAST($1 AS int DEFAULT $2 ON CONVERSION ERROR)
+ 1 | 1 | SELECT CAST($1 AS numeric DEFAULT $2 ON CONVERSION ERROR)
2 | 2 | SELECT DISTINCT $1 AS "int"
0 | 0 | SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"
1 | 1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
@@ -230,7 +251,7 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
| | ) +
| | SELECT f FROM t ORDER BY f
1 | 1 | select $1::jsonb ? $2
-(17 rows)
+(19 rows)
SELECT pg_stat_statements_reset() IS NOT NULL AS t;
t
diff --git a/contrib/pg_stat_statements/sql/select.sql b/contrib/pg_stat_statements/sql/select.sql
index 11662cde08c..7ee8160fd84 100644
--- a/contrib/pg_stat_statements/sql/select.sql
+++ b/contrib/pg_stat_statements/sql/select.sql
@@ -25,6 +25,11 @@ SELECT 1 AS "int" LIMIT 3 OFFSET 3;
SELECT 1 AS "int" OFFSET 1 FETCH FIRST 2 ROW ONLY;
SELECT 1 AS "int" OFFSET 2 FETCH FIRST 3 ROW ONLY;
+--error safe type cast
+SELECT CAST('a' AS int DEFAULT 2 ON CONVERSION ERROR);
+SELECT CAST('11' AS int DEFAULT 2 ON CONVERSION ERROR);
+SELECT CAST('12' AS numeric DEFAULT 2 ON CONVERSION ERROR);
+
-- DISTINCT and ORDER BY patterns
-- Try some query permutations which once produced identical query IDs
SELECT DISTINCT 1 AS "int";
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 9b3aae8603b..957041add7b 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1849,6 +1849,18 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<literal>b</literal> means that the types are binary-coercible, thus no conversion is required.
</para></entry>
</row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>casterrorsafe</structfield> <type>bool</type>
+ </para>
+ <para>
+ This indicates whether the <structfield>castfunc</structfield> function is error safe.
+ If the <structfield>castfunc</structfield> function is error safe, it can be used in
+ For further details see <xref linkend="sql-syntax-type-casts"/>.
+ </para></entry>
+ </row>
+
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml
index 237d7306fe8..9acb7d69645 100644
--- a/doc/src/sgml/syntax.sgml
+++ b/doc/src/sgml/syntax.sgml
@@ -2106,6 +2106,22 @@ CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable>
The <literal>CAST</literal> syntax conforms to SQL; the syntax with
<literal>::</literal> is historical <productname>PostgreSQL</productname>
usage.
+ It can also be written as:
+<synopsis>
+CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable> ERROR ON CONVERSION ERROR )
+</synopsis>
+ </para>
+
+ <para>
+Type cast can also be error safe.
+<synopsis>
+CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable> DEFAULT <replaceable>defexpression</replaceable> ON CONVERSION ERROR )
+</synopsis>
+ For example, the following query will evaluate the default expression and return 42.
+ TODO: more explanation
+<programlisting>
+SELECT CAST('error' AS integer DEFAULT 42 ON CONVERSION ERROR);
+</programlisting>
</para>
<para>
diff --git a/src/backend/catalog/pg_cast.c b/src/backend/catalog/pg_cast.c
index 1773c9c5491..6fe65d24d31 100644
--- a/src/backend/catalog/pg_cast.c
+++ b/src/backend/catalog/pg_cast.c
@@ -84,6 +84,7 @@ CastCreate(Oid sourcetypeid, Oid targettypeid,
values[Anum_pg_cast_castfunc - 1] = ObjectIdGetDatum(funcid);
values[Anum_pg_cast_castcontext - 1] = CharGetDatum(castcontext);
values[Anum_pg_cast_castmethod - 1] = CharGetDatum(castmethod);
+ values[Anum_pg_cast_casterrorsafe - 1] = BoolGetDatum(false);
tuple = heap_form_tuple(RelationGetDescr(relation), values, nulls);
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index b302be36f73..88a647e44fa 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,
@@ -1742,6 +1745,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
elemstate->innermost_caseval = (Datum *) palloc(sizeof(Datum));
elemstate->innermost_casenull = (bool *) palloc(sizeof(bool));
+ elemstate->escontext = state->escontext;
ExecInitExprRec(acoerce->elemexpr, elemstate,
&elemstate->resvalue, &elemstate->resnull);
@@ -2218,6 +2222,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;
@@ -2784,7 +2796,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;
@@ -4782,6 +4794,68 @@ ExecBuildParamSetEqual(TupleDesc desc,
return state;
}
+/*
+ * Push steps to evaluate a SafeTypeCastExpr and its various subsidiary
+ * expressions.
+ */
+static void
+ExecInitSafeTypeCastExpr(SafeTypeCastExpr *stcexpr , ExprState *state,
+ Datum *resv, bool *resnull,
+ ExprEvalStep *scratch)
+{
+ /*
+ * 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
+ {
+ ExprEvalStep *as;
+ SafeTypeCastState *stcstate;
+ ErrorSaveContext *saved_escontext;
+ int jumps_to_end;
+
+ stcstate = palloc0(sizeof(SafeTypeCastState));
+ stcstate->stcexpr = stcexpr;
+ stcstate->escontext.type = T_ErrorSaveContext;
+ state->escontext = &stcstate->escontext;
+
+ /* evaluate argument expression into step's result area */
+ ExecInitExprRec((Expr *) stcexpr->cast_expr,
+ 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 = 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;
+
+ as = &state->steps[jumps_to_end];
+ 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 67f4e00eac4..49da2905810 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,12 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
EEO_NEXT();
}
+ EEO_CASE(EEOP_SAFETYPE_CAST)
+ {
+ /* too complex for an inline implementation */
+ EEO_JUMP(ExecEvalSafeTypeCast(state, op));
+ }
+
EEO_CASE(EEOP_JSONEXPR_PATH)
{
/* too complex for an inline implementation */
@@ -3644,6 +3651,12 @@ 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;
+ }
}
/*
@@ -5183,6 +5196,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 712b35df7e5..f54fe563543 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 ede838cd40c..533e21120a6 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;
@@ -1550,6 +1561,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;
@@ -2321,6 +2341,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:
@@ -3330,6 +3362,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;
@@ -4464,6 +4509,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->def_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/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 3f9ff455ba6..14e0df3bee2 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -2945,6 +2945,30 @@ 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);
+
+ /*
+ * We must not reduce any recognizably constant subexpressions
+ * in cast_expr here, since we don’t want it to fail
+ * prematurely.
+ */
+ 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 21caf2d43bf..c62e9ac8edf 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -649,6 +649,7 @@ 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_opt
%type <node> json_format_clause
json_format_clause_opt
@@ -15982,8 +15983,20 @@ 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_opt ')'
+ {
+ TypeCast *cast = (TypeCast *) makeTypeCast($3, $5, @1);
+ if ($6 == NULL)
+ $$ = (Node *) cast;
+ else
+ {
+ SafeTypeCast *safecast = makeNode(SafeTypeCast);
+ safecast->cast = (Node *) cast;
+ safecast->def_expr = $6;
+
+ $$ = (Node *) safecast;
+ }
+ }
| EXTRACT '(' extract_list ')'
{
$$ = (Node *) makeFuncCall(SystemFuncName("extract"),
@@ -16370,6 +16383,13 @@ func_expr_common_subexpr:
;
+cast_on_error_clause_opt:
+ DEFAULT a_expr ON CONVERSION_P ERROR_P { $$ = $2; }
+ | ERROR_P ON CONVERSION_P ERROR_P { $$ = NULL; }
+ | NULL_P ON CONVERSION_P ERROR_P { $$ = makeNullAConst(-1); }
+ | /* EMPTY */ { $$ = NULL; }
+ ;
+
/*
* SQL/XML support
*/
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 3254c83cc6c..6573e2a93ea 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -486,6 +486,12 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
err = _("grouping operations are not allowed in check constraints");
break;
+ case EXPR_KIND_CAST_DEFAULT:
+ if (isAgg)
+ err = _("aggregate functions are not allowed in CAST DEFAULT expressions");
+ else
+ err = _("grouping operations are not allowed in CAST DEFAULT expressions");
+ break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
@@ -956,6 +962,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_DOMAIN_CHECK:
err = _("window functions are not allowed in check constraints");
break;
+ case EXPR_KIND_CAST_DEFAULT:
+ err = _("window functions are not allowed in CAST DEFAULT expressions");
+ break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
err = _("window functions are not allowed in DEFAULT expressions");
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 12119f147fc..ac60b74dceb 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -17,6 +17,8 @@
#include "access/htup_details.h"
#include "catalog/pg_aggregate.h"
+#include "catalog/pg_cast.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -37,6 +39,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 +63,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 *can_coerce);
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 +80,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);
@@ -107,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 CoerceUnknownConstSafe(ParseState *pstate, Node *node,
+ Oid targetType, int32 targetTypeMod);
/*
@@ -164,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;
@@ -576,6 +587,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
case EXPR_KIND_COPY_WHERE:
case EXPR_KIND_GENERATED_COLUMN:
case EXPR_KIND_CYCLE_MARK:
+ case EXPR_KIND_CAST_DEFAULT:
/* okay */
break;
@@ -1824,6 +1836,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_DOMAIN_CHECK:
err = _("cannot use subquery in check constraint");
break;
+ case EXPR_KIND_CAST_DEFAULT:
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
err = _("cannot use subquery in DEFAULT expression");
@@ -2005,16 +2018,73 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
return result;
}
+/*
+ * Return true if successfuly coerced a Unknown Const to targetType
+*/
+static bool
+CoerceUnknownConstSafe(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;
+}
+
+
/*
* 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().
+ *
+ * Most of the time, the caller will pass can_coerce as NULL.
+ * can_coerce is not NULL only when performing parse analysis for expressions
+ * like CAST(DEFAULT ... ON CONVERSION ERROR).
+ * When can_coerce is not NULL, it should be assumed to default to true. If
+ * coerce array elements to the target type fails, can_coerce will be set to
+ * 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;
@@ -2022,6 +2092,7 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
ListCell *element;
Oid coerce_type;
bool coerce_hard;
+ bool expr_is_const = false;
/*
* Transform the element expressions
@@ -2045,9 +2116,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
@@ -2088,6 +2160,9 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
}
else
{
+ /* We obviously know the target type when performing type-safe cast */
+ Assert(can_coerce == NULL);
+
/* Can't handle an empty array without a target type */
if (newelems == NIL)
ereport(ERROR,
@@ -2140,7 +2215,36 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
Node *e = (Node *) lfirst(element);
Node *newe;
- if (coerce_hard)
+ /*
+ * For error-safe type casting, we need to safely check whether the
+ * UNKNOWN constant can be coerced to the target type. If it cannot, set
+ * can_coerce to false and coerce source element to TEXT data type.
+ * see coerce_type function also.
+ */
+ if (can_coerce != NULL && IsA(e, Const))
+ {
+ if (exprType(e) == UNKNOWNOID)
+ {
+ if (!CoerceUnknownConstSafe(pstate, e, coerce_type, typmod))
+ {
+ *can_coerce = false;
+
+ /* see resolveTargetListUnknowns also */
+ e = coerce_to_target_type(pstate, e,
+ UNKNOWNOID,
+ TEXTOID,
+ -1,
+ COERCION_IMPLICIT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ }
+ }
+ expr_is_const = true;
+ }
+
+ if (can_coerce != NULL && (!*can_coerce))
+ newe = e;
+ else if (coerce_hard)
{
newe = coerce_to_target_type(pstate, e,
exprType(e),
@@ -2149,13 +2253,51 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
COERCION_EXPLICIT,
COERCE_EXPLICIT_CAST,
-1);
- if (newe == NULL)
+ if (newe == NULL && can_coerce == NULL)
ereport(ERROR,
(errcode(ERRCODE_CANNOT_COERCE),
errmsg("cannot cast type %s to %s",
format_type_be(exprType(e)),
format_type_be(coerce_type)),
parser_errposition(pstate, exprLocation(e))));
+
+ if (can_coerce != NULL)
+ {
+ if (newe == NULL)
+ {
+ newe = e;
+ *can_coerce = false;
+ }
+ else if (expr_is_const && !IsA(newe, Const))
+ {
+ /*
+ * Use evaluate_expr_safe to pre-evaluate simple constant
+ * cast expressions early, in a way that tolter errors .
+ *
+ * Rationale:
+ * 1. When deparsing safe cast expressions (or in other
+ * cases), eval_const_expressions might be invoked, but
+ * it cannot handle errors gracefully.
+ * 2. If the cast expression involves only simple constants,
+ * we can safely evaluate it ahead of time. If the
+ * evaluation fails, it indicates that such a cast is not
+ * possible, and we can then fall back to the CAST
+ * DEFAULT expression.
+ */
+ Expr *newe_copy = (Expr *) copyObject(newe);
+
+ newe = (Node *) evaluate_expr_safe(newe_copy,
+ coerce_type,
+ typmod,
+ exprCollation(newe));
+ if (newe == NULL)
+ {
+ newe = e;
+ *can_coerce = false;
+ }
+ }
+ expr_is_const = false;
+ }
}
else
newe = coerce_to_common_type(pstate, e,
@@ -2743,7 +2885,8 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
(A_ArrayExpr *) arg,
targetBaseType,
elementType,
- targetBaseTypmod);
+ targetBaseTypmod,
+ NULL);
}
else
expr = transformExprRecurse(pstate, arg);
@@ -2780,6 +2923,228 @@ 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;
+ TypeCast *tcast = (TypeCast *) tc->cast;
+ Node *def_expr = NULL;
+ Node *cast_expr = NULL;
+ Node *source_expr = NULL;
+ Oid inputType = InvalidOid;
+ Oid targetType;
+ Oid targetBaseType;
+ Oid typeColl;
+ int32 targetTypmod;
+ int32 targetBaseTypmod;
+ bool can_coerce = true;
+ bool expr_is_const = false;
+ int location;
+
+ /* Look up the type name first */
+ typenameTypeIdAndMod(pstate, tcast->typeName, &targetType, &targetTypmod);
+ targetBaseTypmod = targetTypmod;
+
+ typeColl = get_typcollation(targetType);
+ targetBaseType = getBaseTypeAndTypmod(targetType, &targetBaseTypmod);
+
+ /* now looking at cast default expression */
+ def_expr = transformExpr(pstate, tc->def_expr, EXPR_KIND_CAST_DEFAULT);
+
+ def_expr = coerce_to_target_type(pstate, def_expr, exprType(def_expr),
+ targetType, targetTypmod,
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ exprLocation(def_expr));
+
+ assign_expr_collations(pstate, def_expr);
+
+ if (def_expr == NULL)
+ ereport(ERROR,
+ errcode(ERRCODE_CANNOT_COERCE),
+ errmsg("cannot coerce %s expression to type %s",
+ "CAST DEFAULT",
+ format_type_be(targetType)),
+ parser_coercion_errposition(pstate, exprLocation(tc->def_expr), def_expr));
+
+ /*
+ * The collation of DEFAULT expression must match the collation of the
+ * target type.
+ */
+ if (OidIsValid(typeColl))
+ {
+ Oid defColl;
+
+ defColl = exprCollation(def_expr);
+
+ if (OidIsValid(defColl) && typeColl != defColl)
+ ereport(ERROR,
+ errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("the collation of CAST DEFAULT expression conflicts with target type collation"),
+ errdetail("\"%s\" versus \"%s\"",
+ get_collation_name(typeColl),
+ get_collation_name(defColl)),
+ parser_errposition(pstate, exprLocation(def_expr)));
+ }
+
+ /*
+ * If the type cast 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(tcast->arg, A_ArrayExpr))
+ {
+ 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.
+ */
+ elementType = get_element_type(targetBaseType);
+
+ if (OidIsValid(elementType))
+ {
+ source_expr = transformArrayExpr(pstate,
+ (A_ArrayExpr *) tcast->arg,
+ targetBaseType,
+ elementType,
+ targetBaseTypmod,
+ &can_coerce);
+ }
+ else
+ source_expr = transformExprRecurse(pstate, tcast->arg);
+ }
+ else
+ source_expr = transformExprRecurse(pstate, tcast->arg);
+
+ inputType = exprType(source_expr);
+ if (inputType == InvalidOid)
+ return (Node *) NULL; /* return NULL if NULL input */
+
+ if (can_coerce && IsA(source_expr, Const))
+ {
+ if (exprType(source_expr) == UNKNOWNOID)
+ can_coerce = CoerceUnknownConstSafe(pstate,
+ source_expr,
+ targetType,
+ targetTypmod);
+ expr_is_const = true;
+ }
+
+ /*
+ * Location of the coercion is preferentially the location of 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)
+ {
+ Node *origexpr;
+
+ cast_expr = coerce_to_target_type(pstate, source_expr, inputType,
+ targetType, targetTypmod,
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ location);
+ origexpr = cast_expr;
+
+ while (cast_expr && IsA(cast_expr, CollateExpr))
+ cast_expr = (Node *) ((CollateExpr *) cast_expr)->arg;
+
+ if (cast_expr != NULL && IsA(cast_expr, FuncExpr))
+ {
+ HeapTuple tuple;
+ ListCell *lc;
+ Node *sexpr;
+ FuncExpr *fexpr = (FuncExpr *) cast_expr;
+
+ lc = list_head(fexpr->args);
+ sexpr = (Node *) lfirst(lc);
+
+ /* Look in pg_cast */
+ tuple = SearchSysCache2(CASTSOURCETARGET,
+ ObjectIdGetDatum(exprType(sexpr)),
+ ObjectIdGetDatum(targetType));
+
+ if (HeapTupleIsValid(tuple))
+ {
+ Form_pg_cast castForm = (Form_pg_cast) GETSTRUCT(tuple);
+ if (!castForm->casterrorsafe)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot cast type %s to %s when %s expression is specified in %s",
+ format_type_be(inputType),
+ format_type_be(targetType),
+ "DEFAULT",
+ "CAST ... ON CONVERSION ERROR"),
+ errhint("Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast"),
+ parser_errposition(pstate, exprLocation(source_expr)));
+ }
+ else
+ elog(ERROR, "cache lookup failed for pg_cast entry (%s cast to %s)",
+ format_type_be(inputType),
+ format_type_be(targetType));
+ ReleaseSysCache(tuple);
+
+ if (expr_is_const)
+ {
+ /*
+ * Use evaluate_expr_safe to pre-evaluate simple constant cast
+ * expressions early, in a way that tolter errors .
+ *
+ * Rationale:
+ * 1. When deparsing safe cast expressions (or in other cases),
+ * eval_const_expressions might be invoked, but it cannot
+ * handle errors gracefully.
+ * 2. If the cast expression involves only simple constants, we
+ * can safely evaluate it ahead of time. If the evaluation
+ * fails, it indicates that such a cast is not possible, and
+ * we can then fall back to the CAST DEFAULT expression.
+ */
+ FuncExpr *newexpr = (FuncExpr *) copyObject(cast_expr);
+
+ assign_expr_collations(pstate, (Node *) newexpr);
+
+ cast_expr = (Node *) evaluate_expr_safe((Expr *) newexpr,
+ targetType,
+ targetTypmod,
+ typeColl);
+ }
+ }
+
+ /*
+ * set cast_expr back as is, cast_expr can be NULL after
+ * evaluate_expr_safe
+ */
+ if (cast_expr != NULL)
+ cast_expr = origexpr;
+ }
+
+ Assert(can_coerce || cast_expr == NULL);
+
+ result = makeNode(SafeTypeCastExpr);
+ result->source_expr = source_expr;
+ result->cast_expr = cast_expr;
+ result->default_expr = def_expr;
+ result->resulttype = targetType;
+ result->resulttypmod = targetTypmod;
+ result->resultcollid = typeColl;
+
+ return (Node *) result;
+}
+
/*
* Handle an explicit COLLATE clause.
*
@@ -3193,6 +3558,8 @@ ParseExprKindName(ParseExprKind exprKind)
case EXPR_KIND_CHECK_CONSTRAINT:
case EXPR_KIND_DOMAIN_CHECK:
return "CHECK";
+ case EXPR_KIND_CAST_DEFAULT:
+ return "CAST DEFAULT";
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
return "DEFAULT";
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 778d69c6f3c..a90705b9847 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2743,6 +2743,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
case EXPR_KIND_DOMAIN_CHECK:
err = _("set-returning functions are not allowed in check constraints");
break;
+ case EXPR_KIND_CAST_DEFAULT:
+ err = _("set-returning functions are not allowed in CAST DEFAULT expressions");
+ break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
err = _("set-returning functions are not allowed in DEFAULT expressions");
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 905c975d83b..d67e5f8a5b7 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1822,6 +1822,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/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index a8951f55b93..31cb36a56e6 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -3289,6 +3289,19 @@ 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 (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 050eef97a4c..4278e44b4a9 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -10558,6 +10558,22 @@ 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/include/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat
index fbfd669587f..1ef59a982af 100644
--- a/src/include/catalog/pg_cast.dat
+++ b/src/include/catalog/pg_cast.dat
@@ -19,65 +19,65 @@
# int2->int4->int8->numeric->float4->float8, while casts in the
# reverse direction are assignment-only.
{ castsource => 'int8', casttarget => 'int2', castfunc => 'int2(int8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'int4', castfunc => 'int4(int8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'float4', castfunc => 'float4(int8)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'float8', castfunc => 'float8(int8)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'numeric', castfunc => 'numeric(int8)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'int8', castfunc => 'int8(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'int4', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'float4', castfunc => 'float4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'float8', castfunc => 'float8(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'numeric', castfunc => 'numeric(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'int8', castfunc => 'int8(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'int2', castfunc => 'int2(int4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'float4', castfunc => 'float4(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'float8', castfunc => 'float8(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'numeric', castfunc => 'numeric(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'int8', castfunc => 'int8(float4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'int2', castfunc => 'int2(float4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'int4', castfunc => 'int4(float4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'float8', castfunc => 'float8(float4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'numeric',
- castfunc => 'numeric(float4)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'numeric(float4)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'int8', castfunc => 'int8(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'int2', castfunc => 'int2(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'int4', castfunc => 'int4(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'float4', castfunc => 'float4(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'numeric',
- castfunc => 'numeric(float8)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'numeric(float8)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'int8', castfunc => 'int8(numeric)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'int2', castfunc => 'int2(numeric)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'int4', castfunc => 'int4(numeric)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'float4',
- castfunc => 'float4(numeric)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'float4(numeric)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'float8',
- castfunc => 'float8(numeric)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'float8(numeric)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'money', casttarget => 'numeric', castfunc => 'numeric(money)',
castcontext => 'a', castmethod => 'f' },
{ castsource => 'numeric', casttarget => 'money', castfunc => 'money(numeric)',
@@ -89,13 +89,13 @@
# Allow explicit coercions between int4 and bool
{ castsource => 'int4', casttarget => 'bool', castfunc => 'bool(int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'int4', castfunc => 'int4(bool)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Allow explicit coercions between xid8 and xid
{ castsource => 'xid8', casttarget => 'xid', castfunc => 'xid(xid8)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# OID category: allow implicit conversion from any integral type (including
# int8, to support OID literals > 2G) to OID, as well as assignment coercion
@@ -106,13 +106,13 @@
# casts from text and varchar to regclass, which exist mainly to support
# legacy forms of nextval() and related functions.
{ castsource => 'int8', casttarget => 'oid', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'oid', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'oid', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regproc', castfunc => '0',
@@ -120,13 +120,13 @@
{ castsource => 'regproc', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regproc', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regproc', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regproc', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regproc', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regproc', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'regproc', casttarget => 'regprocedure', castfunc => '0',
@@ -138,13 +138,13 @@
{ castsource => 'regprocedure', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regprocedure', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regprocedure', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regprocedure', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regprocedure', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regprocedure', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regoper', castfunc => '0',
@@ -152,13 +152,13 @@
{ castsource => 'regoper', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regoper', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regoper', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regoper', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regoper', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regoper', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'regoper', casttarget => 'regoperator', castfunc => '0',
@@ -170,13 +170,13 @@
{ castsource => 'regoperator', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regoperator', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regoperator', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regoperator', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regoperator', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regoperator', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regclass', castfunc => '0',
@@ -184,13 +184,13 @@
{ castsource => 'regclass', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regclass', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regclass', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regclass', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regclass', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regclass', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regcollation', castfunc => '0',
@@ -198,13 +198,13 @@
{ castsource => 'regcollation', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regcollation', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regcollation', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regcollation', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regcollation', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regcollation', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regtype', castfunc => '0',
@@ -212,13 +212,13 @@
{ castsource => 'regtype', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regtype', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regtype', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regtype', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regtype', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regtype', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regconfig', castfunc => '0',
@@ -226,13 +226,13 @@
{ castsource => 'regconfig', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regconfig', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regconfig', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regconfig', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regconfig', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regconfig', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regdictionary', castfunc => '0',
@@ -240,31 +240,31 @@
{ castsource => 'regdictionary', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regdictionary', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regdictionary', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regdictionary', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regdictionary', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regdictionary', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'text', casttarget => 'regclass', castfunc => 'regclass',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'regclass', castfunc => 'regclass',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'oid', casttarget => 'regrole', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regrole', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regrole', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regrole', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regrole', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regrole', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regrole', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regnamespace', castfunc => '0',
@@ -272,13 +272,13 @@
{ castsource => 'regnamespace', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regnamespace', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regnamespace', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regnamespace', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regnamespace', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regnamespace', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regdatabase', castfunc => '0',
@@ -286,13 +286,13 @@
{ castsource => 'regdatabase', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regdatabase', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regdatabase', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regdatabase', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regdatabase', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regdatabase', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
@@ -302,57 +302,57 @@
{ castsource => 'text', casttarget => 'varchar', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'bpchar', casttarget => 'text', castfunc => 'text(bpchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bpchar', casttarget => 'varchar', castfunc => 'text(bpchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'text', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'varchar', casttarget => 'bpchar', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'char', casttarget => 'text', castfunc => 'text(char)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'char', casttarget => 'bpchar', castfunc => 'bpchar(char)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'char', casttarget => 'varchar', castfunc => 'text(char)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'name', casttarget => 'text', castfunc => 'text(name)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'name', casttarget => 'bpchar', castfunc => 'bpchar(name)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'name', casttarget => 'varchar', castfunc => 'varchar(name)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'text', casttarget => 'char', castfunc => 'char(text)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bpchar', casttarget => 'char', castfunc => 'char(text)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'char', castfunc => 'char(text)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'text', casttarget => 'name', castfunc => 'name(text)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bpchar', casttarget => 'name', castfunc => 'name(bpchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'name', castfunc => 'name(varchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
# Allow explicit coercions between bytea and integer types
{ castsource => 'int2', casttarget => 'bytea', castfunc => 'bytea(int2)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'bytea', castfunc => 'bytea(int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'bytea', castfunc => 'bytea(int8)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bytea', casttarget => 'int2', castfunc => 'int2(bytea)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bytea', casttarget => 'int4', castfunc => 'int4(bytea)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bytea', casttarget => 'int8', castfunc => 'int8(bytea)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Allow explicit coercions between int4 and "char"
{ castsource => 'char', casttarget => 'int4', castfunc => 'int4(char)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'char', castfunc => 'char(int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# pg_node_tree can be coerced to, but not from, text
{ castsource => 'pg_node_tree', casttarget => 'text', castfunc => '0',
@@ -378,73 +378,73 @@
# Datetime category
{ castsource => 'date', casttarget => 'timestamp',
- castfunc => 'timestamp(date)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timestamp(date)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'date', casttarget => 'timestamptz',
- castfunc => 'timestamptz(date)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timestamptz(date)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'time', casttarget => 'interval', castfunc => 'interval(time)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'time', casttarget => 'timetz', castfunc => 'timetz(time)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'date',
- castfunc => 'date(timestamp)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'date(timestamp)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'time',
- castfunc => 'time(timestamp)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'time(timestamp)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'timestamptz',
- castfunc => 'timestamptz(timestamp)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timestamptz(timestamp)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'date',
- castfunc => 'date(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'date(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'time',
- castfunc => 'time(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'time(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'timestamp',
- castfunc => 'timestamp(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'timestamp(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'timetz',
- castfunc => 'timetz(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'timetz(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'interval', casttarget => 'time', castfunc => 'time(interval)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timetz', casttarget => 'time', castfunc => 'time(timetz)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
# Geometric category
{ castsource => 'point', casttarget => 'box', castfunc => 'box(point)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'lseg', casttarget => 'point', castfunc => 'point(lseg)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'path', casttarget => 'polygon', castfunc => 'polygon(path)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'box', casttarget => 'point', castfunc => 'point(box)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'box', casttarget => 'lseg', castfunc => 'lseg(box)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'box', casttarget => 'polygon', castfunc => 'polygon(box)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'box', casttarget => 'circle', castfunc => 'circle(box)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'polygon', casttarget => 'point', castfunc => 'point(polygon)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'polygon', casttarget => 'path', castfunc => 'path',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'polygon', casttarget => 'box', castfunc => 'box(polygon)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'polygon', casttarget => 'circle',
- castfunc => 'circle(polygon)', castcontext => 'e', castmethod => 'f' },
+ castfunc => 'circle(polygon)', castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'circle', casttarget => 'point', castfunc => 'point(circle)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'circle', casttarget => 'box', castfunc => 'box(circle)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'circle', casttarget => 'polygon',
- castfunc => 'polygon(circle)', castcontext => 'e', castmethod => 'f' },
+ castfunc => 'polygon(circle)', castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# MAC address category
{ castsource => 'macaddr', casttarget => 'macaddr8', castfunc => 'macaddr8',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'macaddr8', casttarget => 'macaddr', castfunc => 'macaddr',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
# INET category
{ castsource => 'cidr', casttarget => 'inet', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'inet', casttarget => 'cidr', castfunc => 'cidr',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
# BitString category
{ castsource => 'bit', casttarget => 'varbit', castfunc => '0',
@@ -454,13 +454,13 @@
# Cross-category casts between bit and int4, int8
{ castsource => 'int8', casttarget => 'bit', castfunc => 'bit(int8,int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'bit', castfunc => 'bit(int4,int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bit', casttarget => 'int8', castfunc => 'int8(bit)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bit', casttarget => 'int4', castfunc => 'int4(bit)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Cross-category casts to and from TEXT
# We need entries here only for a few specialized cases where the behavior
@@ -471,68 +471,68 @@
# behavior will ensue when the automatic cast is applied instead of the
# pg_cast entry!
{ castsource => 'cidr', casttarget => 'text', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'inet', casttarget => 'text', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'text', castfunc => 'text(bool)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'xml', casttarget => 'text', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'text', casttarget => 'xml', castfunc => 'xml',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Cross-category casts to and from VARCHAR
# We support all the same casts as for TEXT.
{ castsource => 'cidr', casttarget => 'varchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'inet', casttarget => 'varchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'varchar', castfunc => 'text(bool)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'xml', casttarget => 'varchar', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'varchar', casttarget => 'xml', castfunc => 'xml',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Cross-category casts to and from BPCHAR
# We support all the same casts as for TEXT.
{ castsource => 'cidr', casttarget => 'bpchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'inet', casttarget => 'bpchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'bpchar', castfunc => 'text(bool)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'xml', casttarget => 'bpchar', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'bpchar', casttarget => 'xml', castfunc => 'xml',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Length-coercion functions
{ castsource => 'bpchar', casttarget => 'bpchar',
castfunc => 'bpchar(bpchar,int4,bool)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'varchar',
castfunc => 'varchar(varchar,int4,bool)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'time', casttarget => 'time', castfunc => 'time(time,int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'timestamp',
castfunc => 'timestamp(timestamp,int4)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'timestamptz',
castfunc => 'timestamptz(timestamptz,int4)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'interval', casttarget => 'interval',
castfunc => 'interval(interval,int4)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timetz', casttarget => 'timetz',
- castfunc => 'timetz(timetz,int4)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timetz(timetz,int4)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bit', casttarget => 'bit', castfunc => 'bit(bit,int4,bool)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varbit', casttarget => 'varbit', castfunc => 'varbit',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'numeric',
- castfunc => 'numeric(numeric,int4)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'numeric(numeric,int4)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
# json to/from jsonb
{ castsource => 'json', casttarget => 'jsonb', castfunc => '0',
@@ -542,36 +542,36 @@
# jsonb to numeric and bool types
{ castsource => 'jsonb', casttarget => 'bool', castfunc => 'bool(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'numeric', castfunc => 'numeric(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'int2', castfunc => 'int2(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'int4', castfunc => 'int4(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'int8', castfunc => 'int8(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'float4', castfunc => 'float4(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'float8', castfunc => 'float8(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# range to multirange
{ castsource => 'int4range', casttarget => 'int4multirange',
castfunc => 'int4multirange(int4range)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8range', casttarget => 'int8multirange',
castfunc => 'int8multirange(int8range)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numrange', casttarget => 'nummultirange',
castfunc => 'nummultirange(numrange)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'daterange', casttarget => 'datemultirange',
castfunc => 'datemultirange(daterange)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'tsrange', casttarget => 'tsmultirange',
- castfunc => 'tsmultirange(tsrange)', castcontext => 'e', castmethod => 'f' },
+ castfunc => 'tsmultirange(tsrange)', castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'tstzrange', casttarget => 'tstzmultirange',
castfunc => 'tstzmultirange(tstzrange)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
]
diff --git a/src/include/catalog/pg_cast.h b/src/include/catalog/pg_cast.h
index 6a0ca337153..218d81d535a 100644
--- a/src/include/catalog/pg_cast.h
+++ b/src/include/catalog/pg_cast.h
@@ -47,6 +47,10 @@ CATALOG(pg_cast,2605,CastRelationId)
/* cast method */
char castmethod;
+
+ /* cast function error safe */
+ bool casterrorsafe BKI_DEFAULT(f);
+
} FormData_pg_cast;
/* ----------------
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 75366203706..34a884e3196 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_SAFETYPE_CAST */
+ 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);
+extern 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/nodes/execnodes.h b/src/include/nodes/execnodes.h
index a36653c37f9..015cd9f47e7 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1053,6 +1053,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 dc09d1a3f03..83549151bfb 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -400,6 +400,12 @@ typedef struct TypeCast
ParseLoc location; /* token location, or -1 if unknown */
} TypeCast;
+typedef struct SafeTypeCast
+{
+ NodeTag type;
+ Node *cast;
+ Node *def_expr; /* default expr */
+} SafeTypeCast;
/*
* CollateClause - a COLLATE expression
*/
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 1b4436f2ff6..70b72ed2f40 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -769,6 +769,39 @@ 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
+{
+ Expr xpr;
+
+ /* transformed expression being casted */
+ Node *source_expr;
+
+ /*
+ * The transformed cast expression; It will NULL if can not cast source type
+ * to target type
+ */
+ Node *cast_expr pg_node_attr(query_jumble_ignore);
+
+ /* Fall back to the default expression if cast evaluation fails */
+ Node *default_expr;
+
+ /* cast result data type */
+ Oid resulttype;
+
+ /* cast result data type typmod (usually -1) */
+ int32 resulttypmod;
+
+ /* cast result data type collation */
+ Oid resultcollid;
+
+} SafeTypeCastExpr;
+
/*
* FuncExpr - expression node for a function call
*
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index f7d07c84542..9f5b32e0360 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -67,6 +67,8 @@ typedef enum ParseExprKind
EXPR_KIND_VALUES_SINGLE, /* single-row VALUES (in INSERT only) */
EXPR_KIND_CHECK_CONSTRAINT, /* CHECK constraint for a table */
EXPR_KIND_DOMAIN_CHECK, /* CHECK constraint for a domain */
+ EXPR_KIND_CAST_DEFAULT, /* default expression in
+ CAST DEFAULT ON CONVERSION ERROR */
EXPR_KIND_COLUMN_DEFAULT, /* default value for a table column */
EXPR_KIND_FUNCTION_DEFAULT, /* default parameter value for function */
EXPR_KIND_INDEX_EXPRESSION, /* index expression */
diff --git a/src/test/regress/expected/cast.out b/src/test/regress/expected/cast.out
new file mode 100644
index 00000000000..6614ba3370c
--- /dev/null
+++ b/src/test/regress/expected/cast.out
@@ -0,0 +1,681 @@
+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 DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(1::numeric AS money DEFAULT NULL ON CONVERSION E...
+ ^
+HINT: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(1111 AS "char" DEFAULT NULL ON CONVERSION ERROR);
+ char
+------
+
+(1 row)
+
+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 coerce CAST DEFAULT expression to type date
+LINE 1: SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERR...
+ ^
+-- test array coerce
+SELECT CAST(array[11.12] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+ {11}
+(1 row)
+
+SELECT CAST(array[11.12] AS date[] DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+SELECT CAST(array[11111111111111111] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+SELECT CAST(array[['abc'],[456]] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+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: set-returning functions are not allowed in CAST DEFAULT expressions
+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: aggregate functions are not allowed in CAST DEFAULT expressions
+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: window functions are not allowed in CAST DEFAULT expressions
+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 DOMAIN d_varchar as varchar(3) NOT NULL;
+CREATE TYPE comp_domain_with_typmod AS (a d_char3_not_null, b int);
+CREATE TYPE comp2 AS (a d_varchar);
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION ERROR); --error
+ERROR: value too long for type character varying(3)
+LINE 1: SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION...
+ ^
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(123)' ON CONVERSION ERROR); --ok
+ comp2
+-------
+ (123)
+(1 row)
+
+SELECT CAST(11 AS d_int42 DEFAULT 41 ON CONVERSION ERROR); --error
+ERROR: value for domain d_int42 violates check constraint "d_int42_check"
+SELECT CAST(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)'...
+ ^
+-----safe cast with geometry data type
+SELECT CAST('(1,2)'::point AS box DEFAULT NULL ON CONVERSION ERROR);
+ box
+-------------
+ (1,2),(1,2)
+(1 row)
+
+SELECT CAST('[(NaN,1),(NaN,infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+----------------
+ (NaN,Infinity)
+(1 row)
+
+SELECT CAST('[(1e+300,Infinity),(1e+300,Infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+-------------------
+ (1e+300,Infinity)
+(1 row)
+
+SELECT CAST('[(1,2),(3,4)]'::path as polygon DEFAULT NULL ON CONVERSION ERROR);
+ polygon
+---------
+
+(1 row)
+
+SELECT CAST('(NaN,1.0,NaN,infinity)'::box AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+----------------
+ (NaN,Infinity)
+(1 row)
+
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS lseg DEFAULT NULL ON CONVERSION ERROR);
+ lseg
+---------------
+ [(2,2),(0,0)]
+(1 row)
+
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS polygon DEFAULT NULL ON CONVERSION ERROR);
+ polygon
+---------------------------
+ ((0,0),(0,2),(2,2),(2,0))
+(1 row)
+
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS path DEFAULT NULL ON CONVERSION ERROR);
+ path
+------
+
+(1 row)
+
+SELECT CAST('(2.0,infinity,NaN,infinity)'::box AS circle DEFAULT NULL ON CONVERSION ERROR);
+ circle
+----------------------
+ <(NaN,Infinity),NaN>
+(1 row)
+
+SELECT CAST('(NaN,0.0),(2.0,4.0),(0.0,infinity)'::polygon AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+----------------
+ (NaN,Infinity)
+(1 row)
+
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS path DEFAULT NULL ON CONVERSION ERROR);
+ path
+---------------------
+ ((2,0),(2,4),(0,0))
+(1 row)
+
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS box DEFAULT NULL ON CONVERSION ERROR);
+ box
+-------------
+ (2,4),(0,0)
+(1 row)
+
+SELECT CAST('(NaN,infinity),(2.0,4.0),(0.0,infinity)'::polygon AS circle DEFAULT NULL ON CONVERSION ERROR);
+ circle
+----------------------
+ <(NaN,Infinity),NaN>
+(1 row)
+
+SELECT CAST('<(5,1),3>'::circle AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+-------
+ (5,1)
+(1 row)
+
+SELECT CAST('<(3,5),0>'::circle as box DEFAULT NULL ON CONVERSION ERROR);
+ box
+-------------
+ (3,5),(3,5)
+(1 row)
+
+SELECT CAST('<(3,5),0>'::circle as polygon DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot convert circle with radius zero to polygon
+CONTEXT: SQL function "polygon" statement 1
+-----safe cast from/to money type is not supported, all the following would result error
+SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type bigint to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION E...
+ ^
+HINT: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type integer to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION E...
+ ^
+HINT: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type numeric to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSIO...
+ ^
+HINT: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type money to numeric when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSIO...
+ ^
+HINT: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+/*
+the following are safe cast test with type
+{
+ bytea, bit character, text, character-varying,
+ inet, macaddr8
+ int2, integer, bigint, numeric, float4, float8,
+ date, interval, timestamptz, timestamp
+ jsonb
+}
+*/
+-----safe cast from bytea type to other type
+SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT NULL ON CONVERSION ERROR);
+ int8
+------
+
+(1 row)
+
+SELECT CAST('\x123456789A'::bytea AS int4 DEFAULT NULL ON CONVERSION ERROR);
+ int4
+------
+
+(1 row)
+
+SELECT CAST('\x123456'::bytea AS int2 DEFAULT NULL ON CONVERSION ERROR);
+ int2
+------
+
+(1 row)
+
+SELECT CAST('111111111100001'::bit(100) AS INT DEFAULT NULL ON CONVERSION ERROR);
+ int4
+------
+
+(1 row)
+
+SELECT CAST ('111111111100001'::bit(100) AS INT8 DEFAULT NULL ON CONVERSION ERROR);
+ int8
+------
+
+(1 row)
+
+select CAST('a.b.c.d'::text as regclass default NULL on conversion error);
+ regclass
+----------
+
+(1 row)
+
+CREATE TABLE test_safecast(col0 text);
+INSERT INTO test_safecast(col0) VALUES ('<value>one</value');
+SELECT col0 as text,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) as to_regclass,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) IS NULL as expect_true,
+ CAST(col0 AS "char" DEFAULT NULL ON CONVERSION ERROR) as to_char,
+ CAST(col0 AS name DEFAULT NULL ON CONVERSION ERROR) as to_name,
+ CAST(col0 AS xml DEFAULT NULL ON CONVERSION ERROR) as to_xml
+FROM test_safecast;
+ text | to_regclass | expect_true | to_char | to_name | to_xml
+-------------------+-------------+-------------+---------+-------------------+--------
+ <value>one</value | | t | < | <value>one</value |
+(1 row)
+
+-----safe cast from other type to inet/cidr
+SELECT CAST('192.168.1.x' as inet DEFAULT NULL ON CONVERSION ERROR);
+ inet
+------
+
+(1 row)
+
+SELECT CAST('192.168.1.2/30' as cidr DEFAULT NULL ON CONVERSION ERROR);
+ cidr
+------
+
+(1 row)
+
+SELECT CAST('22:00:5c:08:55:08:01:02'::macaddr8 as macaddr DEFAULT NULL ON CONVERSION ERROR);
+ macaddr
+---------
+
+(1 row)
+
+-----safe cast from range type to other type
+SELECT CAST('[1,2]'::int4range AS int4multirange DEFAULT NULL ON CONVERSION ERROR);
+ int4multirange
+----------------
+ {[1,3)}
+(1 row)
+
+SELECT CAST('[1,2]'::int8range AS int8multirange DEFAULT NULL ON CONVERSION ERROR);
+ int8multirange
+----------------
+ {[1,3)}
+(1 row)
+
+SELECT CAST('[1,2]'::numrange AS nummultirange DEFAULT NULL ON CONVERSION ERROR);
+ nummultirange
+---------------
+ {[1,2]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::daterange AS datemultirange DEFAULT NULL ON CONVERSION ERROR);
+ datemultirange
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::tsrange AS tsmultirange DEFAULT NULL ON CONVERSION ERROR);
+ tsmultirange
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::tstzrange AS tstzmultirange DEFAULT NULL ON CONVERSION ERROR);
+ tstzmultirange
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+--test cast numeric value with fraction to another numeric value
+CREATE TABLE safecast(col1 float4, col2 float8, col3 numeric, col4 numeric[],
+ col5 int2 default 32767,
+ col6 int4 default 32768,
+ col7 int8 default 4294967296);
+INSERT INTO safecast VALUES('11.1234', '11.1234', '9223372036854775808', '{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 col5 as int2,
+ CAST(col5 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col5 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col5 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col5 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col5 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col5 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col5 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col5 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM safecast;
+ int2 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+(4 rows)
+
+SELECT col6 as int4,
+ CAST(col6 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col6 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col6 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col6 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col6 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col6 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col6 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col6 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM safecast;
+ int4 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+(4 rows)
+
+SELECT col7 as int8,
+ CAST(col7 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col7 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col7 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col7 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col7 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col7 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col7 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col7 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM safecast;
+ int8 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+------------+---------+---------+--------+------------+-------------+------------+------------+------------------
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+(4 rows)
+
+SELECT col3 as numeric,
+ CAST(col3 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col3 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col3 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col3 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col3 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col3 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col3 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col3 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM safecast;
+ numeric | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+---------------------+---------+---------+--------+---------+-------------+----------------------+---------------------+------------------
+ 9223372036854775808 | | | | | 9.22337e+18 | 9.22337203685478e+18 | 9223372036854775808 |
+ Infinity | | | | | Infinity | Infinity | Infinity |
+ -Infinity | | | | | -Infinity | -Infinity | -Infinity |
+ NaN | | | | | NaN | NaN | NaN | NaN
+(4 rows)
+
+SELECT col4 as num_arr,
+ CAST(col4 AS int2[] DEFAULT NULL ON CONVERSION ERROR) as int2arr,
+ CAST(col4 AS int4[] DEFAULT NULL ON CONVERSION ERROR) as int4arr,
+ CAST(col4 as int8[] DEFAULT NULL ON CONVERSION ERROR) as int8arr,
+ CAST(col4 as float4[] DEFAULT NULL ON CONVERSION ERROR) as f4arr,
+ CAST(col4 as float8[] DEFAULT NULL ON CONVERSION ERROR) as f8arr,
+ CAST(col4 as numeric(10,1)[] DEFAULT NULL ON CONVERSION ERROR) as numarr
+FROM 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)
+
+SELECT col1 as float4,
+ CAST(col1 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col1 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col1 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col1 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col1 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col1 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col1 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col1 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM safecast;
+ float4 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-----------+---------+---------+--------+---------+-----------+------------------+------------+------------------
+ 11.1234 | 11 | 11 | | 11 | 11.1234 | 11.1233997344971 | 11.1234 | 11.1
+ Infinity | | | | | Infinity | Infinity | Infinity |
+ -Infinity | | | | | -Infinity | -Infinity | -Infinity |
+ NaN | | | | | NaN | NaN | NaN | NaN
+(4 rows)
+
+SELECT col2 as float8,
+ CAST(col2 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col2 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col2 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col2 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col2 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col2 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col2 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col2 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM safecast;
+ float8 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-----------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 11.1234 | 11 | 11 | | 11 | 11.1234 | 11.1234 | 11.1234 | 11.1
+ Infinity | | | | | Infinity | Infinity | Infinity |
+ -Infinity | | | | | -Infinity | -Infinity | -Infinity |
+ NaN | | | | | NaN | NaN | NaN | NaN
+(4 rows)
+
+--test date/timestamp/interval realted cast
+CREATE TABLE safecast1(col0 date, col1 timestamp, col2 timestamptz, col3 interval, col4 time, col5 timetz);
+insert into safecast1 VALUES
+('-infinity', '-infinity', '-infinity', '-infinity',
+ '2003-03-07 15:36:39 America/New_York', '2003-07-07 15:36:39 America/New_York'),
+('-infinity', 'infinity', 'infinity', 'infinity',
+ '11:59:59.99 PM', '11:59:59.99 PM PDT');
+SELECT col0 as date,
+ CAST(col0 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col0 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col0 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col0 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col0 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM safecast1;
+ date | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale
+-----------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity | -infinity | | | -infinity
+ -infinity | -infinity | -infinity | | | -infinity
+(2 rows)
+
+SELECT col1 as timestamp,
+ CAST(col1 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col1 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col1 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col1 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col1 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM safecast1;
+ timestamp | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale
+-----------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity | -infinity | | | -infinity
+ infinity | infinity | infinity | | | infinity
+(2 rows)
+
+SELECT col2 as timestamptz,
+ CAST(col2 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col2 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col2 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col2 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col2 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM safecast1;
+ timestamptz | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale
+-------------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity | -infinity | | | -infinity
+ infinity | infinity | infinity | | | infinity
+(2 rows)
+
+SELECT col3 as interval,
+ CAST(col3 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col3 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col3 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col3 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col3 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale,
+ CAST(col3 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM safecast1;
+ interval | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale | to_interval_scale
+-----------+----------------+---------+----------+-----------+--------------------+-------------------
+ -infinity | | | | | | -infinity
+ infinity | | | | | | infinity
+(2 rows)
+
+SELECT col4 as time,
+ CAST(col4 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col4 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM safecast1;
+ time | to_times | to_timetz | to_interval | to_interval_scale
+-------------+-------------+----------------+-------------------------------+-------------------------------
+ 15:36:39 | 15:36:39 | 15:36:39-07 | @ 15 hours 36 mins 39 secs | @ 15 hours 36 mins 39 secs
+ 23:59:59.99 | 23:59:59.99 | 23:59:59.99-07 | @ 23 hours 59 mins 59.99 secs | @ 23 hours 59 mins 59.99 secs
+(2 rows)
+
+SELECT col5 as timetz,
+ CAST(col5 AS time DEFAULT NULL ON CONVERSION ERROR) as to_time,
+ CAST(col5 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_time_scale,
+ CAST(col5 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col5 AS timetz(6) DEFAULT NULL ON CONVERSION ERROR) as to_timetz_scale,
+ CAST(col5 AS interval DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col5 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM safecast1;
+ timetz | to_time | to_time_scale | to_timetz | to_timetz_scale | to_interval | to_interval_scale
+----------------+-------------+---------------+----------------+-----------------+-------------+-------------------
+ 15:36:39-04 | 15:36:39 | 15:36:39 | 15:36:39-04 | 15:36:39-04 | |
+ 23:59:59.99-07 | 23:59:59.99 | 23:59:59.99 | 23:59:59.99-07 | 23:59:59.99-07 | |
+(2 rows)
+
+CREATE TEMP TABLE test_safecast1(col0 jsonb);
+INSERT INTO test_safecast1(col0) VALUES ('"test"');
+SELECT col0 as jsonb,
+ CAST(col0 AS integer DEFAULT NULL ON CONVERSION ERROR) as to_integer,
+ CAST(col0 AS numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col0 AS bigint DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col0 AS float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col0 AS float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col0 AS boolean DEFAULT NULL ON CONVERSION ERROR) as to_bool,
+ CAST(col0 AS smallint DEFAULT NULL ON CONVERSION ERROR) as to_smallint
+FROM test_safecast1;
+ jsonb | to_integer | to_numeric | to_int8 | to_float4 | to_float8 | to_bool | to_smallint
+--------+------------+------------+---------+-----------+-----------+---------+-------------
+ "test" | | | | | | |
+(1 row)
+
+-- test deparse
+CREATE DOMAIN d_int_arr as int[];
+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'::text], ARRAY['a'::text]] 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 VIEW safecastview1 AS
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS d_int_arr DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1,
+ CAST(ARRAY[['1', '2'], ['three', 'a']] AS d_int_arr DEFAULT '{21,22}' ON CONVERSION ERROR) as cast2;
+\sv safecastview1
+CREATE OR REPLACE VIEW public.safecastview1 AS
+ SELECT CAST(ARRAY[ARRAY[1], ARRAY['three'::text], ARRAY['a'::text]] AS d_int_arr DEFAULT '{1,2}'::integer[]::d_int_arr ON CONVERSION ERROR) AS cast1,
+ CAST(ARRAY[ARRAY[1, 2], ARRAY['three'::text, 'a'::text]] AS d_int_arr DEFAULT '{21,22}'::integer[]::d_int_arr ON CONVERSION ERROR) AS cast2
+SELECT * FROM safecastview1;
+ cast1 | cast2
+-------+---------
+ {1,2} | {21,22}
+(1 row)
+
+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/expected/create_cast.out b/src/test/regress/expected/create_cast.out
index 0e69644bca2..38eab453344 100644
--- a/src/test/regress/expected/create_cast.out
+++ b/src/test/regress/expected/create_cast.out
@@ -88,6 +88,11 @@ SELECT 1234::int4::casttesttype; -- Should work now
bar1234
(1 row)
+SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- error
+ERROR: cannot cast type integer to casttesttype when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVE...
+ ^
+HINT: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
-- check dependencies generated for that
SELECT pg_describe_object(classid, objid, objsubid) as obj,
pg_describe_object(refclassid, refobjid, refobjsubid) as objref,
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index a357e1d0c0e..81ea244859f 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -943,8 +943,8 @@ SELECT *
FROM pg_cast c
WHERE castsource = 0 OR casttarget = 0 OR castcontext NOT IN ('e', 'a', 'i')
OR castmethod NOT IN ('f', 'b' ,'i');
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Check that castfunc is nonzero only for cast methods that need a function,
@@ -953,8 +953,8 @@ SELECT *
FROM pg_cast c
WHERE (castmethod = 'f' AND castfunc = 0)
OR (castmethod IN ('b', 'i') AND castfunc <> 0);
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Look for casts to/from the same type that aren't length coercion functions.
@@ -963,15 +963,15 @@ WHERE (castmethod = 'f' AND castfunc = 0)
SELECT *
FROM pg_cast c
WHERE castsource = casttarget AND castfunc = 0;
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
SELECT c.*
FROM pg_cast c, pg_proc p
WHERE c.castfunc = p.oid AND p.pronargs < 2 AND castsource = casttarget;
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Look for cast functions that don't have the right signature. The
@@ -989,8 +989,8 @@ WHERE c.castfunc = p.oid AND
OR (c.castsource = 'character'::regtype AND
p.proargtypes[0] = 'text'::regtype))
OR NOT binary_coercible(p.prorettype, c.casttarget));
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
SELECT c.*
@@ -998,8 +998,8 @@ FROM pg_cast c, pg_proc p
WHERE c.castfunc = p.oid AND
((p.pronargs > 1 AND p.proargtypes[1] != 'int4'::regtype) OR
(p.pronargs > 2 AND p.proargtypes[2] != 'bool'::regtype));
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Look for binary compatible casts that do not have the reverse
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index f9450cdc477..573a6699a63 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 00000000000..eb68ec21859
--- /dev/null
+++ b/src/test/regress/sql/cast.sql
@@ -0,0 +1,301 @@
+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);
+SELECT CAST(1111 AS "char" 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(array[11.12] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(array[11.12] AS date[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(array[11111111111111111] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(array[['abc'],[456]] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+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 DOMAIN d_varchar as varchar(3) NOT NULL;
+CREATE TYPE comp_domain_with_typmod AS (a d_char3_not_null, b int);
+CREATE TYPE comp2 AS (a d_varchar);
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION ERROR); --error
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(123)' ON CONVERSION ERROR); --ok
+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
+
+-----safe cast with geometry data type
+SELECT CAST('(1,2)'::point AS box DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[(NaN,1),(NaN,infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[(1e+300,Infinity),(1e+300,Infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[(1,2),(3,4)]'::path as polygon DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NaN,1.0,NaN,infinity)'::box AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS lseg DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS polygon DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS path DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,infinity,NaN,infinity)'::box AS circle DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NaN,0.0),(2.0,4.0),(0.0,infinity)'::polygon AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS path DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS box DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NaN,infinity),(2.0,4.0),(0.0,infinity)'::polygon AS circle DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('<(5,1),3>'::circle AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('<(3,5),0>'::circle as box DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('<(3,5),0>'::circle as polygon DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast from/to money type is not supported, all the following would result error
+SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSION ERROR);
+
+/*
+the following are safe cast test with type
+{
+ bytea, bit character, text, character-varying,
+ inet, macaddr8
+ int2, integer, bigint, numeric, float4, float8,
+ date, interval, timestamptz, timestamp
+ jsonb
+}
+*/
+-----safe cast from bytea type to other type
+SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('\x123456789A'::bytea AS int4 DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('\x123456'::bytea AS int2 DEFAULT NULL ON CONVERSION ERROR);
+
+SELECT CAST('111111111100001'::bit(100) AS INT DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST ('111111111100001'::bit(100) AS INT8 DEFAULT NULL ON CONVERSION ERROR);
+
+select CAST('a.b.c.d'::text as regclass default NULL on conversion error);
+
+CREATE TABLE test_safecast(col0 text);
+INSERT INTO test_safecast(col0) VALUES ('<value>one</value');
+
+SELECT col0 as text,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) as to_regclass,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) IS NULL as expect_true,
+ CAST(col0 AS "char" DEFAULT NULL ON CONVERSION ERROR) as to_char,
+ CAST(col0 AS name DEFAULT NULL ON CONVERSION ERROR) as to_name,
+ CAST(col0 AS xml DEFAULT NULL ON CONVERSION ERROR) as to_xml
+FROM test_safecast;
+
+-----safe cast from other type to inet/cidr
+SELECT CAST('192.168.1.x' as inet DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('192.168.1.2/30' as cidr DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('22:00:5c:08:55:08:01:02'::macaddr8 as macaddr DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast from range type to other type
+SELECT CAST('[1,2]'::int4range AS int4multirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[1,2]'::int8range AS int8multirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[1,2]'::numrange AS nummultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::daterange AS datemultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::tsrange AS tsmultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::tstzrange AS tstzmultirange DEFAULT NULL ON CONVERSION ERROR);
+
+--test cast numeric value with fraction to another numeric value
+CREATE TABLE safecast(col1 float4, col2 float8, col3 numeric, col4 numeric[],
+ col5 int2 default 32767,
+ col6 int4 default 32768,
+ col7 int8 default 4294967296);
+INSERT INTO safecast VALUES('11.1234', '11.1234', '9223372036854775808', '{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 col5 as int2,
+ CAST(col5 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col5 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col5 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col5 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col5 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col5 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col5 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col5 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM safecast;
+
+SELECT col6 as int4,
+ CAST(col6 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col6 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col6 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col6 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col6 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col6 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col6 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col6 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM safecast;
+
+SELECT col7 as int8,
+ CAST(col7 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col7 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col7 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col7 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col7 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col7 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col7 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col7 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM 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 oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col3 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col3 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col3 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col3 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col3 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM 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;
+
+SELECT col1 as float4,
+ CAST(col1 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col1 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col1 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col1 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col1 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col1 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col1 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col1 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM 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 oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col2 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col2 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col2 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col2 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col2 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM safecast;
+
+--test date/timestamp/interval realted cast
+CREATE TABLE safecast1(col0 date, col1 timestamp, col2 timestamptz, col3 interval, col4 time, col5 timetz);
+insert into safecast1 VALUES
+('-infinity', '-infinity', '-infinity', '-infinity',
+ '2003-03-07 15:36:39 America/New_York', '2003-07-07 15:36:39 America/New_York'),
+('-infinity', 'infinity', 'infinity', 'infinity',
+ '11:59:59.99 PM', '11:59:59.99 PM PDT');
+
+SELECT col0 as date,
+ CAST(col0 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col0 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col0 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col0 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col0 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM safecast1;
+
+SELECT col1 as timestamp,
+ CAST(col1 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col1 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col1 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col1 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col1 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM safecast1;
+
+SELECT col2 as timestamptz,
+ CAST(col2 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col2 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col2 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col2 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col2 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM safecast1;
+
+SELECT col3 as interval,
+ CAST(col3 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col3 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col3 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col3 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col3 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale,
+ CAST(col3 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM safecast1;
+
+SELECT col4 as time,
+ CAST(col4 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col4 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM safecast1;
+
+SELECT col5 as timetz,
+ CAST(col5 AS time DEFAULT NULL ON CONVERSION ERROR) as to_time,
+ CAST(col5 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_time_scale,
+ CAST(col5 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col5 AS timetz(6) DEFAULT NULL ON CONVERSION ERROR) as to_timetz_scale,
+ CAST(col5 AS interval DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col5 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM safecast1;
+
+CREATE TEMP TABLE test_safecast1(col0 jsonb);
+INSERT INTO test_safecast1(col0) VALUES ('"test"');
+SELECT col0 as jsonb,
+ CAST(col0 AS integer DEFAULT NULL ON CONVERSION ERROR) as to_integer,
+ CAST(col0 AS numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col0 AS bigint DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col0 AS float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col0 AS float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col0 AS boolean DEFAULT NULL ON CONVERSION ERROR) as to_bool,
+ CAST(col0 AS smallint DEFAULT NULL ON CONVERSION ERROR) as to_smallint
+FROM test_safecast1;
+
+-- test deparse
+CREATE DOMAIN d_int_arr as int[];
+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 VIEW safecastview1 AS
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS d_int_arr DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1,
+ CAST(ARRAY[['1', '2'], ['three', 'a']] AS d_int_arr DEFAULT '{21,22}' ON CONVERSION ERROR) as cast2;
+\sv safecastview1
+SELECT * FROM safecastview1;
+
+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/test/regress/sql/create_cast.sql b/src/test/regress/sql/create_cast.sql
index 32187853cc7..0a15a795d87 100644
--- a/src/test/regress/sql/create_cast.sql
+++ b/src/test/regress/sql/create_cast.sql
@@ -62,6 +62,7 @@ $$ SELECT ('bar'::text || $1::text); $$;
CREATE CAST (int4 AS casttesttype) WITH FUNCTION bar_int4_text(int4) AS IMPLICIT;
SELECT 1234::int4::casttesttype; -- Should work now
+SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- error
-- check dependencies generated for that
SELECT pg_describe_object(classid, objid, objsubid) as obj,
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 5290b91e83e..7996854b3a5 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2660,6 +2660,9 @@ STRLEN
SV
SYNCHRONIZATION_BARRIER
SYSTEM_INFO
+SafeTypeCast
+SafeTypeCastExpr
+SafeTypeCastState
SampleScan
SampleScanGetSampleSize_function
SampleScanState
--
2.34.1
v8-0018-error-safe-for-casting-geometry-data-type.patchtext/x-patch; charset=US-ASCII; name=v8-0018-error-safe-for-casting-geometry-data-type.patchDownload
From ed53de0d64021185bc51ec0efd0d566ce3563a7e Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 19:19:04 +0800
Subject: [PATCH v8 18/20] error safe for casting geometry data type
select castsource::regtype, casttarget::regtype, pp.prosrc
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
join pg_type pt on pt.oid = castsource
join pg_type pt1 on pt1.oid = casttarget
and pc.castfunc > 0 and pt.typarray <> 0
and pt.typnamespace = 'pg_catalog'::regnamespace
and pt1.typnamespace = 'pg_catalog'::regnamespace
and (pt.typcategory = 'G' or pt1.typcategory = 'G')
order by castsource::regtype, casttarget::regtype;
castsource | casttarget | prosrc
------------+------------+---------------
point | box | point_box
lseg | point | lseg_center
path | polygon | path_poly
box | point | box_center
box | lseg | box_diagonal
box | polygon | box_poly
box | circle | box_circle
polygon | point | poly_center
polygon | path | poly_path
polygon | box | poly_box
polygon | circle | poly_circle
circle | point | circle_center
circle | box | circle_box
circle | polygon |
(14 rows)
already error safe: point_box, box_diagonal, box_poly, poly_path, poly_box, circle_center
almost error safe: path_poly
discussion: https://postgr.es/m/CACJufxHCMzrHOW=wRe8L30rMhB3sjwAv1LE928Fa7sxMu1Tx-g@mail.gmail.com
---
src/backend/access/spgist/spgproc.c | 2 +-
src/backend/utils/adt/float.c | 8 +-
src/backend/utils/adt/geo_ops.c | 282 ++++++++++++++++++++++++----
src/backend/utils/adt/geo_spgist.c | 2 +-
src/include/utils/float.h | 107 +++++++++++
src/include/utils/geo_decls.h | 4 +-
6 files changed, 358 insertions(+), 47 deletions(-)
diff --git a/src/backend/access/spgist/spgproc.c b/src/backend/access/spgist/spgproc.c
index 660009291da..b3e0fbc59ba 100644
--- a/src/backend/access/spgist/spgproc.c
+++ b/src/backend/access/spgist/spgproc.c
@@ -51,7 +51,7 @@ point_box_distance(Point *point, BOX *box)
else
dy = 0.0;
- return HYPOT(dx, dy);
+ return HYPOT(dx, dy, NULL);
}
/*
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index 6747544b679..06b2b6ae295 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -772,7 +772,7 @@ float8pl(PG_FUNCTION_ARGS)
float8 arg1 = PG_GETARG_FLOAT8(0);
float8 arg2 = PG_GETARG_FLOAT8(1);
- PG_RETURN_FLOAT8(float8_pl(arg1, arg2));
+ PG_RETURN_FLOAT8(float8_pl_safe(arg1, arg2, fcinfo->context));
}
Datum
@@ -781,7 +781,7 @@ float8mi(PG_FUNCTION_ARGS)
float8 arg1 = PG_GETARG_FLOAT8(0);
float8 arg2 = PG_GETARG_FLOAT8(1);
- PG_RETURN_FLOAT8(float8_mi(arg1, arg2));
+ PG_RETURN_FLOAT8(float8_mi_safe(arg1, arg2, fcinfo->context));
}
Datum
@@ -790,7 +790,7 @@ float8mul(PG_FUNCTION_ARGS)
float8 arg1 = PG_GETARG_FLOAT8(0);
float8 arg2 = PG_GETARG_FLOAT8(1);
- PG_RETURN_FLOAT8(float8_mul(arg1, arg2));
+ PG_RETURN_FLOAT8(float8_mul_safe(arg1, arg2, fcinfo->context));
}
Datum
@@ -799,7 +799,7 @@ float8div(PG_FUNCTION_ARGS)
float8 arg1 = PG_GETARG_FLOAT8(0);
float8 arg2 = PG_GETARG_FLOAT8(1);
- PG_RETURN_FLOAT8(float8_div(arg1, arg2));
+ PG_RETURN_FLOAT8(float8_div_safe(arg1, arg2, fcinfo->context));
}
diff --git a/src/backend/utils/adt/geo_ops.c b/src/backend/utils/adt/geo_ops.c
index 377a1b3f3ad..a5f2537da25 100644
--- a/src/backend/utils/adt/geo_ops.c
+++ b/src/backend/utils/adt/geo_ops.c
@@ -78,11 +78,14 @@ enum path_delim
/* Routines for points */
static inline void point_construct(Point *result, float8 x, float8 y);
static inline void point_add_point(Point *result, Point *pt1, Point *pt2);
+static inline bool point_add_point_safe(Point *result, Point *pt1, Point *pt2,
+ Node *escontext);
static inline void point_sub_point(Point *result, Point *pt1, Point *pt2);
static inline void point_mul_point(Point *result, Point *pt1, Point *pt2);
static inline void point_div_point(Point *result, Point *pt1, Point *pt2);
static inline bool point_eq_point(Point *pt1, Point *pt2);
static inline float8 point_dt(Point *pt1, Point *pt2);
+static inline bool point_dt_safe(Point *pt1, Point *pt2, Node *escontext, float8 *result);
static inline float8 point_sl(Point *pt1, Point *pt2);
static int point_inside(Point *p, int npts, Point *plist);
@@ -109,6 +112,7 @@ static float8 lseg_closept_lseg(Point *result, LSEG *on_lseg, LSEG *to_lseg);
/* Routines for boxes */
static inline void box_construct(BOX *result, Point *pt1, Point *pt2);
static void box_cn(Point *center, BOX *box);
+static bool box_cn_safe(Point *center, BOX *box, Node* escontext);
static bool box_ov(BOX *box1, BOX *box2);
static float8 box_ar(BOX *box);
static float8 box_ht(BOX *box);
@@ -125,7 +129,7 @@ static float8 circle_ar(CIRCLE *circle);
/* Routines for polygons */
static void make_bound_box(POLYGON *poly);
-static void poly_to_circle(CIRCLE *result, POLYGON *poly);
+static bool poly_to_circle_safe(CIRCLE *result, POLYGON *poly, Node *escontext);
static bool lseg_inside_poly(Point *a, Point *b, POLYGON *poly, int start);
static bool poly_contain_poly(POLYGON *contains_poly, POLYGON *contained_poly);
static bool plist_same(int npts, Point *p1, Point *p2);
@@ -851,7 +855,8 @@ box_center(PG_FUNCTION_ARGS)
BOX *box = PG_GETARG_BOX_P(0);
Point *result = (Point *) palloc(sizeof(Point));
- box_cn(result, box);
+ if (!box_cn_safe(result, box, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_POINT_P(result);
}
@@ -875,6 +880,31 @@ box_cn(Point *center, BOX *box)
center->y = float8_div(float8_pl(box->high.y, box->low.y), 2.0);
}
+/* safe version of box_cn */
+static bool
+box_cn_safe(Point *center, BOX *box, Node* escontext)
+{
+ float8 x;
+ float8 y;
+
+ x = float8_pl_safe(box->high.x, box->low.x, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ center->x = float8_div_safe(x, 2.0, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ y = float8_pl_safe(box->high.y, box->low.y, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ center->y = float8_div_safe(y, 2.0, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ return true;
+}
/* box_wd - returns the width (length) of the box
* (horizontal magnitude).
@@ -1276,7 +1306,7 @@ line_distance(PG_FUNCTION_ARGS)
PG_RETURN_FLOAT8(float8_div(fabs(float8_mi(l1->C,
float8_mul(ratio, l2->C))),
- HYPOT(l1->A, l1->B)));
+ HYPOT(l1->A, l1->B, NULL)));
}
/* line_interpt()
@@ -2001,9 +2031,34 @@ point_distance(PG_FUNCTION_ARGS)
static inline float8
point_dt(Point *pt1, Point *pt2)
{
- return HYPOT(float8_mi(pt1->x, pt2->x), float8_mi(pt1->y, pt2->y));
+ return HYPOT(float8_mi(pt1->x, pt2->x), float8_mi(pt1->y, pt2->y), NULL);
}
+/* errror safe version of point_dt */
+static inline bool
+point_dt_safe(Point *pt1, Point *pt2, Node *escontext, float8 *result)
+{
+ float8 x;
+ float8 y;
+
+ Assert(result != NULL);
+
+ x = float8_mi_safe(pt1->x, pt2->x, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ y = float8_mi_safe(pt1->y, pt2->y, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ *result = HYPOT(x, y, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ return true;
+}
+
+
Datum
point_slope(PG_FUNCTION_ARGS)
{
@@ -2317,13 +2372,31 @@ lseg_center(PG_FUNCTION_ARGS)
{
LSEG *lseg = PG_GETARG_LSEG_P(0);
Point *result;
+ float8 x;
+ float8 y;
result = (Point *) palloc(sizeof(Point));
- result->x = float8_div(float8_pl(lseg->p[0].x, lseg->p[1].x), 2.0);
- result->y = float8_div(float8_pl(lseg->p[0].y, lseg->p[1].y), 2.0);
+ x = float8_pl_safe(lseg->p[0].x, lseg->p[1].x, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ result->x = float8_div_safe(x, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ y = float8_pl_safe(lseg->p[0].y, lseg->p[1].y, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ result->y = float8_div_safe(y, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
PG_RETURN_POINT_P(result);
+
+fail:
+ PG_RETURN_NULL();
}
@@ -4115,6 +4188,27 @@ point_add_point(Point *result, Point *pt1, Point *pt2)
float8_pl(pt1->y, pt2->y));
}
+/* error safe version of point_add_point */
+static inline bool
+point_add_point_safe(Point *result, Point *pt1, Point *pt2,
+ Node *escontext)
+{
+ float8 x;
+ float8 y;
+
+ x = float8_pl_safe(pt1->x, pt2->x, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ y = float8_pl_safe(pt1->y, pt2->y, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ point_construct(result, x, y);
+
+ return true;
+}
+
Datum
point_add(PG_FUNCTION_ARGS)
{
@@ -4458,10 +4552,14 @@ path_poly(PG_FUNCTION_ARGS)
/* This is not very consistent --- other similar cases return NULL ... */
if (!path->closed)
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("open path cannot be converted to polygon")));
+ PG_RETURN_NULL();
+ }
+
/*
* Never overflows: the old size fit in MaxAllocSize, and the new size is
* just a small constant larger.
@@ -4508,7 +4606,9 @@ poly_center(PG_FUNCTION_ARGS)
result = (Point *) palloc(sizeof(Point));
- poly_to_circle(&circle, poly);
+ if (!poly_to_circle_safe(&circle, poly, fcinfo->context))
+ PG_RETURN_NULL();
+
*result = circle.center;
PG_RETURN_POINT_P(result);
@@ -5005,7 +5105,7 @@ circle_mul_pt(PG_FUNCTION_ARGS)
result = (CIRCLE *) palloc(sizeof(CIRCLE));
point_mul_point(&result->center, &circle->center, point);
- result->radius = float8_mul(circle->radius, HYPOT(point->x, point->y));
+ result->radius = float8_mul(circle->radius, HYPOT(point->x, point->y, NULL));
PG_RETURN_CIRCLE_P(result);
}
@@ -5020,7 +5120,7 @@ circle_div_pt(PG_FUNCTION_ARGS)
result = (CIRCLE *) palloc(sizeof(CIRCLE));
point_div_point(&result->center, &circle->center, point);
- result->radius = float8_div(circle->radius, HYPOT(point->x, point->y));
+ result->radius = float8_div(circle->radius, HYPOT(point->x, point->y, NULL));
PG_RETURN_CIRCLE_P(result);
}
@@ -5191,14 +5291,30 @@ circle_box(PG_FUNCTION_ARGS)
box = (BOX *) palloc(sizeof(BOX));
- delta = float8_div(circle->radius, sqrt(2.0));
+ delta = float8_div_safe(circle->radius, sqrt(2.0), fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
- box->high.x = float8_pl(circle->center.x, delta);
- box->low.x = float8_mi(circle->center.x, delta);
- box->high.y = float8_pl(circle->center.y, delta);
- box->low.y = float8_mi(circle->center.y, delta);
+ box->high.x = float8_pl_safe(circle->center.x, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ box->low.x = float8_mi_safe(circle->center.x, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ box->high.y = float8_pl_safe(circle->center.y, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ box->low.y = float8_mi_safe(circle->center.y, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
PG_RETURN_BOX_P(box);
+
+fail:
+ PG_RETURN_NULL();
}
/* box_circle()
@@ -5209,15 +5325,35 @@ box_circle(PG_FUNCTION_ARGS)
{
BOX *box = PG_GETARG_BOX_P(0);
CIRCLE *circle;
+ float8 x;
+ float8 y;
circle = (CIRCLE *) palloc(sizeof(CIRCLE));
- circle->center.x = float8_div(float8_pl(box->high.x, box->low.x), 2.0);
- circle->center.y = float8_div(float8_pl(box->high.y, box->low.y), 2.0);
+ x = float8_pl_safe(box->high.x, box->low.x, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
- circle->radius = point_dt(&circle->center, &box->high);
+ circle->center.x = float8_div_safe(x, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ y = float8_pl_safe(box->high.y, box->low.y, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ circle->center.y = float8_div_safe(y, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ if (!point_dt_safe(&circle->center, &box->high, fcinfo->context,
+ &circle->radius))
+ goto fail;
PG_RETURN_CIRCLE_P(circle);
+
+fail:
+ PG_RETURN_NULL();
}
@@ -5230,28 +5366,38 @@ circle_poly(PG_FUNCTION_ARGS)
int base_size,
size;
int i;
+ float8 x;
+ float8 y;
float8 angle;
float8 anglestep;
if (FPzero(circle->radius))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert circle with radius zero to polygon")));
+ goto fail;
+ }
if (npts < 2)
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("must request at least 2 points")));
+ goto fail;
+ }
base_size = sizeof(poly->p[0]) * npts;
size = offsetof(POLYGON, p) + base_size;
/* Check for integer overflow */
if (base_size / npts != sizeof(poly->p[0]) || size <= base_size)
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("too many points requested")));
-
+ goto fail;
+ }
poly = (POLYGON *) palloc0(size); /* zero any holes */
SET_VARSIZE(poly, size);
poly->npts = npts;
@@ -5260,17 +5406,33 @@ circle_poly(PG_FUNCTION_ARGS)
for (i = 0; i < npts; i++)
{
- angle = float8_mul(anglestep, i);
+ angle = float8_mul_safe(anglestep, i, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
- poly->p[i].x = float8_mi(circle->center.x,
- float8_mul(circle->radius, cos(angle)));
- poly->p[i].y = float8_pl(circle->center.y,
- float8_mul(circle->radius, sin(angle)));
+ x = float8_mul_safe(circle->radius, cos(angle), fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ poly->p[i].x = float8_mi_safe(circle->center.x, x, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ y = float8_mul_safe(circle->radius, sin(angle),fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ poly->p[i].y = float8_pl_safe(circle->center.y, y, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
}
make_bound_box(poly);
PG_RETURN_POLYGON_P(poly);
+
+fail:
+ PG_RETURN_NULL();
}
/*
@@ -5281,10 +5443,11 @@ circle_poly(PG_FUNCTION_ARGS)
* XXX This algorithm should use weighted means of line segments
* rather than straight average values of points - tgl 97/01/21.
*/
-static void
-poly_to_circle(CIRCLE *result, POLYGON *poly)
+static bool
+poly_to_circle_safe(CIRCLE *result, POLYGON *poly, Node *escontext)
{
int i;
+ float8 x;
Assert(poly->npts > 0);
@@ -5293,14 +5456,41 @@ poly_to_circle(CIRCLE *result, POLYGON *poly)
result->radius = 0;
for (i = 0; i < poly->npts; i++)
- point_add_point(&result->center, &result->center, &poly->p[i]);
- result->center.x = float8_div(result->center.x, poly->npts);
- result->center.y = float8_div(result->center.y, poly->npts);
+ {
+ if (!point_add_point_safe(&result->center,
+ &result->center,
+ &poly->p[i],
+ escontext))
+ return false;
+ }
+
+ result->center.x = float8_div_safe(result->center.x,
+ poly->npts,
+ escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ result->center.y = float8_div_safe(result->center.y,
+ poly->npts,
+ escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
for (i = 0; i < poly->npts; i++)
- result->radius = float8_pl(result->radius,
- point_dt(&poly->p[i], &result->center));
- result->radius = float8_div(result->radius, poly->npts);
+ {
+ if (!point_dt_safe(&poly->p[i], &result->center, escontext, &x))
+ return false;
+
+ result->radius = float8_pl_safe(result->radius, x, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+ }
+
+ result->radius = float8_div_safe(result->radius, poly->npts, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ return true;
}
Datum
@@ -5311,7 +5501,8 @@ poly_circle(PG_FUNCTION_ARGS)
result = (CIRCLE *) palloc(sizeof(CIRCLE));
- poly_to_circle(result, poly);
+ if (!poly_to_circle_safe(result, poly, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_CIRCLE_P(result);
}
@@ -5516,7 +5707,7 @@ plist_same(int npts, Point *p1, Point *p2)
*-----------------------------------------------------------------------
*/
float8
-pg_hypot(float8 x, float8 y)
+pg_hypot(float8 x, float8 y, Node *escontext)
{
float8 yx,
result;
@@ -5554,9 +5745,22 @@ pg_hypot(float8 x, float8 y)
result = x * sqrt(1.0 + (yx * yx));
if (unlikely(isinf(result)))
- float_overflow_error();
+ {
+ errsave(escontext,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: overflow"));
+
+ result = 0.0;
+ }
+
if (unlikely(result == 0.0))
- float_underflow_error();
+ {
+ errsave(escontext,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: underflow"));
+
+ result = 0.0;
+ }
return result;
}
diff --git a/src/backend/utils/adt/geo_spgist.c b/src/backend/utils/adt/geo_spgist.c
index fec33e95372..ffffd3cd2de 100644
--- a/src/backend/utils/adt/geo_spgist.c
+++ b/src/backend/utils/adt/geo_spgist.c
@@ -390,7 +390,7 @@ pointToRectBoxDistance(Point *point, RectBox *rect_box)
else
dy = 0;
- return HYPOT(dx, dy);
+ return HYPOT(dx, dy, NULL);
}
diff --git a/src/include/utils/float.h b/src/include/utils/float.h
index fc2a9cf6475..5a7033f2a60 100644
--- a/src/include/utils/float.h
+++ b/src/include/utils/float.h
@@ -109,6 +109,24 @@ float4_pl(const float4 val1, const float4 val2)
return result;
}
+/* error safe version of float8_pl */
+static inline float8
+float8_pl_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+ float8 result;
+
+ result = val1 + val2;
+ if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
+ {
+ errsave(escontext,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: overflow"));
+ result = 0.0f;
+ }
+
+ return result;
+}
+
static inline float8
float8_pl(const float8 val1, const float8 val2)
{
@@ -133,6 +151,24 @@ float4_mi(const float4 val1, const float4 val2)
return result;
}
+/* error safe version of float8_mi */
+static inline float8
+float8_mi_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+ float8 result;
+
+ result = val1 - val2;
+ if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
+ {
+ errsave(escontext,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: overflow"));
+ result = 0.0f;
+ }
+
+ return result;
+}
+
static inline float8
float8_mi(const float8 val1, const float8 val2)
{
@@ -159,6 +195,36 @@ float4_mul(const float4 val1, const float4 val2)
return result;
}
+/* error safe version of float8_mul */
+static inline float8
+float8_mul_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+ float8 result;
+
+ result = val1 * val2;
+ if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
+ {
+ errsave(escontext,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: overflow"));
+
+ result = 0.0;
+ return result;
+ }
+
+ if (unlikely(result == 0.0) && val1 != 0.0 && val2 != 0.0)
+ {
+ errsave(escontext,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: underflow"));
+
+ result = 0.0;
+ return result;
+ }
+
+ return result;
+}
+
static inline float8
float8_mul(const float8 val1, const float8 val2)
{
@@ -189,6 +255,47 @@ float4_div(const float4 val1, const float4 val2)
return result;
}
+/* error safe version of float8_div */
+static inline float8
+float8_div_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+ float8 result;
+
+ if (unlikely(val2 == 0.0) && !isnan(val1))
+ {
+ errsave(escontext,
+ errcode(ERRCODE_DIVISION_BY_ZERO),
+ errmsg("division by zero"));
+
+ result = 0.0;
+ return result;
+ }
+
+ result = val1 / val2;
+
+ if (unlikely(isinf(result)) && !isinf(val1))
+ {
+ errsave(escontext,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: overflow"));
+
+ result = 0.0;
+ return result;
+ }
+
+ if (unlikely(result == 0.0) && val1 != 0.0 && !isinf(val2))
+ {
+ errsave(escontext,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: underflow"));
+
+ result = 0.0;
+ return result;
+ }
+
+ return result;
+}
+
static inline float8
float8_div(const float8 val1, const float8 val2)
{
diff --git a/src/include/utils/geo_decls.h b/src/include/utils/geo_decls.h
index 8a9df75c93c..0056a26639f 100644
--- a/src/include/utils/geo_decls.h
+++ b/src/include/utils/geo_decls.h
@@ -88,7 +88,7 @@ FPge(double A, double B)
#define FPge(A,B) ((A) >= (B))
#endif
-#define HYPOT(A, B) pg_hypot(A, B)
+#define HYPOT(A, B, escontext) pg_hypot(A, B, escontext)
/*---------------------------------------------------------------------
* Point - (x,y)
@@ -280,6 +280,6 @@ CirclePGetDatum(const CIRCLE *X)
* in geo_ops.c
*/
-extern float8 pg_hypot(float8 x, float8 y);
+extern float8 pg_hypot(float8 x, float8 y, Node *escontext);
#endif /* GEO_DECLS_H */
--
2.34.1
v8-0016-error-safe-for-casting-timestamp-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v8-0016-error-safe-for-casting-timestamp-to-other-types-per-pg_cast.patchDownload
From a16e038803cc64f835ed37174e06652f17c26088 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 18:44:35 +0800
Subject: [PATCH v8 16/20] error safe for casting timestamp to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
and pc.castfunc > 0 and (castsource::regtype ='timestamp'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
-----------------------------+-----------------------------+----------+-------------+------------+-----------------------+-------------
timestamp without time zone | date | 2029 | a | f | timestamp_date | date
timestamp without time zone | time without time zone | 1316 | a | f | timestamp_time | time
timestamp without time zone | timestamp with time zone | 2028 | i | f | timestamp_timestamptz | timestamptz
timestamp without time zone | timestamp without time zone | 1961 | i | f | timestamp_scale | timestamp
(4 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 13 +++++++++++--
src/backend/utils/adt/timestamp.c | 17 +++++++++++++++--
2 files changed, 26 insertions(+), 4 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 111bfd8f519..c5562b563e5 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -1373,7 +1373,16 @@ timestamp_date(PG_FUNCTION_ARGS)
Timestamp timestamp = PG_GETARG_TIMESTAMP(0);
DateADT result;
- result = timestamp2date_opt_overflow(timestamp, NULL);
+ if (likely(!fcinfo->context))
+ result = timestamptz2date_opt_overflow(timestamp, NULL);
+ else
+ {
+ int overflow;
+ result = timestamp2date_opt_overflow(timestamp, &overflow);
+
+ if (overflow != 0)
+ PG_RETURN_NULL();
+ }
PG_RETURN_DATEADT(result);
}
@@ -2089,7 +2098,7 @@ timestamp_time(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 116e3ef28fc..7a57ac3eaf6 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -352,7 +352,8 @@ timestamp_scale(PG_FUNCTION_ARGS)
result = timestamp;
- AdjustTimestampForTypmod(&result, typmod, NULL);
+ if (!AdjustTimestampForTypmod(&result, typmod, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_TIMESTAMP(result);
}
@@ -6430,7 +6431,19 @@ timestamp_timestamptz(PG_FUNCTION_ARGS)
{
Timestamp timestamp = PG_GETARG_TIMESTAMP(0);
- PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(timestamp));
+ if (likely(!fcinfo->context))
+ PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(timestamp));
+ else
+ {
+ TimestampTz result;
+ int overflow;
+
+ result = timestamp2timestamptz_opt_overflow(timestamp, &overflow);
+ if (overflow != 0)
+ PG_RETURN_NULL();
+ else
+ PG_RETURN_TIMESTAMPTZ(result);
+ }
}
/*
--
2.34.1
v8-0015-error-safe-for-casting-timestamptz-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v8-0015-error-safe-for-casting-timestamptz-to-other-types-per-pg_cast.patchDownload
From 8230876c38463a7ff0778a58da8208593e2f6f56 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Mon, 6 Oct 2025 12:39:22 +0800
Subject: [PATCH v8 15/20] error safe for casting timestamptz to other types
per pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
and pc.castfunc > 0 and (castsource::regtype ='timestamptz'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
--------------------------+-----------------------------+----------+-------------+------------+-----------------------+-------------
timestamp with time zone | date | 1178 | a | f | timestamptz_date | date
timestamp with time zone | time without time zone | 2019 | a | f | timestamptz_time | time
timestamp with time zone | timestamp without time zone | 2027 | a | f | timestamptz_timestamp | timestamp
timestamp with time zone | time with time zone | 1388 | a | f | timestamptz_timetz | timetz
timestamp with time zone | timestamp with time zone | 1967 | i | f | timestamptz_scale | timestamptz
(5 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 16 +++++++++++++---
src/backend/utils/adt/timestamp.c | 17 +++++++++++++++--
2 files changed, 28 insertions(+), 5 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 4f0f3d26989..111bfd8f519 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -1468,7 +1468,17 @@ timestamptz_date(PG_FUNCTION_ARGS)
TimestampTz timestamp = PG_GETARG_TIMESTAMP(0);
DateADT result;
- result = timestamptz2date_opt_overflow(timestamp, NULL);
+ if (likely(!fcinfo->context))
+ result = timestamptz2date_opt_overflow(timestamp, NULL);
+ else
+ {
+ int overflow;
+ result = timestamptz2date_opt_overflow(timestamp, &overflow);
+
+ if (overflow != 0)
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_DATEADT(result);
}
@@ -2110,7 +2120,7 @@ timestamptz_time(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
@@ -3029,7 +3039,7 @@ timestamptz_timetz(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 7b565cc6d66..116e3ef28fc 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -875,7 +875,8 @@ timestamptz_scale(PG_FUNCTION_ARGS)
result = timestamp;
- AdjustTimestampForTypmod(&result, typmod, NULL);
+ if (!AdjustTimestampForTypmod(&result, typmod, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_TIMESTAMPTZ(result);
}
@@ -6507,7 +6508,19 @@ timestamptz_timestamp(PG_FUNCTION_ARGS)
{
TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(0);
- PG_RETURN_TIMESTAMP(timestamptz2timestamp(timestamp));
+ if (likely(!fcinfo->context))
+ PG_RETURN_TIMESTAMP(timestamptz2timestamp(timestamp));
+ else
+ {
+ int overflow;
+ Timestamp result;
+
+ result = timestamptz2timestamp_opt_overflow(timestamp, &overflow);
+ if (overflow != 0)
+ PG_RETURN_NULL();
+ else
+ PG_RETURN_TIMESTAMP(result);
+ }
}
/*
--
2.34.1
v8-0013-error-safe-for-casting-date-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v8-0013-error-safe-for-casting-date-to-other-types-per-pg_cast.patchDownload
From 717fdef46a8aa9628dea19f23684a4a6ea2f8659 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 18:42:50 +0800
Subject: [PATCH v8 13/20] error safe for casting date to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'date'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+-----------------------------+----------+-------------+------------+------------------+-------------
date | timestamp without time zone | 2024 | i | f | date_timestamp | timestamp
date | timestamp with time zone | 1174 | i | f | date_timestamptz | timestamptz
(2 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 22 ++++++++++++++++++++--
1 file changed, 20 insertions(+), 2 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 344f58b92f7..c7a3cde2d81 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -1350,7 +1350,16 @@ date_timestamp(PG_FUNCTION_ARGS)
DateADT dateVal = PG_GETARG_DATEADT(0);
Timestamp result;
- result = date2timestamp(dateVal);
+ if (likely(!fcinfo->context))
+ result = date2timestamp(dateVal);
+ else
+ {
+ int overflow;
+
+ result = date2timestamp_opt_overflow(dateVal, &overflow);
+ if (overflow != 0)
+ PG_RETURN_NULL();
+ }
PG_RETURN_TIMESTAMP(result);
}
@@ -1435,7 +1444,16 @@ date_timestamptz(PG_FUNCTION_ARGS)
DateADT dateVal = PG_GETARG_DATEADT(0);
TimestampTz result;
- result = date2timestamptz(dateVal);
+ if (likely(!fcinfo->context))
+ result = date2timestamptz(dateVal);
+ else
+ {
+ int overflow;
+ result = date2timestamptz_opt_overflow(dateVal, &overflow);
+
+ if (overflow != 0)
+ PG_RETURN_NULL();
+ }
PG_RETURN_TIMESTAMP(result);
}
--
2.34.1
v8-0014-error-safe-for-casting-interval-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v8-0014-error-safe-for-casting-interval-to-other-types-per-pg_cast.patchDownload
From 488fab8fb6d4f3be25b347137d743631cff3a7b7 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 18:43:29 +0800
Subject: [PATCH v8 14/20] error safe for casting interval to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'interval'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------------+----------+-------------+------------+----------------+----------
interval | time without time zone | 1419 | a | f | interval_time | time
interval | interval | 1200 | i | f | interval_scale | interval
(2 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 2 +-
src/backend/utils/adt/timestamp.c | 3 ++-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index c7a3cde2d81..4f0f3d26989 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2180,7 +2180,7 @@ interval_time(PG_FUNCTION_ARGS)
TimeADT result;
if (INTERVAL_NOT_FINITE(span))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("cannot convert infinite interval to time")));
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 156a4830ffd..7b565cc6d66 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1334,7 +1334,8 @@ interval_scale(PG_FUNCTION_ARGS)
result = palloc(sizeof(Interval));
*result = *interval;
- AdjustIntervalForTypmod(result, typmod, NULL);
+ if (!AdjustIntervalForTypmod(result, typmod, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_INTERVAL_P(result);
}
--
2.34.1
v8-0011-error-safe-for-casting-float4-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v8-0011-error-safe-for-casting-float4-to-other-types-per-pg_cast.patchDownload
From 0fcd9f5fac1a1d418d35fe642775b2db6932d3f8 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 18:33:57 +0800
Subject: [PATCH v8 11/20] error safe for casting float4 to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'float4'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+----------------+---------
real | bigint | 653 | a | f | ftoi8 | int8
real | smallint | 238 | a | f | ftoi2 | int2
real | integer | 319 | a | f | ftoi4 | int4
real | double precision | 311 | i | f | ftod | float8
real | numeric | 1742 | a | f | float4_numeric | numeric
(5 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/float.c | 12 ++++++++++--
src/backend/utils/adt/int8.c | 6 +++++-
src/backend/utils/adt/numeric.c | 3 ++-
3 files changed, 17 insertions(+), 4 deletions(-)
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index 7b97d2be6ca..dd8a2ff378b 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -1298,10 +1298,14 @@ ftoi4(PG_FUNCTION_ARGS)
/* Range check */
if (unlikely(isnan(num) || !FLOAT4_FITS_IN_INT32(num)))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_INT32((int32) num);
}
@@ -1323,10 +1327,14 @@ ftoi2(PG_FUNCTION_ARGS)
/* Range check */
if (unlikely(isnan(num) || !FLOAT4_FITS_IN_INT16(num)))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_INT16((int16) num);
}
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 3b2d100be92..bcdb020a91c 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -1303,10 +1303,14 @@ ftoi8(PG_FUNCTION_ARGS)
/* Range check */
if (unlikely(isnan(num) || !FLOAT4_FITS_IN_INT64(num)))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("bigint out of range")));
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_INT64((int64) num);
}
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index ce5f71109e7..8839b095f60 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -4668,7 +4668,8 @@ float4_numeric(PG_FUNCTION_ARGS)
init_var(&result);
/* Assume we need not worry about leading/trailing spaces */
- (void) set_var_from_str(buf, buf, &result, &endptr, NULL);
+ if (!set_var_from_str(buf, buf, &result, &endptr, fcinfo->context))
+ PG_RETURN_NULL();
res = make_result(&result);
--
2.34.1
v8-0012-error-safe-for-casting-float8-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v8-0012-error-safe-for-casting-float8-to-other-types-per-pg_cast.patchDownload
From c09d71d0be3e4b35dda2b83f3fdeedd318df76b9 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 18:41:55 +0800
Subject: [PATCH v8 12/20] error safe for casting float8 to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'float8'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------------+------------+----------+-------------+------------+----------------+---------
double precision | bigint | 483 | a | f | dtoi8 | int8
double precision | smallint | 237 | a | f | dtoi2 | int2
double precision | integer | 317 | a | f | dtoi4 | int4
double precision | real | 312 | a | f | dtof | float4
double precision | numeric | 1743 | a | f | float8_numeric | numeric
(5 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/float.c | 29 +++++++++++++++++++++++++----
src/backend/utils/adt/int8.c | 6 +++++-
src/backend/utils/adt/numeric.c | 3 ++-
3 files changed, 32 insertions(+), 6 deletions(-)
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index dd8a2ff378b..6747544b679 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -1199,9 +1199,22 @@ dtof(PG_FUNCTION_ARGS)
result = (float4) num;
if (unlikely(isinf(result)) && !isinf(num))
- float_overflow_error();
+ {
+ errsave(fcinfo->context,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: overflow"));
+
+ PG_RETURN_NULL();
+ }
+
if (unlikely(result == 0.0f) && num != 0.0)
- float_underflow_error();
+ {
+ errsave(fcinfo->context,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: underflow"));
+
+ PG_RETURN_NULL();
+ }
PG_RETURN_FLOAT4(result);
}
@@ -1224,10 +1237,14 @@ dtoi4(PG_FUNCTION_ARGS)
/* Range check */
if (unlikely(isnan(num) || !FLOAT8_FITS_IN_INT32(num)))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_INT32((int32) num);
}
@@ -1249,10 +1266,14 @@ dtoi2(PG_FUNCTION_ARGS)
/* Range check */
if (unlikely(isnan(num) || !FLOAT8_FITS_IN_INT16(num)))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_INT16((int16) num);
}
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index bcdb020a91c..437dbbccd4a 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -1268,10 +1268,14 @@ dtoi8(PG_FUNCTION_ARGS)
/* Range check */
if (unlikely(isnan(num) || !FLOAT8_FITS_IN_INT64(num)))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("bigint out of range")));
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_INT64((int64) num);
}
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 8839b095f60..76cd9800c2a 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -4570,7 +4570,8 @@ float8_numeric(PG_FUNCTION_ARGS)
init_var(&result);
/* Assume we need not worry about leading/trailing spaces */
- (void) set_var_from_str(buf, buf, &result, &endptr, NULL);
+ if (!set_var_from_str(buf, buf, &result, &endptr, fcinfo->context))
+ PG_RETURN_NULL();
res = make_result(&result);
--
2.34.1
v8-0010-error-safe-for-casting-numeric-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v8-0010-error-safe-for-casting-numeric-to-other-types-per-pg_cast.patchDownload
From c1893169d93940053735303d0093632b030ff8c2 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 21:57:31 +0800
Subject: [PATCH v8 10/20] error safe for casting numeric to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'numeric'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+----------------+---------
numeric | bigint | 1779 | a | f | numeric_int8 | int8
numeric | smallint | 1783 | a | f | numeric_int2 | int2
numeric | integer | 1744 | a | f | numeric_int4 | int4
numeric | real | 1745 | i | f | numeric_float4 | float4
numeric | double precision | 1746 | i | f | numeric_float8 | float8
numeric | money | 3824 | a | f | numeric_cash | money
numeric | numeric | 1703 | i | f | numeric | numeric
(7 rows)
discussion: https://postgr.es/m/CACJufxHCMzrHOW=wRe8L30rMhB3sjwAv1LE928Fa7sxMu1Tx-g@mail.gmail.com
---
src/backend/utils/adt/numeric.c | 68 +++++++++++++++++++++++++--------
1 file changed, 53 insertions(+), 15 deletions(-)
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 2501007d981..ce5f71109e7 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -1244,7 +1244,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));
}
@@ -1295,8 +1296,9 @@ numeric (PG_FUNCTION_ARGS)
init_var(&var);
set_var_from_num(num, &var);
- (void) apply_typmod(&var, typmod, NULL);
- new = make_result(&var);
+ if (!apply_typmod(&var, typmod, fcinfo->context))
+ PG_RETURN_NULL();
+ new = make_result_safe(&var, fcinfo->context);
free_var(&var);
@@ -3019,7 +3021,10 @@ numeric_mul(PG_FUNCTION_ARGS)
Numeric num2 = PG_GETARG_NUMERIC(1);
Numeric res;
- res = numeric_mul_safe(num1, num2, NULL);
+ res = numeric_mul_safe(num1, num2, fcinfo->context);
+
+ if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
PG_RETURN_NUMERIC(res);
}
@@ -4393,9 +4398,15 @@ numeric_int4_safe(Numeric num, Node *escontext)
Datum
numeric_int4(PG_FUNCTION_ARGS)
{
+ int32 result;
Numeric num = PG_GETARG_NUMERIC(0);
- PG_RETURN_INT32(numeric_int4_safe(num, NULL));
+ result = numeric_int4_safe(num, fcinfo->context);
+
+ if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
+
+ PG_RETURN_INT32(result);
}
/*
@@ -4463,9 +4474,15 @@ numeric_int8_safe(Numeric num, Node *escontext)
Datum
numeric_int8(PG_FUNCTION_ARGS)
{
+ int64 result;
Numeric num = PG_GETARG_NUMERIC(0);
- PG_RETURN_INT64(numeric_int8_safe(num, NULL));
+ result = numeric_int8_safe(num, fcinfo->context);
+
+ if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
+
+ PG_RETURN_INT64(result);
}
@@ -4489,28 +4506,38 @@ numeric_int2(PG_FUNCTION_ARGS)
if (NUMERIC_IS_SPECIAL(num))
{
if (NUMERIC_IS_NAN(num))
- ereport(ERROR,
+ errsave(fcinfo->context,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert NaN to %s", "smallint")));
else
- ereport(ERROR,
+ errsave(fcinfo->context,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert infinity to %s", "smallint")));
+
+ PG_RETURN_NULL();
}
/* Convert to variable format and thence to int8 */
init_var_from_num(num, &x);
if (!numericvar_to_int64(&x, &val))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
+ PG_RETURN_NULL();
+ }
+
if (unlikely(val < PG_INT16_MIN) || unlikely(val > PG_INT16_MAX))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
+ PG_RETURN_NULL();
+ }
+
/* Down-convert to int2 */
result = (int16) val;
@@ -4572,10 +4599,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);
}
@@ -4667,7 +4698,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
v8-0008-error-safe-for-casting-integer-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v8-0008-error-safe-for-casting-integer-to-other-types-per-pg_cast.patchDownload
From 8fe607708c8291649e6eab1dbaa4159bac06b4b7 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 16:04:07 +0800
Subject: [PATCH v8 08/20] error safe for casting integer to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'integer'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+--------------+---------
integer | bigint | 481 | i | f | int48 | int8
integer | smallint | 314 | a | f | i4toi2 | int2
integer | real | 318 | i | f | i4tof | float4
integer | double precision | 316 | i | f | i4tod | float8
integer | numeric | 1740 | i | f | int4_numeric | numeric
integer | money | 3811 | a | f | int4_cash | money
integer | boolean | 2557 | e | f | int4_bool | bool
integer | bytea | 6368 | e | f | int4_bytea | bytea
integer | "char" | 78 | e | f | i4tochar | char
integer | bit | 1683 | e | f | bitfromint4 | bit
(10 rows)
only int4_cash, i4toi2, i4tochar need take care of error handling. but support
for cash data type is not easy, so only i4toi2, i4tochar function refactoring.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/char.c | 6 +++++-
src/backend/utils/adt/int.c | 5 ++++-
2 files changed, 9 insertions(+), 2 deletions(-)
diff --git a/src/backend/utils/adt/char.c b/src/backend/utils/adt/char.c
index 22dbfc950b1..7cf09d954e7 100644
--- a/src/backend/utils/adt/char.c
+++ b/src/backend/utils/adt/char.c
@@ -192,10 +192,14 @@ i4tochar(PG_FUNCTION_ARGS)
int32 arg1 = PG_GETARG_INT32(0);
if (arg1 < SCHAR_MIN || arg1 > SCHAR_MAX)
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("\"char\" out of range")));
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_CHAR((int8) arg1);
}
diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c
index b5781989a64..4d3ce23a4af 100644
--- a/src/backend/utils/adt/int.c
+++ b/src/backend/utils/adt/int.c
@@ -350,10 +350,13 @@ i4toi2(PG_FUNCTION_ARGS)
int32 arg1 = PG_GETARG_INT32(0);
if (unlikely(arg1 < SHRT_MIN) || unlikely(arg1 > SHRT_MAX))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
+ PG_RETURN_NULL();
+ }
PG_RETURN_INT16((int16) arg1);
}
--
2.34.1
v8-0009-error-safe-for-casting-bigint-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v8-0009-error-safe-for-casting-bigint-to-other-types-per-pg_cast.patchDownload
From 51e40d5a0ab1e41df990d86575a1734e9954f94f Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 16:38:37 +0800
Subject: [PATCH v8 09/20] error safe for casting bigint to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'bigint'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+--------------+---------
bigint | smallint | 714 | a | f | int82 | int2
bigint | integer | 480 | a | f | int84 | int4
bigint | real | 652 | i | f | i8tof | float4
bigint | double precision | 482 | i | f | i8tod | float8
bigint | numeric | 1781 | i | f | int8_numeric | numeric
bigint | money | 3812 | a | f | int8_cash | money
bigint | oid | 1287 | i | f | i8tooid | oid
bigint | regproc | 1287 | i | f | i8tooid | oid
bigint | regprocedure | 1287 | i | f | i8tooid | oid
bigint | regoper | 1287 | i | f | i8tooid | oid
bigint | regoperator | 1287 | i | f | i8tooid | oid
bigint | regclass | 1287 | i | f | i8tooid | oid
bigint | regcollation | 1287 | i | f | i8tooid | oid
bigint | regtype | 1287 | i | f | i8tooid | oid
bigint | regconfig | 1287 | i | f | i8tooid | oid
bigint | regdictionary | 1287 | i | f | i8tooid | oid
bigint | regrole | 1287 | i | f | i8tooid | oid
bigint | regnamespace | 1287 | i | f | i8tooid | oid
bigint | regdatabase | 1287 | i | f | i8tooid | oid
bigint | bytea | 6369 | e | f | int8_bytea | bytea
bigint | bit | 2075 | e | f | bitfromint8 | bit
(21 rows)
already error safe: i8tof, i8tod, int8_numeric, int8_bytea, bitfromint8
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/int8.c | 18 +++++++++++++++---
1 file changed, 15 insertions(+), 3 deletions(-)
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index bdea490202a..3b2d100be92 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -1204,10 +1204,14 @@ int84(PG_FUNCTION_ARGS)
int64 arg = PG_GETARG_INT64(0);
if (unlikely(arg < PG_INT32_MIN) || unlikely(arg > PG_INT32_MAX))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_INT32((int32) arg);
}
@@ -1225,10 +1229,14 @@ int82(PG_FUNCTION_ARGS)
int64 arg = PG_GETARG_INT64(0);
if (unlikely(arg < PG_INT16_MIN) || unlikely(arg > PG_INT16_MAX))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_INT16((int16) arg);
}
@@ -1308,10 +1316,14 @@ i8tooid(PG_FUNCTION_ARGS)
int64 arg = PG_GETARG_INT64(0);
if (unlikely(arg < 0) || unlikely(arg > PG_UINT32_MAX))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("OID out of range")));
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_OID((Oid) arg);
}
--
2.34.1
v8-0007-error-safe-for-casting-macaddr8-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v8-0007-error-safe-for-casting-macaddr8-to-other-types-per-pg_cast.patchDownload
From 63feaffbf971d110e57276e09e7a28dc5cbb6a57 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 19:12:12 +0800
Subject: [PATCH v8 07/20] error safe for casting macaddr8 to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
and pc.castfunc > 0 and castsource::regtype ='macaddr8'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------+----------+-------------+------------+-------------------+---------
macaddr8 | macaddr | 4124 | i | f | macaddr8tomacaddr | macaddr
(1 row)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/mac8.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/backend/utils/adt/mac8.c b/src/backend/utils/adt/mac8.c
index 08e41ba4eea..e2a7a90e42c 100644
--- a/src/backend/utils/adt/mac8.c
+++ b/src/backend/utils/adt/mac8.c
@@ -550,7 +550,8 @@ macaddr8tomacaddr(PG_FUNCTION_ARGS)
result = (macaddr *) palloc0(sizeof(macaddr));
if ((addr->d != 0xFF) || (addr->e != 0xFE))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("macaddr8 data out of range to convert to macaddr"),
errhint("Only addresses that have FF and FE as values in the "
@@ -558,6 +559,9 @@ macaddr8tomacaddr(PG_FUNCTION_ARGS)
"xx:xx:xx:ff:fe:xx:xx:xx, are eligible to be converted "
"from macaddr8 to macaddr.")));
+ PG_RETURN_NULL();
+ }
+
result->a = addr->a;
result->b = addr->b;
result->c = addr->c;
--
2.34.1
v8-0005-error-safe-for-casting-character-varying-to-other-types-per-pg_ca.patchtext/x-patch; charset=US-ASCII; name=v8-0005-error-safe-for-casting-character-varying-to-other-types-per-pg_ca.patchDownload
From dc5cfd3891d76de2aa7eb5d0d694a0eeeb9af9f5 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 19:17:00 +0800
Subject: [PATCH v8 05/20] error safe for casting character varying to other
types per pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc and pc.castfunc > 0 and
(castsource::regtype = 'character varying'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
-------------------+-------------------+----------+-------------+------------+---------------+----------
character varying | regclass | 1079 | i | f | text_regclass | regclass
character varying | "char" | 944 | a | f | text_char | char
character varying | name | 1400 | i | f | text_name | name
character varying | xml | 2896 | e | f | texttoxml | xml
character varying | character varying | 669 | i | f | varchar | varchar
(5 rows)
texttoxml, text_regclass was refactored as error safe in prior patch.
text_char, text_name is already error safe.
so here we only need handle function "varchar".
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/varchar.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c
index a95e4bd094e..fdbef272c39 100644
--- a/src/backend/utils/adt/varchar.c
+++ b/src/backend/utils/adt/varchar.c
@@ -638,10 +638,14 @@ varchar(PG_FUNCTION_ARGS)
{
for (i = maxmblen; i < len; i++)
if (s_data[i] != ' ')
- ereport(ERROR,
+ {
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
errmsg("value too long for type character varying(%d)",
maxlen)));
+
+ PG_RETURN_NULL();
+ }
}
PG_RETURN_VARCHAR_P((VarChar *) cstring_to_text_with_len(s_data,
--
2.34.1
v8-0006-error-safe-for-casting-inet-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v8-0006-error-safe-for-casting-inet-to-other-types-per-pg_cast.patchDownload
From ef691060bbb07a0c8e3d995a70437676bd9dc5f2 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 15:55:55 +0800
Subject: [PATCH v8 06/20] error safe for casting inet to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'inet'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+-------------------+----------+-------------+------------+--------------+---------
inet | cidr | 1715 | a | f | inet_to_cidr | cidr
inet | text | 730 | a | f | network_show | text
inet | character varying | 730 | a | f | network_show | text
inet | character | 730 | a | f | network_show | text
(4 rows)
inet_to_cidr is already error safe.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/network.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/backend/utils/adt/network.c b/src/backend/utils/adt/network.c
index 3cb0ab6829a..f34228763da 100644
--- a/src/backend/utils/adt/network.c
+++ b/src/backend/utils/adt/network.c
@@ -1137,10 +1137,14 @@ network_show(PG_FUNCTION_ARGS)
if (pg_inet_net_ntop(ip_family(ip), ip_addr(ip), ip_maxbits(ip),
tmp, sizeof(tmp)) == NULL)
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
errmsg("could not format inet value: %m")));
+ PG_RETURN_NULL();
+ }
+
/* Add /n if not present (which it won't be) */
if (strchr(tmp, '/') == NULL)
{
--
2.34.1
v8-0003-error-safe-for-casting-character-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v8-0003-error-safe-for-casting-character-to-other-types-per-pg_cast.patchDownload
From e17c43deafe11573da59f2c9022de0412d1eecc6 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 11 Oct 2025 10:11:37 +0800
Subject: [PATCH v8 03/20] error safe for casting character to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype ='character'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+-------------------+----------+-------------+------------+-------------+---------
character | text | 401 | i | f | rtrim1 | text
character | character varying | 401 | i | f | rtrim1 | text
character | "char" | 944 | a | f | text_char | char
character | name | 409 | i | f | bpchar_name | name
character | xml | 2896 | e | f | texttoxml | xml
character | character | 668 | i | f | bpchar | bpchar
(6 rows)
only texttoxml, bpchar(PG_FUNCTION_ARGS) need take care of error handling.
other functions already error safe.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/executor/execExprInterp.c | 2 +-
src/backend/utils/adt/varchar.c | 6 +++++-
src/backend/utils/adt/xml.c | 17 ++++++++++++-----
src/include/utils/xml.h | 2 +-
4 files changed, 19 insertions(+), 8 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 0e1a74976f7..67f4e00eac4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -4542,7 +4542,7 @@ ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op)
*op->resvalue = PointerGetDatum(xmlparse(data,
xexpr->xmloption,
- preserve_whitespace));
+ preserve_whitespace, NULL));
*op->resnull = false;
}
break;
diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c
index 3f40c9da1a0..a95e4bd094e 100644
--- a/src/backend/utils/adt/varchar.c
+++ b/src/backend/utils/adt/varchar.c
@@ -307,10 +307,14 @@ bpchar(PG_FUNCTION_ARGS)
{
for (i = maxmblen; i < len; i++)
if (s[i] != ' ')
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
errmsg("value too long for type character(%d)",
maxlen)));
+
+ PG_RETURN_NULL();
+ }
}
len = maxmblen;
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 66b44183695..ba58c354e50 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -659,7 +659,7 @@ texttoxml(PG_FUNCTION_ARGS)
{
text *data = PG_GETARG_TEXT_PP(0);
- PG_RETURN_XML_P(xmlparse(data, xmloption, true));
+ PG_RETURN_XML_P(xmlparse(data, xmloption, true, fcinfo->context));
}
@@ -1028,18 +1028,25 @@ xmlelement(XmlExpr *xexpr,
xmltype *
-xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace)
+xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, Node *escontext)
{
#ifdef USE_LIBXML
xmlDocPtr doc;
doc = xml_parse(data, xmloption_arg, preserve_whitespace,
- GetDatabaseEncoding(), NULL, NULL, NULL);
- xmlFreeDoc(doc);
+ GetDatabaseEncoding(), NULL, NULL, escontext);
+ if (doc)
+ xmlFreeDoc(doc);
+
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return NULL;
return (xmltype *) data;
#else
- NO_XML_SUPPORT();
+ errsave(escontext,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unsupported XML feature"),
+ errdetail("This functionality requires the server to be built with libxml support."));
return NULL;
#endif
}
diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h
index 0d7a816b9f9..b3d3f11fac4 100644
--- a/src/include/utils/xml.h
+++ b/src/include/utils/xml.h
@@ -73,7 +73,7 @@ extern xmltype *xmlconcat(List *args);
extern xmltype *xmlelement(XmlExpr *xexpr,
Datum *named_argvalue, bool *named_argnull,
Datum *argvalue, bool *argnull);
-extern xmltype *xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace);
+extern xmltype *xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, Node *escontext);
extern xmltype *xmlpi(const char *target, text *arg, bool arg_is_null, bool *result_is_null);
extern xmltype *xmlroot(xmltype *data, text *version, int standalone);
extern bool xml_is_document(xmltype *arg);
--
2.34.1
v8-0004-error-safe-for-casting-text-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v8-0004-error-safe-for-casting-text-to-other-types-per-pg_cast.patchDownload
From 2a5c73509c8030b55906d254f3457cee89c37639 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 15:49:46 +0800
Subject: [PATCH v8 04/20] error safe for casting text to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'text'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------+----------+-------------+------------+---------------+----------
text | regclass | 1079 | i | f | text_regclass | regclass
text | "char" | 944 | a | f | text_char | char
text | name | 407 | i | f | text_name | name
text | xml | 2896 | e | f | texttoxml | xml
(4 rows)
already error safe: text_name, text_char.
texttoxml is refactored in character type error safe patch.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/catalog/namespace.c | 167 ++++++++++++++++++++++++++++++++
src/backend/utils/adt/regproc.c | 33 ++++++-
src/backend/utils/adt/varlena.c | 42 ++++++++
src/include/catalog/namespace.h | 6 ++
src/include/utils/varlena.h | 1 +
5 files changed, 246 insertions(+), 3 deletions(-)
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index d23474da4fb..a04d179c7fb 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -640,6 +640,137 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
return relId;
}
+/* safe version of RangeVarGetRelidExtended */
+Oid
+RangeVarGetRelidExtendedSafe(const RangeVar *relation, LOCKMODE lockmode,
+ uint32 flags, RangeVarGetRelidCallback callback, void *callback_arg,
+ Node *escontext)
+{
+ uint64 inval_count;
+ Oid relId;
+ Oid oldRelId = InvalidOid;
+ bool retry = false;
+ bool missing_ok = (flags & RVR_MISSING_OK) != 0;
+
+ /* verify that flags do no conflict */
+ Assert(!((flags & RVR_NOWAIT) && (flags & RVR_SKIP_LOCKED)));
+
+ /*
+ * We check the catalog name and then ignore it.
+ */
+ if (relation->catalogname)
+ {
+ if (strcmp(relation->catalogname, get_database_name(MyDatabaseId)) != 0)
+ ereturn(escontext, InvalidOid,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cross-database references are not implemented: \"%s.%s.%s\"",
+ relation->catalogname, relation->schemaname,
+ relation->relname));
+ }
+
+ for (;;)
+ {
+ inval_count = SharedInvalidMessageCounter;
+
+ if (relation->relpersistence == RELPERSISTENCE_TEMP)
+ {
+ if (!OidIsValid(myTempNamespace))
+ relId = InvalidOid;
+ else
+ {
+ if (relation->schemaname)
+ {
+ Oid namespaceId;
+
+ namespaceId = LookupExplicitNamespace(relation->schemaname, missing_ok);
+
+ /*
+ * For missing_ok, allow a non-existent schema name to
+ * return InvalidOid.
+ */
+ if (namespaceId != myTempNamespace)
+ ereturn(escontext, InvalidOid,
+ errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("temporary tables cannot specify a schema name"));
+ }
+
+ relId = get_relname_relid(relation->relname, myTempNamespace);
+ }
+ }
+ else if (relation->schemaname)
+ {
+ Oid namespaceId;
+
+ /* use exact schema given */
+ namespaceId = LookupExplicitNamespace(relation->schemaname, missing_ok);
+ if (missing_ok && !OidIsValid(namespaceId))
+ relId = InvalidOid;
+ else
+ relId = get_relname_relid(relation->relname, namespaceId);
+ }
+ else
+ {
+ /* search the namespace path */
+ relId = RelnameGetRelid(relation->relname);
+ }
+
+ if (callback)
+ callback(relation, relId, oldRelId, callback_arg);
+
+ if (lockmode == NoLock)
+ break;
+
+ if (retry)
+ {
+ if (relId == oldRelId)
+ break;
+ if (OidIsValid(oldRelId))
+ UnlockRelationOid(oldRelId, lockmode);
+ }
+
+ if (!OidIsValid(relId))
+ AcceptInvalidationMessages();
+ else if (!(flags & (RVR_NOWAIT | RVR_SKIP_LOCKED)))
+ LockRelationOid(relId, lockmode);
+ else if (!ConditionalLockRelationOid(relId, lockmode))
+ {
+ if (relation->schemaname)
+ ereport(DEBUG1,
+ errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s.%s\"",
+ relation->schemaname, relation->relname));
+ else
+ ereport(DEBUG1,
+ errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s\"",
+ relation->relname));
+
+ return InvalidOid;
+ }
+
+ if (inval_count == SharedInvalidMessageCounter)
+ break;
+
+ retry = true;
+ oldRelId = relId;
+ }
+
+ if (!OidIsValid(relId))
+ {
+ if (relation->schemaname)
+ ereturn(escontext, InvalidOid,
+ errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s.%s\" does not exist",
+ relation->schemaname, relation->relname));
+ else
+ ereturn(escontext, InvalidOid,
+ errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" does not exist",
+ relation->relname));
+ }
+ return relId;
+}
+
/*
* RangeVarGetCreationNamespace
* Given a RangeVar describing a to-be-created relation,
@@ -3650,6 +3781,42 @@ makeRangeVarFromNameList(const List *names)
return rel;
}
+/*
+ * makeRangeVarFromNameListSafe
+ * Utility routine to convert a qualified-name list into RangeVar form.
+ * The result maybe NULL.
+ */
+RangeVar *
+makeRangeVarFromNameListSafe(const List *names, Node *escontext)
+{
+ RangeVar *rel = makeRangeVar(NULL, NULL, -1);
+
+ switch (list_length(names))
+ {
+ case 1:
+ rel->relname = strVal(linitial(names));
+ break;
+ case 2:
+ rel->schemaname = strVal(linitial(names));
+ rel->relname = strVal(lsecond(names));
+ break;
+ case 3:
+ rel->catalogname = strVal(linitial(names));
+ rel->schemaname = strVal(lsecond(names));
+ rel->relname = strVal(lthird(names));
+ break;
+ default:
+ errsave(escontext,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("improper relation name (too many dotted names): %s",
+ NameListToString(names)));
+ rel = NULL;
+ break;
+ }
+
+ return rel;
+}
+
/*
* NameListToString
* Utility routine to convert a qualified-name list into a string.
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index e5c2246f2c9..059d0154335 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -1902,10 +1902,37 @@ text_regclass(PG_FUNCTION_ARGS)
Oid result;
RangeVar *rv;
- rv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
+ if (likely(!fcinfo->context))
+ {
+ rv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
- /* We might not even have permissions on this relation; don't lock it. */
- result = RangeVarGetRelid(rv, NoLock, false);
+ /*
+ * We might not even have permissions on this relation; don't lock it.
+ */
+ result = RangeVarGetRelid(rv, NoLock, false);
+ }
+ else
+ {
+ List *rvnames;
+
+ rvnames = textToQualifiedNameListSafe(relname, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
+
+ rv = makeRangeVarFromNameListSafe(rvnames, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
+
+ result = RangeVarGetRelidExtendedSafe(rv,
+ NoLock,
+ 0,
+ NULL,
+ NULL,
+ fcinfo->context);
+
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
+ }
PG_RETURN_OID(result);
}
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index 2c398cd9e5c..1117939430a 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -2717,6 +2717,48 @@ textToQualifiedNameList(text *textval)
return result;
}
+/* error safe version of textToQualifiedNameList */
+List *
+textToQualifiedNameListSafe(text *textval, Node *escontext)
+{
+ char *rawname;
+ List *result = NIL;
+ List *namelist;
+ ListCell *l;
+
+ /* Convert to C string (handles possible detoasting). */
+ /* Note we rely on being able to modify rawname below. */
+ rawname = text_to_cstring(textval);
+
+ if (!SplitIdentifierString(rawname, '.', &namelist))
+ {
+ errsave(escontext,
+ errcode(ERRCODE_INVALID_NAME),
+ errmsg("invalid name syntax"));
+ return NIL;
+ }
+
+ if (namelist == NIL)
+ {
+ errsave(escontext,
+ errcode(ERRCODE_INVALID_NAME),
+ errmsg("invalid name syntax"));
+ return NIL;
+ }
+
+ foreach(l, namelist)
+ {
+ char *curname = (char *) lfirst(l);
+
+ result = lappend(result, makeString(pstrdup(curname)));
+ }
+
+ pfree(rawname);
+ list_free(namelist);
+
+ return result;
+}
+
/*
* SplitIdentifierString --- parse a string containing identifiers
*
diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h
index f1423f28c32..ab61af55ddc 100644
--- a/src/include/catalog/namespace.h
+++ b/src/include/catalog/namespace.h
@@ -103,6 +103,11 @@ extern Oid RangeVarGetRelidExtended(const RangeVar *relation,
LOCKMODE lockmode, uint32 flags,
RangeVarGetRelidCallback callback,
void *callback_arg);
+extern Oid RangeVarGetRelidExtendedSafe(const RangeVar *relation,
+ LOCKMODE lockmode, uint32 flags,
+ RangeVarGetRelidCallback callback,
+ void *callback_arg,
+ Node *escontext);
extern Oid RangeVarGetCreationNamespace(const RangeVar *newRelation);
extern Oid RangeVarGetAndCheckCreationNamespace(RangeVar *relation,
LOCKMODE lockmode,
@@ -168,6 +173,7 @@ extern Oid LookupCreationNamespace(const char *nspname);
extern void CheckSetNamespace(Oid oldNspOid, Oid nspOid);
extern Oid QualifiedNameGetCreationNamespace(const List *names, char **objname_p);
extern RangeVar *makeRangeVarFromNameList(const List *names);
+extern RangeVar *makeRangeVarFromNameListSafe(const List *names, Node *escontext);
extern char *NameListToString(const List *names);
extern char *NameListToQuotedString(const List *names);
diff --git a/src/include/utils/varlena.h b/src/include/utils/varlena.h
index db9fdf72941..0cf01ae5281 100644
--- a/src/include/utils/varlena.h
+++ b/src/include/utils/varlena.h
@@ -27,6 +27,7 @@ extern int varstr_levenshtein_less_equal(const char *source, int slen,
int ins_c, int del_c, int sub_c,
int max_d, bool trusted);
extern List *textToQualifiedNameList(text *textval);
+extern List *textToQualifiedNameListSafe(text *textval, Node *escontext);
extern bool SplitIdentifierString(char *rawstring, char separator,
List **namelist);
extern bool SplitDirectoriesString(char *rawstring, char separator,
--
2.34.1
v8-0002-error-safe-for-casting-bit-varbit-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v8-0002-error-safe-for-casting-bit-varbit-to-other-types-per-pg_cast.patchDownload
From eb4a6ec310f50ceefaeef98fc31914cf8e48f683 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 19:09:53 +0800
Subject: [PATCH v8 02/20] error safe for casting bit/varbit to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
where pc.castfunc > 0 and (castsource::regtype ='bit'::regtype or
castsource::regtype ='varbit'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
-------------+-------------+----------+-------------+------------+-----------+---------
bit | bigint | 2076 | e | f | bittoint8 | int8
bit | integer | 1684 | e | f | bittoint4 | int4
bit | bit | 1685 | i | f | bit | bit
bit varying | bit varying | 1687 | i | f | varbit | varbit
(4 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/varbit.c | 24 ++++++++++++++++++++----
1 file changed, 20 insertions(+), 4 deletions(-)
diff --git a/src/backend/utils/adt/varbit.c b/src/backend/utils/adt/varbit.c
index 205a67dafc5..bfcea18f4b2 100644
--- a/src/backend/utils/adt/varbit.c
+++ b/src/backend/utils/adt/varbit.c
@@ -401,11 +401,15 @@ bit(PG_FUNCTION_ARGS)
PG_RETURN_VARBIT_P(arg);
if (!isExplicit)
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_STRING_DATA_LENGTH_MISMATCH),
errmsg("bit string length %d does not match type bit(%d)",
VARBITLEN(arg), len)));
+ PG_RETURN_NULL();
+ }
+
rlen = VARBITTOTALLEN(len);
/* set to 0 so that string is zero-padded */
result = (VarBit *) palloc0(rlen);
@@ -752,11 +756,15 @@ varbit(PG_FUNCTION_ARGS)
PG_RETURN_VARBIT_P(arg);
if (!isExplicit)
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
errmsg("bit string too long for type bit varying(%d)",
len)));
+ PG_RETURN_NULL();
+ }
+
rlen = VARBITTOTALLEN(len);
result = (VarBit *) palloc(rlen);
SET_VARSIZE(result, rlen);
@@ -1591,10 +1599,14 @@ bittoint4(PG_FUNCTION_ARGS)
/* Check that the bit string is not too long */
if (VARBITLEN(arg) > sizeof(result) * BITS_PER_BYTE)
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
+ PG_RETURN_NULL();
+ }
+
result = 0;
for (r = VARBITS(arg); r < VARBITEND(arg); r++)
{
@@ -1671,10 +1683,14 @@ bittoint8(PG_FUNCTION_ARGS)
/* Check that the bit string is not too long */
if (VARBITLEN(arg) > sizeof(result) * BITS_PER_BYTE)
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("bigint out of range")));
+ PG_RETURN_NULL();
+ }
+
result = 0;
for (r = VARBITS(arg); r < VARBITEND(arg); r++)
{
--
2.34.1
v8-0001-error-safe-for-casting-bytea-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v8-0001-error-safe-for-casting-bytea-to-other-types-per-pg_cast.patchDownload
From 6765d6091bc3f9272833ef382cd41deb48f3b01d Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 15:36:35 +0800
Subject: [PATCH v8 01/20] error safe for casting bytea to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc and pc.castfunc > 0 and castsource::regtype ='bytea'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------+----------+-------------+------------+------------+---------
bytea | smallint | 6370 | e | f | bytea_int2 | int2
bytea | integer | 6371 | e | f | bytea_int4 | int4
bytea | bigint | 6372 | e | f | bytea_int8 | int8
(3 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/bytea.c | 18 +++++++++++++++---
1 file changed, 15 insertions(+), 3 deletions(-)
diff --git a/src/backend/utils/adt/bytea.c b/src/backend/utils/adt/bytea.c
index 6e7b914c563..4a8adcb8204 100644
--- a/src/backend/utils/adt/bytea.c
+++ b/src/backend/utils/adt/bytea.c
@@ -1027,10 +1027,14 @@ bytea_int2(PG_FUNCTION_ARGS)
/* Check that the byte array is not too long */
if (len > sizeof(result))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range"));
+ PG_RETURN_NULL();
+ }
+
/* Convert it to an integer; most significant bytes come first */
result = 0;
for (int i = 0; i < len; i++)
@@ -1052,10 +1056,14 @@ bytea_int4(PG_FUNCTION_ARGS)
/* Check that the byte array is not too long */
if (len > sizeof(result))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range"));
+ PG_RETURN_NULL();
+ }
+
/* Convert it to an integer; most significant bytes come first */
result = 0;
for (int i = 0; i < len; i++)
@@ -1077,10 +1085,14 @@ bytea_int8(PG_FUNCTION_ARGS)
/* Check that the byte array is not too long */
if (len > sizeof(result))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("bigint out of range"));
+ PG_RETURN_NULL();
+ }
+
/* Convert it to an integer; most significant bytes come first */
result = 0;
for (int i = 0; i < len; i++)
--
2.34.1
On Tue, Oct 14, 2025 at 10:00 AM jian he <jian.universality@gmail.com> wrote:
Please check the attached v8.
hi.
please see the attached V9 patchset.
v9-0001 to v9-0018: refactor pg_cast.castfunc entries, make it error safe.
v9-0019: CAST(... AS ... DEFAULT def_expr ON CONVERSION ERROR)
select pc.castsource::regtype,pc.casttarget::regtype,
castfunc::regproc, pp.prosrc
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
join pg_type pt on pt.oid = castsource
join pg_type pt1 on pt1.oid = casttarget
and pc.castfunc > 0 and pt.typnamespace = 'pg_catalog'::regnamespace
and pt1.typnamespace = 'pg_catalog'::regnamespace and not pc.casterrorsafe;
castsource | casttarget | castfunc | prosrc
------------+------------+----------------------+--------------
circle | polygon | pg_catalog.polygon |
bigint | money | pg_catalog.money | int8_cash
integer | money | pg_catalog.money | int4_cash
numeric | money | pg_catalog.money | numeric_cash
money | numeric | pg_catalog."numeric" | cash_numeric
(5 rows)
The above result shows type casts using functions which cannot be error safe.
Money type related casts still can not be error safe.
Cast from circle to polygon cannot be error safe because the associated cast
function (pg_cast.castfunc) is written in SQL
(see src/backend/catalog/system_functions.sql LINE 112).
It appears impossible to make SQL language functions error safe, because
fmgr_sql ignores fcinfo->context.
eval_const_expressions cannot be error safe, so we need to handle
source_expr as an UNKNOWN constant in an error safe beforehand.
For example, we need handle ('1' AS DATE) in an error safe way
for
SELECT CAST('1' AS date DEFAULT '2011-01-01' ON ERROR);
Since we must handle the source_expr when it is an UNKNOWN constant in an
error safe way, we can apply the same handling when source_expr is a
Const whose type is not UNKNOWN.
For example:
SELECT CAST('[(1,2),(3,4)]'::path AS polygon DEFAULT NULL ON CONVERSION ERROR);
If source_expr is a Const and the cast expression is a FuncExpr, we can be
certain that all arguments (FuncExpr->args) are also Const; see the function
chain coerce_to_target_type → coerce_type → build_coercion_expression.
We don’t need to worry about deparsing the expression because struct
SafeTypeCastExpr includes source_expr, cast_expr, and default_expr. Even if
cast_expr is NULL, we can still use source_expr to reconstruct the original CAST
expression.
so I introduced:
evaluate_expr_safe: error safe version of evaluate_expr
CoerceUnknownConstSafe: tests whether an UNKNOWN Const can be coerced to the
target type.
Attachments:
v9-0017-error-safe-for-casting-jsonb-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v9-0017-error-safe-for-casting-jsonb-to-other-types-per-pg_cast.patchDownload
From 7936c6a941547fd03972e16923fefa27358b9368 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 18:58:40 +0800
Subject: [PATCH v9 17/19] error safe for casting jsonb to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'jsonb'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+---------------+---------
jsonb | boolean | 3556 | e | f | jsonb_bool | bool
jsonb | numeric | 3449 | e | f | jsonb_numeric | numeric
jsonb | smallint | 3450 | e | f | jsonb_int2 | int2
jsonb | integer | 3451 | e | f | jsonb_int4 | int4
jsonb | bigint | 3452 | e | f | jsonb_int8 | int8
jsonb | real | 3453 | e | f | jsonb_float4 | float4
jsonb | double precision | 2580 | e | f | jsonb_float8 | float8
(7 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/jsonb.c | 91 +++++++++++++++++++++++++++++------
1 file changed, 75 insertions(+), 16 deletions(-)
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index da94d424d61..1ff00f72f78 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -2005,7 +2005,7 @@ JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res)
* Emit correct, translatable cast error message
*/
static void
-cannotCastJsonbValue(enum jbvType type, const char *sqltype)
+cannotCastJsonbValue(enum jbvType type, const char *sqltype, Node *escontext)
{
static const struct
{
@@ -2026,9 +2026,12 @@ cannotCastJsonbValue(enum jbvType type, const char *sqltype)
for (i = 0; i < lengthof(messages); i++)
if (messages[i].type == type)
- ereport(ERROR,
+ {
+ errsave(escontext,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(messages[i].msg, sqltype)));
+ return;
+ }
/* should be unreachable */
elog(ERROR, "unknown jsonb type: %d", (int) type);
@@ -2041,7 +2044,11 @@ jsonb_bool(PG_FUNCTION_ARGS)
JsonbValue v;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "boolean");
+ {
+ cannotCastJsonbValue(v.type, "boolean", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2050,7 +2057,11 @@ jsonb_bool(PG_FUNCTION_ARGS)
}
if (v.type != jbvBool)
- cannotCastJsonbValue(v.type, "boolean");
+ {
+ cannotCastJsonbValue(v.type, "boolean", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
PG_FREE_IF_COPY(in, 0);
@@ -2065,7 +2076,11 @@ jsonb_numeric(PG_FUNCTION_ARGS)
Numeric retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "numeric");
+ {
+ cannotCastJsonbValue(v.type, "numeric", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2074,7 +2089,11 @@ jsonb_numeric(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "numeric");
+ {
+ cannotCastJsonbValue(v.type, "numeric", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
/*
* v.val.numeric points into jsonb body, so we need to make a copy to
@@ -2095,7 +2114,11 @@ jsonb_int2(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "smallint");
+ {
+ cannotCastJsonbValue(v.type, "smallint", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2104,7 +2127,11 @@ jsonb_int2(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "smallint");
+ {
+ cannotCastJsonbValue(v.type, "smallint", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_int2,
NumericGetDatum(v.val.numeric));
@@ -2122,7 +2149,11 @@ jsonb_int4(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "integer");
+ {
+ cannotCastJsonbValue(v.type, "integer", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2131,7 +2162,11 @@ jsonb_int4(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "integer");
+ {
+ cannotCastJsonbValue(v.type, "integer", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_int4,
NumericGetDatum(v.val.numeric));
@@ -2149,7 +2184,11 @@ jsonb_int8(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "bigint");
+ {
+ cannotCastJsonbValue(v.type, "bigint", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2158,7 +2197,11 @@ jsonb_int8(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "bigint");
+ {
+ cannotCastJsonbValue(v.type, "bigint", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_int8,
NumericGetDatum(v.val.numeric));
@@ -2176,7 +2219,11 @@ jsonb_float4(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "real");
+ {
+ cannotCastJsonbValue(v.type, "real", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2185,7 +2232,11 @@ jsonb_float4(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "real");
+ {
+ cannotCastJsonbValue(v.type, "real", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_float4,
NumericGetDatum(v.val.numeric));
@@ -2203,7 +2254,11 @@ jsonb_float8(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "double precision");
+ {
+ cannotCastJsonbValue(v.type, "double precision", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2212,7 +2267,11 @@ jsonb_float8(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "double precision");
+ {
+ cannotCastJsonbValue(v.type, "double precision", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_float8,
NumericGetDatum(v.val.numeric));
--
2.34.1
v9-0016-error-safe-for-casting-timestamp-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v9-0016-error-safe-for-casting-timestamp-to-other-types-per-pg_cast.patchDownload
From e53a8808bd501f08a9660920830559b5785745a8 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 18:44:35 +0800
Subject: [PATCH v9 16/19] error safe for casting timestamp to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
and pc.castfunc > 0 and (castsource::regtype ='timestamp'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
-----------------------------+-----------------------------+----------+-------------+------------+-----------------------+-------------
timestamp without time zone | date | 2029 | a | f | timestamp_date | date
timestamp without time zone | time without time zone | 1316 | a | f | timestamp_time | time
timestamp without time zone | timestamp with time zone | 2028 | i | f | timestamp_timestamptz | timestamptz
timestamp without time zone | timestamp without time zone | 1961 | i | f | timestamp_scale | timestamp
(4 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 13 +++++++++++--
src/backend/utils/adt/timestamp.c | 17 +++++++++++++++--
2 files changed, 26 insertions(+), 4 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 111bfd8f519..c5562b563e5 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -1373,7 +1373,16 @@ timestamp_date(PG_FUNCTION_ARGS)
Timestamp timestamp = PG_GETARG_TIMESTAMP(0);
DateADT result;
- result = timestamp2date_opt_overflow(timestamp, NULL);
+ if (likely(!fcinfo->context))
+ result = timestamptz2date_opt_overflow(timestamp, NULL);
+ else
+ {
+ int overflow;
+ result = timestamp2date_opt_overflow(timestamp, &overflow);
+
+ if (overflow != 0)
+ PG_RETURN_NULL();
+ }
PG_RETURN_DATEADT(result);
}
@@ -2089,7 +2098,7 @@ timestamp_time(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 116e3ef28fc..7a57ac3eaf6 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -352,7 +352,8 @@ timestamp_scale(PG_FUNCTION_ARGS)
result = timestamp;
- AdjustTimestampForTypmod(&result, typmod, NULL);
+ if (!AdjustTimestampForTypmod(&result, typmod, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_TIMESTAMP(result);
}
@@ -6430,7 +6431,19 @@ timestamp_timestamptz(PG_FUNCTION_ARGS)
{
Timestamp timestamp = PG_GETARG_TIMESTAMP(0);
- PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(timestamp));
+ if (likely(!fcinfo->context))
+ PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(timestamp));
+ else
+ {
+ TimestampTz result;
+ int overflow;
+
+ result = timestamp2timestamptz_opt_overflow(timestamp, &overflow);
+ if (overflow != 0)
+ PG_RETURN_NULL();
+ else
+ PG_RETURN_TIMESTAMPTZ(result);
+ }
}
/*
--
2.34.1
v9-0019-CAST-expr-AS-newtype-DEFAULT-ON-ERROR.patchtext/x-patch; charset=UTF-8; name=v9-0019-CAST-expr-AS-newtype-DEFAULT-ON-ERROR.patchDownload
From 6b58a093f3937d96dcd404bec314064e20d3ea77 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Tue, 4 Nov 2025 13:45:37 +0800
Subject: [PATCH v9 19/19] CAST(expr AS newtype DEFAULT ON ERROR)
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.
draft doc also added.
We cannot simply prohibit user-defined functions in pg_cast for safe cast
evaluation because CREATE CAST can also utilize built-in functions. So, to
completely disallow custom casts created via CREATE CAST used in safe cast
evaluation, a new field in pg_cast would unfortunately be necessary.
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}
[0]: https://git.postgresql.org/cgit/postgresql.git/commit/?id=aaaf9449ec6be62cb0d30ed3588dc384f56274b
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
.../pg_stat_statements/expected/select.out | 23 +-
contrib/pg_stat_statements/sql/select.sql | 5 +
doc/src/sgml/catalogs.sgml | 12 +
doc/src/sgml/syntax.sgml | 29 +
src/backend/catalog/pg_cast.c | 1 +
src/backend/executor/execExpr.c | 117 ++-
src/backend/executor/execExprInterp.c | 37 +
src/backend/jit/llvm/llvmjit_expr.c | 49 ++
src/backend/nodes/nodeFuncs.c | 67 ++
src/backend/optimizer/util/clauses.c | 93 +++
src/backend/parser/gram.y | 22 +
src/backend/parser/parse_agg.c | 9 +
src/backend/parser/parse_expr.c | 434 ++++++++++-
src/backend/parser/parse_func.c | 3 +
src/backend/parser/parse_target.c | 14 +
src/backend/parser/parse_type.c | 14 +
src/backend/utils/adt/arrayfuncs.c | 8 +
src/backend/utils/adt/ruleutils.c | 22 +
src/backend/utils/fmgr/fmgr.c | 13 +
src/include/catalog/pg_cast.dat | 330 ++++----
src/include/catalog/pg_cast.h | 4 +
src/include/executor/execExpr.h | 8 +
src/include/executor/executor.h | 1 +
src/include/fmgr.h | 3 +
src/include/nodes/execnodes.h | 30 +
src/include/nodes/parsenodes.h | 6 +
src/include/nodes/primnodes.h | 33 +
src/include/optimizer/optimizer.h | 2 +
src/include/parser/parse_node.h | 2 +
src/include/parser/parse_type.h | 2 +
src/test/regress/expected/cast.out | 730 ++++++++++++++++++
src/test/regress/expected/create_cast.out | 5 +
src/test/regress/expected/opr_sanity.out | 24 +-
src/test/regress/parallel_schedule | 2 +-
src/test/regress/sql/cast.sql | 320 ++++++++
src/test/regress/sql/create_cast.sql | 1 +
src/tools/pgindent/typedefs.list | 3 +
37 files changed, 2290 insertions(+), 188 deletions(-)
create mode 100644 src/test/regress/expected/cast.out
create mode 100644 src/test/regress/sql/cast.sql
diff --git a/contrib/pg_stat_statements/expected/select.out b/contrib/pg_stat_statements/expected/select.out
index 75c896f3885..6e67997f0a2 100644
--- a/contrib/pg_stat_statements/expected/select.out
+++ b/contrib/pg_stat_statements/expected/select.out
@@ -73,6 +73,25 @@ SELECT 1 AS "int" OFFSET 2 FETCH FIRST 3 ROW ONLY;
-----
(0 rows)
+--error safe type cast
+SELECT CAST('a' AS int DEFAULT 2 ON CONVERSION ERROR);
+ int4
+------
+ 2
+(1 row)
+
+SELECT CAST('11' AS int DEFAULT 2 ON CONVERSION ERROR);
+ int4
+------
+ 11
+(1 row)
+
+SELECT CAST('12' AS numeric DEFAULT 2 ON CONVERSION ERROR);
+ numeric
+---------
+ 12
+(1 row)
+
-- DISTINCT and ORDER BY patterns
-- Try some query permutations which once produced identical query IDs
SELECT DISTINCT 1 AS "int";
@@ -222,6 +241,8 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
2 | 2 | SELECT $1 AS "int" ORDER BY 1
1 | 2 | SELECT $1 AS i UNION SELECT $2 ORDER BY i
1 | 1 | SELECT $1 || $2
+ 2 | 2 | SELECT CAST($1 AS int DEFAULT $2 ON CONVERSION ERROR)
+ 1 | 1 | SELECT CAST($1 AS numeric DEFAULT $2 ON CONVERSION ERROR)
2 | 2 | SELECT DISTINCT $1 AS "int"
0 | 0 | SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"
1 | 1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
@@ -230,7 +251,7 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
| | ) +
| | SELECT f FROM t ORDER BY f
1 | 1 | select $1::jsonb ? $2
-(17 rows)
+(19 rows)
SELECT pg_stat_statements_reset() IS NOT NULL AS t;
t
diff --git a/contrib/pg_stat_statements/sql/select.sql b/contrib/pg_stat_statements/sql/select.sql
index 11662cde08c..7ee8160fd84 100644
--- a/contrib/pg_stat_statements/sql/select.sql
+++ b/contrib/pg_stat_statements/sql/select.sql
@@ -25,6 +25,11 @@ SELECT 1 AS "int" LIMIT 3 OFFSET 3;
SELECT 1 AS "int" OFFSET 1 FETCH FIRST 2 ROW ONLY;
SELECT 1 AS "int" OFFSET 2 FETCH FIRST 3 ROW ONLY;
+--error safe type cast
+SELECT CAST('a' AS int DEFAULT 2 ON CONVERSION ERROR);
+SELECT CAST('11' AS int DEFAULT 2 ON CONVERSION ERROR);
+SELECT CAST('12' AS numeric DEFAULT 2 ON CONVERSION ERROR);
+
-- DISTINCT and ORDER BY patterns
-- Try some query permutations which once produced identical query IDs
SELECT DISTINCT 1 AS "int";
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 6c8a0f173c9..492c99e37cd 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1849,6 +1849,18 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<literal>b</literal> means that the types are binary-coercible, thus no conversion is required.
</para></entry>
</row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>casterrorsafe</structfield> <type>bool</type>
+ </para>
+ <para>
+ This indicates whether the <structfield>castfunc</structfield> function is error safe.
+ If the <structfield>castfunc</structfield> function is error safe, it can be used in error safe type cast.
+ For further details see <xref linkend="sql-syntax-type-casts-safe"/>.
+ </para></entry>
+ </row>
+
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml
index 237d7306fe8..d31aaef2875 100644
--- a/doc/src/sgml/syntax.sgml
+++ b/doc/src/sgml/syntax.sgml
@@ -2106,6 +2106,10 @@ CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable>
The <literal>CAST</literal> syntax conforms to SQL; the syntax with
<literal>::</literal> is historical <productname>PostgreSQL</productname>
usage.
+ It can also be written as:
+<synopsis>
+CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable> ERROR ON CONVERSION ERROR )
+</synopsis>
</para>
<para>
@@ -2160,6 +2164,31 @@ CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable>
<xref linkend="sql-createcast"/>.
</para>
</note>
+
+ <sect3 id="sql-syntax-type-casts-safe">
+ <title>Safe Type Cast</title>
+ <para>
+Sometimes a type cast may fail; to handle such cases, an <literal>ON ERROR</literal> clause can be
+specified to avoid potential errors. The syntax is:
+<synopsis>
+CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable> DEFAULT <replaceable>expression</replaceable> ON CONVERSION ERROR )
+</synopsis>
+ If cast the source expression to target type fails, it falls back to
+ evaluating the optionally supplied default <replaceable>expression</replaceable>
+ specified in ON <literal>ON ERROR</literal> clause.
+ Currently, this only support built-in system type casts,
+ casts created using <link linkend="sql-createcast">CREATE CAST</link> are not supported.
+ </para>
+
+ <para>
+ For example, the following query attempts to cast a <type>text</type> type value to <type>integer</type> type,
+ but when the conversion fails, it falls back to evaluate the supplied default expression.
+<programlisting>
+SELECT CAST(TEXT 'error' AS integer DEFAULT NULL ON CONVERSION ERROR) IS NULL; <lineannotation>true</lineannotation>
+</programlisting>
+ </para>
+ </sect3>
+
</sect2>
<sect2 id="sql-syntax-collate-exprs">
diff --git a/src/backend/catalog/pg_cast.c b/src/backend/catalog/pg_cast.c
index 1773c9c5491..6fe65d24d31 100644
--- a/src/backend/catalog/pg_cast.c
+++ b/src/backend/catalog/pg_cast.c
@@ -84,6 +84,7 @@ CastCreate(Oid sourcetypeid, Oid targettypeid,
values[Anum_pg_cast_castfunc - 1] = ObjectIdGetDatum(funcid);
values[Anum_pg_cast_castcontext - 1] = CharGetDatum(castcontext);
values[Anum_pg_cast_castmethod - 1] = CharGetDatum(castmethod);
+ values[Anum_pg_cast_casterrorsafe - 1] = BoolGetDatum(false);
tuple = heap_form_tuple(RelationGetDescr(relation), values, nulls);
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index f1569879b52..5a4b9e8b53d 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,
@@ -170,6 +173,47 @@ ExecInitExpr(Expr *node, PlanState *parent)
return state;
}
+/*
+ * ExecInitExprSafe: soft error variant of ExecInitExpr.
+ *
+ * use it only for expression nodes support soft errors, not all expression
+ * nodes support it.
+*/
+ExprState *
+ExecInitExprSafe(Expr *node, PlanState *parent)
+{
+ ExprState *state;
+ ExprEvalStep scratch = {0};
+
+ /* Special case: NULL expression produces a NULL ExprState pointer */
+ if (node == NULL)
+ return NULL;
+
+ /* Initialize ExprState with empty step list */
+ state = makeNode(ExprState);
+ state->expr = node;
+ state->parent = parent;
+ state->ext_params = NULL;
+ state->escontext = makeNode(ErrorSaveContext);
+ state->escontext->type = T_ErrorSaveContext;
+ state->escontext->error_occurred = false;
+ state->escontext->details_wanted = false;
+
+ /* Insert setup steps as needed */
+ ExecCreateExprSetupSteps(state, (Node *) node);
+
+ /* Compile the expression proper */
+ ExecInitExprRec(node, state, &state->resvalue, &state->resnull);
+
+ /* Finally, append a DONE step */
+ scratch.opcode = EEOP_DONE_RETURN;
+ ExprEvalPushStep(state, &scratch);
+
+ ExecReadyExpr(state);
+
+ return state;
+}
+
/*
* ExecInitExprWithParams: prepare a standalone expression tree for execution
*
@@ -1701,6 +1745,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
elemstate->innermost_caseval = (Datum *) palloc(sizeof(Datum));
elemstate->innermost_casenull = (bool *) palloc(sizeof(bool));
+ elemstate->escontext = state->escontext;
ExecInitExprRec(acoerce->elemexpr, elemstate,
&elemstate->resvalue, &elemstate->resnull);
@@ -2177,6 +2222,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;
@@ -2743,7 +2796,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;
@@ -4741,6 +4794,68 @@ ExecBuildParamSetEqual(TupleDesc desc,
return state;
}
+/*
+ * Push steps to evaluate a SafeTypeCastExpr and its various subsidiary
+ * expressions.
+ */
+static void
+ExecInitSafeTypeCastExpr(SafeTypeCastExpr *stcexpr , ExprState *state,
+ Datum *resv, bool *resnull,
+ ExprEvalStep *scratch)
+{
+ /*
+ * If we can not coerce to the target type, fallback to the DEFAULT
+ * expression specified in the ON CONVERSION ERROR clause, and we are done.
+ */
+ if (stcexpr->cast_expr == NULL)
+ {
+ ExecInitExprRec((Expr *) stcexpr->default_expr,
+ state, resv, resnull);
+ return;
+ }
+ else
+ {
+ ExprEvalStep *as;
+ SafeTypeCastState *stcstate;
+ ErrorSaveContext *saved_escontext;
+ int jumps_to_end;
+
+ stcstate = palloc0(sizeof(SafeTypeCastState));
+ stcstate->stcexpr = stcexpr;
+ stcstate->escontext.type = T_ErrorSaveContext;
+ state->escontext = &stcstate->escontext;
+
+ /* evaluate argument expression into step's result area */
+ ExecInitExprRec((Expr *) stcexpr->cast_expr,
+ 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 = 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;
+
+ as = &state->steps[jumps_to_end];
+ 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 67f4e00eac4..bd8b1048c5f 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,12 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
EEO_NEXT();
}
+ EEO_CASE(EEOP_SAFETYPE_CAST)
+ {
+ /* too complex for an inline implementation */
+ EEO_JUMP(ExecEvalSafeTypeCast(state, op));
+ }
+
EEO_CASE(EEOP_JSONEXPR_PATH)
{
/* too complex for an inline implementation */
@@ -3644,6 +3651,12 @@ 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;
+ }
}
/*
@@ -5183,6 +5196,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
+ * 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 712b35df7e5..f54fe563543 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 ede838cd40c..533e21120a6 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;
@@ -1550,6 +1561,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;
@@ -2321,6 +2341,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:
@@ -3330,6 +3362,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;
@@ -4464,6 +4509,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->def_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/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 81d768ff2a2..07b8298dd9d 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -2945,6 +2945,30 @@ 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);
+
+ /*
+ * We must not reduce any recognizably constant subexpressions
+ * in cast_expr here, since we don’t want it to fail
+ * prematurely.
+ */
+ 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:
@@ -5147,6 +5171,75 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
resultTypByVal);
}
+/*
+ * evaluate_expr_safe: error safe version of evaluate_expr
+ *
+ * We use the executor's routine ExecEvalExpr() to avoid duplication of
+ * code and ensure we get the same result as the executor would get.
+ *
+ * return NULL when evaulation failed. Ensure the expression expr can be evaulated
+ * in a soft error way.
+ *
+ * See comments on evaluate_expr too.
+ */
+Expr *
+evaluate_expr_safe(Expr *expr, Oid result_type, int32 result_typmod,
+ Oid result_collation)
+{
+ EState *estate;
+ ExprState *exprstate;
+ MemoryContext oldcontext;
+ Datum const_val;
+ bool const_is_null;
+ int16 resultTypLen;
+ bool resultTypByVal;
+
+ estate = CreateExecutorState();
+
+ /* We can use the estate's working context to avoid memory leaks. */
+ oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
+
+ /* Make sure any opfuncids are filled in. */
+ fix_opfuncids((Node *) expr);
+
+ /*
+ * Prepare expr for execution. (Note: we can't use ExecPrepareExpr
+ * because it'd result in recursively invoking eval_const_expressions.)
+ */
+ exprstate = ExecInitExprSafe(expr, NULL);
+
+ const_val = ExecEvalExprSwitchContext(exprstate,
+ GetPerTupleExprContext(estate),
+ &const_is_null);
+
+ /* Get info needed about result datatype */
+ get_typlenbyval(result_type, &resultTypLen, &resultTypByVal);
+
+ /* Get back to outer memory context */
+ MemoryContextSwitchTo(oldcontext);
+
+ if (SOFT_ERROR_OCCURRED(exprstate->escontext))
+ {
+ FreeExecutorState(estate);
+ return NULL;
+ }
+
+ if (!const_is_null)
+ {
+ if (resultTypLen == -1)
+ const_val = PointerGetDatum(PG_DETOAST_DATUM_COPY(const_val));
+ else
+ const_val = datumCopy(const_val, resultTypByVal, resultTypLen);
+ }
+
+ FreeExecutorState(estate);
+
+ return (Expr *) makeConst(result_type, result_typmod, result_collation,
+ resultTypLen,
+ const_val, const_is_null,
+ resultTypByVal);
+}
+
/*
* inline_set_returning_function
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a4b29c822e8..8d1dd0f278c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -15993,6 +15993,28 @@ func_expr_common_subexpr:
}
| CAST '(' a_expr AS Typename ')'
{ $$ = makeTypeCast($3, $5, @1); }
+ | CAST '(' a_expr AS Typename ERROR_P ON CONVERSION_P ERROR_P ')'
+ { $$ = makeTypeCast($3, $5, @1); }
+ | CAST '(' a_expr AS Typename NULL_P ON CONVERSION_P ERROR_P ')'
+ {
+ TypeCast *cast = (TypeCast *) makeTypeCast($3, $5, @1);
+
+ SafeTypeCast *safecast = makeNode(SafeTypeCast);
+ safecast->cast = (Node *) cast;
+ safecast->def_expr = makeNullAConst(-1);;
+
+ $$ = (Node *) safecast;
+ }
+ | CAST '(' a_expr AS Typename DEFAULT a_expr ON CONVERSION_P ERROR_P ')'
+ {
+ TypeCast *cast = (TypeCast *) makeTypeCast($3, $5, @1);
+
+ SafeTypeCast *safecast = makeNode(SafeTypeCast);
+ safecast->cast = (Node *) cast;
+ safecast->def_expr = $7;
+
+ $$ = (Node *) safecast;
+ }
| EXTRACT '(' extract_list ')'
{
$$ = (Node *) makeFuncCall(SystemFuncName("extract"),
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 3254c83cc6c..6573e2a93ea 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -486,6 +486,12 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
err = _("grouping operations are not allowed in check constraints");
break;
+ case EXPR_KIND_CAST_DEFAULT:
+ if (isAgg)
+ err = _("aggregate functions are not allowed in CAST DEFAULT expressions");
+ else
+ err = _("grouping operations are not allowed in CAST DEFAULT expressions");
+ break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
@@ -956,6 +962,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_DOMAIN_CHECK:
err = _("window functions are not allowed in check constraints");
break;
+ case EXPR_KIND_CAST_DEFAULT:
+ err = _("window functions are not allowed in CAST DEFAULT expressions");
+ break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
err = _("window functions are not allowed in DEFAULT expressions");
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 12119f147fc..596aac69b08 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -17,6 +17,8 @@
#include "access/htup_details.h"
#include "catalog/pg_aggregate.h"
+#include "catalog/pg_cast.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -37,6 +39,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 +63,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 *can_coerce);
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 +80,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);
@@ -107,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 CoerceUnknownConstSafe(ParseState *pstate, Node *node,
+ Oid targetType, int32 targetTypeMod);
/*
@@ -164,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;
@@ -576,6 +587,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
case EXPR_KIND_COPY_WHERE:
case EXPR_KIND_GENERATED_COLUMN:
case EXPR_KIND_CYCLE_MARK:
+ case EXPR_KIND_CAST_DEFAULT:
/* okay */
break;
@@ -1824,6 +1836,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_DOMAIN_CHECK:
err = _("cannot use subquery in check constraint");
break;
+ case EXPR_KIND_CAST_DEFAULT:
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
err = _("cannot use subquery in DEFAULT expression");
@@ -2005,22 +2018,80 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
return result;
}
+/*
+ * Return true if successfuly coerced a Unknown Const to targetType
+*/
+static bool
+CoerceUnknownConstSafe(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;
+}
+
+
/*
* 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().
+ *
+ * Most of the time, the caller will pass can_coerce as NULL.
+ * can_coerce is not NULL only when performing parse analysis for expressions
+ * like CAST(DEFAULT ... ON CONVERSION ERROR).
+ * When can_coerce is not NULL, it should be assumed to default to true. If
+ * coerce array elements to the target type fails, can_coerce will be set to
+ * 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;
List *newcoercedelems = NIL;
ListCell *element;
Oid coerce_type;
+ Oid coerce_type_coll;
bool coerce_hard;
/*
@@ -2045,9 +2116,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
@@ -2088,6 +2160,9 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
}
else
{
+ /* We obviously know the target type when performing type-safe cast */
+ Assert(can_coerce == NULL);
+
/* Can't handle an empty array without a target type */
if (newelems == NIL)
ereport(ERROR,
@@ -2125,6 +2200,8 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
coerce_hard = false;
}
+ coerce_type_coll = get_typcollation(coerce_type);
+
/*
* Coerce elements to target type
*
@@ -2134,13 +2211,43 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
* If the array's type was merely derived from the common type of its
* elements, then the elements are implicitly coerced to the common type.
* This is consistent with other uses of select_common_type().
+ *
+ * If can_coerce is not NULL, we need to safely test whether each element
+ * can be coerced to the target type, similar to what is done in
+ * transformTypeSafeCast.
*/
foreach(element, newelems)
{
Node *e = (Node *) lfirst(element);
+ bool expr_is_const = false;
Node *newe;
- if (coerce_hard)
+ /*
+ * If an UNKNOWN constant cannot be coerced to the target type, set
+ * can_coerce to false and coerce it to the TEXT data type instead.
+ */
+ if (can_coerce != NULL && IsA(e, Const))
+ {
+ expr_is_const = true;
+
+ if (exprType(e) == UNKNOWNOID &&
+ !CoerceUnknownConstSafe(pstate, e, coerce_type, typmod))
+ {
+ *can_coerce = false;
+
+ e = coerce_to_target_type(pstate, e,
+ UNKNOWNOID,
+ TEXTOID,
+ -1,
+ COERCION_IMPLICIT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ }
+ }
+
+ if (can_coerce != NULL && (!*can_coerce))
+ newe = e;
+ else if (coerce_hard)
{
newe = coerce_to_target_type(pstate, e,
exprType(e),
@@ -2149,13 +2256,68 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
COERCION_EXPLICIT,
COERCE_EXPLICIT_CAST,
-1);
- if (newe == NULL)
+ if (newe == NULL && can_coerce == NULL)
ereport(ERROR,
(errcode(ERRCODE_CANNOT_COERCE),
errmsg("cannot cast type %s to %s",
format_type_be(exprType(e)),
format_type_be(coerce_type)),
parser_errposition(pstate, exprLocation(e))));
+
+ if (newe == NULL && can_coerce != NULL)
+ {
+ newe = e;
+ *can_coerce = false;
+ }
+
+ if (newe != NULL && can_coerce != NULL && expr_is_const)
+ {
+ Node *origexpr = newe;
+ Node *result;
+ bool have_collate_expr = false;
+
+ if (IsA(newe, CollateExpr))
+ have_collate_expr = true;
+
+ while (newe && IsA(newe, CollateExpr))
+ newe = (Node *) ((CollateExpr *) newe)->arg;
+
+ if (!IsA(newe, FuncExpr))
+ newe = origexpr;
+ else
+ {
+ /*
+ * Use evaluate_expr_safe to pre-evaluate simple constant
+ * cast expressions early, in a way that tolter errors.
+ */
+ FuncExpr *newexpr = (FuncExpr *) copyObject(newe);
+
+ assign_expr_collations(pstate, (Node *) newexpr);
+
+ result = (Node *) evaluate_expr_safe((Expr *) newexpr,
+ coerce_type,
+ typmod,
+ coerce_type_coll);
+ if (result == NULL)
+ {
+ newe = e;
+ *can_coerce = false;
+ }
+ else if (have_collate_expr)
+ {
+ /* Reinstall top CollateExpr */
+ CollateExpr *coll = (CollateExpr *) origexpr;
+ CollateExpr *newcoll = makeNode(CollateExpr);
+
+ newcoll->arg = (Expr *) result;
+ newcoll->collOid = coll->collOid;
+ newcoll->location = coll->location;
+ newe = (Node *) newcoll;
+ }
+ else
+ newe = result;
+ }
+ }
}
else
newe = coerce_to_common_type(pstate, e,
@@ -2743,7 +2905,8 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
(A_ArrayExpr *) arg,
targetBaseType,
elementType,
- targetBaseTypmod);
+ targetBaseTypmod,
+ NULL);
}
else
expr = transformExprRecurse(pstate, arg);
@@ -2780,6 +2943,259 @@ 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;
+ TypeCast *tcast = (TypeCast *) tc->cast;
+ Node *def_expr = NULL;
+ Node *cast_expr = NULL;
+ Node *source_expr = NULL;
+ Oid inputType = InvalidOid;
+ Oid targetType;
+ Oid targetBaseType;
+ Oid typeColl;
+ int32 targetTypmod;
+ int32 targetBaseTypmod;
+ bool can_coerce = true;
+ bool expr_is_const = false;
+ int location;
+
+ /* Look up the type name first */
+ typenameTypeIdAndMod(pstate, tcast->typeName, &targetType, &targetTypmod);
+ targetBaseTypmod = targetTypmod;
+
+ typeColl = get_typcollation(targetType);
+
+ targetBaseType = getBaseTypeAndTypmod(targetType, &targetBaseTypmod);
+
+ /* now looking at DEFAULT expression */
+ def_expr = transformExpr(pstate, tc->def_expr, EXPR_KIND_CAST_DEFAULT);
+
+ def_expr = coerce_to_target_type(pstate, def_expr, exprType(def_expr),
+ targetType, targetTypmod,
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ exprLocation(def_expr));
+
+ assign_expr_collations(pstate, def_expr);
+
+ if (def_expr == NULL)
+ ereport(ERROR,
+ errcode(ERRCODE_CANNOT_COERCE),
+ errmsg("cannot coerce %s expression to type %s",
+ "CAST DEFAULT",
+ format_type_be(targetType)),
+ parser_coercion_errposition(pstate, exprLocation(tc->def_expr), def_expr));
+
+ /*
+ * The collation of DEFAULT expression must match the collation of the
+ * target type.
+ */
+ if (OidIsValid(typeColl))
+ {
+ Oid defColl;
+
+ defColl = exprCollation(def_expr);
+
+ if (OidIsValid(defColl) && typeColl != defColl)
+ ereport(ERROR,
+ errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("the collation of CAST DEFAULT expression conflicts with target type collation"),
+ errdetail("\"%s\" versus \"%s\"",
+ get_collation_name(typeColl),
+ get_collation_name(defColl)),
+ parser_errposition(pstate, exprLocation(def_expr)));
+ }
+
+ /*
+ * If the type cast 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(tcast->arg, A_ArrayExpr))
+ {
+ 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.
+ */
+ elementType = get_element_type(targetBaseType);
+
+ if (OidIsValid(elementType))
+ {
+ source_expr = transformArrayExpr(pstate,
+ (A_ArrayExpr *) tcast->arg,
+ targetBaseType,
+ elementType,
+ targetBaseTypmod,
+ &can_coerce);
+ }
+ else
+ source_expr = transformExprRecurse(pstate, tcast->arg);
+ }
+ else
+ source_expr = transformExprRecurse(pstate, tcast->arg);
+
+ inputType = exprType(source_expr);
+ if (inputType == InvalidOid)
+ return (Node *) NULL; /* return NULL if NULL input */
+
+ /* Test coerce UNKNOWN constant to the target type */
+ if (can_coerce && IsA(source_expr, Const))
+ {
+ if (exprType(source_expr) == UNKNOWNOID)
+ can_coerce = CoerceUnknownConstSafe(pstate,
+ source_expr,
+ targetType,
+ targetTypmod);
+ expr_is_const = true;
+ }
+
+ /*
+ * Location of the coercion is preferentially the location of 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)
+ {
+ cast_expr = coerce_to_target_type(pstate, source_expr, inputType,
+ targetType, targetTypmod,
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ location);
+
+ if (cast_expr == NULL)
+ can_coerce = false;
+ }
+
+ if (can_coerce)
+ {
+ bool have_collate_expr = false;
+ Node *origexpr = cast_expr;
+
+ if (IsA(cast_expr, CollateExpr))
+ have_collate_expr = true;
+
+ while (cast_expr && IsA(cast_expr, CollateExpr))
+ cast_expr = (Node *) ((CollateExpr *) cast_expr)->arg;
+
+ if (IsA(cast_expr, FuncExpr))
+ {
+ HeapTuple tuple;
+ ListCell *lc;
+ Node *sexpr;
+ Node *result;
+ FuncExpr *fexpr = (FuncExpr *) cast_expr;
+
+ lc = list_head(fexpr->args);
+ sexpr = (Node *) lfirst(lc);
+
+ /* Look in pg_cast */
+ tuple = SearchSysCache2(CASTSOURCETARGET,
+ ObjectIdGetDatum(exprType(sexpr)),
+ ObjectIdGetDatum(targetType));
+
+ if (HeapTupleIsValid(tuple))
+ {
+ Form_pg_cast castForm = (Form_pg_cast) GETSTRUCT(tuple);
+ if (!castForm->casterrorsafe)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot cast type %s to %s when %s expression is specified in %s",
+ format_type_be(inputType),
+ format_type_be(targetType),
+ "DEFAULT",
+ "CAST ... ON CONVERSION ERROR"),
+ errhint("Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast"),
+ parser_errposition(pstate, exprLocation(source_expr)));
+ }
+ else
+ elog(ERROR, "cache lookup failed for pg_cast entry (%s cast to %s)",
+ format_type_be(inputType),
+ format_type_be(targetType));
+ ReleaseSysCache(tuple);
+
+ if (!expr_is_const)
+ cast_expr = origexpr;
+ else
+ {
+ /*
+ * Use evaluate_expr_safe to pre-evaluate simple constant cast
+ * expressions early, in a way that tolter errors.
+ *
+ * Rationale:
+ * 1. When deparsing safe cast expressions (or in other cases),
+ * eval_const_expressions might be invoked, but it cannot
+ * handle errors gracefully.
+ * 2. If the cast expression involves only simple constants, we
+ * can safely evaluate it ahead of time. If the evaluation
+ * fails, it indicates that such a cast is not possible, and
+ * we can then fall back to the CAST DEFAULT expression.
+ * 3. Even if the function has three arguments, the second and
+ * third arguments will also be constants per
+ * coerce_to_target_type.
+ */
+ FuncExpr *newexpr = (FuncExpr *) copyObject(cast_expr);
+
+ assign_expr_collations(pstate, (Node *) newexpr);
+
+ result = (Node *) evaluate_expr_safe((Expr *) newexpr,
+ targetType,
+ targetTypmod,
+ typeColl);
+ if (result == NULL)
+ {
+ can_coerce = false;
+ cast_expr = NULL;
+ }
+ else if (have_collate_expr)
+ {
+ /* Reinstall top CollateExpr */
+ CollateExpr *coll = (CollateExpr *) origexpr;
+ CollateExpr *newcoll = makeNode(CollateExpr);
+
+ newcoll->arg = (Expr *) result;
+ newcoll->collOid = coll->collOid;
+ newcoll->location = coll->location;
+ cast_expr = (Node *) newcoll;
+ }
+ else
+ cast_expr = result;
+ }
+ }
+ else
+ /* Nothing to do, restore cast_expr to its original value */
+ cast_expr = origexpr;
+ }
+
+ Assert(can_coerce || cast_expr == NULL);
+
+ result = makeNode(SafeTypeCastExpr);
+ result->source_expr = source_expr;
+ result->cast_expr = cast_expr;
+ result->default_expr = def_expr;
+ result->resulttype = targetType;
+ result->resulttypmod = targetTypmod;
+ result->resultcollid = typeColl;
+
+ return (Node *) result;
+}
+
/*
* Handle an explicit COLLATE clause.
*
@@ -3193,6 +3609,8 @@ ParseExprKindName(ParseExprKind exprKind)
case EXPR_KIND_CHECK_CONSTRAINT:
case EXPR_KIND_DOMAIN_CHECK:
return "CHECK";
+ case EXPR_KIND_CAST_DEFAULT:
+ return "CAST DEFAULT";
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
return "DEFAULT";
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 778d69c6f3c..a90705b9847 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2743,6 +2743,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
case EXPR_KIND_DOMAIN_CHECK:
err = _("set-returning functions are not allowed in check constraints");
break;
+ case EXPR_KIND_CAST_DEFAULT:
+ err = _("set-returning functions are not allowed in CAST DEFAULT expressions");
+ break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
err = _("set-returning functions are not allowed in DEFAULT expressions");
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 905c975d83b..dc03cf4ce74 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1822,6 +1822,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 7713bdc6af0..d260aeec5dc 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,19 @@ stringTypeDatum(Type tp, char *string, int32 atttypmod)
return OidInputFunctionCall(typinput, string, typioparam, atttypmod);
}
+/* error safe version of stringTypeDatum */
+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,
+ (Node *) &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 a464349ee33..c1a7031a96c 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -3289,6 +3289,14 @@ array_map(Datum arrayd,
/* Apply the given expression to source element */
values[i] = ExecEvalExpr(exprstate, econtext, &nulls[i]);
+ /* Exit early if the evaluation fails */
+ 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 79ec136231b..5cc2f7e0cf2 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -10558,6 +10558,28 @@ get_rule_expr(Node *node, deparse_context *context,
}
break;
+ case T_SafeTypeCastExpr:
+ {
+ SafeTypeCastExpr *stcexpr = castNode(SafeTypeCastExpr, node);
+
+ /*
+ * Here, we cannot simply deparse cast_expr, as it may have been
+ * optimized into a plain constant by transformTypeSafeCast.
+ * However, in this context we need the original CAST
+ * expression.
+ */
+ 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 0fe63c6bb83..aaa4a42b1ea 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, Node *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/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat
index fbfd669587f..ca52cfcd086 100644
--- a/src/include/catalog/pg_cast.dat
+++ b/src/include/catalog/pg_cast.dat
@@ -19,65 +19,65 @@
# int2->int4->int8->numeric->float4->float8, while casts in the
# reverse direction are assignment-only.
{ castsource => 'int8', casttarget => 'int2', castfunc => 'int2(int8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'int4', castfunc => 'int4(int8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'float4', castfunc => 'float4(int8)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'float8', castfunc => 'float8(int8)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'numeric', castfunc => 'numeric(int8)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'int8', castfunc => 'int8(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'int4', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'float4', castfunc => 'float4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'float8', castfunc => 'float8(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'numeric', castfunc => 'numeric(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'int8', castfunc => 'int8(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'int2', castfunc => 'int2(int4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'float4', castfunc => 'float4(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'float8', castfunc => 'float8(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'numeric', castfunc => 'numeric(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'int8', castfunc => 'int8(float4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'int2', castfunc => 'int2(float4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'int4', castfunc => 'int4(float4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'float8', castfunc => 'float8(float4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'numeric',
- castfunc => 'numeric(float4)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'numeric(float4)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'int8', castfunc => 'int8(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'int2', castfunc => 'int2(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'int4', castfunc => 'int4(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'float4', castfunc => 'float4(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'numeric',
- castfunc => 'numeric(float8)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'numeric(float8)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'int8', castfunc => 'int8(numeric)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'int2', castfunc => 'int2(numeric)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'int4', castfunc => 'int4(numeric)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'float4',
- castfunc => 'float4(numeric)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'float4(numeric)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'float8',
- castfunc => 'float8(numeric)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'float8(numeric)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'money', casttarget => 'numeric', castfunc => 'numeric(money)',
castcontext => 'a', castmethod => 'f' },
{ castsource => 'numeric', casttarget => 'money', castfunc => 'money(numeric)',
@@ -89,13 +89,13 @@
# Allow explicit coercions between int4 and bool
{ castsource => 'int4', casttarget => 'bool', castfunc => 'bool(int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'int4', castfunc => 'int4(bool)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Allow explicit coercions between xid8 and xid
{ castsource => 'xid8', casttarget => 'xid', castfunc => 'xid(xid8)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# OID category: allow implicit conversion from any integral type (including
# int8, to support OID literals > 2G) to OID, as well as assignment coercion
@@ -106,13 +106,13 @@
# casts from text and varchar to regclass, which exist mainly to support
# legacy forms of nextval() and related functions.
{ castsource => 'int8', casttarget => 'oid', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'oid', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'oid', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regproc', castfunc => '0',
@@ -120,13 +120,13 @@
{ castsource => 'regproc', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regproc', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regproc', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regproc', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regproc', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regproc', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'regproc', casttarget => 'regprocedure', castfunc => '0',
@@ -138,13 +138,13 @@
{ castsource => 'regprocedure', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regprocedure', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regprocedure', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regprocedure', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regprocedure', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regprocedure', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regoper', castfunc => '0',
@@ -152,13 +152,13 @@
{ castsource => 'regoper', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regoper', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regoper', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regoper', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regoper', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regoper', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'regoper', casttarget => 'regoperator', castfunc => '0',
@@ -170,13 +170,13 @@
{ castsource => 'regoperator', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regoperator', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regoperator', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regoperator', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regoperator', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regoperator', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regclass', castfunc => '0',
@@ -184,13 +184,13 @@
{ castsource => 'regclass', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regclass', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regclass', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regclass', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regclass', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regclass', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regcollation', castfunc => '0',
@@ -198,13 +198,13 @@
{ castsource => 'regcollation', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regcollation', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regcollation', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regcollation', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regcollation', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regcollation', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regtype', castfunc => '0',
@@ -212,13 +212,13 @@
{ castsource => 'regtype', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regtype', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regtype', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regtype', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regtype', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regtype', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regconfig', castfunc => '0',
@@ -226,13 +226,13 @@
{ castsource => 'regconfig', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regconfig', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regconfig', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regconfig', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regconfig', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regconfig', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regdictionary', castfunc => '0',
@@ -240,31 +240,31 @@
{ castsource => 'regdictionary', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regdictionary', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regdictionary', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regdictionary', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regdictionary', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regdictionary', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'text', casttarget => 'regclass', castfunc => 'regclass',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'regclass', castfunc => 'regclass',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'oid', casttarget => 'regrole', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regrole', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regrole', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regrole', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regrole', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regrole', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regrole', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regnamespace', castfunc => '0',
@@ -272,13 +272,13 @@
{ castsource => 'regnamespace', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regnamespace', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regnamespace', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regnamespace', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regnamespace', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regnamespace', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regdatabase', castfunc => '0',
@@ -286,13 +286,13 @@
{ castsource => 'regdatabase', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regdatabase', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regdatabase', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regdatabase', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regdatabase', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regdatabase', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
@@ -302,57 +302,57 @@
{ castsource => 'text', casttarget => 'varchar', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'bpchar', casttarget => 'text', castfunc => 'text(bpchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bpchar', casttarget => 'varchar', castfunc => 'text(bpchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'text', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'varchar', casttarget => 'bpchar', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'char', casttarget => 'text', castfunc => 'text(char)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'char', casttarget => 'bpchar', castfunc => 'bpchar(char)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'char', casttarget => 'varchar', castfunc => 'text(char)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'name', casttarget => 'text', castfunc => 'text(name)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'name', casttarget => 'bpchar', castfunc => 'bpchar(name)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'name', casttarget => 'varchar', castfunc => 'varchar(name)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'text', casttarget => 'char', castfunc => 'char(text)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bpchar', casttarget => 'char', castfunc => 'char(text)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'char', castfunc => 'char(text)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'text', casttarget => 'name', castfunc => 'name(text)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bpchar', casttarget => 'name', castfunc => 'name(bpchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'name', castfunc => 'name(varchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
# Allow explicit coercions between bytea and integer types
{ castsource => 'int2', casttarget => 'bytea', castfunc => 'bytea(int2)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'bytea', castfunc => 'bytea(int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'bytea', castfunc => 'bytea(int8)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bytea', casttarget => 'int2', castfunc => 'int2(bytea)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bytea', casttarget => 'int4', castfunc => 'int4(bytea)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bytea', casttarget => 'int8', castfunc => 'int8(bytea)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Allow explicit coercions between int4 and "char"
{ castsource => 'char', casttarget => 'int4', castfunc => 'int4(char)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'char', castfunc => 'char(int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# pg_node_tree can be coerced to, but not from, text
{ castsource => 'pg_node_tree', casttarget => 'text', castfunc => '0',
@@ -378,73 +378,73 @@
# Datetime category
{ castsource => 'date', casttarget => 'timestamp',
- castfunc => 'timestamp(date)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timestamp(date)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'date', casttarget => 'timestamptz',
- castfunc => 'timestamptz(date)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timestamptz(date)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'time', casttarget => 'interval', castfunc => 'interval(time)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'time', casttarget => 'timetz', castfunc => 'timetz(time)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'date',
- castfunc => 'date(timestamp)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'date(timestamp)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'time',
- castfunc => 'time(timestamp)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'time(timestamp)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'timestamptz',
- castfunc => 'timestamptz(timestamp)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timestamptz(timestamp)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'date',
- castfunc => 'date(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'date(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'time',
- castfunc => 'time(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'time(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'timestamp',
- castfunc => 'timestamp(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'timestamp(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'timetz',
- castfunc => 'timetz(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'timetz(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'interval', casttarget => 'time', castfunc => 'time(interval)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timetz', casttarget => 'time', castfunc => 'time(timetz)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
# Geometric category
{ castsource => 'point', casttarget => 'box', castfunc => 'box(point)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'lseg', casttarget => 'point', castfunc => 'point(lseg)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'path', casttarget => 'polygon', castfunc => 'polygon(path)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'box', casttarget => 'point', castfunc => 'point(box)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'box', casttarget => 'lseg', castfunc => 'lseg(box)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'box', casttarget => 'polygon', castfunc => 'polygon(box)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'box', casttarget => 'circle', castfunc => 'circle(box)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'polygon', casttarget => 'point', castfunc => 'point(polygon)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'polygon', casttarget => 'path', castfunc => 'path',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'polygon', casttarget => 'box', castfunc => 'box(polygon)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'polygon', casttarget => 'circle',
- castfunc => 'circle(polygon)', castcontext => 'e', castmethod => 'f' },
+ castfunc => 'circle(polygon)', castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'circle', casttarget => 'point', castfunc => 'point(circle)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'circle', casttarget => 'box', castfunc => 'box(circle)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'circle', casttarget => 'polygon',
- castfunc => 'polygon(circle)', castcontext => 'e', castmethod => 'f' },
+ castfunc => 'polygon(circle)', castcontext => 'e', castmethod => 'f'},
# MAC address category
{ castsource => 'macaddr', casttarget => 'macaddr8', castfunc => 'macaddr8',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'macaddr8', casttarget => 'macaddr', castfunc => 'macaddr',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
# INET category
{ castsource => 'cidr', casttarget => 'inet', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'inet', casttarget => 'cidr', castfunc => 'cidr',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
# BitString category
{ castsource => 'bit', casttarget => 'varbit', castfunc => '0',
@@ -454,13 +454,13 @@
# Cross-category casts between bit and int4, int8
{ castsource => 'int8', casttarget => 'bit', castfunc => 'bit(int8,int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'bit', castfunc => 'bit(int4,int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bit', casttarget => 'int8', castfunc => 'int8(bit)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bit', casttarget => 'int4', castfunc => 'int4(bit)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Cross-category casts to and from TEXT
# We need entries here only for a few specialized cases where the behavior
@@ -471,68 +471,68 @@
# behavior will ensue when the automatic cast is applied instead of the
# pg_cast entry!
{ castsource => 'cidr', casttarget => 'text', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'inet', casttarget => 'text', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'text', castfunc => 'text(bool)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'xml', casttarget => 'text', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'text', casttarget => 'xml', castfunc => 'xml',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Cross-category casts to and from VARCHAR
# We support all the same casts as for TEXT.
{ castsource => 'cidr', casttarget => 'varchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'inet', casttarget => 'varchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'varchar', castfunc => 'text(bool)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'xml', casttarget => 'varchar', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'varchar', casttarget => 'xml', castfunc => 'xml',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Cross-category casts to and from BPCHAR
# We support all the same casts as for TEXT.
{ castsource => 'cidr', casttarget => 'bpchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'inet', casttarget => 'bpchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'bpchar', castfunc => 'text(bool)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'xml', casttarget => 'bpchar', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'bpchar', casttarget => 'xml', castfunc => 'xml',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Length-coercion functions
{ castsource => 'bpchar', casttarget => 'bpchar',
castfunc => 'bpchar(bpchar,int4,bool)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'varchar',
castfunc => 'varchar(varchar,int4,bool)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'time', casttarget => 'time', castfunc => 'time(time,int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'timestamp',
castfunc => 'timestamp(timestamp,int4)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'timestamptz',
castfunc => 'timestamptz(timestamptz,int4)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'interval', casttarget => 'interval',
castfunc => 'interval(interval,int4)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timetz', casttarget => 'timetz',
- castfunc => 'timetz(timetz,int4)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timetz(timetz,int4)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bit', casttarget => 'bit', castfunc => 'bit(bit,int4,bool)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varbit', casttarget => 'varbit', castfunc => 'varbit',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'numeric',
- castfunc => 'numeric(numeric,int4)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'numeric(numeric,int4)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
# json to/from jsonb
{ castsource => 'json', casttarget => 'jsonb', castfunc => '0',
@@ -542,36 +542,36 @@
# jsonb to numeric and bool types
{ castsource => 'jsonb', casttarget => 'bool', castfunc => 'bool(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'numeric', castfunc => 'numeric(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'int2', castfunc => 'int2(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'int4', castfunc => 'int4(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'int8', castfunc => 'int8(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'float4', castfunc => 'float4(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'float8', castfunc => 'float8(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# range to multirange
{ castsource => 'int4range', casttarget => 'int4multirange',
castfunc => 'int4multirange(int4range)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8range', casttarget => 'int8multirange',
castfunc => 'int8multirange(int8range)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numrange', casttarget => 'nummultirange',
castfunc => 'nummultirange(numrange)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'daterange', casttarget => 'datemultirange',
castfunc => 'datemultirange(daterange)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'tsrange', casttarget => 'tsmultirange',
- castfunc => 'tsmultirange(tsrange)', castcontext => 'e', castmethod => 'f' },
+ castfunc => 'tsmultirange(tsrange)', castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'tstzrange', casttarget => 'tstzmultirange',
castfunc => 'tstzmultirange(tstzrange)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
]
diff --git a/src/include/catalog/pg_cast.h b/src/include/catalog/pg_cast.h
index 6a0ca337153..218d81d535a 100644
--- a/src/include/catalog/pg_cast.h
+++ b/src/include/catalog/pg_cast.h
@@ -47,6 +47,10 @@ CATALOG(pg_cast,2605,CastRelationId)
/* cast method */
char castmethod;
+
+ /* cast function error safe */
+ bool casterrorsafe BKI_DEFAULT(f);
+
} FormData_pg_cast;
/* ----------------
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 75366203706..34a884e3196 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_SAFETYPE_CAST */
+ 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);
+extern 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/executor/executor.h b/src/include/executor/executor.h
index fa2b657fb2f..f99fc26eb1f 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -324,6 +324,7 @@ ExecProcNode(PlanState *node)
* prototypes from functions in execExpr.c
*/
extern ExprState *ExecInitExpr(Expr *node, PlanState *parent);
+extern ExprState *ExecInitExprSafe(Expr *node, PlanState *parent);
extern ExprState *ExecInitExprWithParams(Expr *node, ParamListInfo ext_params);
extern ExprState *ExecInitQual(List *qual, PlanState *parent);
extern ExprState *ExecInitCheck(List *qual, PlanState *parent);
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index 74fe3ea0575..991e14034d3 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, Node *escontext,
+ Datum *result);
extern char *OutputFunctionCall(FmgrInfo *flinfo, Datum val);
extern char *OidOutputFunctionCall(Oid functionId, Datum val);
extern Datum ReceiveFunctionCall(FmgrInfo *flinfo, StringInfo buf,
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 18ae8f0d4bb..f59494c8c6f 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1059,6 +1059,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 ecbddd12e1b..7140093c55d 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -400,6 +400,12 @@ typedef struct TypeCast
ParseLoc location; /* token location, or -1 if unknown */
} TypeCast;
+typedef struct SafeTypeCast
+{
+ NodeTag type;
+ Node *cast;
+ Node *def_expr; /* default expr */
+} SafeTypeCast;
/*
* CollateClause - a COLLATE expression
*/
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 1b4436f2ff6..70b72ed2f40 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -769,6 +769,39 @@ 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
+{
+ Expr xpr;
+
+ /* transformed expression being casted */
+ Node *source_expr;
+
+ /*
+ * The transformed cast expression; It will NULL if can not cast source type
+ * to target type
+ */
+ Node *cast_expr pg_node_attr(query_jumble_ignore);
+
+ /* Fall back to the default expression if cast evaluation fails */
+ Node *default_expr;
+
+ /* cast result data type */
+ Oid resulttype;
+
+ /* cast result data type typmod (usually -1) */
+ int32 resulttypmod;
+
+ /* cast result data type collation */
+ Oid resultcollid;
+
+} SafeTypeCastExpr;
+
/*
* FuncExpr - expression node for a function call
*
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index d0aa8ab0c1c..964ec024de7 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -145,6 +145,8 @@ extern Node *estimate_expression_value(PlannerInfo *root, Node *node);
extern Expr *evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
Oid result_collation);
+extern Expr *evaluate_expr_safe(Expr *expr, Oid result_type, int32 result_typmod,
+ Oid result_collation);
extern bool var_is_nonnullable(PlannerInfo *root, Var *var, bool use_rel_info);
extern List *expand_function_arguments(List *args, bool include_out_arguments,
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index f7d07c84542..9f5b32e0360 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -67,6 +67,8 @@ typedef enum ParseExprKind
EXPR_KIND_VALUES_SINGLE, /* single-row VALUES (in INSERT only) */
EXPR_KIND_CHECK_CONSTRAINT, /* CHECK constraint for a table */
EXPR_KIND_DOMAIN_CHECK, /* CHECK constraint for a domain */
+ EXPR_KIND_CAST_DEFAULT, /* default expression in
+ CAST DEFAULT ON CONVERSION ERROR */
EXPR_KIND_COLUMN_DEFAULT, /* default value for a table column */
EXPR_KIND_FUNCTION_DEFAULT, /* default parameter value for function */
EXPR_KIND_INDEX_EXPRESSION, /* index expression */
diff --git a/src/include/parser/parse_type.h b/src/include/parser/parse_type.h
index 0d919d8bfa2..12381aed64c 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 00000000000..6a0d32b4120
--- /dev/null
+++ b/src/test/regress/expected/cast.out
@@ -0,0 +1,730 @@
+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 DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(1::numeric AS money DEFAULT NULL ON CONVERSION E...
+ ^
+HINT: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(1111 AS "char" DEFAULT NULL ON CONVERSION ERROR);
+ char
+------
+
+(1 row)
+
+--the default expression’s collation should match the collation of the cast
+--target type
+VALUES (CAST('error' AS TEXT DEFAULT '1' COLLATE "C" ON CONVERSION ERROR));
+ERROR: the collation of CAST DEFAULT expression conflicts with target type collation
+LINE 1: VALUES (CAST('error' AS TEXT DEFAULT '1' COLLATE "C" ON CONV...
+ ^
+DETAIL: "default" versus "C"
+VALUES (CAST('error' AS int2vector DEFAULT '1 3' ON CONVERSION ERROR));
+ column1
+---------
+ 1 3
+(1 row)
+
+VALUES (CAST('error' AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR));
+ column1
+---------
+ {"1 3"}
+(1 row)
+
+-- test source expression contain subquery
+SELECT CAST((SELECT b FROM generate_series(1, 1), (VALUES('H')) s(b))
+ AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR);
+ b
+---------
+ {"1 3"}
+(1 row)
+
+SELECT CAST((SELECT ARRAY((SELECT b FROM generate_series(1, 2) g, (VALUES('H')) s(b))))
+ AS INT[]
+ DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+CREATE 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 coerce CAST DEFAULT expression to type date
+LINE 1: SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERR...
+ ^
+-- test array coerce
+SELECT CAST(array[11.12] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+ {11}
+(1 row)
+
+SELECT CAST(array[11.12] AS date[] DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+SELECT CAST(array[11111111111111111] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+SELECT CAST(array[['abc'],[456]] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+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: set-returning functions are not allowed in CAST DEFAULT expressions
+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: aggregate functions are not allowed in CAST DEFAULT expressions
+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: window functions are not allowed in CAST DEFAULT expressions
+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 DOMAIN d_varchar as varchar(3) NOT NULL;
+CREATE TYPE comp_domain_with_typmod AS (a d_char3_not_null, b int);
+CREATE TYPE comp2 AS (a d_varchar);
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION ERROR); --error
+ERROR: value too long for type character varying(3)
+LINE 1: SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION...
+ ^
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(123)' ON CONVERSION ERROR); --ok
+ comp2
+-------
+ (123)
+(1 row)
+
+SELECT CAST(11 AS d_int42 DEFAULT 41 ON CONVERSION ERROR); --error
+ERROR: value for domain d_int42 violates check constraint "d_int42_check"
+SELECT CAST(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)'...
+ ^
+-----safe cast with geometry data type
+SELECT CAST('(1,2)'::point AS box DEFAULT NULL ON CONVERSION ERROR);
+ box
+-------------
+ (1,2),(1,2)
+(1 row)
+
+SELECT CAST('[(NaN,1),(NaN,infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+----------------
+ (NaN,Infinity)
+(1 row)
+
+SELECT CAST('[(1e+300,Infinity),(1e+300,Infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+-------------------
+ (1e+300,Infinity)
+(1 row)
+
+SELECT CAST('[(1,2),(3,4)]'::path as polygon DEFAULT NULL ON CONVERSION ERROR);
+ polygon
+---------
+
+(1 row)
+
+SELECT CAST('(NaN,1.0,NaN,infinity)'::box AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+----------------
+ (NaN,Infinity)
+(1 row)
+
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS lseg DEFAULT NULL ON CONVERSION ERROR);
+ lseg
+---------------
+ [(2,2),(0,0)]
+(1 row)
+
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS polygon DEFAULT NULL ON CONVERSION ERROR);
+ polygon
+---------------------------
+ ((0,0),(0,2),(2,2),(2,0))
+(1 row)
+
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS path DEFAULT NULL ON CONVERSION ERROR);
+ path
+------
+
+(1 row)
+
+SELECT CAST('(2.0,infinity,NaN,infinity)'::box AS circle DEFAULT NULL ON CONVERSION ERROR);
+ circle
+----------------------
+ <(NaN,Infinity),NaN>
+(1 row)
+
+SELECT CAST('(NaN,0.0),(2.0,4.0),(0.0,infinity)'::polygon AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+----------------
+ (NaN,Infinity)
+(1 row)
+
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS path DEFAULT NULL ON CONVERSION ERROR);
+ path
+---------------------
+ ((2,0),(2,4),(0,0))
+(1 row)
+
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS box DEFAULT NULL ON CONVERSION ERROR);
+ box
+-------------
+ (2,4),(0,0)
+(1 row)
+
+SELECT CAST('(NaN,infinity),(2.0,4.0),(0.0,infinity)'::polygon AS circle DEFAULT NULL ON CONVERSION ERROR);
+ circle
+----------------------
+ <(NaN,Infinity),NaN>
+(1 row)
+
+SELECT CAST('<(5,1),3>'::circle AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+-------
+ (5,1)
+(1 row)
+
+SELECT CAST('<(3,5),0>'::circle as box DEFAULT NULL ON CONVERSION ERROR);
+ box
+-------------
+ (3,5),(3,5)
+(1 row)
+
+-- not supported because the cast from circle to polygon is implemented as a SQL
+-- function, which cannot be error-safe.
+SELECT CAST('<(3,5),0>'::circle as polygon DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type circle to polygon when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST('<(3,5),0>'::circle as polygon DEFAULT NULL ON C...
+ ^
+HINT: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+-----safe cast from/to money type is not supported, all the following would result error
+SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type bigint to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION E...
+ ^
+HINT: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type integer to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION E...
+ ^
+HINT: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type numeric to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSIO...
+ ^
+HINT: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type money to numeric when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSIO...
+ ^
+HINT: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
+-----safe cast from bytea type to other data types
+SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT NULL ON CONVERSION ERROR);
+ int8
+------
+
+(1 row)
+
+SELECT CAST('\x123456789A'::bytea AS int4 DEFAULT NULL ON CONVERSION ERROR);
+ int4
+------
+
+(1 row)
+
+SELECT CAST('\x123456'::bytea AS int2 DEFAULT NULL ON CONVERSION ERROR);
+ int2
+------
+
+(1 row)
+
+-----safe cast from bit type to other data types
+SELECT CAST('111111111100001'::bit(100) AS INT DEFAULT NULL ON CONVERSION ERROR);
+ int4
+------
+
+(1 row)
+
+SELECT CAST ('111111111100001'::bit(100) AS INT8 DEFAULT NULL ON CONVERSION ERROR);
+ int8
+------
+
+(1 row)
+
+-----safe cast from text type to other data types
+select CAST('a.b.c.d'::text as regclass default NULL on conversion error);
+ regclass
+----------
+
+(1 row)
+
+CREATE TABLE test_safecast(col0 text);
+INSERT INTO test_safecast(col0) VALUES ('<value>one</value');
+SELECT col0 as text,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) as to_regclass,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) IS NULL as expect_true,
+ CAST(col0 AS "char" DEFAULT NULL ON CONVERSION ERROR) as to_char,
+ CAST(col0 AS name DEFAULT NULL ON CONVERSION ERROR) as to_name,
+ CAST(col0 AS xml DEFAULT NULL ON CONVERSION ERROR) as to_xml
+FROM test_safecast;
+ text | to_regclass | expect_true | to_char | to_name | to_xml
+-------------------+-------------+-------------+---------+-------------------+--------
+ <value>one</value | | t | < | <value>one</value |
+(1 row)
+
+SELECT CAST('192.168.1.x' as inet DEFAULT NULL ON CONVERSION ERROR);
+ inet
+------
+
+(1 row)
+
+SELECT CAST('192.168.1.2/30' as cidr DEFAULT NULL ON CONVERSION ERROR);
+ cidr
+------
+
+(1 row)
+
+SELECT CAST('22:00:5c:08:55:08:01:02'::macaddr8 as macaddr DEFAULT NULL ON CONVERSION ERROR);
+ macaddr
+---------
+
+(1 row)
+
+-----safe cast betweeen range data types
+SELECT CAST('[1,2]'::int4range AS int4multirange DEFAULT NULL ON CONVERSION ERROR);
+ int4multirange
+----------------
+ {[1,3)}
+(1 row)
+
+SELECT CAST('[1,2]'::int8range AS int8multirange DEFAULT NULL ON CONVERSION ERROR);
+ int8multirange
+----------------
+ {[1,3)}
+(1 row)
+
+SELECT CAST('[1,2]'::numrange AS nummultirange DEFAULT NULL ON CONVERSION ERROR);
+ nummultirange
+---------------
+ {[1,2]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::daterange AS datemultirange DEFAULT NULL ON CONVERSION ERROR);
+ datemultirange
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::tsrange AS tsmultirange DEFAULT NULL ON CONVERSION ERROR);
+ tsmultirange
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::tstzrange AS tstzmultirange DEFAULT NULL ON CONVERSION ERROR);
+ tstzmultirange
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+-----safe cast betweeen numeric data types
+CREATE TABLE test_safecast1(
+ col1 float4, col2 float8, col3 numeric, col4 numeric[],
+ col5 int2 default 32767,
+ col6 int4 default 32768,
+ col7 int8 default 4294967296);
+INSERT INTO test_safecast1 VALUES('11.1234', '11.1234', '9223372036854775808', '{11.1234}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('inf', 'inf', 'inf', '{11.1234, 12, inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('-inf', '-inf', '-inf', '{11.1234, 12, -inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('NaN', 'NaN', 'NaN', '{11.1234, 12, -inf, NaN}'::numeric[]);
+SELECT col5 as int2,
+ CAST(col5 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col5 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col5 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col5 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col5 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col5 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col5 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col5 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ int2 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+(4 rows)
+
+SELECT col6 as int4,
+ CAST(col6 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col6 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col6 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col6 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col6 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col6 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col6 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col6 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ int4 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+(4 rows)
+
+SELECT col7 as int8,
+ CAST(col7 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col7 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col7 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col7 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col7 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col7 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col7 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col7 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ int8 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+------------+---------+---------+--------+------------+-------------+------------+------------+------------------
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+(4 rows)
+
+SELECT col3 as numeric,
+ CAST(col3 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col3 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col3 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col3 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col3 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col3 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col3 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col3 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ numeric | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+---------------------+---------+---------+--------+---------+-------------+----------------------+---------------------+------------------
+ 9223372036854775808 | | | | | 9.22337e+18 | 9.22337203685478e+18 | 9223372036854775808 |
+ Infinity | | | | | Infinity | Infinity | Infinity |
+ -Infinity | | | | | -Infinity | -Infinity | -Infinity |
+ NaN | | | | | NaN | NaN | NaN | NaN
+(4 rows)
+
+SELECT col4 as num_arr,
+ CAST(col4 AS int2[] DEFAULT NULL ON CONVERSION ERROR) as int2arr,
+ CAST(col4 AS int4[] DEFAULT NULL ON CONVERSION ERROR) as int4arr,
+ CAST(col4 as int8[] DEFAULT NULL ON CONVERSION ERROR) as int8arr,
+ CAST(col4 as float4[] DEFAULT NULL ON CONVERSION ERROR) as f4arr,
+ CAST(col4 as float8[] DEFAULT NULL ON CONVERSION ERROR) as f8arr,
+ CAST(col4 as numeric(10,1)[] DEFAULT NULL ON CONVERSION ERROR) as numarr
+FROM test_safecast1;
+ num_arr | int2arr | int4arr | int8arr | f4arr | f8arr | numarr
+----------------------------+---------+---------+---------+----------------------------+----------------------------+--------
+ {11.1234} | {11} | {11} | {11} | {11.1234} | {11.1234} | {11.1}
+ {11.1234,12,Infinity,NaN} | | | | {11.1234,12,Infinity,NaN} | {11.1234,12,Infinity,NaN} |
+ {11.1234,12,-Infinity,NaN} | | | | {11.1234,12,-Infinity,NaN} | {11.1234,12,-Infinity,NaN} |
+ {11.1234,12,-Infinity,NaN} | | | | {11.1234,12,-Infinity,NaN} | {11.1234,12,-Infinity,NaN} |
+(4 rows)
+
+SELECT col1 as float4,
+ CAST(col1 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col1 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col1 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col1 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col1 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col1 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col1 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col1 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ float4 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-----------+---------+---------+--------+---------+-----------+------------------+------------+------------------
+ 11.1234 | 11 | 11 | | 11 | 11.1234 | 11.1233997344971 | 11.1234 | 11.1
+ Infinity | | | | | Infinity | Infinity | Infinity |
+ -Infinity | | | | | -Infinity | -Infinity | -Infinity |
+ NaN | | | | | NaN | NaN | NaN | NaN
+(4 rows)
+
+SELECT col2 as float8,
+ CAST(col2 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col2 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col2 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col2 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col2 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col2 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col2 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col2 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ float8 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-----------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 11.1234 | 11 | 11 | | 11 | 11.1234 | 11.1234 | 11.1234 | 11.1
+ Infinity | | | | | Infinity | Infinity | Infinity |
+ -Infinity | | | | | -Infinity | -Infinity | -Infinity |
+ NaN | | | | | NaN | NaN | NaN | NaN
+(4 rows)
+
+-- date/timestamp/interval related safe type cast
+CREATE TABLE test_safecast2(
+ col0 date, col1 timestamp, col2 timestamptz,
+ col3 interval, col4 time, col5 timetz);
+INSERT INTO test_safecast2 VALUES
+('-infinity', '-infinity', '-infinity', '-infinity',
+ '2003-03-07 15:36:39 America/New_York', '2003-07-07 15:36:39 America/New_York'),
+('-infinity', 'infinity', 'infinity', 'infinity',
+ '11:59:59.99 PM', '11:59:59.99 PM PDT');
+SELECT col0 as date,
+ CAST(col0 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col0 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col0 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col0 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col0 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+ date | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale
+-----------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity | -infinity | | | -infinity
+ -infinity | -infinity | -infinity | | | -infinity
+(2 rows)
+
+SELECT col1 as timestamp,
+ CAST(col1 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col1 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col1 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col1 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col1 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+ timestamp | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale
+-----------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity | -infinity | | | -infinity
+ infinity | infinity | infinity | | | infinity
+(2 rows)
+
+SELECT col2 as timestamptz,
+ CAST(col2 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col2 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col2 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col2 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col2 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+ timestamptz | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale
+-------------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity | -infinity | | | -infinity
+ infinity | infinity | infinity | | | infinity
+(2 rows)
+
+SELECT col3 as interval,
+ CAST(col3 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col3 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col3 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col3 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col3 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale,
+ CAST(col3 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+ interval | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale | to_interval_scale
+-----------+----------------+---------+----------+-----------+--------------------+-------------------
+ -infinity | | | | | | -infinity
+ infinity | | | | | | infinity
+(2 rows)
+
+SELECT col4 as time,
+ CAST(col4 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col4 AS timetz DEFAULT NULL ON CONVERSION ERROR) IS NOT NULL as to_timetz,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+ time | to_times | to_timetz | to_interval | to_interval_scale
+-------------+-------------+-----------+-------------------------------+-------------------------------
+ 15:36:39 | 15:36:39 | t | @ 15 hours 36 mins 39 secs | @ 15 hours 36 mins 39 secs
+ 23:59:59.99 | 23:59:59.99 | t | @ 23 hours 59 mins 59.99 secs | @ 23 hours 59 mins 59.99 secs
+(2 rows)
+
+SELECT col5 as timetz,
+ CAST(col5 AS time DEFAULT NULL ON CONVERSION ERROR) as to_time,
+ CAST(col5 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_time_scale,
+ CAST(col5 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col5 AS timetz(6) DEFAULT NULL ON CONVERSION ERROR) as to_timetz_scale,
+ CAST(col5 AS interval DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col5 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+ timetz | to_time | to_time_scale | to_timetz | to_timetz_scale | to_interval | to_interval_scale
+----------------+-------------+---------------+----------------+-----------------+-------------+-------------------
+ 15:36:39-04 | 15:36:39 | 15:36:39 | 15:36:39-04 | 15:36:39-04 | |
+ 23:59:59.99-07 | 23:59:59.99 | 23:59:59.99 | 23:59:59.99-07 | 23:59:59.99-07 | |
+(2 rows)
+
+CREATE TEMP TABLE test_safecast3(col0 jsonb);
+INSERT INTO test_safecast3(col0) VALUES ('"test"');
+SELECT col0 as jsonb,
+ CAST(col0 AS integer DEFAULT NULL ON CONVERSION ERROR) as to_integer,
+ CAST(col0 AS numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col0 AS bigint DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col0 AS float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col0 AS float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col0 AS boolean DEFAULT NULL ON CONVERSION ERROR) as to_bool,
+ CAST(col0 AS smallint DEFAULT NULL ON CONVERSION ERROR) as to_smallint
+FROM test_safecast3;
+ jsonb | to_integer | to_numeric | to_int8 | to_float4 | to_float8 | to_bool | to_smallint
+--------+------------+------------+---------+-----------+-----------+---------+-------------
+ "test" | | | | | | |
+(1 row)
+
+-- test deparse
+CREATE DOMAIN d_int_arr as int[];
+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 test_safecast1,
+ 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 test_safecast1,
+ CAST(ARRAY[ARRAY[1], ARRAY['three'::text], ARRAY['a'::text]] 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 VIEW safecastview1 AS
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS d_int_arr DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1,
+ CAST(ARRAY[['1', '2'], ['three', 'a']] AS d_int_arr DEFAULT '{21,22}' ON CONVERSION ERROR) as cast2;
+\sv safecastview1
+CREATE OR REPLACE VIEW public.safecastview1 AS
+ SELECT CAST(ARRAY[ARRAY[1], ARRAY['three'::text], ARRAY['a'::text]] AS d_int_arr DEFAULT '{1,2}'::integer[]::d_int_arr ON CONVERSION ERROR) AS cast1,
+ CAST(ARRAY[ARRAY[1, 2], ARRAY['three'::text, 'a'::text]] AS d_int_arr DEFAULT '{21,22}'::integer[]::d_int_arr ON CONVERSION ERROR) AS cast2
+SELECT * FROM safecastview1;
+ cast1 | cast2
+-------+---------
+ {1,2} | {21,22}
+(1 row)
+
+--error, default expression is mutable
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as int DEFAULT random(min=>1, max=>1) ON CONVERSION ERROR)));
+ERROR: functions in index expression must be marked IMMUTABLE
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as xid DEFAULT NULL ON CONVERSION ERROR)));
+ERROR: data type xid has no default operator class for access method "btree"
+HINT: You must specify an operator class for the index or define a default operator class for the data type.
+CREATE INDEX test_safecast3_idx ON test_safecast3((CAST(col0 as int DEFAULT NULL ON CONVERSION ERROR))); --ok
+SELECT pg_get_indexdef('test_safecast3_idx'::regclass);
+ pg_get_indexdef
+-------------------------------------------------------------------------------------------------------------------------------------------
+ CREATE INDEX test_safecast3_idx ON pg_temp.test_safecast3 USING btree ((CAST(col0 AS integer DEFAULT NULL::integer ON CONVERSION ERROR)))
+(1 row)
+
+DROP TABLE test_safecast;
+DROP TABLE test_safecast1;
+DROP TABLE test_safecast2;
+DROP TABLE test_safecast3;
+DROP TABLE tcast;
+RESET extra_float_digits;
diff --git a/src/test/regress/expected/create_cast.out b/src/test/regress/expected/create_cast.out
index 0e69644bca2..38eab453344 100644
--- a/src/test/regress/expected/create_cast.out
+++ b/src/test/regress/expected/create_cast.out
@@ -88,6 +88,11 @@ SELECT 1234::int4::casttesttype; -- Should work now
bar1234
(1 row)
+SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- error
+ERROR: cannot cast type integer to casttesttype when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVE...
+ ^
+HINT: Currently CAST ... DEFAULT ON CONVERSION ERROR does not support this cast
-- check dependencies generated for that
SELECT pg_describe_object(classid, objid, objsubid) as obj,
pg_describe_object(refclassid, refobjid, refobjsubid) as objref,
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index a357e1d0c0e..81ea244859f 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -943,8 +943,8 @@ SELECT *
FROM pg_cast c
WHERE castsource = 0 OR casttarget = 0 OR castcontext NOT IN ('e', 'a', 'i')
OR castmethod NOT IN ('f', 'b' ,'i');
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Check that castfunc is nonzero only for cast methods that need a function,
@@ -953,8 +953,8 @@ SELECT *
FROM pg_cast c
WHERE (castmethod = 'f' AND castfunc = 0)
OR (castmethod IN ('b', 'i') AND castfunc <> 0);
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Look for casts to/from the same type that aren't length coercion functions.
@@ -963,15 +963,15 @@ WHERE (castmethod = 'f' AND castfunc = 0)
SELECT *
FROM pg_cast c
WHERE castsource = casttarget AND castfunc = 0;
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
SELECT c.*
FROM pg_cast c, pg_proc p
WHERE c.castfunc = p.oid AND p.pronargs < 2 AND castsource = casttarget;
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Look for cast functions that don't have the right signature. The
@@ -989,8 +989,8 @@ WHERE c.castfunc = p.oid AND
OR (c.castsource = 'character'::regtype AND
p.proargtypes[0] = 'text'::regtype))
OR NOT binary_coercible(p.prorettype, c.casttarget));
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
SELECT c.*
@@ -998,8 +998,8 @@ FROM pg_cast c, pg_proc p
WHERE c.castfunc = p.oid AND
((p.pronargs > 1 AND p.proargtypes[1] != 'int4'::regtype) OR
(p.pronargs > 2 AND p.proargtypes[2] != 'bool'::regtype));
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Look for binary compatible casts that do not have the reverse
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index a0f5fab0f5d..c6936f64a87 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -81,7 +81,7 @@ test: create_table_like alter_generic alter_operator misc async dbsize merge mis
# collate.linux.utf8 and collate.icu.utf8 tests cannot be run in parallel with each other
# psql depends on create_am
# amutils depends on geometry, create_index_spgist, hash_index, brin
-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 00000000000..9233b88309d
--- /dev/null
+++ b/src/test/regress/sql/cast.sql
@@ -0,0 +1,320 @@
+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);
+SELECT CAST(1111 AS "char" DEFAULT NULL ON CONVERSION ERROR);
+--the default expression’s collation should match the collation of the cast
+--target type
+VALUES (CAST('error' AS TEXT DEFAULT '1' COLLATE "C" ON CONVERSION ERROR));
+
+VALUES (CAST('error' AS int2vector DEFAULT '1 3' ON CONVERSION ERROR));
+VALUES (CAST('error' AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR));
+
+-- test source expression contain subquery
+SELECT CAST((SELECT b FROM generate_series(1, 1), (VALUES('H')) s(b))
+ AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR);
+SELECT CAST((SELECT ARRAY((SELECT b FROM generate_series(1, 2) g, (VALUES('H')) s(b))))
+ AS INT[]
+ DEFAULT NULL ON CONVERSION ERROR);
+
+CREATE 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(array[11.12] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(array[11.12] AS date[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(array[11111111111111111] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(array[['abc'],[456]] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+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 DOMAIN d_varchar as varchar(3) NOT NULL;
+CREATE TYPE comp_domain_with_typmod AS (a d_char3_not_null, b int);
+CREATE TYPE comp2 AS (a d_varchar);
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION ERROR); --error
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(123)' ON CONVERSION ERROR); --ok
+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
+
+-----safe cast with geometry data type
+SELECT CAST('(1,2)'::point AS box DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[(NaN,1),(NaN,infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[(1e+300,Infinity),(1e+300,Infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[(1,2),(3,4)]'::path as polygon DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NaN,1.0,NaN,infinity)'::box AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS lseg DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS polygon DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS path DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,infinity,NaN,infinity)'::box AS circle DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NaN,0.0),(2.0,4.0),(0.0,infinity)'::polygon AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS path DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS box DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NaN,infinity),(2.0,4.0),(0.0,infinity)'::polygon AS circle DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('<(5,1),3>'::circle AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('<(3,5),0>'::circle as box DEFAULT NULL ON CONVERSION ERROR);
+-- not supported because the cast from circle to polygon is implemented as a SQL
+-- function, which cannot be error-safe.
+SELECT CAST('<(3,5),0>'::circle as polygon DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast from/to money type is not supported, all the following would result error
+SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast from bytea type to other data types
+SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('\x123456789A'::bytea AS int4 DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('\x123456'::bytea AS int2 DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast from bit type to other data types
+SELECT CAST('111111111100001'::bit(100) AS INT DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST ('111111111100001'::bit(100) AS INT8 DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast from text type to other data types
+select CAST('a.b.c.d'::text as regclass default NULL on conversion error);
+
+CREATE TABLE test_safecast(col0 text);
+INSERT INTO test_safecast(col0) VALUES ('<value>one</value');
+
+SELECT col0 as text,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) as to_regclass,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) IS NULL as expect_true,
+ CAST(col0 AS "char" DEFAULT NULL ON CONVERSION ERROR) as to_char,
+ CAST(col0 AS name DEFAULT NULL ON CONVERSION ERROR) as to_name,
+ CAST(col0 AS xml DEFAULT NULL ON CONVERSION ERROR) as to_xml
+FROM test_safecast;
+
+SELECT CAST('192.168.1.x' as inet DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('192.168.1.2/30' as cidr DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('22:00:5c:08:55:08:01:02'::macaddr8 as macaddr DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast betweeen range data types
+SELECT CAST('[1,2]'::int4range AS int4multirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[1,2]'::int8range AS int8multirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[1,2]'::numrange AS nummultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::daterange AS datemultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::tsrange AS tsmultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::tstzrange AS tstzmultirange DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast betweeen numeric data types
+CREATE TABLE test_safecast1(
+ col1 float4, col2 float8, col3 numeric, col4 numeric[],
+ col5 int2 default 32767,
+ col6 int4 default 32768,
+ col7 int8 default 4294967296);
+INSERT INTO test_safecast1 VALUES('11.1234', '11.1234', '9223372036854775808', '{11.1234}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('inf', 'inf', 'inf', '{11.1234, 12, inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('-inf', '-inf', '-inf', '{11.1234, 12, -inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('NaN', 'NaN', 'NaN', '{11.1234, 12, -inf, NaN}'::numeric[]);
+
+SELECT col5 as int2,
+ CAST(col5 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col5 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col5 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col5 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col5 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col5 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col5 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col5 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col6 as int4,
+ CAST(col6 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col6 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col6 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col6 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col6 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col6 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col6 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col6 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col7 as int8,
+ CAST(col7 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col7 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col7 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col7 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col7 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col7 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col7 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col7 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col3 as numeric,
+ CAST(col3 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col3 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col3 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col3 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col3 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col3 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col3 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col3 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col4 as num_arr,
+ CAST(col4 AS int2[] DEFAULT NULL ON CONVERSION ERROR) as int2arr,
+ CAST(col4 AS int4[] DEFAULT NULL ON CONVERSION ERROR) as int4arr,
+ CAST(col4 as int8[] DEFAULT NULL ON CONVERSION ERROR) as int8arr,
+ CAST(col4 as float4[] DEFAULT NULL ON CONVERSION ERROR) as f4arr,
+ CAST(col4 as float8[] DEFAULT NULL ON CONVERSION ERROR) as f8arr,
+ CAST(col4 as numeric(10,1)[] DEFAULT NULL ON CONVERSION ERROR) as numarr
+FROM test_safecast1;
+
+SELECT col1 as float4,
+ CAST(col1 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col1 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col1 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col1 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col1 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col1 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col1 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col1 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col2 as float8,
+ CAST(col2 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col2 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col2 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col2 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col2 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col2 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col2 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col2 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+-- date/timestamp/interval related safe type cast
+CREATE TABLE test_safecast2(
+ col0 date, col1 timestamp, col2 timestamptz,
+ col3 interval, col4 time, col5 timetz);
+INSERT INTO test_safecast2 VALUES
+('-infinity', '-infinity', '-infinity', '-infinity',
+ '2003-03-07 15:36:39 America/New_York', '2003-07-07 15:36:39 America/New_York'),
+('-infinity', 'infinity', 'infinity', 'infinity',
+ '11:59:59.99 PM', '11:59:59.99 PM PDT');
+
+SELECT col0 as date,
+ CAST(col0 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col0 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col0 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col0 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col0 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+
+SELECT col1 as timestamp,
+ CAST(col1 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col1 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col1 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col1 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col1 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+
+SELECT col2 as timestamptz,
+ CAST(col2 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col2 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col2 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col2 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col2 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+
+SELECT col3 as interval,
+ CAST(col3 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col3 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col3 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col3 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col3 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale,
+ CAST(col3 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+
+SELECT col4 as time,
+ CAST(col4 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col4 AS timetz DEFAULT NULL ON CONVERSION ERROR) IS NOT NULL as to_timetz,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+
+SELECT col5 as timetz,
+ CAST(col5 AS time DEFAULT NULL ON CONVERSION ERROR) as to_time,
+ CAST(col5 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_time_scale,
+ CAST(col5 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col5 AS timetz(6) DEFAULT NULL ON CONVERSION ERROR) as to_timetz_scale,
+ CAST(col5 AS interval DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col5 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+
+CREATE TEMP TABLE test_safecast3(col0 jsonb);
+INSERT INTO test_safecast3(col0) VALUES ('"test"');
+SELECT col0 as jsonb,
+ CAST(col0 AS integer DEFAULT NULL ON CONVERSION ERROR) as to_integer,
+ CAST(col0 AS numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col0 AS bigint DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col0 AS float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col0 AS float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col0 AS boolean DEFAULT NULL ON CONVERSION ERROR) as to_bool,
+ CAST(col0 AS smallint DEFAULT NULL ON CONVERSION ERROR) as to_smallint
+FROM test_safecast3;
+
+-- test deparse
+CREATE DOMAIN d_int_arr as int[];
+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 test_safecast1,
+ 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 VIEW safecastview1 AS
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS d_int_arr DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1,
+ CAST(ARRAY[['1', '2'], ['three', 'a']] AS d_int_arr DEFAULT '{21,22}' ON CONVERSION ERROR) as cast2;
+\sv safecastview1
+SELECT * FROM safecastview1;
+
+--error, default expression is mutable
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as int DEFAULT random(min=>1, max=>1) ON CONVERSION ERROR)));
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as xid DEFAULT NULL ON CONVERSION ERROR)));
+CREATE INDEX test_safecast3_idx ON test_safecast3((CAST(col0 as int DEFAULT NULL ON CONVERSION ERROR))); --ok
+SELECT pg_get_indexdef('test_safecast3_idx'::regclass);
+
+DROP TABLE test_safecast;
+DROP TABLE test_safecast1;
+DROP TABLE test_safecast2;
+DROP TABLE test_safecast3;
+DROP TABLE tcast;
+RESET extra_float_digits;
diff --git a/src/test/regress/sql/create_cast.sql b/src/test/regress/sql/create_cast.sql
index 32187853cc7..0a15a795d87 100644
--- a/src/test/regress/sql/create_cast.sql
+++ b/src/test/regress/sql/create_cast.sql
@@ -62,6 +62,7 @@ $$ SELECT ('bar'::text || $1::text); $$;
CREATE CAST (int4 AS casttesttype) WITH FUNCTION bar_int4_text(int4) AS IMPLICIT;
SELECT 1234::int4::casttesttype; -- Should work now
+SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- error
-- check dependencies generated for that
SELECT pg_describe_object(classid, objid, objsubid) as obj,
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 018b5919cf6..c3e6432ec35 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2662,6 +2662,9 @@ STRLEN
SV
SYNCHRONIZATION_BARRIER
SYSTEM_INFO
+SafeTypeCast
+SafeTypeCastExpr
+SafeTypeCastState
SampleScan
SampleScanGetSampleSize_function
SampleScanState
--
2.34.1
v9-0015-error-safe-for-casting-timestamptz-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v9-0015-error-safe-for-casting-timestamptz-to-other-types-per-pg_cast.patchDownload
From 4266d9902fbb4f7b11fa9e882b7e9a7f5083ef6d Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Mon, 6 Oct 2025 12:39:22 +0800
Subject: [PATCH v9 15/19] error safe for casting timestamptz to other types
per pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
and pc.castfunc > 0 and (castsource::regtype ='timestamptz'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
--------------------------+-----------------------------+----------+-------------+------------+-----------------------+-------------
timestamp with time zone | date | 1178 | a | f | timestamptz_date | date
timestamp with time zone | time without time zone | 2019 | a | f | timestamptz_time | time
timestamp with time zone | timestamp without time zone | 2027 | a | f | timestamptz_timestamp | timestamp
timestamp with time zone | time with time zone | 1388 | a | f | timestamptz_timetz | timetz
timestamp with time zone | timestamp with time zone | 1967 | i | f | timestamptz_scale | timestamptz
(5 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 16 +++++++++++++---
src/backend/utils/adt/timestamp.c | 17 +++++++++++++++--
2 files changed, 28 insertions(+), 5 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 4f0f3d26989..111bfd8f519 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -1468,7 +1468,17 @@ timestamptz_date(PG_FUNCTION_ARGS)
TimestampTz timestamp = PG_GETARG_TIMESTAMP(0);
DateADT result;
- result = timestamptz2date_opt_overflow(timestamp, NULL);
+ if (likely(!fcinfo->context))
+ result = timestamptz2date_opt_overflow(timestamp, NULL);
+ else
+ {
+ int overflow;
+ result = timestamptz2date_opt_overflow(timestamp, &overflow);
+
+ if (overflow != 0)
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_DATEADT(result);
}
@@ -2110,7 +2120,7 @@ timestamptz_time(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
@@ -3029,7 +3039,7 @@ timestamptz_timetz(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 7b565cc6d66..116e3ef28fc 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -875,7 +875,8 @@ timestamptz_scale(PG_FUNCTION_ARGS)
result = timestamp;
- AdjustTimestampForTypmod(&result, typmod, NULL);
+ if (!AdjustTimestampForTypmod(&result, typmod, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_TIMESTAMPTZ(result);
}
@@ -6507,7 +6508,19 @@ timestamptz_timestamp(PG_FUNCTION_ARGS)
{
TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(0);
- PG_RETURN_TIMESTAMP(timestamptz2timestamp(timestamp));
+ if (likely(!fcinfo->context))
+ PG_RETURN_TIMESTAMP(timestamptz2timestamp(timestamp));
+ else
+ {
+ int overflow;
+ Timestamp result;
+
+ result = timestamptz2timestamp_opt_overflow(timestamp, &overflow);
+ if (overflow != 0)
+ PG_RETURN_NULL();
+ else
+ PG_RETURN_TIMESTAMP(result);
+ }
}
/*
--
2.34.1
v9-0018-error-safe-for-casting-geometry-data-type.patchtext/x-patch; charset=US-ASCII; name=v9-0018-error-safe-for-casting-geometry-data-type.patchDownload
From c784c0b1ef9b11a72c6e372ca3bda750b817ace6 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Fri, 31 Oct 2025 08:32:32 +0800
Subject: [PATCH v9 18/19] error safe for casting geometry data type
select castsource::regtype, casttarget::regtype, pp.prosrc
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
join pg_type pt on pt.oid = castsource
join pg_type pt1 on pt1.oid = casttarget
and pc.castfunc > 0 and pt.typarray <> 0
and pt.typnamespace = 'pg_catalog'::regnamespace
and pt1.typnamespace = 'pg_catalog'::regnamespace
and (pt.typcategory = 'G' or pt1.typcategory = 'G')
order by castsource::regtype, casttarget::regtype;
castsource | casttarget | prosrc
------------+------------+---------------
point | box | point_box
lseg | point | lseg_center
path | polygon | path_poly
box | point | box_center
box | lseg | box_diagonal
box | polygon | box_poly
box | circle | box_circle
polygon | point | poly_center
polygon | path | poly_path
polygon | box | poly_box
polygon | circle | poly_circle
circle | point | circle_center
circle | box | circle_box
circle | polygon |
(14 rows)
already error safe: point_box, box_diagonal, box_poly, poly_path, poly_box, circle_center
almost error safe: path_poly
can not error safe: cast circle to polygon, because it's a SQL function
discussion: https://postgr.es/m/CACJufxHCMzrHOW=wRe8L30rMhB3sjwAv1LE928Fa7sxMu1Tx-g@mail.gmail.com
---
src/backend/access/spgist/spgproc.c | 2 +-
src/backend/utils/adt/float.c | 8 +-
src/backend/utils/adt/geo_ops.c | 238 ++++++++++++++++++++++++----
src/backend/utils/adt/geo_spgist.c | 2 +-
src/include/utils/float.h | 107 +++++++++++++
src/include/utils/geo_decls.h | 4 +-
6 files changed, 323 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/spgist/spgproc.c b/src/backend/access/spgist/spgproc.c
index 660009291da..b3e0fbc59ba 100644
--- a/src/backend/access/spgist/spgproc.c
+++ b/src/backend/access/spgist/spgproc.c
@@ -51,7 +51,7 @@ point_box_distance(Point *point, BOX *box)
else
dy = 0.0;
- return HYPOT(dx, dy);
+ return HYPOT(dx, dy, NULL);
}
/*
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index 6747544b679..06b2b6ae295 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -772,7 +772,7 @@ float8pl(PG_FUNCTION_ARGS)
float8 arg1 = PG_GETARG_FLOAT8(0);
float8 arg2 = PG_GETARG_FLOAT8(1);
- PG_RETURN_FLOAT8(float8_pl(arg1, arg2));
+ PG_RETURN_FLOAT8(float8_pl_safe(arg1, arg2, fcinfo->context));
}
Datum
@@ -781,7 +781,7 @@ float8mi(PG_FUNCTION_ARGS)
float8 arg1 = PG_GETARG_FLOAT8(0);
float8 arg2 = PG_GETARG_FLOAT8(1);
- PG_RETURN_FLOAT8(float8_mi(arg1, arg2));
+ PG_RETURN_FLOAT8(float8_mi_safe(arg1, arg2, fcinfo->context));
}
Datum
@@ -790,7 +790,7 @@ float8mul(PG_FUNCTION_ARGS)
float8 arg1 = PG_GETARG_FLOAT8(0);
float8 arg2 = PG_GETARG_FLOAT8(1);
- PG_RETURN_FLOAT8(float8_mul(arg1, arg2));
+ PG_RETURN_FLOAT8(float8_mul_safe(arg1, arg2, fcinfo->context));
}
Datum
@@ -799,7 +799,7 @@ float8div(PG_FUNCTION_ARGS)
float8 arg1 = PG_GETARG_FLOAT8(0);
float8 arg2 = PG_GETARG_FLOAT8(1);
- PG_RETURN_FLOAT8(float8_div(arg1, arg2));
+ PG_RETURN_FLOAT8(float8_div_safe(arg1, arg2, fcinfo->context));
}
diff --git a/src/backend/utils/adt/geo_ops.c b/src/backend/utils/adt/geo_ops.c
index 377a1b3f3ad..bd031805858 100644
--- a/src/backend/utils/adt/geo_ops.c
+++ b/src/backend/utils/adt/geo_ops.c
@@ -78,11 +78,14 @@ enum path_delim
/* Routines for points */
static inline void point_construct(Point *result, float8 x, float8 y);
static inline void point_add_point(Point *result, Point *pt1, Point *pt2);
+static inline bool point_add_point_safe(Point *result, Point *pt1, Point *pt2,
+ Node *escontext);
static inline void point_sub_point(Point *result, Point *pt1, Point *pt2);
static inline void point_mul_point(Point *result, Point *pt1, Point *pt2);
static inline void point_div_point(Point *result, Point *pt1, Point *pt2);
static inline bool point_eq_point(Point *pt1, Point *pt2);
static inline float8 point_dt(Point *pt1, Point *pt2);
+static inline bool point_dt_safe(Point *pt1, Point *pt2, Node *escontext, float8 *result);
static inline float8 point_sl(Point *pt1, Point *pt2);
static int point_inside(Point *p, int npts, Point *plist);
@@ -109,6 +112,7 @@ static float8 lseg_closept_lseg(Point *result, LSEG *on_lseg, LSEG *to_lseg);
/* Routines for boxes */
static inline void box_construct(BOX *result, Point *pt1, Point *pt2);
static void box_cn(Point *center, BOX *box);
+static bool box_cn_safe(Point *center, BOX *box, Node* escontext);
static bool box_ov(BOX *box1, BOX *box2);
static float8 box_ar(BOX *box);
static float8 box_ht(BOX *box);
@@ -125,7 +129,7 @@ static float8 circle_ar(CIRCLE *circle);
/* Routines for polygons */
static void make_bound_box(POLYGON *poly);
-static void poly_to_circle(CIRCLE *result, POLYGON *poly);
+static bool poly_to_circle_safe(CIRCLE *result, POLYGON *poly, Node *escontext);
static bool lseg_inside_poly(Point *a, Point *b, POLYGON *poly, int start);
static bool poly_contain_poly(POLYGON *contains_poly, POLYGON *contained_poly);
static bool plist_same(int npts, Point *p1, Point *p2);
@@ -851,7 +855,8 @@ box_center(PG_FUNCTION_ARGS)
BOX *box = PG_GETARG_BOX_P(0);
Point *result = (Point *) palloc(sizeof(Point));
- box_cn(result, box);
+ if (!box_cn_safe(result, box, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_POINT_P(result);
}
@@ -875,6 +880,31 @@ box_cn(Point *center, BOX *box)
center->y = float8_div(float8_pl(box->high.y, box->low.y), 2.0);
}
+/* safe version of box_cn */
+static bool
+box_cn_safe(Point *center, BOX *box, Node* escontext)
+{
+ float8 x;
+ float8 y;
+
+ x = float8_pl_safe(box->high.x, box->low.x, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ center->x = float8_div_safe(x, 2.0, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ y = float8_pl_safe(box->high.y, box->low.y, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ center->y = float8_div_safe(y, 2.0, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ return true;
+}
/* box_wd - returns the width (length) of the box
* (horizontal magnitude).
@@ -1276,7 +1306,7 @@ line_distance(PG_FUNCTION_ARGS)
PG_RETURN_FLOAT8(float8_div(fabs(float8_mi(l1->C,
float8_mul(ratio, l2->C))),
- HYPOT(l1->A, l1->B)));
+ HYPOT(l1->A, l1->B, NULL)));
}
/* line_interpt()
@@ -2001,9 +2031,34 @@ point_distance(PG_FUNCTION_ARGS)
static inline float8
point_dt(Point *pt1, Point *pt2)
{
- return HYPOT(float8_mi(pt1->x, pt2->x), float8_mi(pt1->y, pt2->y));
+ return HYPOT(float8_mi(pt1->x, pt2->x), float8_mi(pt1->y, pt2->y), NULL);
}
+/* errror safe version of point_dt */
+static inline bool
+point_dt_safe(Point *pt1, Point *pt2, Node *escontext, float8 *result)
+{
+ float8 x;
+ float8 y;
+
+ Assert(result != NULL);
+
+ x = float8_mi_safe(pt1->x, pt2->x, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ y = float8_mi_safe(pt1->y, pt2->y, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ *result = HYPOT(x, y, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ return true;
+}
+
+
Datum
point_slope(PG_FUNCTION_ARGS)
{
@@ -2317,13 +2372,31 @@ lseg_center(PG_FUNCTION_ARGS)
{
LSEG *lseg = PG_GETARG_LSEG_P(0);
Point *result;
+ float8 x;
+ float8 y;
result = (Point *) palloc(sizeof(Point));
- result->x = float8_div(float8_pl(lseg->p[0].x, lseg->p[1].x), 2.0);
- result->y = float8_div(float8_pl(lseg->p[0].y, lseg->p[1].y), 2.0);
+ x = float8_pl_safe(lseg->p[0].x, lseg->p[1].x, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ result->x = float8_div_safe(x, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ y = float8_pl_safe(lseg->p[0].y, lseg->p[1].y, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ result->y = float8_div_safe(y, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
PG_RETURN_POINT_P(result);
+
+fail:
+ PG_RETURN_NULL();
}
@@ -4115,6 +4188,27 @@ point_add_point(Point *result, Point *pt1, Point *pt2)
float8_pl(pt1->y, pt2->y));
}
+/* error safe version of point_add_point */
+static inline bool
+point_add_point_safe(Point *result, Point *pt1, Point *pt2,
+ Node *escontext)
+{
+ float8 x;
+ float8 y;
+
+ x = float8_pl_safe(pt1->x, pt2->x, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ y = float8_pl_safe(pt1->y, pt2->y, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ point_construct(result, x, y);
+
+ return true;
+}
+
Datum
point_add(PG_FUNCTION_ARGS)
{
@@ -4458,10 +4552,14 @@ path_poly(PG_FUNCTION_ARGS)
/* This is not very consistent --- other similar cases return NULL ... */
if (!path->closed)
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("open path cannot be converted to polygon")));
+ PG_RETURN_NULL();
+ }
+
/*
* Never overflows: the old size fit in MaxAllocSize, and the new size is
* just a small constant larger.
@@ -4508,7 +4606,9 @@ poly_center(PG_FUNCTION_ARGS)
result = (Point *) palloc(sizeof(Point));
- poly_to_circle(&circle, poly);
+ if (!poly_to_circle_safe(&circle, poly, fcinfo->context))
+ PG_RETURN_NULL();
+
*result = circle.center;
PG_RETURN_POINT_P(result);
@@ -5005,7 +5105,7 @@ circle_mul_pt(PG_FUNCTION_ARGS)
result = (CIRCLE *) palloc(sizeof(CIRCLE));
point_mul_point(&result->center, &circle->center, point);
- result->radius = float8_mul(circle->radius, HYPOT(point->x, point->y));
+ result->radius = float8_mul(circle->radius, HYPOT(point->x, point->y, NULL));
PG_RETURN_CIRCLE_P(result);
}
@@ -5020,7 +5120,7 @@ circle_div_pt(PG_FUNCTION_ARGS)
result = (CIRCLE *) palloc(sizeof(CIRCLE));
point_div_point(&result->center, &circle->center, point);
- result->radius = float8_div(circle->radius, HYPOT(point->x, point->y));
+ result->radius = float8_div(circle->radius, HYPOT(point->x, point->y, NULL));
PG_RETURN_CIRCLE_P(result);
}
@@ -5191,14 +5291,30 @@ circle_box(PG_FUNCTION_ARGS)
box = (BOX *) palloc(sizeof(BOX));
- delta = float8_div(circle->radius, sqrt(2.0));
+ delta = float8_div_safe(circle->radius, sqrt(2.0), fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
- box->high.x = float8_pl(circle->center.x, delta);
- box->low.x = float8_mi(circle->center.x, delta);
- box->high.y = float8_pl(circle->center.y, delta);
- box->low.y = float8_mi(circle->center.y, delta);
+ box->high.x = float8_pl_safe(circle->center.x, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ box->low.x = float8_mi_safe(circle->center.x, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ box->high.y = float8_pl_safe(circle->center.y, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ box->low.y = float8_mi_safe(circle->center.y, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
PG_RETURN_BOX_P(box);
+
+fail:
+ PG_RETURN_NULL();
}
/* box_circle()
@@ -5209,15 +5325,35 @@ box_circle(PG_FUNCTION_ARGS)
{
BOX *box = PG_GETARG_BOX_P(0);
CIRCLE *circle;
+ float8 x;
+ float8 y;
circle = (CIRCLE *) palloc(sizeof(CIRCLE));
- circle->center.x = float8_div(float8_pl(box->high.x, box->low.x), 2.0);
- circle->center.y = float8_div(float8_pl(box->high.y, box->low.y), 2.0);
+ x = float8_pl_safe(box->high.x, box->low.x, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
- circle->radius = point_dt(&circle->center, &box->high);
+ circle->center.x = float8_div_safe(x, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ y = float8_pl_safe(box->high.y, box->low.y, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ circle->center.y = float8_div_safe(y, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ if (!point_dt_safe(&circle->center, &box->high, fcinfo->context,
+ &circle->radius))
+ goto fail;
PG_RETURN_CIRCLE_P(circle);
+
+fail:
+ PG_RETURN_NULL();
}
@@ -5281,10 +5417,11 @@ circle_poly(PG_FUNCTION_ARGS)
* XXX This algorithm should use weighted means of line segments
* rather than straight average values of points - tgl 97/01/21.
*/
-static void
-poly_to_circle(CIRCLE *result, POLYGON *poly)
+static bool
+poly_to_circle_safe(CIRCLE *result, POLYGON *poly, Node *escontext)
{
int i;
+ float8 x;
Assert(poly->npts > 0);
@@ -5293,14 +5430,41 @@ poly_to_circle(CIRCLE *result, POLYGON *poly)
result->radius = 0;
for (i = 0; i < poly->npts; i++)
- point_add_point(&result->center, &result->center, &poly->p[i]);
- result->center.x = float8_div(result->center.x, poly->npts);
- result->center.y = float8_div(result->center.y, poly->npts);
+ {
+ if (!point_add_point_safe(&result->center,
+ &result->center,
+ &poly->p[i],
+ escontext))
+ return false;
+ }
+
+ result->center.x = float8_div_safe(result->center.x,
+ poly->npts,
+ escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ result->center.y = float8_div_safe(result->center.y,
+ poly->npts,
+ escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
for (i = 0; i < poly->npts; i++)
- result->radius = float8_pl(result->radius,
- point_dt(&poly->p[i], &result->center));
- result->radius = float8_div(result->radius, poly->npts);
+ {
+ if (!point_dt_safe(&poly->p[i], &result->center, escontext, &x))
+ return false;
+
+ result->radius = float8_pl_safe(result->radius, x, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+ }
+
+ result->radius = float8_div_safe(result->radius, poly->npts, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ return true;
}
Datum
@@ -5311,7 +5475,8 @@ poly_circle(PG_FUNCTION_ARGS)
result = (CIRCLE *) palloc(sizeof(CIRCLE));
- poly_to_circle(result, poly);
+ if (!poly_to_circle_safe(result, poly, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_CIRCLE_P(result);
}
@@ -5516,7 +5681,7 @@ plist_same(int npts, Point *p1, Point *p2)
*-----------------------------------------------------------------------
*/
float8
-pg_hypot(float8 x, float8 y)
+pg_hypot(float8 x, float8 y, Node *escontext)
{
float8 yx,
result;
@@ -5554,9 +5719,22 @@ pg_hypot(float8 x, float8 y)
result = x * sqrt(1.0 + (yx * yx));
if (unlikely(isinf(result)))
- float_overflow_error();
+ {
+ errsave(escontext,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: overflow"));
+
+ result = 0.0;
+ }
+
if (unlikely(result == 0.0))
- float_underflow_error();
+ {
+ errsave(escontext,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: underflow"));
+
+ result = 0.0;
+ }
return result;
}
diff --git a/src/backend/utils/adt/geo_spgist.c b/src/backend/utils/adt/geo_spgist.c
index fec33e95372..ffffd3cd2de 100644
--- a/src/backend/utils/adt/geo_spgist.c
+++ b/src/backend/utils/adt/geo_spgist.c
@@ -390,7 +390,7 @@ pointToRectBoxDistance(Point *point, RectBox *rect_box)
else
dy = 0;
- return HYPOT(dx, dy);
+ return HYPOT(dx, dy, NULL);
}
diff --git a/src/include/utils/float.h b/src/include/utils/float.h
index fc2a9cf6475..5a7033f2a60 100644
--- a/src/include/utils/float.h
+++ b/src/include/utils/float.h
@@ -109,6 +109,24 @@ float4_pl(const float4 val1, const float4 val2)
return result;
}
+/* error safe version of float8_pl */
+static inline float8
+float8_pl_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+ float8 result;
+
+ result = val1 + val2;
+ if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
+ {
+ errsave(escontext,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: overflow"));
+ result = 0.0f;
+ }
+
+ return result;
+}
+
static inline float8
float8_pl(const float8 val1, const float8 val2)
{
@@ -133,6 +151,24 @@ float4_mi(const float4 val1, const float4 val2)
return result;
}
+/* error safe version of float8_mi */
+static inline float8
+float8_mi_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+ float8 result;
+
+ result = val1 - val2;
+ if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
+ {
+ errsave(escontext,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: overflow"));
+ result = 0.0f;
+ }
+
+ return result;
+}
+
static inline float8
float8_mi(const float8 val1, const float8 val2)
{
@@ -159,6 +195,36 @@ float4_mul(const float4 val1, const float4 val2)
return result;
}
+/* error safe version of float8_mul */
+static inline float8
+float8_mul_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+ float8 result;
+
+ result = val1 * val2;
+ if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
+ {
+ errsave(escontext,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: overflow"));
+
+ result = 0.0;
+ return result;
+ }
+
+ if (unlikely(result == 0.0) && val1 != 0.0 && val2 != 0.0)
+ {
+ errsave(escontext,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: underflow"));
+
+ result = 0.0;
+ return result;
+ }
+
+ return result;
+}
+
static inline float8
float8_mul(const float8 val1, const float8 val2)
{
@@ -189,6 +255,47 @@ float4_div(const float4 val1, const float4 val2)
return result;
}
+/* error safe version of float8_div */
+static inline float8
+float8_div_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+ float8 result;
+
+ if (unlikely(val2 == 0.0) && !isnan(val1))
+ {
+ errsave(escontext,
+ errcode(ERRCODE_DIVISION_BY_ZERO),
+ errmsg("division by zero"));
+
+ result = 0.0;
+ return result;
+ }
+
+ result = val1 / val2;
+
+ if (unlikely(isinf(result)) && !isinf(val1))
+ {
+ errsave(escontext,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: overflow"));
+
+ result = 0.0;
+ return result;
+ }
+
+ if (unlikely(result == 0.0) && val1 != 0.0 && !isinf(val2))
+ {
+ errsave(escontext,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: underflow"));
+
+ result = 0.0;
+ return result;
+ }
+
+ return result;
+}
+
static inline float8
float8_div(const float8 val1, const float8 val2)
{
diff --git a/src/include/utils/geo_decls.h b/src/include/utils/geo_decls.h
index 8a9df75c93c..0056a26639f 100644
--- a/src/include/utils/geo_decls.h
+++ b/src/include/utils/geo_decls.h
@@ -88,7 +88,7 @@ FPge(double A, double B)
#define FPge(A,B) ((A) >= (B))
#endif
-#define HYPOT(A, B) pg_hypot(A, B)
+#define HYPOT(A, B, escontext) pg_hypot(A, B, escontext)
/*---------------------------------------------------------------------
* Point - (x,y)
@@ -280,6 +280,6 @@ CirclePGetDatum(const CIRCLE *X)
* in geo_ops.c
*/
-extern float8 pg_hypot(float8 x, float8 y);
+extern float8 pg_hypot(float8 x, float8 y, Node *escontext);
#endif /* GEO_DECLS_H */
--
2.34.1
v9-0014-error-safe-for-casting-interval-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v9-0014-error-safe-for-casting-interval-to-other-types-per-pg_cast.patchDownload
From efe7ec0bc44e16399f80a0fa457c168f3a109368 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 18:43:29 +0800
Subject: [PATCH v9 14/19] error safe for casting interval to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'interval'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------------+----------+-------------+------------+----------------+----------
interval | time without time zone | 1419 | a | f | interval_time | time
interval | interval | 1200 | i | f | interval_scale | interval
(2 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 2 +-
src/backend/utils/adt/timestamp.c | 3 ++-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index c7a3cde2d81..4f0f3d26989 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2180,7 +2180,7 @@ interval_time(PG_FUNCTION_ARGS)
TimeADT result;
if (INTERVAL_NOT_FINITE(span))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("cannot convert infinite interval to time")));
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 156a4830ffd..7b565cc6d66 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1334,7 +1334,8 @@ interval_scale(PG_FUNCTION_ARGS)
result = palloc(sizeof(Interval));
*result = *interval;
- AdjustIntervalForTypmod(result, typmod, NULL);
+ if (!AdjustIntervalForTypmod(result, typmod, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_INTERVAL_P(result);
}
--
2.34.1
v9-0012-error-safe-for-casting-float8-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v9-0012-error-safe-for-casting-float8-to-other-types-per-pg_cast.patchDownload
From 65929bff29519aee2935a16a393bba4df7c74d50 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 18:41:55 +0800
Subject: [PATCH v9 12/19] error safe for casting float8 to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'float8'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------------+------------+----------+-------------+------------+----------------+---------
double precision | bigint | 483 | a | f | dtoi8 | int8
double precision | smallint | 237 | a | f | dtoi2 | int2
double precision | integer | 317 | a | f | dtoi4 | int4
double precision | real | 312 | a | f | dtof | float4
double precision | numeric | 1743 | a | f | float8_numeric | numeric
(5 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/float.c | 29 +++++++++++++++++++++++++----
src/backend/utils/adt/int8.c | 6 +++++-
src/backend/utils/adt/numeric.c | 3 ++-
3 files changed, 32 insertions(+), 6 deletions(-)
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index dd8a2ff378b..6747544b679 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -1199,9 +1199,22 @@ dtof(PG_FUNCTION_ARGS)
result = (float4) num;
if (unlikely(isinf(result)) && !isinf(num))
- float_overflow_error();
+ {
+ errsave(fcinfo->context,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: overflow"));
+
+ PG_RETURN_NULL();
+ }
+
if (unlikely(result == 0.0f) && num != 0.0)
- float_underflow_error();
+ {
+ errsave(fcinfo->context,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: underflow"));
+
+ PG_RETURN_NULL();
+ }
PG_RETURN_FLOAT4(result);
}
@@ -1224,10 +1237,14 @@ dtoi4(PG_FUNCTION_ARGS)
/* Range check */
if (unlikely(isnan(num) || !FLOAT8_FITS_IN_INT32(num)))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_INT32((int32) num);
}
@@ -1249,10 +1266,14 @@ dtoi2(PG_FUNCTION_ARGS)
/* Range check */
if (unlikely(isnan(num) || !FLOAT8_FITS_IN_INT16(num)))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_INT16((int16) num);
}
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index bcdb020a91c..437dbbccd4a 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -1268,10 +1268,14 @@ dtoi8(PG_FUNCTION_ARGS)
/* Range check */
if (unlikely(isnan(num) || !FLOAT8_FITS_IN_INT64(num)))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("bigint out of range")));
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_INT64((int64) num);
}
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 8839b095f60..76cd9800c2a 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -4570,7 +4570,8 @@ float8_numeric(PG_FUNCTION_ARGS)
init_var(&result);
/* Assume we need not worry about leading/trailing spaces */
- (void) set_var_from_str(buf, buf, &result, &endptr, NULL);
+ if (!set_var_from_str(buf, buf, &result, &endptr, fcinfo->context))
+ PG_RETURN_NULL();
res = make_result(&result);
--
2.34.1
v9-0013-error-safe-for-casting-date-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v9-0013-error-safe-for-casting-date-to-other-types-per-pg_cast.patchDownload
From 52145f4ed22021d5eab95886b246076425bd0382 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 18:42:50 +0800
Subject: [PATCH v9 13/19] error safe for casting date to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'date'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+-----------------------------+----------+-------------+------------+------------------+-------------
date | timestamp without time zone | 2024 | i | f | date_timestamp | timestamp
date | timestamp with time zone | 1174 | i | f | date_timestamptz | timestamptz
(2 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 22 ++++++++++++++++++++--
1 file changed, 20 insertions(+), 2 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 344f58b92f7..c7a3cde2d81 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -1350,7 +1350,16 @@ date_timestamp(PG_FUNCTION_ARGS)
DateADT dateVal = PG_GETARG_DATEADT(0);
Timestamp result;
- result = date2timestamp(dateVal);
+ if (likely(!fcinfo->context))
+ result = date2timestamp(dateVal);
+ else
+ {
+ int overflow;
+
+ result = date2timestamp_opt_overflow(dateVal, &overflow);
+ if (overflow != 0)
+ PG_RETURN_NULL();
+ }
PG_RETURN_TIMESTAMP(result);
}
@@ -1435,7 +1444,16 @@ date_timestamptz(PG_FUNCTION_ARGS)
DateADT dateVal = PG_GETARG_DATEADT(0);
TimestampTz result;
- result = date2timestamptz(dateVal);
+ if (likely(!fcinfo->context))
+ result = date2timestamptz(dateVal);
+ else
+ {
+ int overflow;
+ result = date2timestamptz_opt_overflow(dateVal, &overflow);
+
+ if (overflow != 0)
+ PG_RETURN_NULL();
+ }
PG_RETURN_TIMESTAMP(result);
}
--
2.34.1
v9-0011-error-safe-for-casting-float4-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v9-0011-error-safe-for-casting-float4-to-other-types-per-pg_cast.patchDownload
From dbb44df103a821bf9769549447ee98313803a1f6 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 18:33:57 +0800
Subject: [PATCH v9 11/19] error safe for casting float4 to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'float4'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+----------------+---------
real | bigint | 653 | a | f | ftoi8 | int8
real | smallint | 238 | a | f | ftoi2 | int2
real | integer | 319 | a | f | ftoi4 | int4
real | double precision | 311 | i | f | ftod | float8
real | numeric | 1742 | a | f | float4_numeric | numeric
(5 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/float.c | 12 ++++++++++--
src/backend/utils/adt/int8.c | 6 +++++-
src/backend/utils/adt/numeric.c | 3 ++-
3 files changed, 17 insertions(+), 4 deletions(-)
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index 7b97d2be6ca..dd8a2ff378b 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -1298,10 +1298,14 @@ ftoi4(PG_FUNCTION_ARGS)
/* Range check */
if (unlikely(isnan(num) || !FLOAT4_FITS_IN_INT32(num)))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_INT32((int32) num);
}
@@ -1323,10 +1327,14 @@ ftoi2(PG_FUNCTION_ARGS)
/* Range check */
if (unlikely(isnan(num) || !FLOAT4_FITS_IN_INT16(num)))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_INT16((int16) num);
}
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 3b2d100be92..bcdb020a91c 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -1303,10 +1303,14 @@ ftoi8(PG_FUNCTION_ARGS)
/* Range check */
if (unlikely(isnan(num) || !FLOAT4_FITS_IN_INT64(num)))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("bigint out of range")));
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_INT64((int64) num);
}
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index ce5f71109e7..8839b095f60 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -4668,7 +4668,8 @@ float4_numeric(PG_FUNCTION_ARGS)
init_var(&result);
/* Assume we need not worry about leading/trailing spaces */
- (void) set_var_from_str(buf, buf, &result, &endptr, NULL);
+ if (!set_var_from_str(buf, buf, &result, &endptr, fcinfo->context))
+ PG_RETURN_NULL();
res = make_result(&result);
--
2.34.1
v9-0010-error-safe-for-casting-numeric-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v9-0010-error-safe-for-casting-numeric-to-other-types-per-pg_cast.patchDownload
From 8229b22d674b51e4e42c9de0abb59ba123857595 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 21:57:31 +0800
Subject: [PATCH v9 10/19] error safe for casting numeric to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'numeric'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+----------------+---------
numeric | bigint | 1779 | a | f | numeric_int8 | int8
numeric | smallint | 1783 | a | f | numeric_int2 | int2
numeric | integer | 1744 | a | f | numeric_int4 | int4
numeric | real | 1745 | i | f | numeric_float4 | float4
numeric | double precision | 1746 | i | f | numeric_float8 | float8
numeric | money | 3824 | a | f | numeric_cash | money
numeric | numeric | 1703 | i | f | numeric | numeric
(7 rows)
discussion: https://postgr.es/m/CACJufxHCMzrHOW=wRe8L30rMhB3sjwAv1LE928Fa7sxMu1Tx-g@mail.gmail.com
---
src/backend/utils/adt/numeric.c | 68 +++++++++++++++++++++++++--------
1 file changed, 53 insertions(+), 15 deletions(-)
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 2501007d981..ce5f71109e7 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -1244,7 +1244,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));
}
@@ -1295,8 +1296,9 @@ numeric (PG_FUNCTION_ARGS)
init_var(&var);
set_var_from_num(num, &var);
- (void) apply_typmod(&var, typmod, NULL);
- new = make_result(&var);
+ if (!apply_typmod(&var, typmod, fcinfo->context))
+ PG_RETURN_NULL();
+ new = make_result_safe(&var, fcinfo->context);
free_var(&var);
@@ -3019,7 +3021,10 @@ numeric_mul(PG_FUNCTION_ARGS)
Numeric num2 = PG_GETARG_NUMERIC(1);
Numeric res;
- res = numeric_mul_safe(num1, num2, NULL);
+ res = numeric_mul_safe(num1, num2, fcinfo->context);
+
+ if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
PG_RETURN_NUMERIC(res);
}
@@ -4393,9 +4398,15 @@ numeric_int4_safe(Numeric num, Node *escontext)
Datum
numeric_int4(PG_FUNCTION_ARGS)
{
+ int32 result;
Numeric num = PG_GETARG_NUMERIC(0);
- PG_RETURN_INT32(numeric_int4_safe(num, NULL));
+ result = numeric_int4_safe(num, fcinfo->context);
+
+ if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
+
+ PG_RETURN_INT32(result);
}
/*
@@ -4463,9 +4474,15 @@ numeric_int8_safe(Numeric num, Node *escontext)
Datum
numeric_int8(PG_FUNCTION_ARGS)
{
+ int64 result;
Numeric num = PG_GETARG_NUMERIC(0);
- PG_RETURN_INT64(numeric_int8_safe(num, NULL));
+ result = numeric_int8_safe(num, fcinfo->context);
+
+ if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
+
+ PG_RETURN_INT64(result);
}
@@ -4489,28 +4506,38 @@ numeric_int2(PG_FUNCTION_ARGS)
if (NUMERIC_IS_SPECIAL(num))
{
if (NUMERIC_IS_NAN(num))
- ereport(ERROR,
+ errsave(fcinfo->context,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert NaN to %s", "smallint")));
else
- ereport(ERROR,
+ errsave(fcinfo->context,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert infinity to %s", "smallint")));
+
+ PG_RETURN_NULL();
}
/* Convert to variable format and thence to int8 */
init_var_from_num(num, &x);
if (!numericvar_to_int64(&x, &val))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
+ PG_RETURN_NULL();
+ }
+
if (unlikely(val < PG_INT16_MIN) || unlikely(val > PG_INT16_MAX))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
+ PG_RETURN_NULL();
+ }
+
/* Down-convert to int2 */
result = (int16) val;
@@ -4572,10 +4599,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);
}
@@ -4667,7 +4698,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
v9-0009-error-safe-for-casting-bigint-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v9-0009-error-safe-for-casting-bigint-to-other-types-per-pg_cast.patchDownload
From 325a86c72c4535614aa91f74d8cda542e0b6c3be Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 16:38:37 +0800
Subject: [PATCH v9 09/19] error safe for casting bigint to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'bigint'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+--------------+---------
bigint | smallint | 714 | a | f | int82 | int2
bigint | integer | 480 | a | f | int84 | int4
bigint | real | 652 | i | f | i8tof | float4
bigint | double precision | 482 | i | f | i8tod | float8
bigint | numeric | 1781 | i | f | int8_numeric | numeric
bigint | money | 3812 | a | f | int8_cash | money
bigint | oid | 1287 | i | f | i8tooid | oid
bigint | regproc | 1287 | i | f | i8tooid | oid
bigint | regprocedure | 1287 | i | f | i8tooid | oid
bigint | regoper | 1287 | i | f | i8tooid | oid
bigint | regoperator | 1287 | i | f | i8tooid | oid
bigint | regclass | 1287 | i | f | i8tooid | oid
bigint | regcollation | 1287 | i | f | i8tooid | oid
bigint | regtype | 1287 | i | f | i8tooid | oid
bigint | regconfig | 1287 | i | f | i8tooid | oid
bigint | regdictionary | 1287 | i | f | i8tooid | oid
bigint | regrole | 1287 | i | f | i8tooid | oid
bigint | regnamespace | 1287 | i | f | i8tooid | oid
bigint | regdatabase | 1287 | i | f | i8tooid | oid
bigint | bytea | 6369 | e | f | int8_bytea | bytea
bigint | bit | 2075 | e | f | bitfromint8 | bit
(21 rows)
already error safe: i8tof, i8tod, int8_numeric, int8_bytea, bitfromint8
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/int8.c | 18 +++++++++++++++---
1 file changed, 15 insertions(+), 3 deletions(-)
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index bdea490202a..3b2d100be92 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -1204,10 +1204,14 @@ int84(PG_FUNCTION_ARGS)
int64 arg = PG_GETARG_INT64(0);
if (unlikely(arg < PG_INT32_MIN) || unlikely(arg > PG_INT32_MAX))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_INT32((int32) arg);
}
@@ -1225,10 +1229,14 @@ int82(PG_FUNCTION_ARGS)
int64 arg = PG_GETARG_INT64(0);
if (unlikely(arg < PG_INT16_MIN) || unlikely(arg > PG_INT16_MAX))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_INT16((int16) arg);
}
@@ -1308,10 +1316,14 @@ i8tooid(PG_FUNCTION_ARGS)
int64 arg = PG_GETARG_INT64(0);
if (unlikely(arg < 0) || unlikely(arg > PG_UINT32_MAX))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("OID out of range")));
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_OID((Oid) arg);
}
--
2.34.1
v9-0008-error-safe-for-casting-integer-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v9-0008-error-safe-for-casting-integer-to-other-types-per-pg_cast.patchDownload
From 6db7ba9916baf975d2a1c895cbdb89ac918bbc8e Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 16:04:07 +0800
Subject: [PATCH v9 08/19] error safe for casting integer to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'integer'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+--------------+---------
integer | bigint | 481 | i | f | int48 | int8
integer | smallint | 314 | a | f | i4toi2 | int2
integer | real | 318 | i | f | i4tof | float4
integer | double precision | 316 | i | f | i4tod | float8
integer | numeric | 1740 | i | f | int4_numeric | numeric
integer | money | 3811 | a | f | int4_cash | money
integer | boolean | 2557 | e | f | int4_bool | bool
integer | bytea | 6368 | e | f | int4_bytea | bytea
integer | "char" | 78 | e | f | i4tochar | char
integer | bit | 1683 | e | f | bitfromint4 | bit
(10 rows)
only int4_cash, i4toi2, i4tochar need take care of error handling. but support
for cash data type is not easy, so only i4toi2, i4tochar function refactoring.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/char.c | 6 +++++-
src/backend/utils/adt/int.c | 5 ++++-
2 files changed, 9 insertions(+), 2 deletions(-)
diff --git a/src/backend/utils/adt/char.c b/src/backend/utils/adt/char.c
index 22dbfc950b1..7cf09d954e7 100644
--- a/src/backend/utils/adt/char.c
+++ b/src/backend/utils/adt/char.c
@@ -192,10 +192,14 @@ i4tochar(PG_FUNCTION_ARGS)
int32 arg1 = PG_GETARG_INT32(0);
if (arg1 < SCHAR_MIN || arg1 > SCHAR_MAX)
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("\"char\" out of range")));
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_CHAR((int8) arg1);
}
diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c
index b5781989a64..4d3ce23a4af 100644
--- a/src/backend/utils/adt/int.c
+++ b/src/backend/utils/adt/int.c
@@ -350,10 +350,13 @@ i4toi2(PG_FUNCTION_ARGS)
int32 arg1 = PG_GETARG_INT32(0);
if (unlikely(arg1 < SHRT_MIN) || unlikely(arg1 > SHRT_MAX))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
+ PG_RETURN_NULL();
+ }
PG_RETURN_INT16((int16) arg1);
}
--
2.34.1
v9-0005-error-safe-for-casting-character-varying-to-other-types-per-pg_ca.patchtext/x-patch; charset=US-ASCII; name=v9-0005-error-safe-for-casting-character-varying-to-other-types-per-pg_ca.patchDownload
From aabf3932a2ae4a7b6646ab5de19b98522303dd9b Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 19:17:00 +0800
Subject: [PATCH v9 05/19] error safe for casting character varying to other
types per pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc and pc.castfunc > 0 and
(castsource::regtype = 'character varying'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
-------------------+-------------------+----------+-------------+------------+---------------+----------
character varying | regclass | 1079 | i | f | text_regclass | regclass
character varying | "char" | 944 | a | f | text_char | char
character varying | name | 1400 | i | f | text_name | name
character varying | xml | 2896 | e | f | texttoxml | xml
character varying | character varying | 669 | i | f | varchar | varchar
(5 rows)
texttoxml, text_regclass was refactored as error safe in prior patch.
text_char, text_name is already error safe.
so here we only need handle function "varchar".
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/varchar.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c
index a95e4bd094e..fdbef272c39 100644
--- a/src/backend/utils/adt/varchar.c
+++ b/src/backend/utils/adt/varchar.c
@@ -638,10 +638,14 @@ varchar(PG_FUNCTION_ARGS)
{
for (i = maxmblen; i < len; i++)
if (s_data[i] != ' ')
- ereport(ERROR,
+ {
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
errmsg("value too long for type character varying(%d)",
maxlen)));
+
+ PG_RETURN_NULL();
+ }
}
PG_RETURN_VARCHAR_P((VarChar *) cstring_to_text_with_len(s_data,
--
2.34.1
v9-0007-error-safe-for-casting-macaddr8-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v9-0007-error-safe-for-casting-macaddr8-to-other-types-per-pg_cast.patchDownload
From ce1908874819044361f16c8976e1d883232fc265 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 19:12:12 +0800
Subject: [PATCH v9 07/19] error safe for casting macaddr8 to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
and pc.castfunc > 0 and castsource::regtype ='macaddr8'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------+----------+-------------+------------+-------------------+---------
macaddr8 | macaddr | 4124 | i | f | macaddr8tomacaddr | macaddr
(1 row)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/mac8.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/backend/utils/adt/mac8.c b/src/backend/utils/adt/mac8.c
index 08e41ba4eea..e2a7a90e42c 100644
--- a/src/backend/utils/adt/mac8.c
+++ b/src/backend/utils/adt/mac8.c
@@ -550,7 +550,8 @@ macaddr8tomacaddr(PG_FUNCTION_ARGS)
result = (macaddr *) palloc0(sizeof(macaddr));
if ((addr->d != 0xFF) || (addr->e != 0xFE))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("macaddr8 data out of range to convert to macaddr"),
errhint("Only addresses that have FF and FE as values in the "
@@ -558,6 +559,9 @@ macaddr8tomacaddr(PG_FUNCTION_ARGS)
"xx:xx:xx:ff:fe:xx:xx:xx, are eligible to be converted "
"from macaddr8 to macaddr.")));
+ PG_RETURN_NULL();
+ }
+
result->a = addr->a;
result->b = addr->b;
result->c = addr->c;
--
2.34.1
v9-0006-error-safe-for-casting-inet-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v9-0006-error-safe-for-casting-inet-to-other-types-per-pg_cast.patchDownload
From 67c06609b4d565e8ebb35c855db04462d4536a50 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 15:55:55 +0800
Subject: [PATCH v9 06/19] error safe for casting inet to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'inet'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+-------------------+----------+-------------+------------+--------------+---------
inet | cidr | 1715 | a | f | inet_to_cidr | cidr
inet | text | 730 | a | f | network_show | text
inet | character varying | 730 | a | f | network_show | text
inet | character | 730 | a | f | network_show | text
(4 rows)
inet_to_cidr is already error safe.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/network.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/backend/utils/adt/network.c b/src/backend/utils/adt/network.c
index 3cb0ab6829a..f34228763da 100644
--- a/src/backend/utils/adt/network.c
+++ b/src/backend/utils/adt/network.c
@@ -1137,10 +1137,14 @@ network_show(PG_FUNCTION_ARGS)
if (pg_inet_net_ntop(ip_family(ip), ip_addr(ip), ip_maxbits(ip),
tmp, sizeof(tmp)) == NULL)
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
errmsg("could not format inet value: %m")));
+ PG_RETURN_NULL();
+ }
+
/* Add /n if not present (which it won't be) */
if (strchr(tmp, '/') == NULL)
{
--
2.34.1
v9-0004-error-safe-for-casting-text-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v9-0004-error-safe-for-casting-text-to-other-types-per-pg_cast.patchDownload
From 15886c5fa6b9ef57e155774f8179f541fed9843c Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 15:49:46 +0800
Subject: [PATCH v9 04/19] error safe for casting text to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'text'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------+----------+-------------+------------+---------------+----------
text | regclass | 1079 | i | f | text_regclass | regclass
text | "char" | 944 | a | f | text_char | char
text | name | 407 | i | f | text_name | name
text | xml | 2896 | e | f | texttoxml | xml
(4 rows)
already error safe: text_name, text_char.
texttoxml is refactored in character type error safe patch.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/catalog/namespace.c | 167 ++++++++++++++++++++++++++++++++
src/backend/utils/adt/regproc.c | 33 ++++++-
src/backend/utils/adt/varlena.c | 42 ++++++++
src/include/catalog/namespace.h | 6 ++
src/include/utils/varlena.h | 1 +
5 files changed, 246 insertions(+), 3 deletions(-)
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index d23474da4fb..a04d179c7fb 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -640,6 +640,137 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
return relId;
}
+/* safe version of RangeVarGetRelidExtended */
+Oid
+RangeVarGetRelidExtendedSafe(const RangeVar *relation, LOCKMODE lockmode,
+ uint32 flags, RangeVarGetRelidCallback callback, void *callback_arg,
+ Node *escontext)
+{
+ uint64 inval_count;
+ Oid relId;
+ Oid oldRelId = InvalidOid;
+ bool retry = false;
+ bool missing_ok = (flags & RVR_MISSING_OK) != 0;
+
+ /* verify that flags do no conflict */
+ Assert(!((flags & RVR_NOWAIT) && (flags & RVR_SKIP_LOCKED)));
+
+ /*
+ * We check the catalog name and then ignore it.
+ */
+ if (relation->catalogname)
+ {
+ if (strcmp(relation->catalogname, get_database_name(MyDatabaseId)) != 0)
+ ereturn(escontext, InvalidOid,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cross-database references are not implemented: \"%s.%s.%s\"",
+ relation->catalogname, relation->schemaname,
+ relation->relname));
+ }
+
+ for (;;)
+ {
+ inval_count = SharedInvalidMessageCounter;
+
+ if (relation->relpersistence == RELPERSISTENCE_TEMP)
+ {
+ if (!OidIsValid(myTempNamespace))
+ relId = InvalidOid;
+ else
+ {
+ if (relation->schemaname)
+ {
+ Oid namespaceId;
+
+ namespaceId = LookupExplicitNamespace(relation->schemaname, missing_ok);
+
+ /*
+ * For missing_ok, allow a non-existent schema name to
+ * return InvalidOid.
+ */
+ if (namespaceId != myTempNamespace)
+ ereturn(escontext, InvalidOid,
+ errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("temporary tables cannot specify a schema name"));
+ }
+
+ relId = get_relname_relid(relation->relname, myTempNamespace);
+ }
+ }
+ else if (relation->schemaname)
+ {
+ Oid namespaceId;
+
+ /* use exact schema given */
+ namespaceId = LookupExplicitNamespace(relation->schemaname, missing_ok);
+ if (missing_ok && !OidIsValid(namespaceId))
+ relId = InvalidOid;
+ else
+ relId = get_relname_relid(relation->relname, namespaceId);
+ }
+ else
+ {
+ /* search the namespace path */
+ relId = RelnameGetRelid(relation->relname);
+ }
+
+ if (callback)
+ callback(relation, relId, oldRelId, callback_arg);
+
+ if (lockmode == NoLock)
+ break;
+
+ if (retry)
+ {
+ if (relId == oldRelId)
+ break;
+ if (OidIsValid(oldRelId))
+ UnlockRelationOid(oldRelId, lockmode);
+ }
+
+ if (!OidIsValid(relId))
+ AcceptInvalidationMessages();
+ else if (!(flags & (RVR_NOWAIT | RVR_SKIP_LOCKED)))
+ LockRelationOid(relId, lockmode);
+ else if (!ConditionalLockRelationOid(relId, lockmode))
+ {
+ if (relation->schemaname)
+ ereport(DEBUG1,
+ errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s.%s\"",
+ relation->schemaname, relation->relname));
+ else
+ ereport(DEBUG1,
+ errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s\"",
+ relation->relname));
+
+ return InvalidOid;
+ }
+
+ if (inval_count == SharedInvalidMessageCounter)
+ break;
+
+ retry = true;
+ oldRelId = relId;
+ }
+
+ if (!OidIsValid(relId))
+ {
+ if (relation->schemaname)
+ ereturn(escontext, InvalidOid,
+ errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s.%s\" does not exist",
+ relation->schemaname, relation->relname));
+ else
+ ereturn(escontext, InvalidOid,
+ errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" does not exist",
+ relation->relname));
+ }
+ return relId;
+}
+
/*
* RangeVarGetCreationNamespace
* Given a RangeVar describing a to-be-created relation,
@@ -3650,6 +3781,42 @@ makeRangeVarFromNameList(const List *names)
return rel;
}
+/*
+ * makeRangeVarFromNameListSafe
+ * Utility routine to convert a qualified-name list into RangeVar form.
+ * The result maybe NULL.
+ */
+RangeVar *
+makeRangeVarFromNameListSafe(const List *names, Node *escontext)
+{
+ RangeVar *rel = makeRangeVar(NULL, NULL, -1);
+
+ switch (list_length(names))
+ {
+ case 1:
+ rel->relname = strVal(linitial(names));
+ break;
+ case 2:
+ rel->schemaname = strVal(linitial(names));
+ rel->relname = strVal(lsecond(names));
+ break;
+ case 3:
+ rel->catalogname = strVal(linitial(names));
+ rel->schemaname = strVal(lsecond(names));
+ rel->relname = strVal(lthird(names));
+ break;
+ default:
+ errsave(escontext,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("improper relation name (too many dotted names): %s",
+ NameListToString(names)));
+ rel = NULL;
+ break;
+ }
+
+ return rel;
+}
+
/*
* NameListToString
* Utility routine to convert a qualified-name list into a string.
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index e5c2246f2c9..059d0154335 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -1902,10 +1902,37 @@ text_regclass(PG_FUNCTION_ARGS)
Oid result;
RangeVar *rv;
- rv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
+ if (likely(!fcinfo->context))
+ {
+ rv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
- /* We might not even have permissions on this relation; don't lock it. */
- result = RangeVarGetRelid(rv, NoLock, false);
+ /*
+ * We might not even have permissions on this relation; don't lock it.
+ */
+ result = RangeVarGetRelid(rv, NoLock, false);
+ }
+ else
+ {
+ List *rvnames;
+
+ rvnames = textToQualifiedNameListSafe(relname, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
+
+ rv = makeRangeVarFromNameListSafe(rvnames, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
+
+ result = RangeVarGetRelidExtendedSafe(rv,
+ NoLock,
+ 0,
+ NULL,
+ NULL,
+ fcinfo->context);
+
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
+ }
PG_RETURN_OID(result);
}
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index 8d735786e51..5e2676f7180 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -2717,6 +2717,48 @@ textToQualifiedNameList(text *textval)
return result;
}
+/* error safe version of textToQualifiedNameList */
+List *
+textToQualifiedNameListSafe(text *textval, Node *escontext)
+{
+ char *rawname;
+ List *result = NIL;
+ List *namelist;
+ ListCell *l;
+
+ /* Convert to C string (handles possible detoasting). */
+ /* Note we rely on being able to modify rawname below. */
+ rawname = text_to_cstring(textval);
+
+ if (!SplitIdentifierString(rawname, '.', &namelist))
+ {
+ errsave(escontext,
+ errcode(ERRCODE_INVALID_NAME),
+ errmsg("invalid name syntax"));
+ return NIL;
+ }
+
+ if (namelist == NIL)
+ {
+ errsave(escontext,
+ errcode(ERRCODE_INVALID_NAME),
+ errmsg("invalid name syntax"));
+ return NIL;
+ }
+
+ foreach(l, namelist)
+ {
+ char *curname = (char *) lfirst(l);
+
+ result = lappend(result, makeString(pstrdup(curname)));
+ }
+
+ pfree(rawname);
+ list_free(namelist);
+
+ return result;
+}
+
/*
* SplitIdentifierString --- parse a string containing identifiers
*
diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h
index f1423f28c32..ab61af55ddc 100644
--- a/src/include/catalog/namespace.h
+++ b/src/include/catalog/namespace.h
@@ -103,6 +103,11 @@ extern Oid RangeVarGetRelidExtended(const RangeVar *relation,
LOCKMODE lockmode, uint32 flags,
RangeVarGetRelidCallback callback,
void *callback_arg);
+extern Oid RangeVarGetRelidExtendedSafe(const RangeVar *relation,
+ LOCKMODE lockmode, uint32 flags,
+ RangeVarGetRelidCallback callback,
+ void *callback_arg,
+ Node *escontext);
extern Oid RangeVarGetCreationNamespace(const RangeVar *newRelation);
extern Oid RangeVarGetAndCheckCreationNamespace(RangeVar *relation,
LOCKMODE lockmode,
@@ -168,6 +173,7 @@ extern Oid LookupCreationNamespace(const char *nspname);
extern void CheckSetNamespace(Oid oldNspOid, Oid nspOid);
extern Oid QualifiedNameGetCreationNamespace(const List *names, char **objname_p);
extern RangeVar *makeRangeVarFromNameList(const List *names);
+extern RangeVar *makeRangeVarFromNameListSafe(const List *names, Node *escontext);
extern char *NameListToString(const List *names);
extern char *NameListToQuotedString(const List *names);
diff --git a/src/include/utils/varlena.h b/src/include/utils/varlena.h
index db9fdf72941..0cf01ae5281 100644
--- a/src/include/utils/varlena.h
+++ b/src/include/utils/varlena.h
@@ -27,6 +27,7 @@ extern int varstr_levenshtein_less_equal(const char *source, int slen,
int ins_c, int del_c, int sub_c,
int max_d, bool trusted);
extern List *textToQualifiedNameList(text *textval);
+extern List *textToQualifiedNameListSafe(text *textval, Node *escontext);
extern bool SplitIdentifierString(char *rawstring, char separator,
List **namelist);
extern bool SplitDirectoriesString(char *rawstring, char separator,
--
2.34.1
v9-0002-error-safe-for-casting-bit-varbit-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v9-0002-error-safe-for-casting-bit-varbit-to-other-types-per-pg_cast.patchDownload
From 5f7fa88b9e44b7dc684a4989471c27d0017190a7 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 19:09:53 +0800
Subject: [PATCH v9 02/19] error safe for casting bit/varbit to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
where pc.castfunc > 0 and (castsource::regtype ='bit'::regtype or
castsource::regtype ='varbit'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
-------------+-------------+----------+-------------+------------+-----------+---------
bit | bigint | 2076 | e | f | bittoint8 | int8
bit | integer | 1684 | e | f | bittoint4 | int4
bit | bit | 1685 | i | f | bit | bit
bit varying | bit varying | 1687 | i | f | varbit | varbit
(4 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/varbit.c | 24 ++++++++++++++++++++----
1 file changed, 20 insertions(+), 4 deletions(-)
diff --git a/src/backend/utils/adt/varbit.c b/src/backend/utils/adt/varbit.c
index 205a67dafc5..bfcea18f4b2 100644
--- a/src/backend/utils/adt/varbit.c
+++ b/src/backend/utils/adt/varbit.c
@@ -401,11 +401,15 @@ bit(PG_FUNCTION_ARGS)
PG_RETURN_VARBIT_P(arg);
if (!isExplicit)
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_STRING_DATA_LENGTH_MISMATCH),
errmsg("bit string length %d does not match type bit(%d)",
VARBITLEN(arg), len)));
+ PG_RETURN_NULL();
+ }
+
rlen = VARBITTOTALLEN(len);
/* set to 0 so that string is zero-padded */
result = (VarBit *) palloc0(rlen);
@@ -752,11 +756,15 @@ varbit(PG_FUNCTION_ARGS)
PG_RETURN_VARBIT_P(arg);
if (!isExplicit)
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
errmsg("bit string too long for type bit varying(%d)",
len)));
+ PG_RETURN_NULL();
+ }
+
rlen = VARBITTOTALLEN(len);
result = (VarBit *) palloc(rlen);
SET_VARSIZE(result, rlen);
@@ -1591,10 +1599,14 @@ bittoint4(PG_FUNCTION_ARGS)
/* Check that the bit string is not too long */
if (VARBITLEN(arg) > sizeof(result) * BITS_PER_BYTE)
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
+ PG_RETURN_NULL();
+ }
+
result = 0;
for (r = VARBITS(arg); r < VARBITEND(arg); r++)
{
@@ -1671,10 +1683,14 @@ bittoint8(PG_FUNCTION_ARGS)
/* Check that the bit string is not too long */
if (VARBITLEN(arg) > sizeof(result) * BITS_PER_BYTE)
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("bigint out of range")));
+ PG_RETURN_NULL();
+ }
+
result = 0;
for (r = VARBITS(arg); r < VARBITEND(arg); r++)
{
--
2.34.1
v9-0003-error-safe-for-casting-character-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v9-0003-error-safe-for-casting-character-to-other-types-per-pg_cast.patchDownload
From ad556d145cf33a3e7a9e6cbb5572b2228b1938be Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Mon, 3 Nov 2025 11:58:46 +0800
Subject: [PATCH v9 03/19] error safe for casting character to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype ='character'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+-------------------+----------+-------------+------------+-------------+---------
character | text | 401 | i | f | rtrim1 | text
character | character varying | 401 | i | f | rtrim1 | text
character | "char" | 944 | a | f | text_char | char
character | name | 409 | i | f | bpchar_name | name
character | xml | 2896 | e | f | texttoxml | xml
character | character | 668 | i | f | bpchar | bpchar
(6 rows)
only texttoxml, bpchar(PG_FUNCTION_ARGS) need take care of error handling.
other functions already error safe.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/executor/execExprInterp.c | 2 +-
src/backend/utils/adt/varchar.c | 6 +++++-
src/backend/utils/adt/xml.c | 17 ++++++++++++-----
src/include/utils/xml.h | 2 +-
4 files changed, 19 insertions(+), 8 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 0e1a74976f7..67f4e00eac4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -4542,7 +4542,7 @@ ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op)
*op->resvalue = PointerGetDatum(xmlparse(data,
xexpr->xmloption,
- preserve_whitespace));
+ preserve_whitespace, NULL));
*op->resnull = false;
}
break;
diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c
index 3f40c9da1a0..a95e4bd094e 100644
--- a/src/backend/utils/adt/varchar.c
+++ b/src/backend/utils/adt/varchar.c
@@ -307,10 +307,14 @@ bpchar(PG_FUNCTION_ARGS)
{
for (i = maxmblen; i < len; i++)
if (s[i] != ' ')
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
errmsg("value too long for type character(%d)",
maxlen)));
+
+ PG_RETURN_NULL();
+ }
}
len = maxmblen;
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 35c915573a1..4723d13928f 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -659,7 +659,7 @@ texttoxml(PG_FUNCTION_ARGS)
{
text *data = PG_GETARG_TEXT_PP(0);
- PG_RETURN_XML_P(xmlparse(data, xmloption, true));
+ PG_RETURN_XML_P(xmlparse(data, xmloption, true, fcinfo->context));
}
@@ -1028,18 +1028,25 @@ xmlelement(XmlExpr *xexpr,
xmltype *
-xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace)
+xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, Node *escontext)
{
#ifdef USE_LIBXML
xmlDocPtr doc;
doc = xml_parse(data, xmloption_arg, preserve_whitespace,
- GetDatabaseEncoding(), NULL, NULL, NULL);
- xmlFreeDoc(doc);
+ GetDatabaseEncoding(), NULL, NULL, escontext);
+ if (doc)
+ xmlFreeDoc(doc);
+
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return NULL;
return (xmltype *) data;
#else
- NO_XML_SUPPORT();
+ errsave(escontext,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unsupported XML feature"),
+ errdetail("This functionality requires the server to be built with libxml support."));
return NULL;
#endif
}
diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h
index 732dac47bc4..b15168c430e 100644
--- a/src/include/utils/xml.h
+++ b/src/include/utils/xml.h
@@ -73,7 +73,7 @@ extern xmltype *xmlconcat(List *args);
extern xmltype *xmlelement(XmlExpr *xexpr,
const Datum *named_argvalue, const bool *named_argnull,
const Datum *argvalue, const bool *argnull);
-extern xmltype *xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace);
+extern xmltype *xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, Node *escontext);
extern xmltype *xmlpi(const char *target, text *arg, bool arg_is_null, bool *result_is_null);
extern xmltype *xmlroot(xmltype *data, text *version, int standalone);
extern bool xml_is_document(xmltype *arg);
--
2.34.1
v9-0001-error-safe-for-casting-bytea-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v9-0001-error-safe-for-casting-bytea-to-other-types-per-pg_cast.patchDownload
From 346525569c67eb8fb9d17057a46746e2a612fc94 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 15:36:35 +0800
Subject: [PATCH v9 01/19] error safe for casting bytea to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc and pc.castfunc > 0 and castsource::regtype ='bytea'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------+----------+-------------+------------+------------+---------
bytea | smallint | 6370 | e | f | bytea_int2 | int2
bytea | integer | 6371 | e | f | bytea_int4 | int4
bytea | bigint | 6372 | e | f | bytea_int8 | int8
(3 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/bytea.c | 18 +++++++++++++++---
1 file changed, 15 insertions(+), 3 deletions(-)
diff --git a/src/backend/utils/adt/bytea.c b/src/backend/utils/adt/bytea.c
index 6e7b914c563..4a8adcb8204 100644
--- a/src/backend/utils/adt/bytea.c
+++ b/src/backend/utils/adt/bytea.c
@@ -1027,10 +1027,14 @@ bytea_int2(PG_FUNCTION_ARGS)
/* Check that the byte array is not too long */
if (len > sizeof(result))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range"));
+ PG_RETURN_NULL();
+ }
+
/* Convert it to an integer; most significant bytes come first */
result = 0;
for (int i = 0; i < len; i++)
@@ -1052,10 +1056,14 @@ bytea_int4(PG_FUNCTION_ARGS)
/* Check that the byte array is not too long */
if (len > sizeof(result))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range"));
+ PG_RETURN_NULL();
+ }
+
/* Convert it to an integer; most significant bytes come first */
result = 0;
for (int i = 0; i < len; i++)
@@ -1077,10 +1085,14 @@ bytea_int8(PG_FUNCTION_ARGS)
/* Check that the byte array is not too long */
if (len > sizeof(result))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("bigint out of range"));
+ PG_RETURN_NULL();
+ }
+
/* Convert it to an integer; most significant bytes come first */
result = 0;
for (int i = 0; i < len; i++)
--
2.34.1
The above result shows type casts using functions which cannot be error
safe.
Money type related casts still can not be error safe.Cast from circle to polygon cannot be error safe because the associated
cast
function (pg_cast.castfunc) is written in SQL
(see src/backend/catalog/system_functions.sql LINE 112).
It appears impossible to make SQL language functions error safe, because
fmgr_sql ignores fcinfo->context.
It is inevitable that some typecast functions are not type safe, either
because they cannot easily be made so, or because they come from an
extension that has not yet made them so.
eval_const_expressions cannot be error safe, so we need to handle
source_expr as an UNKNOWN constant in an error safe beforehand.
For example, we need handle ('1' AS DATE) in an error safe way
for
SELECT CAST('1' AS date DEFAULT '2011-01-01' ON ERROR);Since we must handle the source_expr when it is an UNKNOWN constant in an
error safe way, we can apply the same handling when source_expr is a
Const whose type is not UNKNOWN.
For example:
SELECT CAST('[(1,2),(3,4)]'::path AS polygon DEFAULT NULL ON CONVERSION
ERROR);
In the case you've presented here, the cast to type "path" doesn't need to
be safe, but the cast path->polygon does. Or rather, if it isn't safe we
should raise an error.
so I introduced:
evaluate_expr_safe: error safe version of evaluate_expr
CoerceUnknownConstSafe: tests whether an UNKNOWN Const can be coerced to
the
target type.
Good. That's the missing bit I knew we needed to add. Sorry I wasn't able
to find time.
Issue 1:
+++ b/doc/src/sgml/syntax.sgml
@@ -2106,6 +2106,10 @@ CAST ( <replaceable>expression</replaceable> AS
<replaceable>type</replaceable>
The <literal>CAST</literal> syntax conforms to SQL; the syntax with
<literal>::</literal> is historical
<productname>PostgreSQL</productname>
usage.
+ It can also be written as:
+<synopsis>
+CAST ( <replaceable>expression</replaceable> AS
<replaceable>type</replaceable> ERROR ON CONVERSION ERROR )
+</synopsis>
The wording implies that this syntax is a new shortcut to a previously
established ON CONVERSION ERROR syntax, when it was the only way to do it
until this patch.
Issue 2:
s/is historical/is the original/
s/It can be also be written as:/The equivalent ON CONVERSION ERROR behavior
is:/
+ /*
+ * Use evaluate_expr_safe to pre-evaluate simple constant cast
+ * expressions early, in a way that tolter errors.
typo on tolter? This happens in 2 places.
Issue 3:
+ errhint("Currently CAST ... DEFAULT ON CONVERSION ERROR does not support
this cast"),
Suggest: errhint("Explicit cast is defined but definition is not declared
as safe")
This hint helps distinguish the fact that it's the cast function getting in
the way of this cast-on-default working. If the cast had been defined as IO
then we wouldn't have had a problem.
It is true that we presently have no means of declaring a cast safe aside
from making it so in pg_proc.dat, but that's coming shortly.
Issue 4:
catversion.h is not updated. Which isn't a bad thing because that brings me
to...
Issue 5:
I think 0019 is a bit big for a committer to digest all in one sitting.
Currently it:
- introduces the type-safe cast node
- introduces the cast on default syntax
- redefines
- adds in test cases for all safe functions defined in patches 1-18.
As tedious as it might be, I think we want to move this patch to the front,
move all pg_cast.dat changes to their respective patches that introduce
that datatype's safe typecast function, as well as the test cases that are
made possible by that new safe typecast. That will make it easier to ensure
that each new cast has test coverage.
Yes, that's going to be a lot of catversion bumps, requiring somebody to
fudge the dates as we presently only allow for 10 catversion bumps in a
day, but the committer will either combine a few of the patches or spread
the work out over a few days.
Overall:
I like where this patchset is going. The introduction of error contexts has
eliminated a lot of the issues I was having carrying the error state
forward into the next eval step.
On Thu, Nov 6, 2025 at 6:00 AM Corey Huinker <corey.huinker@gmail.com> wrote:
Issue 5:
I think 0019 is a bit big for a committer to digest all in one sitting. Currently it:
- introduces the type-safe cast node
- introduces the cast on default syntax
- redefines
- adds in test cases for all safe functions defined in patches 1-18.As tedious as it might be, I think we want to move this patch to the front, move all pg_cast.dat changes to their respective patches that introduce that datatype's safe typecast function, as well as the test cases that are made possible by that new safe typecast. That will make it easier to ensure that each new cast has test coverage.
Yes, that's going to be a lot of catversion bumps, requiring somebody to fudge the dates as we presently only allow for 10 catversion bumps in a day, but the committer will either combine a few of the patches or spread the work out over a few days.
Currently, patches v9-0001 through v9-0018 focus solely on refactoring
pg_cast.castfunc. This refactoring is already valuable on its own because it
establishes the infrastructure needed for error safe type casts.
As mentioned before, to make
CAST(source_expr AS target_type DEFAULT expr ON CONVERSION ERROR);
work,
we cannot just simply replace casting FuncExpr nodes with CoerceViaIO, since
type modifiers behave differently in these two Nodes.
(e.g., casting numeric 1.11 to integer is not equivalent to casting the literal
"1.11" to integer).
Also, are we settled on this new pg_cast column name (pg_cast.casterrorsafe)?
Overall, I think it's better not to touch pg_cast.dat in each of these
refactoring patches.
Without first refactoring pg_cast.castfunc (01 to 18), making CAST ...
DEFAULT as the first patch (0001)
won't work, since pg_cast.castfunc itself is not error safe yet, and we
have no way to test the CAST DEFAULT syntax.
So we have to *first* refactor pg_cast.castfunc, make it error safe
then implement CAST DEFAULT.
--------------------------------------------------
The CAST DEFAULT patch is large, I tried to split some newly created function
into a seperate patch.
see v10-0019-invent-some-error-safe-functions.patch.
SELECT CAST('five' AS INTEGER DEFAULT 6 ON CONVERSION ERROR);
is not hard to make it error safe. ('five' is a simple Cons node)
However, I found out, to make
SELECT CAST('five'::INTEGER AS INTEGER DEFAULT 6 ON CONVERSION ERROR);
error safe is hard.
('five'::INTEGER) is a TypeCast node, normally it will error out in
transformTypeCast->
coerce_to_target_type->coerce_type ```(if (inputTypeId == UNKNOWNOID
&& IsA(node, Const)))```
If we do not error out, then we need a Node to represent the failed cast, mainly
for deparse purposes.
that means for CAST(source_expr .... DEFAULT defexpr ON CONVERSION ERROR);
The only corner case we handle is when source_expr is a simple Const node.
In all other cases, source_expr is processed through transformExpr,
which does all
the normal parse analysis for a node.
Attachments:
v10-0018-error-safe-for-casting-geometry-data-type.patchtext/x-patch; charset=US-ASCII; name=v10-0018-error-safe-for-casting-geometry-data-type.patchDownload
From c784c0b1ef9b11a72c6e372ca3bda750b817ace6 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Fri, 31 Oct 2025 08:32:32 +0800
Subject: [PATCH v10 18/20] error safe for casting geometry data type
select castsource::regtype, casttarget::regtype, pp.prosrc
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
join pg_type pt on pt.oid = castsource
join pg_type pt1 on pt1.oid = casttarget
and pc.castfunc > 0 and pt.typarray <> 0
and pt.typnamespace = 'pg_catalog'::regnamespace
and pt1.typnamespace = 'pg_catalog'::regnamespace
and (pt.typcategory = 'G' or pt1.typcategory = 'G')
order by castsource::regtype, casttarget::regtype;
castsource | casttarget | prosrc
------------+------------+---------------
point | box | point_box
lseg | point | lseg_center
path | polygon | path_poly
box | point | box_center
box | lseg | box_diagonal
box | polygon | box_poly
box | circle | box_circle
polygon | point | poly_center
polygon | path | poly_path
polygon | box | poly_box
polygon | circle | poly_circle
circle | point | circle_center
circle | box | circle_box
circle | polygon |
(14 rows)
already error safe: point_box, box_diagonal, box_poly, poly_path, poly_box, circle_center
almost error safe: path_poly
can not error safe: cast circle to polygon, because it's a SQL function
discussion: https://postgr.es/m/CACJufxHCMzrHOW=wRe8L30rMhB3sjwAv1LE928Fa7sxMu1Tx-g@mail.gmail.com
---
src/backend/access/spgist/spgproc.c | 2 +-
src/backend/utils/adt/float.c | 8 +-
src/backend/utils/adt/geo_ops.c | 238 ++++++++++++++++++++++++----
src/backend/utils/adt/geo_spgist.c | 2 +-
src/include/utils/float.h | 107 +++++++++++++
src/include/utils/geo_decls.h | 4 +-
6 files changed, 323 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/spgist/spgproc.c b/src/backend/access/spgist/spgproc.c
index 660009291da..b3e0fbc59ba 100644
--- a/src/backend/access/spgist/spgproc.c
+++ b/src/backend/access/spgist/spgproc.c
@@ -51,7 +51,7 @@ point_box_distance(Point *point, BOX *box)
else
dy = 0.0;
- return HYPOT(dx, dy);
+ return HYPOT(dx, dy, NULL);
}
/*
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index 6747544b679..06b2b6ae295 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -772,7 +772,7 @@ float8pl(PG_FUNCTION_ARGS)
float8 arg1 = PG_GETARG_FLOAT8(0);
float8 arg2 = PG_GETARG_FLOAT8(1);
- PG_RETURN_FLOAT8(float8_pl(arg1, arg2));
+ PG_RETURN_FLOAT8(float8_pl_safe(arg1, arg2, fcinfo->context));
}
Datum
@@ -781,7 +781,7 @@ float8mi(PG_FUNCTION_ARGS)
float8 arg1 = PG_GETARG_FLOAT8(0);
float8 arg2 = PG_GETARG_FLOAT8(1);
- PG_RETURN_FLOAT8(float8_mi(arg1, arg2));
+ PG_RETURN_FLOAT8(float8_mi_safe(arg1, arg2, fcinfo->context));
}
Datum
@@ -790,7 +790,7 @@ float8mul(PG_FUNCTION_ARGS)
float8 arg1 = PG_GETARG_FLOAT8(0);
float8 arg2 = PG_GETARG_FLOAT8(1);
- PG_RETURN_FLOAT8(float8_mul(arg1, arg2));
+ PG_RETURN_FLOAT8(float8_mul_safe(arg1, arg2, fcinfo->context));
}
Datum
@@ -799,7 +799,7 @@ float8div(PG_FUNCTION_ARGS)
float8 arg1 = PG_GETARG_FLOAT8(0);
float8 arg2 = PG_GETARG_FLOAT8(1);
- PG_RETURN_FLOAT8(float8_div(arg1, arg2));
+ PG_RETURN_FLOAT8(float8_div_safe(arg1, arg2, fcinfo->context));
}
diff --git a/src/backend/utils/adt/geo_ops.c b/src/backend/utils/adt/geo_ops.c
index 377a1b3f3ad..bd031805858 100644
--- a/src/backend/utils/adt/geo_ops.c
+++ b/src/backend/utils/adt/geo_ops.c
@@ -78,11 +78,14 @@ enum path_delim
/* Routines for points */
static inline void point_construct(Point *result, float8 x, float8 y);
static inline void point_add_point(Point *result, Point *pt1, Point *pt2);
+static inline bool point_add_point_safe(Point *result, Point *pt1, Point *pt2,
+ Node *escontext);
static inline void point_sub_point(Point *result, Point *pt1, Point *pt2);
static inline void point_mul_point(Point *result, Point *pt1, Point *pt2);
static inline void point_div_point(Point *result, Point *pt1, Point *pt2);
static inline bool point_eq_point(Point *pt1, Point *pt2);
static inline float8 point_dt(Point *pt1, Point *pt2);
+static inline bool point_dt_safe(Point *pt1, Point *pt2, Node *escontext, float8 *result);
static inline float8 point_sl(Point *pt1, Point *pt2);
static int point_inside(Point *p, int npts, Point *plist);
@@ -109,6 +112,7 @@ static float8 lseg_closept_lseg(Point *result, LSEG *on_lseg, LSEG *to_lseg);
/* Routines for boxes */
static inline void box_construct(BOX *result, Point *pt1, Point *pt2);
static void box_cn(Point *center, BOX *box);
+static bool box_cn_safe(Point *center, BOX *box, Node* escontext);
static bool box_ov(BOX *box1, BOX *box2);
static float8 box_ar(BOX *box);
static float8 box_ht(BOX *box);
@@ -125,7 +129,7 @@ static float8 circle_ar(CIRCLE *circle);
/* Routines for polygons */
static void make_bound_box(POLYGON *poly);
-static void poly_to_circle(CIRCLE *result, POLYGON *poly);
+static bool poly_to_circle_safe(CIRCLE *result, POLYGON *poly, Node *escontext);
static bool lseg_inside_poly(Point *a, Point *b, POLYGON *poly, int start);
static bool poly_contain_poly(POLYGON *contains_poly, POLYGON *contained_poly);
static bool plist_same(int npts, Point *p1, Point *p2);
@@ -851,7 +855,8 @@ box_center(PG_FUNCTION_ARGS)
BOX *box = PG_GETARG_BOX_P(0);
Point *result = (Point *) palloc(sizeof(Point));
- box_cn(result, box);
+ if (!box_cn_safe(result, box, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_POINT_P(result);
}
@@ -875,6 +880,31 @@ box_cn(Point *center, BOX *box)
center->y = float8_div(float8_pl(box->high.y, box->low.y), 2.0);
}
+/* safe version of box_cn */
+static bool
+box_cn_safe(Point *center, BOX *box, Node* escontext)
+{
+ float8 x;
+ float8 y;
+
+ x = float8_pl_safe(box->high.x, box->low.x, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ center->x = float8_div_safe(x, 2.0, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ y = float8_pl_safe(box->high.y, box->low.y, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ center->y = float8_div_safe(y, 2.0, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ return true;
+}
/* box_wd - returns the width (length) of the box
* (horizontal magnitude).
@@ -1276,7 +1306,7 @@ line_distance(PG_FUNCTION_ARGS)
PG_RETURN_FLOAT8(float8_div(fabs(float8_mi(l1->C,
float8_mul(ratio, l2->C))),
- HYPOT(l1->A, l1->B)));
+ HYPOT(l1->A, l1->B, NULL)));
}
/* line_interpt()
@@ -2001,9 +2031,34 @@ point_distance(PG_FUNCTION_ARGS)
static inline float8
point_dt(Point *pt1, Point *pt2)
{
- return HYPOT(float8_mi(pt1->x, pt2->x), float8_mi(pt1->y, pt2->y));
+ return HYPOT(float8_mi(pt1->x, pt2->x), float8_mi(pt1->y, pt2->y), NULL);
}
+/* errror safe version of point_dt */
+static inline bool
+point_dt_safe(Point *pt1, Point *pt2, Node *escontext, float8 *result)
+{
+ float8 x;
+ float8 y;
+
+ Assert(result != NULL);
+
+ x = float8_mi_safe(pt1->x, pt2->x, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ y = float8_mi_safe(pt1->y, pt2->y, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ *result = HYPOT(x, y, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ return true;
+}
+
+
Datum
point_slope(PG_FUNCTION_ARGS)
{
@@ -2317,13 +2372,31 @@ lseg_center(PG_FUNCTION_ARGS)
{
LSEG *lseg = PG_GETARG_LSEG_P(0);
Point *result;
+ float8 x;
+ float8 y;
result = (Point *) palloc(sizeof(Point));
- result->x = float8_div(float8_pl(lseg->p[0].x, lseg->p[1].x), 2.0);
- result->y = float8_div(float8_pl(lseg->p[0].y, lseg->p[1].y), 2.0);
+ x = float8_pl_safe(lseg->p[0].x, lseg->p[1].x, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ result->x = float8_div_safe(x, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ y = float8_pl_safe(lseg->p[0].y, lseg->p[1].y, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ result->y = float8_div_safe(y, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
PG_RETURN_POINT_P(result);
+
+fail:
+ PG_RETURN_NULL();
}
@@ -4115,6 +4188,27 @@ point_add_point(Point *result, Point *pt1, Point *pt2)
float8_pl(pt1->y, pt2->y));
}
+/* error safe version of point_add_point */
+static inline bool
+point_add_point_safe(Point *result, Point *pt1, Point *pt2,
+ Node *escontext)
+{
+ float8 x;
+ float8 y;
+
+ x = float8_pl_safe(pt1->x, pt2->x, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ y = float8_pl_safe(pt1->y, pt2->y, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ point_construct(result, x, y);
+
+ return true;
+}
+
Datum
point_add(PG_FUNCTION_ARGS)
{
@@ -4458,10 +4552,14 @@ path_poly(PG_FUNCTION_ARGS)
/* This is not very consistent --- other similar cases return NULL ... */
if (!path->closed)
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("open path cannot be converted to polygon")));
+ PG_RETURN_NULL();
+ }
+
/*
* Never overflows: the old size fit in MaxAllocSize, and the new size is
* just a small constant larger.
@@ -4508,7 +4606,9 @@ poly_center(PG_FUNCTION_ARGS)
result = (Point *) palloc(sizeof(Point));
- poly_to_circle(&circle, poly);
+ if (!poly_to_circle_safe(&circle, poly, fcinfo->context))
+ PG_RETURN_NULL();
+
*result = circle.center;
PG_RETURN_POINT_P(result);
@@ -5005,7 +5105,7 @@ circle_mul_pt(PG_FUNCTION_ARGS)
result = (CIRCLE *) palloc(sizeof(CIRCLE));
point_mul_point(&result->center, &circle->center, point);
- result->radius = float8_mul(circle->radius, HYPOT(point->x, point->y));
+ result->radius = float8_mul(circle->radius, HYPOT(point->x, point->y, NULL));
PG_RETURN_CIRCLE_P(result);
}
@@ -5020,7 +5120,7 @@ circle_div_pt(PG_FUNCTION_ARGS)
result = (CIRCLE *) palloc(sizeof(CIRCLE));
point_div_point(&result->center, &circle->center, point);
- result->radius = float8_div(circle->radius, HYPOT(point->x, point->y));
+ result->radius = float8_div(circle->radius, HYPOT(point->x, point->y, NULL));
PG_RETURN_CIRCLE_P(result);
}
@@ -5191,14 +5291,30 @@ circle_box(PG_FUNCTION_ARGS)
box = (BOX *) palloc(sizeof(BOX));
- delta = float8_div(circle->radius, sqrt(2.0));
+ delta = float8_div_safe(circle->radius, sqrt(2.0), fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
- box->high.x = float8_pl(circle->center.x, delta);
- box->low.x = float8_mi(circle->center.x, delta);
- box->high.y = float8_pl(circle->center.y, delta);
- box->low.y = float8_mi(circle->center.y, delta);
+ box->high.x = float8_pl_safe(circle->center.x, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ box->low.x = float8_mi_safe(circle->center.x, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ box->high.y = float8_pl_safe(circle->center.y, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ box->low.y = float8_mi_safe(circle->center.y, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
PG_RETURN_BOX_P(box);
+
+fail:
+ PG_RETURN_NULL();
}
/* box_circle()
@@ -5209,15 +5325,35 @@ box_circle(PG_FUNCTION_ARGS)
{
BOX *box = PG_GETARG_BOX_P(0);
CIRCLE *circle;
+ float8 x;
+ float8 y;
circle = (CIRCLE *) palloc(sizeof(CIRCLE));
- circle->center.x = float8_div(float8_pl(box->high.x, box->low.x), 2.0);
- circle->center.y = float8_div(float8_pl(box->high.y, box->low.y), 2.0);
+ x = float8_pl_safe(box->high.x, box->low.x, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
- circle->radius = point_dt(&circle->center, &box->high);
+ circle->center.x = float8_div_safe(x, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ y = float8_pl_safe(box->high.y, box->low.y, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ circle->center.y = float8_div_safe(y, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ if (!point_dt_safe(&circle->center, &box->high, fcinfo->context,
+ &circle->radius))
+ goto fail;
PG_RETURN_CIRCLE_P(circle);
+
+fail:
+ PG_RETURN_NULL();
}
@@ -5281,10 +5417,11 @@ circle_poly(PG_FUNCTION_ARGS)
* XXX This algorithm should use weighted means of line segments
* rather than straight average values of points - tgl 97/01/21.
*/
-static void
-poly_to_circle(CIRCLE *result, POLYGON *poly)
+static bool
+poly_to_circle_safe(CIRCLE *result, POLYGON *poly, Node *escontext)
{
int i;
+ float8 x;
Assert(poly->npts > 0);
@@ -5293,14 +5430,41 @@ poly_to_circle(CIRCLE *result, POLYGON *poly)
result->radius = 0;
for (i = 0; i < poly->npts; i++)
- point_add_point(&result->center, &result->center, &poly->p[i]);
- result->center.x = float8_div(result->center.x, poly->npts);
- result->center.y = float8_div(result->center.y, poly->npts);
+ {
+ if (!point_add_point_safe(&result->center,
+ &result->center,
+ &poly->p[i],
+ escontext))
+ return false;
+ }
+
+ result->center.x = float8_div_safe(result->center.x,
+ poly->npts,
+ escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ result->center.y = float8_div_safe(result->center.y,
+ poly->npts,
+ escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
for (i = 0; i < poly->npts; i++)
- result->radius = float8_pl(result->radius,
- point_dt(&poly->p[i], &result->center));
- result->radius = float8_div(result->radius, poly->npts);
+ {
+ if (!point_dt_safe(&poly->p[i], &result->center, escontext, &x))
+ return false;
+
+ result->radius = float8_pl_safe(result->radius, x, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+ }
+
+ result->radius = float8_div_safe(result->radius, poly->npts, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ return true;
}
Datum
@@ -5311,7 +5475,8 @@ poly_circle(PG_FUNCTION_ARGS)
result = (CIRCLE *) palloc(sizeof(CIRCLE));
- poly_to_circle(result, poly);
+ if (!poly_to_circle_safe(result, poly, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_CIRCLE_P(result);
}
@@ -5516,7 +5681,7 @@ plist_same(int npts, Point *p1, Point *p2)
*-----------------------------------------------------------------------
*/
float8
-pg_hypot(float8 x, float8 y)
+pg_hypot(float8 x, float8 y, Node *escontext)
{
float8 yx,
result;
@@ -5554,9 +5719,22 @@ pg_hypot(float8 x, float8 y)
result = x * sqrt(1.0 + (yx * yx));
if (unlikely(isinf(result)))
- float_overflow_error();
+ {
+ errsave(escontext,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: overflow"));
+
+ result = 0.0;
+ }
+
if (unlikely(result == 0.0))
- float_underflow_error();
+ {
+ errsave(escontext,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: underflow"));
+
+ result = 0.0;
+ }
return result;
}
diff --git a/src/backend/utils/adt/geo_spgist.c b/src/backend/utils/adt/geo_spgist.c
index fec33e95372..ffffd3cd2de 100644
--- a/src/backend/utils/adt/geo_spgist.c
+++ b/src/backend/utils/adt/geo_spgist.c
@@ -390,7 +390,7 @@ pointToRectBoxDistance(Point *point, RectBox *rect_box)
else
dy = 0;
- return HYPOT(dx, dy);
+ return HYPOT(dx, dy, NULL);
}
diff --git a/src/include/utils/float.h b/src/include/utils/float.h
index fc2a9cf6475..5a7033f2a60 100644
--- a/src/include/utils/float.h
+++ b/src/include/utils/float.h
@@ -109,6 +109,24 @@ float4_pl(const float4 val1, const float4 val2)
return result;
}
+/* error safe version of float8_pl */
+static inline float8
+float8_pl_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+ float8 result;
+
+ result = val1 + val2;
+ if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
+ {
+ errsave(escontext,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: overflow"));
+ result = 0.0f;
+ }
+
+ return result;
+}
+
static inline float8
float8_pl(const float8 val1, const float8 val2)
{
@@ -133,6 +151,24 @@ float4_mi(const float4 val1, const float4 val2)
return result;
}
+/* error safe version of float8_mi */
+static inline float8
+float8_mi_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+ float8 result;
+
+ result = val1 - val2;
+ if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
+ {
+ errsave(escontext,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: overflow"));
+ result = 0.0f;
+ }
+
+ return result;
+}
+
static inline float8
float8_mi(const float8 val1, const float8 val2)
{
@@ -159,6 +195,36 @@ float4_mul(const float4 val1, const float4 val2)
return result;
}
+/* error safe version of float8_mul */
+static inline float8
+float8_mul_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+ float8 result;
+
+ result = val1 * val2;
+ if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
+ {
+ errsave(escontext,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: overflow"));
+
+ result = 0.0;
+ return result;
+ }
+
+ if (unlikely(result == 0.0) && val1 != 0.0 && val2 != 0.0)
+ {
+ errsave(escontext,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: underflow"));
+
+ result = 0.0;
+ return result;
+ }
+
+ return result;
+}
+
static inline float8
float8_mul(const float8 val1, const float8 val2)
{
@@ -189,6 +255,47 @@ float4_div(const float4 val1, const float4 val2)
return result;
}
+/* error safe version of float8_div */
+static inline float8
+float8_div_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+ float8 result;
+
+ if (unlikely(val2 == 0.0) && !isnan(val1))
+ {
+ errsave(escontext,
+ errcode(ERRCODE_DIVISION_BY_ZERO),
+ errmsg("division by zero"));
+
+ result = 0.0;
+ return result;
+ }
+
+ result = val1 / val2;
+
+ if (unlikely(isinf(result)) && !isinf(val1))
+ {
+ errsave(escontext,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: overflow"));
+
+ result = 0.0;
+ return result;
+ }
+
+ if (unlikely(result == 0.0) && val1 != 0.0 && !isinf(val2))
+ {
+ errsave(escontext,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: underflow"));
+
+ result = 0.0;
+ return result;
+ }
+
+ return result;
+}
+
static inline float8
float8_div(const float8 val1, const float8 val2)
{
diff --git a/src/include/utils/geo_decls.h b/src/include/utils/geo_decls.h
index 8a9df75c93c..0056a26639f 100644
--- a/src/include/utils/geo_decls.h
+++ b/src/include/utils/geo_decls.h
@@ -88,7 +88,7 @@ FPge(double A, double B)
#define FPge(A,B) ((A) >= (B))
#endif
-#define HYPOT(A, B) pg_hypot(A, B)
+#define HYPOT(A, B, escontext) pg_hypot(A, B, escontext)
/*---------------------------------------------------------------------
* Point - (x,y)
@@ -280,6 +280,6 @@ CirclePGetDatum(const CIRCLE *X)
* in geo_ops.c
*/
-extern float8 pg_hypot(float8 x, float8 y);
+extern float8 pg_hypot(float8 x, float8 y, Node *escontext);
#endif /* GEO_DECLS_H */
--
2.34.1
v10-0016-error-safe-for-casting-timestamp-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v10-0016-error-safe-for-casting-timestamp-to-other-types-per-pg_cast.patchDownload
From e53a8808bd501f08a9660920830559b5785745a8 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 18:44:35 +0800
Subject: [PATCH v10 16/20] error safe for casting timestamp to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
and pc.castfunc > 0 and (castsource::regtype ='timestamp'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
-----------------------------+-----------------------------+----------+-------------+------------+-----------------------+-------------
timestamp without time zone | date | 2029 | a | f | timestamp_date | date
timestamp without time zone | time without time zone | 1316 | a | f | timestamp_time | time
timestamp without time zone | timestamp with time zone | 2028 | i | f | timestamp_timestamptz | timestamptz
timestamp without time zone | timestamp without time zone | 1961 | i | f | timestamp_scale | timestamp
(4 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 13 +++++++++++--
src/backend/utils/adt/timestamp.c | 17 +++++++++++++++--
2 files changed, 26 insertions(+), 4 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 111bfd8f519..c5562b563e5 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -1373,7 +1373,16 @@ timestamp_date(PG_FUNCTION_ARGS)
Timestamp timestamp = PG_GETARG_TIMESTAMP(0);
DateADT result;
- result = timestamp2date_opt_overflow(timestamp, NULL);
+ if (likely(!fcinfo->context))
+ result = timestamptz2date_opt_overflow(timestamp, NULL);
+ else
+ {
+ int overflow;
+ result = timestamp2date_opt_overflow(timestamp, &overflow);
+
+ if (overflow != 0)
+ PG_RETURN_NULL();
+ }
PG_RETURN_DATEADT(result);
}
@@ -2089,7 +2098,7 @@ timestamp_time(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 116e3ef28fc..7a57ac3eaf6 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -352,7 +352,8 @@ timestamp_scale(PG_FUNCTION_ARGS)
result = timestamp;
- AdjustTimestampForTypmod(&result, typmod, NULL);
+ if (!AdjustTimestampForTypmod(&result, typmod, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_TIMESTAMP(result);
}
@@ -6430,7 +6431,19 @@ timestamp_timestamptz(PG_FUNCTION_ARGS)
{
Timestamp timestamp = PG_GETARG_TIMESTAMP(0);
- PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(timestamp));
+ if (likely(!fcinfo->context))
+ PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(timestamp));
+ else
+ {
+ TimestampTz result;
+ int overflow;
+
+ result = timestamp2timestamptz_opt_overflow(timestamp, &overflow);
+ if (overflow != 0)
+ PG_RETURN_NULL();
+ else
+ PG_RETURN_TIMESTAMPTZ(result);
+ }
}
/*
--
2.34.1
v10-0019-invent-some-error-safe-functions.patchtext/x-patch; charset=US-ASCII; name=v10-0019-invent-some-error-safe-functions.patchDownload
From 873c03536869c1c1aae9dbd600d0921c4d3fe2d5 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 8 Nov 2025 13:05:39 +0800
Subject: [PATCH v10 19/20] invent some error safe functions
stringTypeDatumSafe: error safe version of stringTypeDatum
evaluate_expr_safe: error safe version of evaluate_expr
ExecInitExprSafe: soft error variant of ExecInitExpr
OidInputFunctionCallSafe: soft error variant of OidInputFunctionCall
CoerceUnknownConstSafe: attempts to coerce an UNKNOWN Const to the target type.
Returns NULL if the coercion is not possible.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/executor/execExpr.c | 41 +++++++++++++
src/backend/optimizer/util/clauses.c | 69 +++++++++++++++++++++
src/backend/parser/parse_coerce.c | 92 ++++++++++++++++++++++++++++
src/backend/parser/parse_type.c | 14 +++++
src/backend/utils/fmgr/fmgr.c | 13 ++++
src/include/executor/executor.h | 1 +
src/include/fmgr.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/parser/parse_coerce.h | 4 ++
src/include/parser/parse_type.h | 2 +
10 files changed, 241 insertions(+)
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index f1569879b52..b302be36f73 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -170,6 +170,47 @@ ExecInitExpr(Expr *node, PlanState *parent)
return state;
}
+/*
+ * ExecInitExprSafe: soft error variant of ExecInitExpr.
+ *
+ * use it only for expression nodes support soft errors, not all expression
+ * nodes support it.
+*/
+ExprState *
+ExecInitExprSafe(Expr *node, PlanState *parent)
+{
+ ExprState *state;
+ ExprEvalStep scratch = {0};
+
+ /* Special case: NULL expression produces a NULL ExprState pointer */
+ if (node == NULL)
+ return NULL;
+
+ /* Initialize ExprState with empty step list */
+ state = makeNode(ExprState);
+ state->expr = node;
+ state->parent = parent;
+ state->ext_params = NULL;
+ state->escontext = makeNode(ErrorSaveContext);
+ state->escontext->type = T_ErrorSaveContext;
+ state->escontext->error_occurred = false;
+ state->escontext->details_wanted = false;
+
+ /* Insert setup steps as needed */
+ ExecCreateExprSetupSteps(state, (Node *) node);
+
+ /* Compile the expression proper */
+ ExecInitExprRec(node, state, &state->resvalue, &state->resnull);
+
+ /* Finally, append a DONE step */
+ scratch.opcode = EEOP_DONE_RETURN;
+ ExprEvalPushStep(state, &scratch);
+
+ ExecReadyExpr(state);
+
+ return state;
+}
+
/*
* ExecInitExprWithParams: prepare a standalone expression tree for execution
*
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 81d768ff2a2..3f9ff455ba6 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -5147,6 +5147,75 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
resultTypByVal);
}
+/*
+ * evaluate_expr_safe: error safe version of evaluate_expr
+ *
+ * We use the executor's routine ExecEvalExpr() to avoid duplication of
+ * code and ensure we get the same result as the executor would get.
+ *
+ * return NULL when evaulation failed. Ensure the expression expr can be evaulated
+ * in a soft error way.
+ *
+ * See comments on evaluate_expr too.
+ */
+Expr *
+evaluate_expr_safe(Expr *expr, Oid result_type, int32 result_typmod,
+ Oid result_collation)
+{
+ EState *estate;
+ ExprState *exprstate;
+ MemoryContext oldcontext;
+ Datum const_val;
+ bool const_is_null;
+ int16 resultTypLen;
+ bool resultTypByVal;
+
+ estate = CreateExecutorState();
+
+ /* We can use the estate's working context to avoid memory leaks. */
+ oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
+
+ /* Make sure any opfuncids are filled in. */
+ fix_opfuncids((Node *) expr);
+
+ /*
+ * Prepare expr for execution. (Note: we can't use ExecPrepareExpr
+ * because it'd result in recursively invoking eval_const_expressions.)
+ */
+ exprstate = ExecInitExprSafe(expr, NULL);
+
+ const_val = ExecEvalExprSwitchContext(exprstate,
+ GetPerTupleExprContext(estate),
+ &const_is_null);
+
+ /* Get info needed about result datatype */
+ get_typlenbyval(result_type, &resultTypLen, &resultTypByVal);
+
+ /* Get back to outer memory context */
+ MemoryContextSwitchTo(oldcontext);
+
+ if (SOFT_ERROR_OCCURRED(exprstate->escontext))
+ {
+ FreeExecutorState(estate);
+ return NULL;
+ }
+
+ if (!const_is_null)
+ {
+ if (resultTypLen == -1)
+ const_val = PointerGetDatum(PG_DETOAST_DATUM_COPY(const_val));
+ else
+ const_val = datumCopy(const_val, resultTypByVal, resultTypLen);
+ }
+
+ FreeExecutorState(estate);
+
+ return (Expr *) makeConst(result_type, result_typmod, result_collation,
+ resultTypLen,
+ const_val, const_is_null,
+ resultTypByVal);
+}
+
/*
* inline_set_returning_function
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 78b1e366ad7..cdcdd44a799 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -657,6 +657,98 @@ can_coerce_type(int nargs, const Oid *input_typeids, const Oid *target_typeids,
return true;
}
+/*
+ * Create an expression tree to represent coercion a UNKNOWN Const node.
+ *
+ * 'node': the input expression
+ * 'baseTypeId': base type of domain
+ * 'baseTypeMod': base type typmod of domain
+ * 'targetType': target type to coerce to
+ * 'targetTypeMod': target type typmod
+ * 'ccontext': context indicator to control coercions
+ * 'cformat': coercion display format
+ * 'location': coercion request location
+ * 'hideInputCoercion': if true, hide the input coercion under this one.
+ */
+Node *
+CoerceUnknownConstSafe(ParseState *pstate, Node *node, Oid targetType, int32 targetTypeMod,
+ CoercionContext ccontext, CoercionForm cformat, int location,
+ bool hideInputCoercion)
+{
+ Oid baseTypeId;
+ int32 baseTypeMod;
+ int32 inputTypeMod;
+ Type baseType;
+ char *string;
+ Datum datum;
+ Const *newcon;
+ Node *result = NULL;
+ Const *con = (Const *) node;
+
+ Assert(IsA(node, Const));
+ Assert(exprType(node) == UNKNOWNOID);
+
+ 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;
+
+ if (!stringTypeDatumSafe(baseType,
+ string,
+ inputTypeMod,
+ &datum))
+ {
+ ReleaseSysCache(baseType);
+ return NULL;
+ }
+
+ newcon = makeNode(Const);
+ newcon->consttype = baseTypeId;
+ newcon->consttypmod = inputTypeMod;
+ newcon->constcollid = typeTypeCollation(baseType);
+ newcon->constlen = typeLen(baseType);
+ newcon->constbyval = typeByVal(baseType);
+ newcon->constisnull = con->constisnull;
+ newcon->constvalue = datum;
+
+ /*
+ * 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)
+ newcon->constvalue =
+ PointerGetDatum(PG_DETOAST_DATUM(newcon->constvalue));
+
+ result = (Node *) newcon;
+
+ /* If target is a domain, apply constraints. */
+ if (baseTypeId != targetType)
+ result = coerce_to_domain(result,
+ baseTypeId, baseTypeMod,
+ targetType,
+ ccontext, cformat, location,
+ false);
+
+ ReleaseSysCache(baseType);
+
+ return result;
+}
+
/*
* Create an expression tree to represent coercion to a domain type.
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index 7713bdc6af0..d260aeec5dc 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,19 @@ stringTypeDatum(Type tp, char *string, int32 atttypmod)
return OidInputFunctionCall(typinput, string, typioparam, atttypmod);
}
+/* error safe version of stringTypeDatum */
+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,
+ (Node *) &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/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index 0fe63c6bb83..aaa4a42b1ea 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, Node *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/executor.h b/src/include/executor/executor.h
index fa2b657fb2f..f99fc26eb1f 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -324,6 +324,7 @@ ExecProcNode(PlanState *node)
* prototypes from functions in execExpr.c
*/
extern ExprState *ExecInitExpr(Expr *node, PlanState *parent);
+extern ExprState *ExecInitExprSafe(Expr *node, PlanState *parent);
extern ExprState *ExecInitExprWithParams(Expr *node, ParamListInfo ext_params);
extern ExprState *ExecInitQual(List *qual, PlanState *parent);
extern ExprState *ExecInitCheck(List *qual, PlanState *parent);
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index 74fe3ea0575..991e14034d3 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, Node *escontext,
+ Datum *result);
extern char *OutputFunctionCall(FmgrInfo *flinfo, Datum val);
extern char *OidOutputFunctionCall(Oid functionId, Datum val);
extern Datum ReceiveFunctionCall(FmgrInfo *flinfo, StringInfo buf,
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index d0aa8ab0c1c..964ec024de7 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -145,6 +145,8 @@ extern Node *estimate_expression_value(PlannerInfo *root, Node *node);
extern Expr *evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
Oid result_collation);
+extern Expr *evaluate_expr_safe(Expr *expr, Oid result_type, int32 result_typmod,
+ Oid result_collation);
extern bool var_is_nonnullable(PlannerInfo *root, Var *var, bool use_rel_info);
extern List *expand_function_arguments(List *args, bool include_out_arguments,
diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h
index 8d775c72c59..ad16cdd7022 100644
--- a/src/include/parser/parse_coerce.h
+++ b/src/include/parser/parse_coerce.h
@@ -48,6 +48,10 @@ extern bool can_coerce_type(int nargs, const Oid *input_typeids, const Oid *targ
extern Node *coerce_type(ParseState *pstate, Node *node,
Oid inputTypeId, Oid targetTypeId, int32 targetTypeMod,
CoercionContext ccontext, CoercionForm cformat, int location);
+extern Node *CoerceUnknownConstSafe(ParseState *pstate, Node *node,
+ Oid targetType, int32 targetTypeMod,
+ CoercionContext ccontext, CoercionForm cformat,
+ int location, bool hideInputCoercion);
extern Node *coerce_to_domain(Node *arg, Oid baseTypeId, int32 baseTypeMod,
Oid typeId,
CoercionContext ccontext, CoercionForm cformat, int location,
diff --git a/src/include/parser/parse_type.h b/src/include/parser/parse_type.h
index 0d919d8bfa2..12381aed64c 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);
--
2.34.1
v10-0017-error-safe-for-casting-jsonb-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v10-0017-error-safe-for-casting-jsonb-to-other-types-per-pg_cast.patchDownload
From 7936c6a941547fd03972e16923fefa27358b9368 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 18:58:40 +0800
Subject: [PATCH v10 17/20] error safe for casting jsonb to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'jsonb'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+---------------+---------
jsonb | boolean | 3556 | e | f | jsonb_bool | bool
jsonb | numeric | 3449 | e | f | jsonb_numeric | numeric
jsonb | smallint | 3450 | e | f | jsonb_int2 | int2
jsonb | integer | 3451 | e | f | jsonb_int4 | int4
jsonb | bigint | 3452 | e | f | jsonb_int8 | int8
jsonb | real | 3453 | e | f | jsonb_float4 | float4
jsonb | double precision | 2580 | e | f | jsonb_float8 | float8
(7 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/jsonb.c | 91 +++++++++++++++++++++++++++++------
1 file changed, 75 insertions(+), 16 deletions(-)
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index da94d424d61..1ff00f72f78 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -2005,7 +2005,7 @@ JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res)
* Emit correct, translatable cast error message
*/
static void
-cannotCastJsonbValue(enum jbvType type, const char *sqltype)
+cannotCastJsonbValue(enum jbvType type, const char *sqltype, Node *escontext)
{
static const struct
{
@@ -2026,9 +2026,12 @@ cannotCastJsonbValue(enum jbvType type, const char *sqltype)
for (i = 0; i < lengthof(messages); i++)
if (messages[i].type == type)
- ereport(ERROR,
+ {
+ errsave(escontext,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(messages[i].msg, sqltype)));
+ return;
+ }
/* should be unreachable */
elog(ERROR, "unknown jsonb type: %d", (int) type);
@@ -2041,7 +2044,11 @@ jsonb_bool(PG_FUNCTION_ARGS)
JsonbValue v;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "boolean");
+ {
+ cannotCastJsonbValue(v.type, "boolean", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2050,7 +2057,11 @@ jsonb_bool(PG_FUNCTION_ARGS)
}
if (v.type != jbvBool)
- cannotCastJsonbValue(v.type, "boolean");
+ {
+ cannotCastJsonbValue(v.type, "boolean", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
PG_FREE_IF_COPY(in, 0);
@@ -2065,7 +2076,11 @@ jsonb_numeric(PG_FUNCTION_ARGS)
Numeric retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "numeric");
+ {
+ cannotCastJsonbValue(v.type, "numeric", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2074,7 +2089,11 @@ jsonb_numeric(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "numeric");
+ {
+ cannotCastJsonbValue(v.type, "numeric", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
/*
* v.val.numeric points into jsonb body, so we need to make a copy to
@@ -2095,7 +2114,11 @@ jsonb_int2(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "smallint");
+ {
+ cannotCastJsonbValue(v.type, "smallint", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2104,7 +2127,11 @@ jsonb_int2(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "smallint");
+ {
+ cannotCastJsonbValue(v.type, "smallint", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_int2,
NumericGetDatum(v.val.numeric));
@@ -2122,7 +2149,11 @@ jsonb_int4(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "integer");
+ {
+ cannotCastJsonbValue(v.type, "integer", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2131,7 +2162,11 @@ jsonb_int4(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "integer");
+ {
+ cannotCastJsonbValue(v.type, "integer", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_int4,
NumericGetDatum(v.val.numeric));
@@ -2149,7 +2184,11 @@ jsonb_int8(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "bigint");
+ {
+ cannotCastJsonbValue(v.type, "bigint", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2158,7 +2197,11 @@ jsonb_int8(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "bigint");
+ {
+ cannotCastJsonbValue(v.type, "bigint", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_int8,
NumericGetDatum(v.val.numeric));
@@ -2176,7 +2219,11 @@ jsonb_float4(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "real");
+ {
+ cannotCastJsonbValue(v.type, "real", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2185,7 +2232,11 @@ jsonb_float4(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "real");
+ {
+ cannotCastJsonbValue(v.type, "real", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_float4,
NumericGetDatum(v.val.numeric));
@@ -2203,7 +2254,11 @@ jsonb_float8(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "double precision");
+ {
+ cannotCastJsonbValue(v.type, "double precision", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2212,7 +2267,11 @@ jsonb_float8(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "double precision");
+ {
+ cannotCastJsonbValue(v.type, "double precision", fcinfo->context);
+
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_float8,
NumericGetDatum(v.val.numeric));
--
2.34.1
v10-0020-CAST-expr-AS-newtype-DEFAULT-ON-ERROR.patchtext/x-patch; charset=UTF-8; name=v10-0020-CAST-expr-AS-newtype-DEFAULT-ON-ERROR.patchDownload
From 636f9467c9c6a82604b847129a55411674fe8a23 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Mon, 10 Nov 2025 14:08:49 +0800
Subject: [PATCH v10 20/20] CAST(expr AS newtype DEFAULT ON ERROR)
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.
draft doc also added.
We cannot simply prohibit user-defined functions in pg_cast for safe cast
evaluation because CREATE CAST can also utilize built-in functions. So, to
completely disallow custom casts created via CREATE CAST used in safe cast
evaluation, a new field in pg_cast would unfortunately be necessary.
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}
[0]: https://git.postgresql.org/cgit/postgresql.git/commit/?id=aaaf9449ec6be62cb0d30ed3588dc384f56274b
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
.../pg_stat_statements/expected/select.out | 23 +-
contrib/pg_stat_statements/sql/select.sql | 5 +
doc/src/sgml/catalogs.sgml | 12 +
doc/src/sgml/syntax.sgml | 29 +
src/backend/catalog/pg_cast.c | 1 +
src/backend/executor/execExpr.c | 76 +-
src/backend/executor/execExprInterp.c | 37 +
src/backend/jit/llvm/llvmjit_expr.c | 49 ++
src/backend/nodes/nodeFuncs.c | 61 ++
src/backend/optimizer/util/clauses.c | 25 +
src/backend/parser/gram.y | 22 +
src/backend/parser/parse_agg.c | 9 +
src/backend/parser/parse_expr.c | 424 +++++++++-
src/backend/parser/parse_func.c | 3 +
src/backend/parser/parse_target.c | 14 +
src/backend/utils/adt/arrayfuncs.c | 8 +
src/backend/utils/adt/ruleutils.c | 21 +
src/include/catalog/pg_cast.dat | 330 ++++----
src/include/catalog/pg_cast.h | 4 +
src/include/executor/execExpr.h | 8 +
src/include/nodes/execnodes.h | 30 +
src/include/nodes/parsenodes.h | 11 +
src/include/nodes/primnodes.h | 36 +
src/include/parser/parse_node.h | 2 +
src/test/regress/expected/cast.out | 767 ++++++++++++++++++
src/test/regress/expected/create_cast.out | 5 +
src/test/regress/expected/opr_sanity.out | 24 +-
src/test/regress/parallel_schedule | 2 +-
src/test/regress/sql/cast.sql | 335 ++++++++
src/test/regress/sql/create_cast.sql | 1 +
src/tools/pgindent/typedefs.list | 3 +
31 files changed, 2183 insertions(+), 194 deletions(-)
create mode 100644 src/test/regress/expected/cast.out
create mode 100644 src/test/regress/sql/cast.sql
diff --git a/contrib/pg_stat_statements/expected/select.out b/contrib/pg_stat_statements/expected/select.out
index 75c896f3885..6e67997f0a2 100644
--- a/contrib/pg_stat_statements/expected/select.out
+++ b/contrib/pg_stat_statements/expected/select.out
@@ -73,6 +73,25 @@ SELECT 1 AS "int" OFFSET 2 FETCH FIRST 3 ROW ONLY;
-----
(0 rows)
+--error safe type cast
+SELECT CAST('a' AS int DEFAULT 2 ON CONVERSION ERROR);
+ int4
+------
+ 2
+(1 row)
+
+SELECT CAST('11' AS int DEFAULT 2 ON CONVERSION ERROR);
+ int4
+------
+ 11
+(1 row)
+
+SELECT CAST('12' AS numeric DEFAULT 2 ON CONVERSION ERROR);
+ numeric
+---------
+ 12
+(1 row)
+
-- DISTINCT and ORDER BY patterns
-- Try some query permutations which once produced identical query IDs
SELECT DISTINCT 1 AS "int";
@@ -222,6 +241,8 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
2 | 2 | SELECT $1 AS "int" ORDER BY 1
1 | 2 | SELECT $1 AS i UNION SELECT $2 ORDER BY i
1 | 1 | SELECT $1 || $2
+ 2 | 2 | SELECT CAST($1 AS int DEFAULT $2 ON CONVERSION ERROR)
+ 1 | 1 | SELECT CAST($1 AS numeric DEFAULT $2 ON CONVERSION ERROR)
2 | 2 | SELECT DISTINCT $1 AS "int"
0 | 0 | SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"
1 | 1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
@@ -230,7 +251,7 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
| | ) +
| | SELECT f FROM t ORDER BY f
1 | 1 | select $1::jsonb ? $2
-(17 rows)
+(19 rows)
SELECT pg_stat_statements_reset() IS NOT NULL AS t;
t
diff --git a/contrib/pg_stat_statements/sql/select.sql b/contrib/pg_stat_statements/sql/select.sql
index 11662cde08c..7ee8160fd84 100644
--- a/contrib/pg_stat_statements/sql/select.sql
+++ b/contrib/pg_stat_statements/sql/select.sql
@@ -25,6 +25,11 @@ SELECT 1 AS "int" LIMIT 3 OFFSET 3;
SELECT 1 AS "int" OFFSET 1 FETCH FIRST 2 ROW ONLY;
SELECT 1 AS "int" OFFSET 2 FETCH FIRST 3 ROW ONLY;
+--error safe type cast
+SELECT CAST('a' AS int DEFAULT 2 ON CONVERSION ERROR);
+SELECT CAST('11' AS int DEFAULT 2 ON CONVERSION ERROR);
+SELECT CAST('12' AS numeric DEFAULT 2 ON CONVERSION ERROR);
+
-- DISTINCT and ORDER BY patterns
-- Try some query permutations which once produced identical query IDs
SELECT DISTINCT 1 AS "int";
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 6c8a0f173c9..492c99e37cd 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1849,6 +1849,18 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<literal>b</literal> means that the types are binary-coercible, thus no conversion is required.
</para></entry>
</row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>casterrorsafe</structfield> <type>bool</type>
+ </para>
+ <para>
+ This indicates whether the <structfield>castfunc</structfield> function is error safe.
+ If the <structfield>castfunc</structfield> function is error safe, it can be used in error safe type cast.
+ For further details see <xref linkend="sql-syntax-type-casts-safe"/>.
+ </para></entry>
+ </row>
+
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml
index 237d7306fe8..3dc7baf08b9 100644
--- a/doc/src/sgml/syntax.sgml
+++ b/doc/src/sgml/syntax.sgml
@@ -2106,6 +2106,10 @@ CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable>
The <literal>CAST</literal> syntax conforms to SQL; the syntax with
<literal>::</literal> is historical <productname>PostgreSQL</productname>
usage.
+ The equivalent ON CONVERSION ERROR behavior is:
+<synopsis>
+CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable> ERROR ON CONVERSION ERROR )
+</synopsis>
</para>
<para>
@@ -2160,6 +2164,31 @@ CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable>
<xref linkend="sql-createcast"/>.
</para>
</note>
+
+ <sect3 id="sql-syntax-type-casts-safe">
+ <title>Safe Type Cast</title>
+ <para>
+Sometimes a type cast may fail; to handle such cases, an <literal>ON ERROR</literal> clause can be
+specified to avoid potential errors. The syntax is:
+<synopsis>
+CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable> DEFAULT <replaceable>expression</replaceable> ON CONVERSION ERROR )
+</synopsis>
+ If cast the source expression to target type fails, it falls back to
+ evaluating the optionally supplied default <replaceable>expression</replaceable>
+ specified in ON <literal>ON ERROR</literal> clause.
+ Currently, this only support built-in system type casts,
+ casts created using <link linkend="sql-createcast">CREATE CAST</link> are not supported.
+ </para>
+
+ <para>
+ For example, the following query attempts to cast a <type>text</type> type value to <type>integer</type> type,
+ but when the conversion fails, it falls back to evaluate the supplied default expression.
+<programlisting>
+SELECT CAST(TEXT 'error' AS integer DEFAULT NULL ON CONVERSION ERROR) IS NULL; <lineannotation>true</lineannotation>
+</programlisting>
+ </para>
+ </sect3>
+
</sect2>
<sect2 id="sql-syntax-collate-exprs">
diff --git a/src/backend/catalog/pg_cast.c b/src/backend/catalog/pg_cast.c
index 1773c9c5491..6fe65d24d31 100644
--- a/src/backend/catalog/pg_cast.c
+++ b/src/backend/catalog/pg_cast.c
@@ -84,6 +84,7 @@ CastCreate(Oid sourcetypeid, Oid targettypeid,
values[Anum_pg_cast_castfunc - 1] = ObjectIdGetDatum(funcid);
values[Anum_pg_cast_castcontext - 1] = CharGetDatum(castcontext);
values[Anum_pg_cast_castmethod - 1] = CharGetDatum(castmethod);
+ values[Anum_pg_cast_casterrorsafe - 1] = BoolGetDatum(false);
tuple = heap_form_tuple(RelationGetDescr(relation), values, nulls);
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index b302be36f73..5a4b9e8b53d 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,
@@ -1742,6 +1745,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
elemstate->innermost_caseval = (Datum *) palloc(sizeof(Datum));
elemstate->innermost_casenull = (bool *) palloc(sizeof(bool));
+ elemstate->escontext = state->escontext;
ExecInitExprRec(acoerce->elemexpr, elemstate,
&elemstate->resvalue, &elemstate->resnull);
@@ -2218,6 +2222,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;
@@ -2784,7 +2796,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;
@@ -4782,6 +4794,68 @@ ExecBuildParamSetEqual(TupleDesc desc,
return state;
}
+/*
+ * Push steps to evaluate a SafeTypeCastExpr and its various subsidiary
+ * expressions.
+ */
+static void
+ExecInitSafeTypeCastExpr(SafeTypeCastExpr *stcexpr , ExprState *state,
+ Datum *resv, bool *resnull,
+ ExprEvalStep *scratch)
+{
+ /*
+ * If we can not coerce to the target type, fallback to the DEFAULT
+ * expression specified in the ON CONVERSION ERROR clause, and we are done.
+ */
+ if (stcexpr->cast_expr == NULL)
+ {
+ ExecInitExprRec((Expr *) stcexpr->default_expr,
+ state, resv, resnull);
+ return;
+ }
+ else
+ {
+ ExprEvalStep *as;
+ SafeTypeCastState *stcstate;
+ ErrorSaveContext *saved_escontext;
+ int jumps_to_end;
+
+ stcstate = palloc0(sizeof(SafeTypeCastState));
+ stcstate->stcexpr = stcexpr;
+ stcstate->escontext.type = T_ErrorSaveContext;
+ state->escontext = &stcstate->escontext;
+
+ /* evaluate argument expression into step's result area */
+ ExecInitExprRec((Expr *) stcexpr->cast_expr,
+ 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 = 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;
+
+ as = &state->steps[jumps_to_end];
+ 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 67f4e00eac4..bd8b1048c5f 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,12 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
EEO_NEXT();
}
+ EEO_CASE(EEOP_SAFETYPE_CAST)
+ {
+ /* too complex for an inline implementation */
+ EEO_JUMP(ExecEvalSafeTypeCast(state, op));
+ }
+
EEO_CASE(EEOP_JSONEXPR_PATH)
{
/* too complex for an inline implementation */
@@ -3644,6 +3651,12 @@ 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;
+ }
}
/*
@@ -5183,6 +5196,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
+ * 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 712b35df7e5..f54fe563543 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 ede838cd40c..fe3e6b11a08 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;
@@ -1706,6 +1717,9 @@ exprLocation(const Node *expr)
loc = leftmostLoc(loc, tc->location);
}
break;
+ case T_SafeTypeCastExpr:
+ loc = ((const SafeTypeCastExpr *) expr)->location;
+ break;
case T_CollateClause:
/* just use argument's location */
loc = exprLocation(((const CollateClause *) expr)->arg);
@@ -2321,6 +2335,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:
@@ -3330,6 +3356,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;
@@ -4464,6 +4503,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->raw_default))
+ 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/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 3f9ff455ba6..56649162845 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -2945,6 +2945,31 @@ 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;
+
+ if (stc->cast_expr != NULL)
+ source_expr = eval_const_expressions_mutator(source_expr, context);
+ default_expr = eval_const_expressions_mutator(default_expr, context);
+
+ /*
+ * We must not reduce any recognizably constant subexpressions
+ * in cast_expr here, since we don’t want it to fail
+ * prematurely.
+ */
+ 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 a4b29c822e8..a8c75f53363 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -15993,6 +15993,28 @@ func_expr_common_subexpr:
}
| CAST '(' a_expr AS Typename ')'
{ $$ = makeTypeCast($3, $5, @1); }
+ | CAST '(' a_expr AS Typename ERROR_P ON CONVERSION_P ERROR_P ')'
+ { $$ = makeTypeCast($3, $5, @1); }
+ | CAST '(' a_expr AS Typename NULL_P ON CONVERSION_P ERROR_P ')'
+ {
+ TypeCast *cast = (TypeCast *) makeTypeCast($3, $5, @1);
+
+ SafeTypeCast *safecast = makeNode(SafeTypeCast);
+ safecast->cast = (Node *) cast;
+ safecast->raw_default = makeNullAConst(-1);;
+
+ $$ = (Node *) safecast;
+ }
+ | CAST '(' a_expr AS Typename DEFAULT a_expr ON CONVERSION_P ERROR_P ')'
+ {
+ TypeCast *cast = (TypeCast *) makeTypeCast($3, $5, @1);
+
+ SafeTypeCast *safecast = makeNode(SafeTypeCast);
+ safecast->cast = (Node *) cast;
+ safecast->raw_default = $7;
+
+ $$ = (Node *) safecast;
+ }
| EXTRACT '(' extract_list ')'
{
$$ = (Node *) makeFuncCall(SystemFuncName("extract"),
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 3254c83cc6c..6573e2a93ea 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -486,6 +486,12 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
err = _("grouping operations are not allowed in check constraints");
break;
+ case EXPR_KIND_CAST_DEFAULT:
+ if (isAgg)
+ err = _("aggregate functions are not allowed in CAST DEFAULT expressions");
+ else
+ err = _("grouping operations are not allowed in CAST DEFAULT expressions");
+ break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
@@ -956,6 +962,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_DOMAIN_CHECK:
err = _("window functions are not allowed in check constraints");
break;
+ case EXPR_KIND_CAST_DEFAULT:
+ err = _("window functions are not allowed in CAST DEFAULT expressions");
+ break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
err = _("window functions are not allowed in DEFAULT expressions");
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 12119f147fc..8fc8ee47aea 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -17,6 +17,8 @@
#include "access/htup_details.h"
#include "catalog/pg_aggregate.h"
+#include "catalog/pg_cast.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -37,6 +39,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 +63,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 *can_coerce);
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 +80,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);
@@ -164,13 +169,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;
@@ -564,6 +573,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
case EXPR_KIND_VALUES_SINGLE:
case EXPR_KIND_CHECK_CONSTRAINT:
case EXPR_KIND_DOMAIN_CHECK:
+ case EXPR_KIND_CAST_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
case EXPR_KIND_INDEX_EXPRESSION:
case EXPR_KIND_INDEX_PREDICATE:
@@ -1824,6 +1834,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_DOMAIN_CHECK:
err = _("cannot use subquery in check constraint");
break;
+ case EXPR_KIND_CAST_DEFAULT:
+ err = _("cannot use subquery in CAST DEFAULT expression");
+ break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
err = _("cannot use subquery in DEFAULT expression");
@@ -2011,16 +2024,23 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
* 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().
+ *
+ * Most of the time, can_coerce will be NULL.
+ * can_coerce is not NULL only when performing parse analysis for expressions
+ * like CAST(DEFAULT ... ON CONVERSION ERROR). can_coerce should be assumed to
+ * default to true. If coerce array elements to the target type fails,
+ * can_coerce will be set to 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;
List *newcoercedelems = NIL;
ListCell *element;
Oid coerce_type;
+ Oid coerce_type_coll;
bool coerce_hard;
/*
@@ -2045,9 +2065,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
@@ -2088,6 +2109,9 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
}
else
{
+ /* The target type is valid when performing type-safe cast */
+ Assert(can_coerce == NULL);
+
/* Can't handle an empty array without a target type */
if (newelems == NIL)
ereport(ERROR,
@@ -2125,6 +2149,8 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
coerce_hard = false;
}
+ coerce_type_coll = get_typcollation(coerce_type);
+
/*
* Coerce elements to target type
*
@@ -2134,13 +2160,53 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
* If the array's type was merely derived from the common type of its
* elements, then the elements are implicitly coerced to the common type.
* This is consistent with other uses of select_common_type().
+ *
+ * If can_coerce is not NULL, we need to safely test whether each element
+ * can be coerced to the target type, similar to what is done in
+ * transformTypeSafeCast.
*/
foreach(element, newelems)
{
Node *e = (Node *) lfirst(element);
- Node *newe;
+ bool expr_is_const = false;
+ Node *newe = NULL;
- if (coerce_hard)
+ if (can_coerce != NULL && IsA(e, Const))
+ {
+ expr_is_const = true;
+
+ /* coerce UNKNOWN Const to the target type */
+ if (*can_coerce && exprType(e) == UNKNOWNOID)
+ {
+ newe = CoerceUnknownConstSafe(pstate, e, coerce_type, typmod,
+ COERCION_IMPLICIT,
+ COERCE_IMPLICIT_CAST,
+ -1,
+ false);
+ if (newe == NULL)
+ {
+ newe = e;
+ *can_coerce = false;
+ }
+ newcoercedelems = lappend(newcoercedelems, newe);
+
+ /*
+ * Done handling this UNKNOWN Const element; move on to the
+ * next.
+ */
+ continue;
+ }
+ }
+
+ if (can_coerce != NULL && (!*can_coerce))
+ {
+ /*
+ * Can not coerce source expression to the target type, just append
+ * the transformed element expression to the list.
+ */
+ newe = e;
+ }
+ else if (coerce_hard)
{
newe = coerce_to_target_type(pstate, e,
exprType(e),
@@ -2150,12 +2216,72 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
COERCE_EXPLICIT_CAST,
-1);
if (newe == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_CANNOT_COERCE),
- errmsg("cannot cast type %s to %s",
- format_type_be(exprType(e)),
- format_type_be(coerce_type)),
- parser_errposition(pstate, exprLocation(e))));
+ {
+ if (can_coerce == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_CANNOT_COERCE),
+ errmsg("cannot cast type %s to %s",
+ format_type_be(exprType(e)),
+ format_type_be(coerce_type)),
+ parser_errposition(pstate, exprLocation(e))));
+ else
+ {
+ /*
+ * Can not coerce source expression to the target type, just
+ * append the transformed element expression to the list.
+ */
+ newe = e;
+ *can_coerce = false;
+ }
+ }
+ else if (can_coerce != NULL && expr_is_const)
+ {
+ Node *origexpr = newe;
+ Node *result;
+ bool have_collate_expr = false;
+
+ if (IsA(newe, CollateExpr))
+ have_collate_expr = true;
+
+ while (newe && IsA(newe, CollateExpr))
+ newe = (Node *) ((CollateExpr *) newe)->arg;
+
+ if (!IsA(newe, FuncExpr))
+ newe = origexpr;
+ else
+ {
+ /*
+ * Use evaluate_expr_safe to pre-evaluate simple constant
+ * cast expressions early, in a way that tolerate errors.
+ */
+ FuncExpr *newexpr = (FuncExpr *) copyObject(newe);
+
+ assign_expr_collations(pstate, (Node *) newexpr);
+
+ result = (Node *) evaluate_expr_safe((Expr *) newexpr,
+ coerce_type,
+ typmod,
+ coerce_type_coll);
+ if (result == NULL)
+ {
+ newe = e;
+ *can_coerce = false;
+ }
+ else if (have_collate_expr)
+ {
+ /* Reinstall top CollateExpr */
+ CollateExpr *coll = (CollateExpr *) origexpr;
+ CollateExpr *newcoll = makeNode(CollateExpr);
+
+ newcoll->arg = (Expr *) result;
+ newcoll->collOid = coll->collOid;
+ newcoll->location = coll->location;
+ newe = (Node *) newcoll;
+ }
+ else
+ newe = result;
+ }
+ }
}
else
newe = coerce_to_common_type(pstate, e,
@@ -2743,7 +2869,8 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
(A_ArrayExpr *) arg,
targetBaseType,
elementType,
- targetBaseTypmod);
+ targetBaseTypmod,
+ NULL);
}
else
expr = transformExprRecurse(pstate, arg);
@@ -2780,6 +2907,273 @@ 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;
+ TypeCast *tcast = (TypeCast *) tc->cast;
+ Node *source_expr;
+ Node *def_expr;
+ Node *cast_expr = NULL;
+ Oid inputType;
+ Oid targetType;
+ Oid targetBaseType;
+ Oid typeColl;
+ int32 targetTypmod;
+ int32 targetBaseTypmod;
+ bool can_coerce = true;
+ bool expr_is_const = false;
+ ParseLoc location;
+
+ /* Look up the type name first */
+ typenameTypeIdAndMod(pstate, tcast->typeName, &targetType, &targetTypmod);
+ targetBaseTypmod = targetTypmod;
+
+ typeColl = get_typcollation(targetType);
+
+ targetBaseType = getBaseTypeAndTypmod(targetType, &targetBaseTypmod);
+
+ /* now looking at DEFAULT expression */
+ def_expr = transformExpr(pstate, tc->raw_default, EXPR_KIND_CAST_DEFAULT);
+
+ def_expr = coerce_to_target_type(pstate, def_expr, exprType(def_expr),
+ targetType, targetTypmod,
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ exprLocation(def_expr));
+
+ assign_expr_collations(pstate, def_expr);
+
+ if (def_expr == NULL)
+ ereport(ERROR,
+ errcode(ERRCODE_CANNOT_COERCE),
+ errmsg("cannot coerce %s expression to type %s",
+ "CAST DEFAULT",
+ format_type_be(targetType)),
+ parser_coercion_errposition(pstate, exprLocation(tc->raw_default), def_expr));
+
+ /*
+ * The collation of DEFAULT expression must match the collation of the
+ * target type.
+ */
+ if (OidIsValid(typeColl))
+ {
+ Oid defColl;
+
+ defColl = exprCollation(def_expr);
+
+ if (OidIsValid(defColl) && typeColl != defColl)
+ ereport(ERROR,
+ errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("the collation of CAST DEFAULT expression conflicts with target type collation"),
+ errdetail("\"%s\" versus \"%s\"",
+ get_collation_name(typeColl),
+ get_collation_name(defColl)),
+ parser_errposition(pstate, exprLocation(def_expr)));
+ }
+
+ /*
+ * If the type cast 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(tcast->arg, A_ArrayExpr))
+ {
+ 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.
+ */
+ elementType = get_element_type(targetBaseType);
+
+ if (OidIsValid(elementType))
+ {
+ source_expr = transformArrayExpr(pstate,
+ (A_ArrayExpr *) tcast->arg,
+ targetBaseType,
+ elementType,
+ targetBaseTypmod,
+ &can_coerce);
+ }
+ else
+ source_expr = transformExprRecurse(pstate, tcast->arg);
+ }
+ else
+ source_expr = transformExprRecurse(pstate, tcast->arg);
+
+ inputType = exprType(source_expr);
+ if (inputType == InvalidOid)
+ return (Node *) NULL; /* return NULL if NULL input */
+
+ /*
+ * Location of the coercion is preferentially the location of 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 && IsA(source_expr, Const))
+ {
+ expr_is_const = true;
+
+ /* coerce UNKNOWN Const to the target type */
+ if (exprType(source_expr) == UNKNOWNOID)
+ {
+ cast_expr = CoerceUnknownConstSafe(pstate, source_expr,
+ targetType, targetTypmod,
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ location, false);
+ if (cast_expr == NULL)
+ can_coerce = false;
+ }
+ }
+
+ if (can_coerce && cast_expr == NULL)
+ {
+ cast_expr = coerce_to_target_type(pstate, source_expr, inputType,
+ targetType, targetTypmod,
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ location);
+
+ if (cast_expr == NULL)
+ can_coerce = false;
+ }
+
+ if (can_coerce)
+ {
+ Node *origexpr = cast_expr;
+ bool have_collate_expr = false;
+
+ Assert(cast_expr != NULL);
+
+ if (IsA(cast_expr, CollateExpr))
+ have_collate_expr = true;
+
+ while (cast_expr && IsA(cast_expr, CollateExpr))
+ cast_expr = (Node *) ((CollateExpr *) cast_expr)->arg;
+
+ if (IsA(cast_expr, FuncExpr))
+ {
+ HeapTuple tuple;
+ ListCell *lc;
+ Node *sexpr;
+ Node *result;
+ FuncExpr *fexpr = (FuncExpr *) cast_expr;
+
+ lc = list_head(fexpr->args);
+ sexpr = (Node *) lfirst(lc);
+
+ /* Look in pg_cast */
+ tuple = SearchSysCache2(CASTSOURCETARGET,
+ ObjectIdGetDatum(exprType(sexpr)),
+ ObjectIdGetDatum(targetType));
+
+ if (HeapTupleIsValid(tuple))
+ {
+ Form_pg_cast castForm = (Form_pg_cast) GETSTRUCT(tuple);
+ if (!castForm->casterrorsafe)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot cast type %s to %s when %s expression is specified in %s",
+ format_type_be(inputType),
+ format_type_be(targetType),
+ "DEFAULT",
+ "CAST ... ON CONVERSION ERROR"),
+ errhint("Explicit cast is defined but definition is not declared as safe"),
+ parser_errposition(pstate, exprLocation(source_expr)));
+ }
+ else
+ elog(ERROR, "cache lookup failed for pg_cast entry (%s cast to %s)",
+ format_type_be(inputType),
+ format_type_be(targetType));
+ ReleaseSysCache(tuple);
+
+ if (!expr_is_const)
+ cast_expr = origexpr;
+ else
+ {
+ /*
+ * Use evaluate_expr_safe to pre-evaluate simple constant cast
+ * expressions early, in a way that tolerate errors.
+ *
+ * Rationale:
+ * 1. When deparsing safe cast expressions (or in other cases),
+ * eval_const_expressions might be invoked, but it cannot
+ * handle errors gracefully.
+ * 2. If the cast expression involves only simple constants, we
+ * can safely evaluate it ahead of time. If the evaluation
+ * fails, it indicates that such a cast is not possible, and
+ * we can then fall back to the CAST DEFAULT expression.
+ * 3. Even if FuncExpr (for castfunc) node has three arguments,
+ * the second and third arguments will also be constants per
+ * coerce_to_target_type. Evaluating a FuncExpr whose
+ * arguments are all Const is safe.
+ */
+ FuncExpr *newexpr = (FuncExpr *) copyObject(cast_expr);
+
+ assign_expr_collations(pstate, (Node *) newexpr);
+
+ result = (Node *) evaluate_expr_safe((Expr *) newexpr,
+ targetType,
+ targetTypmod,
+ typeColl);
+ if (result == NULL)
+ {
+ /*
+ * It has been pre-evaluated that the source expression can
+ * not coerce to the target type.
+ */
+ can_coerce = false;
+ cast_expr = NULL;
+ }
+ else if (have_collate_expr)
+ {
+ /* Reinstall top CollateExpr */
+ CollateExpr *coll = (CollateExpr *) origexpr;
+ CollateExpr *newcoll = makeNode(CollateExpr);
+
+ newcoll->arg = (Expr *) result;
+ newcoll->collOid = coll->collOid;
+ newcoll->location = coll->location;
+ cast_expr = (Node *) newcoll;
+ }
+ else
+ cast_expr = result;
+ }
+ }
+ else
+ /* Nothing to do, restore cast_expr to its original value */
+ cast_expr = origexpr;
+ }
+
+ Assert(can_coerce || cast_expr == NULL);
+
+ result = makeNode(SafeTypeCastExpr);
+ result->source_expr = source_expr;
+ result->cast_expr = cast_expr;
+ result->default_expr = def_expr;
+ result->resulttype = targetType;
+ result->resulttypmod = targetTypmod;
+ result->resultcollid = typeColl;
+ result->location = location;
+
+ return (Node *) result;
+}
+
/*
* Handle an explicit COLLATE clause.
*
@@ -3193,6 +3587,8 @@ ParseExprKindName(ParseExprKind exprKind)
case EXPR_KIND_CHECK_CONSTRAINT:
case EXPR_KIND_DOMAIN_CHECK:
return "CHECK";
+ case EXPR_KIND_CAST_DEFAULT:
+ return "CAST DEFAULT";
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
return "DEFAULT";
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 778d69c6f3c..a90705b9847 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2743,6 +2743,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
case EXPR_KIND_DOMAIN_CHECK:
err = _("set-returning functions are not allowed in check constraints");
break;
+ case EXPR_KIND_CAST_DEFAULT:
+ err = _("set-returning functions are not allowed in CAST DEFAULT expressions");
+ break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
err = _("set-returning functions are not allowed in DEFAULT expressions");
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 905c975d83b..dc03cf4ce74 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1822,6 +1822,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/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index a464349ee33..c1a7031a96c 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -3289,6 +3289,14 @@ array_map(Datum arrayd,
/* Apply the given expression to source element */
values[i] = ExecEvalExpr(exprstate, econtext, &nulls[i]);
+ /* Exit early if the evaluation fails */
+ 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 79ec136231b..e3dd86a6520 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -10558,6 +10558,27 @@ get_rule_expr(Node *node, deparse_context *context,
}
break;
+ case T_SafeTypeCastExpr:
+ {
+ SafeTypeCastExpr *stcexpr = castNode(SafeTypeCastExpr, node);
+
+ /*
+ * We cannot deparsing cast_expr directly, since
+ * transformTypeSafeCast may have folded it into a simple
+ * constant or NULL. Instead, we use source_expr and
+ * default_expr to reconstruct the CAST DEFAULT clause.
+ */
+ 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/include/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat
index fbfd669587f..ca52cfcd086 100644
--- a/src/include/catalog/pg_cast.dat
+++ b/src/include/catalog/pg_cast.dat
@@ -19,65 +19,65 @@
# int2->int4->int8->numeric->float4->float8, while casts in the
# reverse direction are assignment-only.
{ castsource => 'int8', casttarget => 'int2', castfunc => 'int2(int8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'int4', castfunc => 'int4(int8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'float4', castfunc => 'float4(int8)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'float8', castfunc => 'float8(int8)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'numeric', castfunc => 'numeric(int8)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'int8', castfunc => 'int8(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'int4', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'float4', castfunc => 'float4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'float8', castfunc => 'float8(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'numeric', castfunc => 'numeric(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'int8', castfunc => 'int8(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'int2', castfunc => 'int2(int4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'float4', castfunc => 'float4(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'float8', castfunc => 'float8(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'numeric', castfunc => 'numeric(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'int8', castfunc => 'int8(float4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'int2', castfunc => 'int2(float4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'int4', castfunc => 'int4(float4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'float8', castfunc => 'float8(float4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'numeric',
- castfunc => 'numeric(float4)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'numeric(float4)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'int8', castfunc => 'int8(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'int2', castfunc => 'int2(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'int4', castfunc => 'int4(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'float4', castfunc => 'float4(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'numeric',
- castfunc => 'numeric(float8)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'numeric(float8)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'int8', castfunc => 'int8(numeric)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'int2', castfunc => 'int2(numeric)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'int4', castfunc => 'int4(numeric)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'float4',
- castfunc => 'float4(numeric)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'float4(numeric)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'float8',
- castfunc => 'float8(numeric)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'float8(numeric)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'money', casttarget => 'numeric', castfunc => 'numeric(money)',
castcontext => 'a', castmethod => 'f' },
{ castsource => 'numeric', casttarget => 'money', castfunc => 'money(numeric)',
@@ -89,13 +89,13 @@
# Allow explicit coercions between int4 and bool
{ castsource => 'int4', casttarget => 'bool', castfunc => 'bool(int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'int4', castfunc => 'int4(bool)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Allow explicit coercions between xid8 and xid
{ castsource => 'xid8', casttarget => 'xid', castfunc => 'xid(xid8)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# OID category: allow implicit conversion from any integral type (including
# int8, to support OID literals > 2G) to OID, as well as assignment coercion
@@ -106,13 +106,13 @@
# casts from text and varchar to regclass, which exist mainly to support
# legacy forms of nextval() and related functions.
{ castsource => 'int8', casttarget => 'oid', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'oid', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'oid', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regproc', castfunc => '0',
@@ -120,13 +120,13 @@
{ castsource => 'regproc', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regproc', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regproc', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regproc', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regproc', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regproc', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'regproc', casttarget => 'regprocedure', castfunc => '0',
@@ -138,13 +138,13 @@
{ castsource => 'regprocedure', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regprocedure', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regprocedure', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regprocedure', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regprocedure', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regprocedure', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regoper', castfunc => '0',
@@ -152,13 +152,13 @@
{ castsource => 'regoper', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regoper', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regoper', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regoper', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regoper', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regoper', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'regoper', casttarget => 'regoperator', castfunc => '0',
@@ -170,13 +170,13 @@
{ castsource => 'regoperator', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regoperator', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regoperator', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regoperator', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regoperator', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regoperator', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regclass', castfunc => '0',
@@ -184,13 +184,13 @@
{ castsource => 'regclass', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regclass', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regclass', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regclass', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regclass', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regclass', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regcollation', castfunc => '0',
@@ -198,13 +198,13 @@
{ castsource => 'regcollation', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regcollation', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regcollation', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regcollation', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regcollation', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regcollation', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regtype', castfunc => '0',
@@ -212,13 +212,13 @@
{ castsource => 'regtype', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regtype', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regtype', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regtype', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regtype', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regtype', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regconfig', castfunc => '0',
@@ -226,13 +226,13 @@
{ castsource => 'regconfig', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regconfig', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regconfig', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regconfig', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regconfig', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regconfig', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regdictionary', castfunc => '0',
@@ -240,31 +240,31 @@
{ castsource => 'regdictionary', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regdictionary', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regdictionary', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regdictionary', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regdictionary', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regdictionary', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'text', casttarget => 'regclass', castfunc => 'regclass',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'regclass', castfunc => 'regclass',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'oid', casttarget => 'regrole', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regrole', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regrole', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regrole', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regrole', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regrole', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regrole', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regnamespace', castfunc => '0',
@@ -272,13 +272,13 @@
{ castsource => 'regnamespace', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regnamespace', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regnamespace', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regnamespace', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regnamespace', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regnamespace', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regdatabase', castfunc => '0',
@@ -286,13 +286,13 @@
{ castsource => 'regdatabase', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regdatabase', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regdatabase', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regdatabase', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regdatabase', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regdatabase', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
@@ -302,57 +302,57 @@
{ castsource => 'text', casttarget => 'varchar', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'bpchar', casttarget => 'text', castfunc => 'text(bpchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bpchar', casttarget => 'varchar', castfunc => 'text(bpchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'text', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'varchar', casttarget => 'bpchar', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'char', casttarget => 'text', castfunc => 'text(char)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'char', casttarget => 'bpchar', castfunc => 'bpchar(char)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'char', casttarget => 'varchar', castfunc => 'text(char)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'name', casttarget => 'text', castfunc => 'text(name)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'name', casttarget => 'bpchar', castfunc => 'bpchar(name)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'name', casttarget => 'varchar', castfunc => 'varchar(name)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'text', casttarget => 'char', castfunc => 'char(text)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bpchar', casttarget => 'char', castfunc => 'char(text)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'char', castfunc => 'char(text)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'text', casttarget => 'name', castfunc => 'name(text)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bpchar', casttarget => 'name', castfunc => 'name(bpchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'name', castfunc => 'name(varchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
# Allow explicit coercions between bytea and integer types
{ castsource => 'int2', casttarget => 'bytea', castfunc => 'bytea(int2)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'bytea', castfunc => 'bytea(int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'bytea', castfunc => 'bytea(int8)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bytea', casttarget => 'int2', castfunc => 'int2(bytea)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bytea', casttarget => 'int4', castfunc => 'int4(bytea)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bytea', casttarget => 'int8', castfunc => 'int8(bytea)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Allow explicit coercions between int4 and "char"
{ castsource => 'char', casttarget => 'int4', castfunc => 'int4(char)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'char', castfunc => 'char(int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# pg_node_tree can be coerced to, but not from, text
{ castsource => 'pg_node_tree', casttarget => 'text', castfunc => '0',
@@ -378,73 +378,73 @@
# Datetime category
{ castsource => 'date', casttarget => 'timestamp',
- castfunc => 'timestamp(date)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timestamp(date)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'date', casttarget => 'timestamptz',
- castfunc => 'timestamptz(date)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timestamptz(date)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'time', casttarget => 'interval', castfunc => 'interval(time)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'time', casttarget => 'timetz', castfunc => 'timetz(time)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'date',
- castfunc => 'date(timestamp)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'date(timestamp)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'time',
- castfunc => 'time(timestamp)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'time(timestamp)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'timestamptz',
- castfunc => 'timestamptz(timestamp)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timestamptz(timestamp)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'date',
- castfunc => 'date(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'date(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'time',
- castfunc => 'time(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'time(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'timestamp',
- castfunc => 'timestamp(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'timestamp(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'timetz',
- castfunc => 'timetz(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'timetz(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'interval', casttarget => 'time', castfunc => 'time(interval)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timetz', casttarget => 'time', castfunc => 'time(timetz)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
# Geometric category
{ castsource => 'point', casttarget => 'box', castfunc => 'box(point)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'lseg', casttarget => 'point', castfunc => 'point(lseg)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'path', casttarget => 'polygon', castfunc => 'polygon(path)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'box', casttarget => 'point', castfunc => 'point(box)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'box', casttarget => 'lseg', castfunc => 'lseg(box)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'box', casttarget => 'polygon', castfunc => 'polygon(box)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'box', casttarget => 'circle', castfunc => 'circle(box)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'polygon', casttarget => 'point', castfunc => 'point(polygon)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'polygon', casttarget => 'path', castfunc => 'path',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'polygon', casttarget => 'box', castfunc => 'box(polygon)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'polygon', casttarget => 'circle',
- castfunc => 'circle(polygon)', castcontext => 'e', castmethod => 'f' },
+ castfunc => 'circle(polygon)', castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'circle', casttarget => 'point', castfunc => 'point(circle)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'circle', casttarget => 'box', castfunc => 'box(circle)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'circle', casttarget => 'polygon',
- castfunc => 'polygon(circle)', castcontext => 'e', castmethod => 'f' },
+ castfunc => 'polygon(circle)', castcontext => 'e', castmethod => 'f'},
# MAC address category
{ castsource => 'macaddr', casttarget => 'macaddr8', castfunc => 'macaddr8',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'macaddr8', casttarget => 'macaddr', castfunc => 'macaddr',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
# INET category
{ castsource => 'cidr', casttarget => 'inet', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'inet', casttarget => 'cidr', castfunc => 'cidr',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
# BitString category
{ castsource => 'bit', casttarget => 'varbit', castfunc => '0',
@@ -454,13 +454,13 @@
# Cross-category casts between bit and int4, int8
{ castsource => 'int8', casttarget => 'bit', castfunc => 'bit(int8,int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'bit', castfunc => 'bit(int4,int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bit', casttarget => 'int8', castfunc => 'int8(bit)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bit', casttarget => 'int4', castfunc => 'int4(bit)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Cross-category casts to and from TEXT
# We need entries here only for a few specialized cases where the behavior
@@ -471,68 +471,68 @@
# behavior will ensue when the automatic cast is applied instead of the
# pg_cast entry!
{ castsource => 'cidr', casttarget => 'text', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'inet', casttarget => 'text', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'text', castfunc => 'text(bool)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'xml', casttarget => 'text', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'text', casttarget => 'xml', castfunc => 'xml',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Cross-category casts to and from VARCHAR
# We support all the same casts as for TEXT.
{ castsource => 'cidr', casttarget => 'varchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'inet', casttarget => 'varchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'varchar', castfunc => 'text(bool)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'xml', casttarget => 'varchar', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'varchar', casttarget => 'xml', castfunc => 'xml',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Cross-category casts to and from BPCHAR
# We support all the same casts as for TEXT.
{ castsource => 'cidr', casttarget => 'bpchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'inet', casttarget => 'bpchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'bpchar', castfunc => 'text(bool)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'xml', casttarget => 'bpchar', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'bpchar', casttarget => 'xml', castfunc => 'xml',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Length-coercion functions
{ castsource => 'bpchar', casttarget => 'bpchar',
castfunc => 'bpchar(bpchar,int4,bool)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'varchar',
castfunc => 'varchar(varchar,int4,bool)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'time', casttarget => 'time', castfunc => 'time(time,int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'timestamp',
castfunc => 'timestamp(timestamp,int4)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'timestamptz',
castfunc => 'timestamptz(timestamptz,int4)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'interval', casttarget => 'interval',
castfunc => 'interval(interval,int4)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timetz', casttarget => 'timetz',
- castfunc => 'timetz(timetz,int4)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timetz(timetz,int4)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bit', casttarget => 'bit', castfunc => 'bit(bit,int4,bool)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varbit', casttarget => 'varbit', castfunc => 'varbit',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'numeric',
- castfunc => 'numeric(numeric,int4)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'numeric(numeric,int4)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
# json to/from jsonb
{ castsource => 'json', casttarget => 'jsonb', castfunc => '0',
@@ -542,36 +542,36 @@
# jsonb to numeric and bool types
{ castsource => 'jsonb', casttarget => 'bool', castfunc => 'bool(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'numeric', castfunc => 'numeric(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'int2', castfunc => 'int2(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'int4', castfunc => 'int4(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'int8', castfunc => 'int8(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'float4', castfunc => 'float4(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'float8', castfunc => 'float8(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# range to multirange
{ castsource => 'int4range', casttarget => 'int4multirange',
castfunc => 'int4multirange(int4range)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8range', casttarget => 'int8multirange',
castfunc => 'int8multirange(int8range)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numrange', casttarget => 'nummultirange',
castfunc => 'nummultirange(numrange)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'daterange', casttarget => 'datemultirange',
castfunc => 'datemultirange(daterange)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'tsrange', casttarget => 'tsmultirange',
- castfunc => 'tsmultirange(tsrange)', castcontext => 'e', castmethod => 'f' },
+ castfunc => 'tsmultirange(tsrange)', castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'tstzrange', casttarget => 'tstzmultirange',
castfunc => 'tstzmultirange(tstzrange)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
]
diff --git a/src/include/catalog/pg_cast.h b/src/include/catalog/pg_cast.h
index 6a0ca337153..218d81d535a 100644
--- a/src/include/catalog/pg_cast.h
+++ b/src/include/catalog/pg_cast.h
@@ -47,6 +47,10 @@ CATALOG(pg_cast,2605,CastRelationId)
/* cast method */
char castmethod;
+
+ /* cast function error safe */
+ bool casterrorsafe BKI_DEFAULT(f);
+
} FormData_pg_cast;
/* ----------------
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 75366203706..34a884e3196 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_SAFETYPE_CAST */
+ 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);
+extern 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/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 18ae8f0d4bb..f59494c8c6f 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1059,6 +1059,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 ecbddd12e1b..c47d412b560 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -400,6 +400,17 @@ typedef struct TypeCast
ParseLoc location; /* token location, or -1 if unknown */
} TypeCast;
+/*
+ * SafeTypeCast - a CAST(source_expr AS target_type) DEFAULT ON CONVERSION ERROR
+ * construct
+ */
+typedef struct SafeTypeCast
+{
+ NodeTag type;
+ Node *cast; /* TypeCast expression */
+ Node *raw_default; /* DEFAULT expression */
+} SafeTypeCast;
+
/*
* CollateClause - a COLLATE expression
*/
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 1b4436f2ff6..c3bd722cb2f 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -769,6 +769,42 @@ 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
+{
+ Expr xpr;
+
+ /* transformed expression being casted */
+ Node *source_expr;
+
+ /*
+ * The transformed cast expression; NULL if we can not cocerce source
+ * expression to the target type
+ */
+ Node *cast_expr pg_node_attr(query_jumble_ignore);
+
+ /* Fall back to the default expression if cast evaluation fails */
+ Node *default_expr;
+
+ /* cast result data type */
+ Oid resulttype;
+
+ /* cast result data type typmod (usually -1) */
+ int32 resulttypmod;
+
+ /* cast result data type collation */
+ Oid resultcollid;
+
+ /* Original SafeTypeCastExpr's location */
+ ParseLoc location;
+
+} SafeTypeCastExpr;
+
/*
* FuncExpr - expression node for a function call
*
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index f7d07c84542..9f5b32e0360 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -67,6 +67,8 @@ typedef enum ParseExprKind
EXPR_KIND_VALUES_SINGLE, /* single-row VALUES (in INSERT only) */
EXPR_KIND_CHECK_CONSTRAINT, /* CHECK constraint for a table */
EXPR_KIND_DOMAIN_CHECK, /* CHECK constraint for a domain */
+ EXPR_KIND_CAST_DEFAULT, /* default expression in
+ CAST DEFAULT ON CONVERSION ERROR */
EXPR_KIND_COLUMN_DEFAULT, /* default value for a table column */
EXPR_KIND_FUNCTION_DEFAULT, /* default parameter value for function */
EXPR_KIND_INDEX_EXPRESSION, /* index expression */
diff --git a/src/test/regress/expected/cast.out b/src/test/regress/expected/cast.out
new file mode 100644
index 00000000000..9fbd6310ea6
--- /dev/null
+++ b/src/test/regress/expected/cast.out
@@ -0,0 +1,767 @@
+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 DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(1::numeric AS money DEFAULT NULL ON CONVERSION E...
+ ^
+HINT: Explicit cast is defined but definition is not declared as safe
+SELECT CAST(1111 AS "char" DEFAULT NULL ON CONVERSION ERROR);
+ char
+------
+
+(1 row)
+
+--the default expression’s collation should match the collation of the cast
+--target type
+VALUES (CAST('error' AS text DEFAULT '1' COLLATE "C" ON CONVERSION ERROR));
+ERROR: the collation of CAST DEFAULT expression conflicts with target type collation
+LINE 1: VALUES (CAST('error' AS text DEFAULT '1' COLLATE "C" ON CONV...
+ ^
+DETAIL: "default" versus "C"
+VALUES (CAST('error' AS int2vector DEFAULT '1 3' ON CONVERSION ERROR));
+ column1
+---------
+ 1 3
+(1 row)
+
+VALUES (CAST('error' AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR));
+ column1
+---------
+ {"1 3"}
+(1 row)
+
+-- test source expression contain subquery
+SELECT CAST((SELECT b FROM generate_series(1, 1), (VALUES('H')) s(b))
+ AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR);
+ b
+---------
+ {"1 3"}
+(1 row)
+
+SELECT CAST((SELECT ARRAY((SELECT b FROM generate_series(1, 2) g, (VALUES('H')) s(b))))
+ AS INT[]
+ DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+CREATE FUNCTION ret_int8() RETURNS BIGINT AS
+$$
+BEGIN RETURN 2147483648; END;
+$$
+LANGUAGE plpgsql IMMUTABLE;
+SELECT CAST('a' as int DEFAULT ret_int8() ON CONVERSION ERROR); --error
+ERROR: integer out of range
+SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERROR); --error
+ERROR: cannot coerce CAST DEFAULT expression to type date
+LINE 1: SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERR...
+ ^
+-- test array coerce
+SELECT CAST(array['a'::text] AS int[] DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR: invalid input syntax for type integer: "a"
+SELECT CAST(array['a'] AS int[] DEFAULT NULL ON CONVERSION ERROR); --ok
+ array
+-------
+
+(1 row)
+
+SELECT CAST(array[11.12] AS date[] DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+SELECT CAST(array[11111111111111111] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+SELECT CAST(array[['abc'],[456]] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+SELECT CAST('{123,abc,456}' AS int[] DEFAULT '{-789}' ON CONVERSION ERROR);
+ int4
+--------
+ {-789}
+(1 row)
+
+SELECT CAST('{234,def,567}'::text[] AS integer[] DEFAULT '{-1011}' ON CONVERSION ERROR);
+ int4
+---------
+ {-1011}
+(1 row)
+
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS int[] DEFAULT '{1,2}' ON CONVERSION ERROR);
+ array
+-------
+ {1,2}
+(1 row)
+
+SELECT CAST(ARRAY[['1', '2'], ['three', 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --ok
+ array
+---------
+ {21,22}
+(1 row)
+
+SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --error
+ERROR: invalid input syntax for type integer: "three"
+LINE 1: SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] ...
+ ^
+SELECT CAST(ARRAY[1, 'three'::int] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --error
+ERROR: invalid input syntax for type integer: "three"
+LINE 1: SELECT CAST(ARRAY[1, 'three'::int] AS int[] DEFAULT '{21,22}...
+ ^
+-- test valid DEFAULT expression for CAST = ON CONVERSION ERROR
+CREATE 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: set-returning functions are not allowed in CAST DEFAULT expressions
+LINE 1: SELECT CAST('a' as int DEFAULT ret_setint() ON CONVERSION ER...
+ ^
+SELECT CAST(c::date::json as date DEFAULT NULL ON CONVERSION ERROR) FROM tcast; --error
+ERROR: cannot cast type date to json
+LINE 1: SELECT CAST(c::date::json as date DEFAULT NULL ON CONVERSION...
+ ^
+SELECT CAST('a'::int as int DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR: invalid input syntax for type integer: "a"
+LINE 1: SELECT CAST('a'::int as int DEFAULT NULL ON CONVERSION ERROR...
+ ^
+SELECT CAST('a' as int DEFAULT NULL ON CONVERSION ERROR); --ok
+ int4
+------
+
+(1 row)
+
+SELECT CAST('a' as int DEFAULT sum(1) ON CONVERSION ERROR); --error
+ERROR: aggregate functions are not allowed in CAST DEFAULT expressions
+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: window functions are not allowed in CAST DEFAULT expressions
+LINE 1: SELECT CAST('a' as int DEFAULT sum(1) over() ON CONVERSION E...
+ ^
+SELECT CAST('a' as int DEFAULT (SELECT NULL) ON CONVERSION ERROR); --error
+ERROR: cannot use subquery in CAST DEFAULT expression
+LINE 1: SELECT CAST('a' as int DEFAULT (SELECT NULL) 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 DOMAIN d_varchar as varchar(3) NOT NULL;
+CREATE TYPE comp_domain_with_typmod AS (a d_char3_not_null, b int);
+CREATE TYPE comp2 AS (a d_varchar);
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION ERROR); --error
+ERROR: value too long for type character varying(3)
+LINE 1: SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION...
+ ^
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(123)' ON CONVERSION ERROR); --ok
+ comp2
+-------
+ (123)
+(1 row)
+
+SELECT CAST(11 AS d_int42 DEFAULT 41 ON CONVERSION ERROR); --error
+ERROR: value for domain d_int42 violates check constraint "d_int42_check"
+SELECT CAST(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)'...
+ ^
+-----safe cast with geometry data type
+SELECT CAST('(1,2)'::point AS box DEFAULT NULL ON CONVERSION ERROR);
+ box
+-------------
+ (1,2),(1,2)
+(1 row)
+
+SELECT CAST('[(NaN,1),(NaN,infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+----------------
+ (NaN,Infinity)
+(1 row)
+
+SELECT CAST('[(1e+300,Infinity),(1e+300,Infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+-------------------
+ (1e+300,Infinity)
+(1 row)
+
+SELECT CAST('[(1,2),(3,4)]'::path as polygon DEFAULT NULL ON CONVERSION ERROR);
+ polygon
+---------
+
+(1 row)
+
+SELECT CAST('(NaN,1.0,NaN,infinity)'::box AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+----------------
+ (NaN,Infinity)
+(1 row)
+
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS lseg DEFAULT NULL ON CONVERSION ERROR);
+ lseg
+---------------
+ [(2,2),(0,0)]
+(1 row)
+
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS polygon DEFAULT NULL ON CONVERSION ERROR);
+ polygon
+---------------------------
+ ((0,0),(0,2),(2,2),(2,0))
+(1 row)
+
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS path DEFAULT NULL ON CONVERSION ERROR);
+ path
+------
+
+(1 row)
+
+SELECT CAST('(2.0,infinity,NaN,infinity)'::box AS circle DEFAULT NULL ON CONVERSION ERROR);
+ circle
+----------------------
+ <(NaN,Infinity),NaN>
+(1 row)
+
+SELECT CAST('(NaN,0.0),(2.0,4.0),(0.0,infinity)'::polygon AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+----------------
+ (NaN,Infinity)
+(1 row)
+
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS path DEFAULT NULL ON CONVERSION ERROR);
+ path
+---------------------
+ ((2,0),(2,4),(0,0))
+(1 row)
+
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS box DEFAULT NULL ON CONVERSION ERROR);
+ box
+-------------
+ (2,4),(0,0)
+(1 row)
+
+SELECT CAST('(NaN,infinity),(2.0,4.0),(0.0,infinity)'::polygon AS circle DEFAULT NULL ON CONVERSION ERROR);
+ circle
+----------------------
+ <(NaN,Infinity),NaN>
+(1 row)
+
+SELECT CAST('<(5,1),3>'::circle AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+-------
+ (5,1)
+(1 row)
+
+SELECT CAST('<(3,5),0>'::circle as box DEFAULT NULL ON CONVERSION ERROR);
+ box
+-------------
+ (3,5),(3,5)
+(1 row)
+
+-- not supported because the cast from circle to polygon is implemented as a SQL
+-- function, which cannot be error-safe.
+SELECT CAST('<(3,5),0>'::circle as polygon DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type circle to polygon when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST('<(3,5),0>'::circle as polygon DEFAULT NULL ON C...
+ ^
+HINT: Explicit cast is defined but definition is not declared as safe
+-----safe cast from/to money type is not supported, all the following would result error
+SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type bigint to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION E...
+ ^
+HINT: Explicit cast is defined but definition is not declared as safe
+SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type integer to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION E...
+ ^
+HINT: Explicit cast is defined but definition is not declared as safe
+SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type numeric to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSIO...
+ ^
+HINT: Explicit cast is defined but definition is not declared as safe
+SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type money to numeric when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSIO...
+ ^
+HINT: Explicit cast is defined but definition is not declared as safe
+-----safe cast from bytea type to other data types
+SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT NULL ON CONVERSION ERROR);
+ int8
+------
+
+(1 row)
+
+SELECT CAST('\x123456789A'::bytea AS int4 DEFAULT NULL ON CONVERSION ERROR);
+ int4
+------
+
+(1 row)
+
+SELECT CAST('\x123456'::bytea AS int2 DEFAULT NULL ON CONVERSION ERROR);
+ int2
+------
+
+(1 row)
+
+-----safe cast from bit type to other data types
+SELECT CAST('111111111100001'::bit(100) AS INT DEFAULT NULL ON CONVERSION ERROR);
+ int4
+------
+
+(1 row)
+
+SELECT CAST ('111111111100001'::bit(100) AS INT8 DEFAULT NULL ON CONVERSION ERROR);
+ int8
+------
+
+(1 row)
+
+-----safe cast from text type to other data types
+select CAST('a.b.c.d'::text as regclass default NULL on conversion error);
+ regclass
+----------
+
+(1 row)
+
+CREATE TABLE test_safecast(col0 text);
+INSERT INTO test_safecast(col0) VALUES ('<value>one</value');
+SELECT col0 as text,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) as to_regclass,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) IS NULL as expect_true,
+ CAST(col0 AS "char" DEFAULT NULL ON CONVERSION ERROR) as to_char,
+ CAST(col0 AS name DEFAULT NULL ON CONVERSION ERROR) as to_name,
+ CAST(col0 AS xml DEFAULT NULL ON CONVERSION ERROR) as to_xml
+FROM test_safecast;
+ text | to_regclass | expect_true | to_char | to_name | to_xml
+-------------------+-------------+-------------+---------+-------------------+--------
+ <value>one</value | | t | < | <value>one</value |
+(1 row)
+
+SELECT CAST('192.168.1.x' as inet DEFAULT NULL ON CONVERSION ERROR);
+ inet
+------
+
+(1 row)
+
+SELECT CAST('192.168.1.2/30' as cidr DEFAULT NULL ON CONVERSION ERROR);
+ cidr
+------
+
+(1 row)
+
+SELECT CAST('22:00:5c:08:55:08:01:02'::macaddr8 as macaddr DEFAULT NULL ON CONVERSION ERROR);
+ macaddr
+---------
+
+(1 row)
+
+-----safe cast betweeen range data types
+SELECT CAST('[1,2]'::int4range AS int4multirange DEFAULT NULL ON CONVERSION ERROR);
+ int4multirange
+----------------
+ {[1,3)}
+(1 row)
+
+SELECT CAST('[1,2]'::int8range AS int8multirange DEFAULT NULL ON CONVERSION ERROR);
+ int8multirange
+----------------
+ {[1,3)}
+(1 row)
+
+SELECT CAST('[1,2]'::numrange AS nummultirange DEFAULT NULL ON CONVERSION ERROR);
+ nummultirange
+---------------
+ {[1,2]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::daterange AS datemultirange DEFAULT NULL ON CONVERSION ERROR);
+ datemultirange
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::tsrange AS tsmultirange DEFAULT NULL ON CONVERSION ERROR);
+ tsmultirange
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::tstzrange AS tstzmultirange DEFAULT NULL ON CONVERSION ERROR);
+ tstzmultirange
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+-----safe cast betweeen numeric data types
+CREATE TABLE test_safecast1(
+ col1 float4, col2 float8, col3 numeric, col4 numeric[],
+ col5 int2 default 32767,
+ col6 int4 default 32768,
+ col7 int8 default 4294967296);
+INSERT INTO test_safecast1 VALUES('11.1234', '11.1234', '9223372036854775808', '{11.1234}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('inf', 'inf', 'inf', '{11.1234, 12, inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('-inf', '-inf', '-inf', '{11.1234, 12, -inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('NaN', 'NaN', 'NaN', '{11.1234, 12, -inf, NaN}'::numeric[]);
+SELECT col5 as int2,
+ CAST(col5 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col5 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col5 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col5 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col5 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col5 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col5 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col5 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ int2 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+(4 rows)
+
+SELECT col6 as int4,
+ CAST(col6 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col6 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col6 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col6 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col6 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col6 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col6 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col6 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ int4 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+(4 rows)
+
+SELECT col7 as int8,
+ CAST(col7 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col7 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col7 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col7 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col7 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col7 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col7 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col7 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ int8 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+------------+---------+---------+--------+------------+-------------+------------+------------+------------------
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+(4 rows)
+
+SELECT col3 as numeric,
+ CAST(col3 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col3 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col3 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col3 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col3 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col3 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col3 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col3 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ numeric | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+---------------------+---------+---------+--------+---------+-------------+----------------------+---------------------+------------------
+ 9223372036854775808 | | | | | 9.22337e+18 | 9.22337203685478e+18 | 9223372036854775808 |
+ Infinity | | | | | Infinity | Infinity | Infinity |
+ -Infinity | | | | | -Infinity | -Infinity | -Infinity |
+ NaN | | | | | NaN | NaN | NaN | NaN
+(4 rows)
+
+SELECT col4 as num_arr,
+ CAST(col4 AS int2[] DEFAULT NULL ON CONVERSION ERROR) as int2arr,
+ CAST(col4 AS int4[] DEFAULT NULL ON CONVERSION ERROR) as int4arr,
+ CAST(col4 as int8[] DEFAULT NULL ON CONVERSION ERROR) as int8arr,
+ CAST(col4 as float4[] DEFAULT NULL ON CONVERSION ERROR) as f4arr,
+ CAST(col4 as float8[] DEFAULT NULL ON CONVERSION ERROR) as f8arr,
+ CAST(col4 as numeric(10,1)[] DEFAULT NULL ON CONVERSION ERROR) as numarr
+FROM test_safecast1;
+ num_arr | int2arr | int4arr | int8arr | f4arr | f8arr | numarr
+----------------------------+---------+---------+---------+----------------------------+----------------------------+--------
+ {11.1234} | {11} | {11} | {11} | {11.1234} | {11.1234} | {11.1}
+ {11.1234,12,Infinity,NaN} | | | | {11.1234,12,Infinity,NaN} | {11.1234,12,Infinity,NaN} |
+ {11.1234,12,-Infinity,NaN} | | | | {11.1234,12,-Infinity,NaN} | {11.1234,12,-Infinity,NaN} |
+ {11.1234,12,-Infinity,NaN} | | | | {11.1234,12,-Infinity,NaN} | {11.1234,12,-Infinity,NaN} |
+(4 rows)
+
+SELECT col1 as float4,
+ CAST(col1 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col1 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col1 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col1 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col1 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col1 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col1 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col1 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ float4 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-----------+---------+---------+--------+---------+-----------+------------------+------------+------------------
+ 11.1234 | 11 | 11 | | 11 | 11.1234 | 11.1233997344971 | 11.1234 | 11.1
+ Infinity | | | | | Infinity | Infinity | Infinity |
+ -Infinity | | | | | -Infinity | -Infinity | -Infinity |
+ NaN | | | | | NaN | NaN | NaN | NaN
+(4 rows)
+
+SELECT col2 as float8,
+ CAST(col2 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col2 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col2 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col2 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col2 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col2 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col2 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col2 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ float8 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-----------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 11.1234 | 11 | 11 | | 11 | 11.1234 | 11.1234 | 11.1234 | 11.1
+ Infinity | | | | | Infinity | Infinity | Infinity |
+ -Infinity | | | | | -Infinity | -Infinity | -Infinity |
+ NaN | | | | | NaN | NaN | NaN | NaN
+(4 rows)
+
+-- date/timestamp/interval related safe type cast
+CREATE TABLE test_safecast2(
+ col0 date, col1 timestamp, col2 timestamptz,
+ col3 interval, col4 time, col5 timetz);
+INSERT INTO test_safecast2 VALUES
+('-infinity', '-infinity', '-infinity', '-infinity',
+ '2003-03-07 15:36:39 America/New_York', '2003-07-07 15:36:39 America/New_York'),
+('-infinity', 'infinity', 'infinity', 'infinity',
+ '11:59:59.99 PM', '11:59:59.99 PM PDT');
+SELECT col0 as date,
+ CAST(col0 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col0 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col0 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col0 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col0 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+ date | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale
+-----------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity | -infinity | | | -infinity
+ -infinity | -infinity | -infinity | | | -infinity
+(2 rows)
+
+SELECT col1 as timestamp,
+ CAST(col1 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col1 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col1 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col1 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col1 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+ timestamp | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale
+-----------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity | -infinity | | | -infinity
+ infinity | infinity | infinity | | | infinity
+(2 rows)
+
+SELECT col2 as timestamptz,
+ CAST(col2 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col2 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col2 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col2 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col2 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+ timestamptz | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale
+-------------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity | -infinity | | | -infinity
+ infinity | infinity | infinity | | | infinity
+(2 rows)
+
+SELECT col3 as interval,
+ CAST(col3 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col3 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col3 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col3 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col3 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale,
+ CAST(col3 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+ interval | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale | to_interval_scale
+-----------+----------------+---------+----------+-----------+--------------------+-------------------
+ -infinity | | | | | | -infinity
+ infinity | | | | | | infinity
+(2 rows)
+
+SELECT col4 as time,
+ CAST(col4 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col4 AS timetz DEFAULT NULL ON CONVERSION ERROR) IS NOT NULL as to_timetz,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+ time | to_times | to_timetz | to_interval | to_interval_scale
+-------------+-------------+-----------+-------------------------------+-------------------------------
+ 15:36:39 | 15:36:39 | t | @ 15 hours 36 mins 39 secs | @ 15 hours 36 mins 39 secs
+ 23:59:59.99 | 23:59:59.99 | t | @ 23 hours 59 mins 59.99 secs | @ 23 hours 59 mins 59.99 secs
+(2 rows)
+
+SELECT col5 as timetz,
+ CAST(col5 AS time DEFAULT NULL ON CONVERSION ERROR) as to_time,
+ CAST(col5 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_time_scale,
+ CAST(col5 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col5 AS timetz(6) DEFAULT NULL ON CONVERSION ERROR) as to_timetz_scale,
+ CAST(col5 AS interval DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col5 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+ timetz | to_time | to_time_scale | to_timetz | to_timetz_scale | to_interval | to_interval_scale
+----------------+-------------+---------------+----------------+-----------------+-------------+-------------------
+ 15:36:39-04 | 15:36:39 | 15:36:39 | 15:36:39-04 | 15:36:39-04 | |
+ 23:59:59.99-07 | 23:59:59.99 | 23:59:59.99 | 23:59:59.99-07 | 23:59:59.99-07 | |
+(2 rows)
+
+CREATE TABLE test_safecast3(col0 jsonb);
+INSERT INTO test_safecast3(col0) VALUES ('"test"');
+SELECT col0 as jsonb,
+ CAST(col0 AS integer DEFAULT NULL ON CONVERSION ERROR) as to_integer,
+ CAST(col0 AS numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col0 AS bigint DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col0 AS float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col0 AS float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col0 AS boolean DEFAULT NULL ON CONVERSION ERROR) as to_bool,
+ CAST(col0 AS smallint DEFAULT NULL ON CONVERSION ERROR) as to_smallint
+FROM test_safecast3;
+ jsonb | to_integer | to_numeric | to_int8 | to_float4 | to_float8 | to_bool | to_smallint
+--------+------------+------------+---------+-----------+-----------+---------+-------------
+ "test" | | | | | | |
+(1 row)
+
+-- test deparse
+CREATE DOMAIN d_int_arr as int[];
+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 test_safecast1,
+ CAST(ARRAY[['1'], ['three'],['a']] AS int[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1,
+ CAST(ARRAY[['1', '2'], ['three', 'a']] AS date[] DEFAULT NULL ON CONVERSION ERROR) as cast2,
+ CAST(ARRAY['three'] AS INT[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast3;
+\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 test_safecast1,
+ CAST(ARRAY[ARRAY[1], ARRAY['three'], ARRAY['a']] AS integer[] DEFAULT '{1,2}'::integer[] ON CONVERSION ERROR) AS cast1,
+ CAST(ARRAY[ARRAY['1', '2'], ARRAY['three', 'a']] AS date[] DEFAULT NULL::date[] ON CONVERSION ERROR) AS cast2,
+ CAST(ARRAY['three'] AS integer[] DEFAULT '{1,2}'::integer[] ON CONVERSION ERROR) AS cast3
+CREATE VIEW safecastview1 AS
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS d_int_arr DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1,
+ CAST(ARRAY[['1', '2'], ['three', 'a']] AS d_int_arr DEFAULT '{21,22}' ON CONVERSION ERROR) as cast2;
+\sv safecastview1
+CREATE OR REPLACE VIEW public.safecastview1 AS
+ SELECT CAST(ARRAY[ARRAY[1], ARRAY['three'], ARRAY['a']] AS d_int_arr DEFAULT '{1,2}'::integer[]::d_int_arr ON CONVERSION ERROR) AS cast1,
+ CAST(ARRAY[ARRAY[1, 2], ARRAY['three', 'a']] AS d_int_arr DEFAULT '{21,22}'::integer[]::d_int_arr ON CONVERSION ERROR) AS cast2
+SELECT * FROM safecastview1;
+ cast1 | cast2
+-------+---------
+ {1,2} | {21,22}
+(1 row)
+
+--error, default expression is mutable
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as int DEFAULT random(min=>1, max=>1) ON CONVERSION ERROR)));
+ERROR: functions in index expression must be marked IMMUTABLE
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as xid DEFAULT NULL ON CONVERSION ERROR)));
+ERROR: data type xid has no default operator class for access method "btree"
+HINT: You must specify an operator class for the index or define a default operator class for the data type.
+CREATE INDEX test_safecast3_idx ON test_safecast3((CAST(col0 as int DEFAULT NULL ON CONVERSION ERROR))); --ok
+SELECT pg_get_indexdef('test_safecast3_idx'::regclass);
+ pg_get_indexdef
+------------------------------------------------------------------------------------------------------------------------------------------
+ CREATE INDEX test_safecast3_idx ON public.test_safecast3 USING btree ((CAST(col0 AS integer DEFAULT NULL::integer ON CONVERSION ERROR)))
+(1 row)
+
+DROP TABLE test_safecast;
+DROP TABLE test_safecast1;
+DROP TABLE test_safecast2;
+DROP TABLE test_safecast3;
+DROP TABLE tcast;
+DROP FUNCTION ret_int8;
+DROP FUNCTION ret_setint;
+DROP TYPE comp_domain_with_typmod;
+DROP TYPE comp2;
+DROP DOMAIN d_varchar;
+DROP DOMAIN d_int42;
+DROP DOMAIN d_char3_not_null;
+RESET extra_float_digits;
diff --git a/src/test/regress/expected/create_cast.out b/src/test/regress/expected/create_cast.out
index 0e69644bca2..f4035c338aa 100644
--- a/src/test/regress/expected/create_cast.out
+++ b/src/test/regress/expected/create_cast.out
@@ -88,6 +88,11 @@ SELECT 1234::int4::casttesttype; -- Should work now
bar1234
(1 row)
+SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- error
+ERROR: cannot cast type integer to casttesttype when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVE...
+ ^
+HINT: Explicit cast is defined but definition is not declared as safe
-- check dependencies generated for that
SELECT pg_describe_object(classid, objid, objsubid) as obj,
pg_describe_object(refclassid, refobjid, refobjsubid) as objref,
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index a357e1d0c0e..81ea244859f 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -943,8 +943,8 @@ SELECT *
FROM pg_cast c
WHERE castsource = 0 OR casttarget = 0 OR castcontext NOT IN ('e', 'a', 'i')
OR castmethod NOT IN ('f', 'b' ,'i');
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Check that castfunc is nonzero only for cast methods that need a function,
@@ -953,8 +953,8 @@ SELECT *
FROM pg_cast c
WHERE (castmethod = 'f' AND castfunc = 0)
OR (castmethod IN ('b', 'i') AND castfunc <> 0);
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Look for casts to/from the same type that aren't length coercion functions.
@@ -963,15 +963,15 @@ WHERE (castmethod = 'f' AND castfunc = 0)
SELECT *
FROM pg_cast c
WHERE castsource = casttarget AND castfunc = 0;
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
SELECT c.*
FROM pg_cast c, pg_proc p
WHERE c.castfunc = p.oid AND p.pronargs < 2 AND castsource = casttarget;
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Look for cast functions that don't have the right signature. The
@@ -989,8 +989,8 @@ WHERE c.castfunc = p.oid AND
OR (c.castsource = 'character'::regtype AND
p.proargtypes[0] = 'text'::regtype))
OR NOT binary_coercible(p.prorettype, c.casttarget));
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
SELECT c.*
@@ -998,8 +998,8 @@ FROM pg_cast c, pg_proc p
WHERE c.castfunc = p.oid AND
((p.pronargs > 1 AND p.proargtypes[1] != 'int4'::regtype) OR
(p.pronargs > 2 AND p.proargtypes[2] != 'bool'::regtype));
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Look for binary compatible casts that do not have the reverse
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index a0f5fab0f5d..c6936f64a87 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -81,7 +81,7 @@ test: create_table_like alter_generic alter_operator misc async dbsize merge mis
# collate.linux.utf8 and collate.icu.utf8 tests cannot be run in parallel with each other
# psql depends on create_am
# amutils depends on geometry, create_index_spgist, hash_index, brin
-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 00000000000..f93ddaa1bb2
--- /dev/null
+++ b/src/test/regress/sql/cast.sql
@@ -0,0 +1,335 @@
+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);
+SELECT CAST(1111 AS "char" DEFAULT NULL ON CONVERSION ERROR);
+--the default expression’s collation should match the collation of the cast
+--target type
+VALUES (CAST('error' AS text DEFAULT '1' COLLATE "C" ON CONVERSION ERROR));
+
+VALUES (CAST('error' AS int2vector DEFAULT '1 3' ON CONVERSION ERROR));
+VALUES (CAST('error' AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR));
+
+-- test source expression contain subquery
+SELECT CAST((SELECT b FROM generate_series(1, 1), (VALUES('H')) s(b))
+ AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR);
+SELECT CAST((SELECT ARRAY((SELECT b FROM generate_series(1, 2) g, (VALUES('H')) s(b))))
+ AS INT[]
+ DEFAULT NULL ON CONVERSION ERROR);
+
+CREATE FUNCTION ret_int8() RETURNS BIGINT AS
+$$
+BEGIN RETURN 2147483648; END;
+$$
+LANGUAGE plpgsql IMMUTABLE;
+
+SELECT CAST('a' as int DEFAULT ret_int8() ON CONVERSION ERROR); --error
+SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERROR); --error
+
+-- test array coerce
+SELECT CAST(array['a'::text] AS int[] DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(array['a'] AS int[] DEFAULT NULL ON CONVERSION ERROR); --ok
+SELECT CAST(array[11.12] AS date[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(array[11111111111111111] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(array[['abc'],[456]] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('{123,abc,456}' AS int[] 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 int[] DEFAULT '{1,2}' ON CONVERSION ERROR);
+SELECT CAST(ARRAY[['1', '2'], ['three', 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --ok
+SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --error
+SELECT CAST(ARRAY[1, 'three'::int] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --error
+
+-- test valid DEFAULT expression for CAST = ON CONVERSION ERROR
+CREATE 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(c::date::json as date DEFAULT NULL ON CONVERSION ERROR) FROM tcast; --error
+SELECT CAST('a'::int as int DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST('a' as int DEFAULT NULL ON CONVERSION ERROR); --ok
+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 (SELECT NULL) 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 DOMAIN d_varchar as varchar(3) NOT NULL;
+CREATE TYPE comp_domain_with_typmod AS (a d_char3_not_null, b int);
+CREATE TYPE comp2 AS (a d_varchar);
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION ERROR); --error
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(123)' ON CONVERSION ERROR); --ok
+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
+
+-----safe cast with geometry data type
+SELECT CAST('(1,2)'::point AS box DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[(NaN,1),(NaN,infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[(1e+300,Infinity),(1e+300,Infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[(1,2),(3,4)]'::path as polygon DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NaN,1.0,NaN,infinity)'::box AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS lseg DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS polygon DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS path DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,infinity,NaN,infinity)'::box AS circle DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NaN,0.0),(2.0,4.0),(0.0,infinity)'::polygon AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS path DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS box DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NaN,infinity),(2.0,4.0),(0.0,infinity)'::polygon AS circle DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('<(5,1),3>'::circle AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('<(3,5),0>'::circle as box DEFAULT NULL ON CONVERSION ERROR);
+-- not supported because the cast from circle to polygon is implemented as a SQL
+-- function, which cannot be error-safe.
+SELECT CAST('<(3,5),0>'::circle as polygon DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast from/to money type is not supported, all the following would result error
+SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast from bytea type to other data types
+SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('\x123456789A'::bytea AS int4 DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('\x123456'::bytea AS int2 DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast from bit type to other data types
+SELECT CAST('111111111100001'::bit(100) AS INT DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST ('111111111100001'::bit(100) AS INT8 DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast from text type to other data types
+select CAST('a.b.c.d'::text as regclass default NULL on conversion error);
+
+CREATE TABLE test_safecast(col0 text);
+INSERT INTO test_safecast(col0) VALUES ('<value>one</value');
+
+SELECT col0 as text,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) as to_regclass,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) IS NULL as expect_true,
+ CAST(col0 AS "char" DEFAULT NULL ON CONVERSION ERROR) as to_char,
+ CAST(col0 AS name DEFAULT NULL ON CONVERSION ERROR) as to_name,
+ CAST(col0 AS xml DEFAULT NULL ON CONVERSION ERROR) as to_xml
+FROM test_safecast;
+
+SELECT CAST('192.168.1.x' as inet DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('192.168.1.2/30' as cidr DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('22:00:5c:08:55:08:01:02'::macaddr8 as macaddr DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast betweeen range data types
+SELECT CAST('[1,2]'::int4range AS int4multirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[1,2]'::int8range AS int8multirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[1,2]'::numrange AS nummultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::daterange AS datemultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::tsrange AS tsmultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::tstzrange AS tstzmultirange DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast betweeen numeric data types
+CREATE TABLE test_safecast1(
+ col1 float4, col2 float8, col3 numeric, col4 numeric[],
+ col5 int2 default 32767,
+ col6 int4 default 32768,
+ col7 int8 default 4294967296);
+INSERT INTO test_safecast1 VALUES('11.1234', '11.1234', '9223372036854775808', '{11.1234}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('inf', 'inf', 'inf', '{11.1234, 12, inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('-inf', '-inf', '-inf', '{11.1234, 12, -inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('NaN', 'NaN', 'NaN', '{11.1234, 12, -inf, NaN}'::numeric[]);
+
+SELECT col5 as int2,
+ CAST(col5 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col5 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col5 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col5 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col5 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col5 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col5 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col5 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col6 as int4,
+ CAST(col6 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col6 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col6 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col6 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col6 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col6 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col6 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col6 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col7 as int8,
+ CAST(col7 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col7 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col7 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col7 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col7 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col7 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col7 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col7 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col3 as numeric,
+ CAST(col3 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col3 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col3 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col3 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col3 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col3 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col3 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col3 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col4 as num_arr,
+ CAST(col4 AS int2[] DEFAULT NULL ON CONVERSION ERROR) as int2arr,
+ CAST(col4 AS int4[] DEFAULT NULL ON CONVERSION ERROR) as int4arr,
+ CAST(col4 as int8[] DEFAULT NULL ON CONVERSION ERROR) as int8arr,
+ CAST(col4 as float4[] DEFAULT NULL ON CONVERSION ERROR) as f4arr,
+ CAST(col4 as float8[] DEFAULT NULL ON CONVERSION ERROR) as f8arr,
+ CAST(col4 as numeric(10,1)[] DEFAULT NULL ON CONVERSION ERROR) as numarr
+FROM test_safecast1;
+
+SELECT col1 as float4,
+ CAST(col1 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col1 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col1 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col1 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col1 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col1 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col1 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col1 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col2 as float8,
+ CAST(col2 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col2 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col2 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col2 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col2 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col2 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col2 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col2 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+-- date/timestamp/interval related safe type cast
+CREATE TABLE test_safecast2(
+ col0 date, col1 timestamp, col2 timestamptz,
+ col3 interval, col4 time, col5 timetz);
+INSERT INTO test_safecast2 VALUES
+('-infinity', '-infinity', '-infinity', '-infinity',
+ '2003-03-07 15:36:39 America/New_York', '2003-07-07 15:36:39 America/New_York'),
+('-infinity', 'infinity', 'infinity', 'infinity',
+ '11:59:59.99 PM', '11:59:59.99 PM PDT');
+
+SELECT col0 as date,
+ CAST(col0 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col0 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col0 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col0 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col0 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+
+SELECT col1 as timestamp,
+ CAST(col1 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col1 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col1 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col1 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col1 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+
+SELECT col2 as timestamptz,
+ CAST(col2 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col2 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col2 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col2 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col2 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+
+SELECT col3 as interval,
+ CAST(col3 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col3 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col3 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col3 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col3 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale,
+ CAST(col3 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+
+SELECT col4 as time,
+ CAST(col4 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col4 AS timetz DEFAULT NULL ON CONVERSION ERROR) IS NOT NULL as to_timetz,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+
+SELECT col5 as timetz,
+ CAST(col5 AS time DEFAULT NULL ON CONVERSION ERROR) as to_time,
+ CAST(col5 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_time_scale,
+ CAST(col5 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col5 AS timetz(6) DEFAULT NULL ON CONVERSION ERROR) as to_timetz_scale,
+ CAST(col5 AS interval DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col5 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+
+CREATE TABLE test_safecast3(col0 jsonb);
+INSERT INTO test_safecast3(col0) VALUES ('"test"');
+SELECT col0 as jsonb,
+ CAST(col0 AS integer DEFAULT NULL ON CONVERSION ERROR) as to_integer,
+ CAST(col0 AS numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col0 AS bigint DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col0 AS float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col0 AS float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col0 AS boolean DEFAULT NULL ON CONVERSION ERROR) as to_bool,
+ CAST(col0 AS smallint DEFAULT NULL ON CONVERSION ERROR) as to_smallint
+FROM test_safecast3;
+
+-- test deparse
+CREATE DOMAIN d_int_arr as int[];
+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 test_safecast1,
+ CAST(ARRAY[['1'], ['three'],['a']] AS int[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1,
+ CAST(ARRAY[['1', '2'], ['three', 'a']] AS date[] DEFAULT NULL ON CONVERSION ERROR) as cast2,
+ CAST(ARRAY['three'] AS INT[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast3;
+\sv safecastview
+
+CREATE VIEW safecastview1 AS
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS d_int_arr DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1,
+ CAST(ARRAY[['1', '2'], ['three', 'a']] AS d_int_arr DEFAULT '{21,22}' ON CONVERSION ERROR) as cast2;
+\sv safecastview1
+SELECT * FROM safecastview1;
+
+--error, default expression is mutable
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as int DEFAULT random(min=>1, max=>1) ON CONVERSION ERROR)));
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as xid DEFAULT NULL ON CONVERSION ERROR)));
+CREATE INDEX test_safecast3_idx ON test_safecast3((CAST(col0 as int DEFAULT NULL ON CONVERSION ERROR))); --ok
+SELECT pg_get_indexdef('test_safecast3_idx'::regclass);
+
+DROP TABLE test_safecast;
+DROP TABLE test_safecast1;
+DROP TABLE test_safecast2;
+DROP TABLE test_safecast3;
+DROP TABLE tcast;
+DROP FUNCTION ret_int8;
+DROP FUNCTION ret_setint;
+DROP TYPE comp_domain_with_typmod;
+DROP TYPE comp2;
+DROP DOMAIN d_varchar;
+DROP DOMAIN d_int42;
+DROP DOMAIN d_char3_not_null;
+RESET extra_float_digits;
diff --git a/src/test/regress/sql/create_cast.sql b/src/test/regress/sql/create_cast.sql
index 32187853cc7..0a15a795d87 100644
--- a/src/test/regress/sql/create_cast.sql
+++ b/src/test/regress/sql/create_cast.sql
@@ -62,6 +62,7 @@ $$ SELECT ('bar'::text || $1::text); $$;
CREATE CAST (int4 AS casttesttype) WITH FUNCTION bar_int4_text(int4) AS IMPLICIT;
SELECT 1234::int4::casttesttype; -- Should work now
+SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- error
-- check dependencies generated for that
SELECT pg_describe_object(classid, objid, objsubid) as obj,
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 018b5919cf6..c3e6432ec35 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2662,6 +2662,9 @@ STRLEN
SV
SYNCHRONIZATION_BARRIER
SYSTEM_INFO
+SafeTypeCast
+SafeTypeCastExpr
+SafeTypeCastState
SampleScan
SampleScanGetSampleSize_function
SampleScanState
--
2.34.1
v10-0015-error-safe-for-casting-timestamptz-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v10-0015-error-safe-for-casting-timestamptz-to-other-types-per-pg_cast.patchDownload
From 4266d9902fbb4f7b11fa9e882b7e9a7f5083ef6d Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Mon, 6 Oct 2025 12:39:22 +0800
Subject: [PATCH v10 15/20] error safe for casting timestamptz to other types
per pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
and pc.castfunc > 0 and (castsource::regtype ='timestamptz'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
--------------------------+-----------------------------+----------+-------------+------------+-----------------------+-------------
timestamp with time zone | date | 1178 | a | f | timestamptz_date | date
timestamp with time zone | time without time zone | 2019 | a | f | timestamptz_time | time
timestamp with time zone | timestamp without time zone | 2027 | a | f | timestamptz_timestamp | timestamp
timestamp with time zone | time with time zone | 1388 | a | f | timestamptz_timetz | timetz
timestamp with time zone | timestamp with time zone | 1967 | i | f | timestamptz_scale | timestamptz
(5 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 16 +++++++++++++---
src/backend/utils/adt/timestamp.c | 17 +++++++++++++++--
2 files changed, 28 insertions(+), 5 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 4f0f3d26989..111bfd8f519 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -1468,7 +1468,17 @@ timestamptz_date(PG_FUNCTION_ARGS)
TimestampTz timestamp = PG_GETARG_TIMESTAMP(0);
DateADT result;
- result = timestamptz2date_opt_overflow(timestamp, NULL);
+ if (likely(!fcinfo->context))
+ result = timestamptz2date_opt_overflow(timestamp, NULL);
+ else
+ {
+ int overflow;
+ result = timestamptz2date_opt_overflow(timestamp, &overflow);
+
+ if (overflow != 0)
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_DATEADT(result);
}
@@ -2110,7 +2120,7 @@ timestamptz_time(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
@@ -3029,7 +3039,7 @@ timestamptz_timetz(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 7b565cc6d66..116e3ef28fc 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -875,7 +875,8 @@ timestamptz_scale(PG_FUNCTION_ARGS)
result = timestamp;
- AdjustTimestampForTypmod(&result, typmod, NULL);
+ if (!AdjustTimestampForTypmod(&result, typmod, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_TIMESTAMPTZ(result);
}
@@ -6507,7 +6508,19 @@ timestamptz_timestamp(PG_FUNCTION_ARGS)
{
TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(0);
- PG_RETURN_TIMESTAMP(timestamptz2timestamp(timestamp));
+ if (likely(!fcinfo->context))
+ PG_RETURN_TIMESTAMP(timestamptz2timestamp(timestamp));
+ else
+ {
+ int overflow;
+ Timestamp result;
+
+ result = timestamptz2timestamp_opt_overflow(timestamp, &overflow);
+ if (overflow != 0)
+ PG_RETURN_NULL();
+ else
+ PG_RETURN_TIMESTAMP(result);
+ }
}
/*
--
2.34.1
v10-0014-error-safe-for-casting-interval-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v10-0014-error-safe-for-casting-interval-to-other-types-per-pg_cast.patchDownload
From efe7ec0bc44e16399f80a0fa457c168f3a109368 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 18:43:29 +0800
Subject: [PATCH v10 14/20] error safe for casting interval to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'interval'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------------+----------+-------------+------------+----------------+----------
interval | time without time zone | 1419 | a | f | interval_time | time
interval | interval | 1200 | i | f | interval_scale | interval
(2 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 2 +-
src/backend/utils/adt/timestamp.c | 3 ++-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index c7a3cde2d81..4f0f3d26989 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2180,7 +2180,7 @@ interval_time(PG_FUNCTION_ARGS)
TimeADT result;
if (INTERVAL_NOT_FINITE(span))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("cannot convert infinite interval to time")));
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 156a4830ffd..7b565cc6d66 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1334,7 +1334,8 @@ interval_scale(PG_FUNCTION_ARGS)
result = palloc(sizeof(Interval));
*result = *interval;
- AdjustIntervalForTypmod(result, typmod, NULL);
+ if (!AdjustIntervalForTypmod(result, typmod, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_INTERVAL_P(result);
}
--
2.34.1
v10-0012-error-safe-for-casting-float8-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v10-0012-error-safe-for-casting-float8-to-other-types-per-pg_cast.patchDownload
From 65929bff29519aee2935a16a393bba4df7c74d50 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 18:41:55 +0800
Subject: [PATCH v10 12/20] error safe for casting float8 to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'float8'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------------+------------+----------+-------------+------------+----------------+---------
double precision | bigint | 483 | a | f | dtoi8 | int8
double precision | smallint | 237 | a | f | dtoi2 | int2
double precision | integer | 317 | a | f | dtoi4 | int4
double precision | real | 312 | a | f | dtof | float4
double precision | numeric | 1743 | a | f | float8_numeric | numeric
(5 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/float.c | 29 +++++++++++++++++++++++++----
src/backend/utils/adt/int8.c | 6 +++++-
src/backend/utils/adt/numeric.c | 3 ++-
3 files changed, 32 insertions(+), 6 deletions(-)
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index dd8a2ff378b..6747544b679 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -1199,9 +1199,22 @@ dtof(PG_FUNCTION_ARGS)
result = (float4) num;
if (unlikely(isinf(result)) && !isinf(num))
- float_overflow_error();
+ {
+ errsave(fcinfo->context,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: overflow"));
+
+ PG_RETURN_NULL();
+ }
+
if (unlikely(result == 0.0f) && num != 0.0)
- float_underflow_error();
+ {
+ errsave(fcinfo->context,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: underflow"));
+
+ PG_RETURN_NULL();
+ }
PG_RETURN_FLOAT4(result);
}
@@ -1224,10 +1237,14 @@ dtoi4(PG_FUNCTION_ARGS)
/* Range check */
if (unlikely(isnan(num) || !FLOAT8_FITS_IN_INT32(num)))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_INT32((int32) num);
}
@@ -1249,10 +1266,14 @@ dtoi2(PG_FUNCTION_ARGS)
/* Range check */
if (unlikely(isnan(num) || !FLOAT8_FITS_IN_INT16(num)))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_INT16((int16) num);
}
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index bcdb020a91c..437dbbccd4a 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -1268,10 +1268,14 @@ dtoi8(PG_FUNCTION_ARGS)
/* Range check */
if (unlikely(isnan(num) || !FLOAT8_FITS_IN_INT64(num)))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("bigint out of range")));
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_INT64((int64) num);
}
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 8839b095f60..76cd9800c2a 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -4570,7 +4570,8 @@ float8_numeric(PG_FUNCTION_ARGS)
init_var(&result);
/* Assume we need not worry about leading/trailing spaces */
- (void) set_var_from_str(buf, buf, &result, &endptr, NULL);
+ if (!set_var_from_str(buf, buf, &result, &endptr, fcinfo->context))
+ PG_RETURN_NULL();
res = make_result(&result);
--
2.34.1
v10-0013-error-safe-for-casting-date-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v10-0013-error-safe-for-casting-date-to-other-types-per-pg_cast.patchDownload
From 52145f4ed22021d5eab95886b246076425bd0382 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 18:42:50 +0800
Subject: [PATCH v10 13/20] error safe for casting date to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'date'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+-----------------------------+----------+-------------+------------+------------------+-------------
date | timestamp without time zone | 2024 | i | f | date_timestamp | timestamp
date | timestamp with time zone | 1174 | i | f | date_timestamptz | timestamptz
(2 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 22 ++++++++++++++++++++--
1 file changed, 20 insertions(+), 2 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 344f58b92f7..c7a3cde2d81 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -1350,7 +1350,16 @@ date_timestamp(PG_FUNCTION_ARGS)
DateADT dateVal = PG_GETARG_DATEADT(0);
Timestamp result;
- result = date2timestamp(dateVal);
+ if (likely(!fcinfo->context))
+ result = date2timestamp(dateVal);
+ else
+ {
+ int overflow;
+
+ result = date2timestamp_opt_overflow(dateVal, &overflow);
+ if (overflow != 0)
+ PG_RETURN_NULL();
+ }
PG_RETURN_TIMESTAMP(result);
}
@@ -1435,7 +1444,16 @@ date_timestamptz(PG_FUNCTION_ARGS)
DateADT dateVal = PG_GETARG_DATEADT(0);
TimestampTz result;
- result = date2timestamptz(dateVal);
+ if (likely(!fcinfo->context))
+ result = date2timestamptz(dateVal);
+ else
+ {
+ int overflow;
+ result = date2timestamptz_opt_overflow(dateVal, &overflow);
+
+ if (overflow != 0)
+ PG_RETURN_NULL();
+ }
PG_RETURN_TIMESTAMP(result);
}
--
2.34.1
v10-0011-error-safe-for-casting-float4-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v10-0011-error-safe-for-casting-float4-to-other-types-per-pg_cast.patchDownload
From dbb44df103a821bf9769549447ee98313803a1f6 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 18:33:57 +0800
Subject: [PATCH v10 11/20] error safe for casting float4 to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'float4'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+----------------+---------
real | bigint | 653 | a | f | ftoi8 | int8
real | smallint | 238 | a | f | ftoi2 | int2
real | integer | 319 | a | f | ftoi4 | int4
real | double precision | 311 | i | f | ftod | float8
real | numeric | 1742 | a | f | float4_numeric | numeric
(5 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/float.c | 12 ++++++++++--
src/backend/utils/adt/int8.c | 6 +++++-
src/backend/utils/adt/numeric.c | 3 ++-
3 files changed, 17 insertions(+), 4 deletions(-)
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index 7b97d2be6ca..dd8a2ff378b 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -1298,10 +1298,14 @@ ftoi4(PG_FUNCTION_ARGS)
/* Range check */
if (unlikely(isnan(num) || !FLOAT4_FITS_IN_INT32(num)))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_INT32((int32) num);
}
@@ -1323,10 +1327,14 @@ ftoi2(PG_FUNCTION_ARGS)
/* Range check */
if (unlikely(isnan(num) || !FLOAT4_FITS_IN_INT16(num)))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_INT16((int16) num);
}
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 3b2d100be92..bcdb020a91c 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -1303,10 +1303,14 @@ ftoi8(PG_FUNCTION_ARGS)
/* Range check */
if (unlikely(isnan(num) || !FLOAT4_FITS_IN_INT64(num)))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("bigint out of range")));
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_INT64((int64) num);
}
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index ce5f71109e7..8839b095f60 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -4668,7 +4668,8 @@ float4_numeric(PG_FUNCTION_ARGS)
init_var(&result);
/* Assume we need not worry about leading/trailing spaces */
- (void) set_var_from_str(buf, buf, &result, &endptr, NULL);
+ if (!set_var_from_str(buf, buf, &result, &endptr, fcinfo->context))
+ PG_RETURN_NULL();
res = make_result(&result);
--
2.34.1
v10-0009-error-safe-for-casting-bigint-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v10-0009-error-safe-for-casting-bigint-to-other-types-per-pg_cast.patchDownload
From 325a86c72c4535614aa91f74d8cda542e0b6c3be Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 16:38:37 +0800
Subject: [PATCH v10 09/20] error safe for casting bigint to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'bigint'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+--------------+---------
bigint | smallint | 714 | a | f | int82 | int2
bigint | integer | 480 | a | f | int84 | int4
bigint | real | 652 | i | f | i8tof | float4
bigint | double precision | 482 | i | f | i8tod | float8
bigint | numeric | 1781 | i | f | int8_numeric | numeric
bigint | money | 3812 | a | f | int8_cash | money
bigint | oid | 1287 | i | f | i8tooid | oid
bigint | regproc | 1287 | i | f | i8tooid | oid
bigint | regprocedure | 1287 | i | f | i8tooid | oid
bigint | regoper | 1287 | i | f | i8tooid | oid
bigint | regoperator | 1287 | i | f | i8tooid | oid
bigint | regclass | 1287 | i | f | i8tooid | oid
bigint | regcollation | 1287 | i | f | i8tooid | oid
bigint | regtype | 1287 | i | f | i8tooid | oid
bigint | regconfig | 1287 | i | f | i8tooid | oid
bigint | regdictionary | 1287 | i | f | i8tooid | oid
bigint | regrole | 1287 | i | f | i8tooid | oid
bigint | regnamespace | 1287 | i | f | i8tooid | oid
bigint | regdatabase | 1287 | i | f | i8tooid | oid
bigint | bytea | 6369 | e | f | int8_bytea | bytea
bigint | bit | 2075 | e | f | bitfromint8 | bit
(21 rows)
already error safe: i8tof, i8tod, int8_numeric, int8_bytea, bitfromint8
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/int8.c | 18 +++++++++++++++---
1 file changed, 15 insertions(+), 3 deletions(-)
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index bdea490202a..3b2d100be92 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -1204,10 +1204,14 @@ int84(PG_FUNCTION_ARGS)
int64 arg = PG_GETARG_INT64(0);
if (unlikely(arg < PG_INT32_MIN) || unlikely(arg > PG_INT32_MAX))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_INT32((int32) arg);
}
@@ -1225,10 +1229,14 @@ int82(PG_FUNCTION_ARGS)
int64 arg = PG_GETARG_INT64(0);
if (unlikely(arg < PG_INT16_MIN) || unlikely(arg > PG_INT16_MAX))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_INT16((int16) arg);
}
@@ -1308,10 +1316,14 @@ i8tooid(PG_FUNCTION_ARGS)
int64 arg = PG_GETARG_INT64(0);
if (unlikely(arg < 0) || unlikely(arg > PG_UINT32_MAX))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("OID out of range")));
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_OID((Oid) arg);
}
--
2.34.1
v10-0007-error-safe-for-casting-macaddr8-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v10-0007-error-safe-for-casting-macaddr8-to-other-types-per-pg_cast.patchDownload
From ce1908874819044361f16c8976e1d883232fc265 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 19:12:12 +0800
Subject: [PATCH v10 07/20] error safe for casting macaddr8 to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
and pc.castfunc > 0 and castsource::regtype ='macaddr8'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------+----------+-------------+------------+-------------------+---------
macaddr8 | macaddr | 4124 | i | f | macaddr8tomacaddr | macaddr
(1 row)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/mac8.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/backend/utils/adt/mac8.c b/src/backend/utils/adt/mac8.c
index 08e41ba4eea..e2a7a90e42c 100644
--- a/src/backend/utils/adt/mac8.c
+++ b/src/backend/utils/adt/mac8.c
@@ -550,7 +550,8 @@ macaddr8tomacaddr(PG_FUNCTION_ARGS)
result = (macaddr *) palloc0(sizeof(macaddr));
if ((addr->d != 0xFF) || (addr->e != 0xFE))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("macaddr8 data out of range to convert to macaddr"),
errhint("Only addresses that have FF and FE as values in the "
@@ -558,6 +559,9 @@ macaddr8tomacaddr(PG_FUNCTION_ARGS)
"xx:xx:xx:ff:fe:xx:xx:xx, are eligible to be converted "
"from macaddr8 to macaddr.")));
+ PG_RETURN_NULL();
+ }
+
result->a = addr->a;
result->b = addr->b;
result->c = addr->c;
--
2.34.1
v10-0006-error-safe-for-casting-inet-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v10-0006-error-safe-for-casting-inet-to-other-types-per-pg_cast.patchDownload
From 67c06609b4d565e8ebb35c855db04462d4536a50 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 15:55:55 +0800
Subject: [PATCH v10 06/20] error safe for casting inet to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'inet'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+-------------------+----------+-------------+------------+--------------+---------
inet | cidr | 1715 | a | f | inet_to_cidr | cidr
inet | text | 730 | a | f | network_show | text
inet | character varying | 730 | a | f | network_show | text
inet | character | 730 | a | f | network_show | text
(4 rows)
inet_to_cidr is already error safe.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/network.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/backend/utils/adt/network.c b/src/backend/utils/adt/network.c
index 3cb0ab6829a..f34228763da 100644
--- a/src/backend/utils/adt/network.c
+++ b/src/backend/utils/adt/network.c
@@ -1137,10 +1137,14 @@ network_show(PG_FUNCTION_ARGS)
if (pg_inet_net_ntop(ip_family(ip), ip_addr(ip), ip_maxbits(ip),
tmp, sizeof(tmp)) == NULL)
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
errmsg("could not format inet value: %m")));
+ PG_RETURN_NULL();
+ }
+
/* Add /n if not present (which it won't be) */
if (strchr(tmp, '/') == NULL)
{
--
2.34.1
v10-0010-error-safe-for-casting-numeric-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v10-0010-error-safe-for-casting-numeric-to-other-types-per-pg_cast.patchDownload
From 8229b22d674b51e4e42c9de0abb59ba123857595 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 21:57:31 +0800
Subject: [PATCH v10 10/20] error safe for casting numeric to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'numeric'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+----------------+---------
numeric | bigint | 1779 | a | f | numeric_int8 | int8
numeric | smallint | 1783 | a | f | numeric_int2 | int2
numeric | integer | 1744 | a | f | numeric_int4 | int4
numeric | real | 1745 | i | f | numeric_float4 | float4
numeric | double precision | 1746 | i | f | numeric_float8 | float8
numeric | money | 3824 | a | f | numeric_cash | money
numeric | numeric | 1703 | i | f | numeric | numeric
(7 rows)
discussion: https://postgr.es/m/CACJufxHCMzrHOW=wRe8L30rMhB3sjwAv1LE928Fa7sxMu1Tx-g@mail.gmail.com
---
src/backend/utils/adt/numeric.c | 68 +++++++++++++++++++++++++--------
1 file changed, 53 insertions(+), 15 deletions(-)
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 2501007d981..ce5f71109e7 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -1244,7 +1244,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));
}
@@ -1295,8 +1296,9 @@ numeric (PG_FUNCTION_ARGS)
init_var(&var);
set_var_from_num(num, &var);
- (void) apply_typmod(&var, typmod, NULL);
- new = make_result(&var);
+ if (!apply_typmod(&var, typmod, fcinfo->context))
+ PG_RETURN_NULL();
+ new = make_result_safe(&var, fcinfo->context);
free_var(&var);
@@ -3019,7 +3021,10 @@ numeric_mul(PG_FUNCTION_ARGS)
Numeric num2 = PG_GETARG_NUMERIC(1);
Numeric res;
- res = numeric_mul_safe(num1, num2, NULL);
+ res = numeric_mul_safe(num1, num2, fcinfo->context);
+
+ if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
PG_RETURN_NUMERIC(res);
}
@@ -4393,9 +4398,15 @@ numeric_int4_safe(Numeric num, Node *escontext)
Datum
numeric_int4(PG_FUNCTION_ARGS)
{
+ int32 result;
Numeric num = PG_GETARG_NUMERIC(0);
- PG_RETURN_INT32(numeric_int4_safe(num, NULL));
+ result = numeric_int4_safe(num, fcinfo->context);
+
+ if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
+
+ PG_RETURN_INT32(result);
}
/*
@@ -4463,9 +4474,15 @@ numeric_int8_safe(Numeric num, Node *escontext)
Datum
numeric_int8(PG_FUNCTION_ARGS)
{
+ int64 result;
Numeric num = PG_GETARG_NUMERIC(0);
- PG_RETURN_INT64(numeric_int8_safe(num, NULL));
+ result = numeric_int8_safe(num, fcinfo->context);
+
+ if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
+
+ PG_RETURN_INT64(result);
}
@@ -4489,28 +4506,38 @@ numeric_int2(PG_FUNCTION_ARGS)
if (NUMERIC_IS_SPECIAL(num))
{
if (NUMERIC_IS_NAN(num))
- ereport(ERROR,
+ errsave(fcinfo->context,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert NaN to %s", "smallint")));
else
- ereport(ERROR,
+ errsave(fcinfo->context,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert infinity to %s", "smallint")));
+
+ PG_RETURN_NULL();
}
/* Convert to variable format and thence to int8 */
init_var_from_num(num, &x);
if (!numericvar_to_int64(&x, &val))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
+ PG_RETURN_NULL();
+ }
+
if (unlikely(val < PG_INT16_MIN) || unlikely(val > PG_INT16_MAX))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
+ PG_RETURN_NULL();
+ }
+
/* Down-convert to int2 */
result = (int16) val;
@@ -4572,10 +4599,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);
}
@@ -4667,7 +4698,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
v10-0008-error-safe-for-casting-integer-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v10-0008-error-safe-for-casting-integer-to-other-types-per-pg_cast.patchDownload
From 6db7ba9916baf975d2a1c895cbdb89ac918bbc8e Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 16:04:07 +0800
Subject: [PATCH v10 08/20] error safe for casting integer to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'integer'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+--------------+---------
integer | bigint | 481 | i | f | int48 | int8
integer | smallint | 314 | a | f | i4toi2 | int2
integer | real | 318 | i | f | i4tof | float4
integer | double precision | 316 | i | f | i4tod | float8
integer | numeric | 1740 | i | f | int4_numeric | numeric
integer | money | 3811 | a | f | int4_cash | money
integer | boolean | 2557 | e | f | int4_bool | bool
integer | bytea | 6368 | e | f | int4_bytea | bytea
integer | "char" | 78 | e | f | i4tochar | char
integer | bit | 1683 | e | f | bitfromint4 | bit
(10 rows)
only int4_cash, i4toi2, i4tochar need take care of error handling. but support
for cash data type is not easy, so only i4toi2, i4tochar function refactoring.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/char.c | 6 +++++-
src/backend/utils/adt/int.c | 5 ++++-
2 files changed, 9 insertions(+), 2 deletions(-)
diff --git a/src/backend/utils/adt/char.c b/src/backend/utils/adt/char.c
index 22dbfc950b1..7cf09d954e7 100644
--- a/src/backend/utils/adt/char.c
+++ b/src/backend/utils/adt/char.c
@@ -192,10 +192,14 @@ i4tochar(PG_FUNCTION_ARGS)
int32 arg1 = PG_GETARG_INT32(0);
if (arg1 < SCHAR_MIN || arg1 > SCHAR_MAX)
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("\"char\" out of range")));
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_CHAR((int8) arg1);
}
diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c
index b5781989a64..4d3ce23a4af 100644
--- a/src/backend/utils/adt/int.c
+++ b/src/backend/utils/adt/int.c
@@ -350,10 +350,13 @@ i4toi2(PG_FUNCTION_ARGS)
int32 arg1 = PG_GETARG_INT32(0);
if (unlikely(arg1 < SHRT_MIN) || unlikely(arg1 > SHRT_MAX))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
+ PG_RETURN_NULL();
+ }
PG_RETURN_INT16((int16) arg1);
}
--
2.34.1
v10-0004-error-safe-for-casting-text-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v10-0004-error-safe-for-casting-text-to-other-types-per-pg_cast.patchDownload
From 15886c5fa6b9ef57e155774f8179f541fed9843c Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 15:49:46 +0800
Subject: [PATCH v10 04/20] error safe for casting text to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'text'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------+----------+-------------+------------+---------------+----------
text | regclass | 1079 | i | f | text_regclass | regclass
text | "char" | 944 | a | f | text_char | char
text | name | 407 | i | f | text_name | name
text | xml | 2896 | e | f | texttoxml | xml
(4 rows)
already error safe: text_name, text_char.
texttoxml is refactored in character type error safe patch.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/catalog/namespace.c | 167 ++++++++++++++++++++++++++++++++
src/backend/utils/adt/regproc.c | 33 ++++++-
src/backend/utils/adt/varlena.c | 42 ++++++++
src/include/catalog/namespace.h | 6 ++
src/include/utils/varlena.h | 1 +
5 files changed, 246 insertions(+), 3 deletions(-)
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index d23474da4fb..a04d179c7fb 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -640,6 +640,137 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
return relId;
}
+/* safe version of RangeVarGetRelidExtended */
+Oid
+RangeVarGetRelidExtendedSafe(const RangeVar *relation, LOCKMODE lockmode,
+ uint32 flags, RangeVarGetRelidCallback callback, void *callback_arg,
+ Node *escontext)
+{
+ uint64 inval_count;
+ Oid relId;
+ Oid oldRelId = InvalidOid;
+ bool retry = false;
+ bool missing_ok = (flags & RVR_MISSING_OK) != 0;
+
+ /* verify that flags do no conflict */
+ Assert(!((flags & RVR_NOWAIT) && (flags & RVR_SKIP_LOCKED)));
+
+ /*
+ * We check the catalog name and then ignore it.
+ */
+ if (relation->catalogname)
+ {
+ if (strcmp(relation->catalogname, get_database_name(MyDatabaseId)) != 0)
+ ereturn(escontext, InvalidOid,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cross-database references are not implemented: \"%s.%s.%s\"",
+ relation->catalogname, relation->schemaname,
+ relation->relname));
+ }
+
+ for (;;)
+ {
+ inval_count = SharedInvalidMessageCounter;
+
+ if (relation->relpersistence == RELPERSISTENCE_TEMP)
+ {
+ if (!OidIsValid(myTempNamespace))
+ relId = InvalidOid;
+ else
+ {
+ if (relation->schemaname)
+ {
+ Oid namespaceId;
+
+ namespaceId = LookupExplicitNamespace(relation->schemaname, missing_ok);
+
+ /*
+ * For missing_ok, allow a non-existent schema name to
+ * return InvalidOid.
+ */
+ if (namespaceId != myTempNamespace)
+ ereturn(escontext, InvalidOid,
+ errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("temporary tables cannot specify a schema name"));
+ }
+
+ relId = get_relname_relid(relation->relname, myTempNamespace);
+ }
+ }
+ else if (relation->schemaname)
+ {
+ Oid namespaceId;
+
+ /* use exact schema given */
+ namespaceId = LookupExplicitNamespace(relation->schemaname, missing_ok);
+ if (missing_ok && !OidIsValid(namespaceId))
+ relId = InvalidOid;
+ else
+ relId = get_relname_relid(relation->relname, namespaceId);
+ }
+ else
+ {
+ /* search the namespace path */
+ relId = RelnameGetRelid(relation->relname);
+ }
+
+ if (callback)
+ callback(relation, relId, oldRelId, callback_arg);
+
+ if (lockmode == NoLock)
+ break;
+
+ if (retry)
+ {
+ if (relId == oldRelId)
+ break;
+ if (OidIsValid(oldRelId))
+ UnlockRelationOid(oldRelId, lockmode);
+ }
+
+ if (!OidIsValid(relId))
+ AcceptInvalidationMessages();
+ else if (!(flags & (RVR_NOWAIT | RVR_SKIP_LOCKED)))
+ LockRelationOid(relId, lockmode);
+ else if (!ConditionalLockRelationOid(relId, lockmode))
+ {
+ if (relation->schemaname)
+ ereport(DEBUG1,
+ errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s.%s\"",
+ relation->schemaname, relation->relname));
+ else
+ ereport(DEBUG1,
+ errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s\"",
+ relation->relname));
+
+ return InvalidOid;
+ }
+
+ if (inval_count == SharedInvalidMessageCounter)
+ break;
+
+ retry = true;
+ oldRelId = relId;
+ }
+
+ if (!OidIsValid(relId))
+ {
+ if (relation->schemaname)
+ ereturn(escontext, InvalidOid,
+ errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s.%s\" does not exist",
+ relation->schemaname, relation->relname));
+ else
+ ereturn(escontext, InvalidOid,
+ errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" does not exist",
+ relation->relname));
+ }
+ return relId;
+}
+
/*
* RangeVarGetCreationNamespace
* Given a RangeVar describing a to-be-created relation,
@@ -3650,6 +3781,42 @@ makeRangeVarFromNameList(const List *names)
return rel;
}
+/*
+ * makeRangeVarFromNameListSafe
+ * Utility routine to convert a qualified-name list into RangeVar form.
+ * The result maybe NULL.
+ */
+RangeVar *
+makeRangeVarFromNameListSafe(const List *names, Node *escontext)
+{
+ RangeVar *rel = makeRangeVar(NULL, NULL, -1);
+
+ switch (list_length(names))
+ {
+ case 1:
+ rel->relname = strVal(linitial(names));
+ break;
+ case 2:
+ rel->schemaname = strVal(linitial(names));
+ rel->relname = strVal(lsecond(names));
+ break;
+ case 3:
+ rel->catalogname = strVal(linitial(names));
+ rel->schemaname = strVal(lsecond(names));
+ rel->relname = strVal(lthird(names));
+ break;
+ default:
+ errsave(escontext,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("improper relation name (too many dotted names): %s",
+ NameListToString(names)));
+ rel = NULL;
+ break;
+ }
+
+ return rel;
+}
+
/*
* NameListToString
* Utility routine to convert a qualified-name list into a string.
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index e5c2246f2c9..059d0154335 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -1902,10 +1902,37 @@ text_regclass(PG_FUNCTION_ARGS)
Oid result;
RangeVar *rv;
- rv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
+ if (likely(!fcinfo->context))
+ {
+ rv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
- /* We might not even have permissions on this relation; don't lock it. */
- result = RangeVarGetRelid(rv, NoLock, false);
+ /*
+ * We might not even have permissions on this relation; don't lock it.
+ */
+ result = RangeVarGetRelid(rv, NoLock, false);
+ }
+ else
+ {
+ List *rvnames;
+
+ rvnames = textToQualifiedNameListSafe(relname, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
+
+ rv = makeRangeVarFromNameListSafe(rvnames, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
+
+ result = RangeVarGetRelidExtendedSafe(rv,
+ NoLock,
+ 0,
+ NULL,
+ NULL,
+ fcinfo->context);
+
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
+ }
PG_RETURN_OID(result);
}
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index 8d735786e51..5e2676f7180 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -2717,6 +2717,48 @@ textToQualifiedNameList(text *textval)
return result;
}
+/* error safe version of textToQualifiedNameList */
+List *
+textToQualifiedNameListSafe(text *textval, Node *escontext)
+{
+ char *rawname;
+ List *result = NIL;
+ List *namelist;
+ ListCell *l;
+
+ /* Convert to C string (handles possible detoasting). */
+ /* Note we rely on being able to modify rawname below. */
+ rawname = text_to_cstring(textval);
+
+ if (!SplitIdentifierString(rawname, '.', &namelist))
+ {
+ errsave(escontext,
+ errcode(ERRCODE_INVALID_NAME),
+ errmsg("invalid name syntax"));
+ return NIL;
+ }
+
+ if (namelist == NIL)
+ {
+ errsave(escontext,
+ errcode(ERRCODE_INVALID_NAME),
+ errmsg("invalid name syntax"));
+ return NIL;
+ }
+
+ foreach(l, namelist)
+ {
+ char *curname = (char *) lfirst(l);
+
+ result = lappend(result, makeString(pstrdup(curname)));
+ }
+
+ pfree(rawname);
+ list_free(namelist);
+
+ return result;
+}
+
/*
* SplitIdentifierString --- parse a string containing identifiers
*
diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h
index f1423f28c32..ab61af55ddc 100644
--- a/src/include/catalog/namespace.h
+++ b/src/include/catalog/namespace.h
@@ -103,6 +103,11 @@ extern Oid RangeVarGetRelidExtended(const RangeVar *relation,
LOCKMODE lockmode, uint32 flags,
RangeVarGetRelidCallback callback,
void *callback_arg);
+extern Oid RangeVarGetRelidExtendedSafe(const RangeVar *relation,
+ LOCKMODE lockmode, uint32 flags,
+ RangeVarGetRelidCallback callback,
+ void *callback_arg,
+ Node *escontext);
extern Oid RangeVarGetCreationNamespace(const RangeVar *newRelation);
extern Oid RangeVarGetAndCheckCreationNamespace(RangeVar *relation,
LOCKMODE lockmode,
@@ -168,6 +173,7 @@ extern Oid LookupCreationNamespace(const char *nspname);
extern void CheckSetNamespace(Oid oldNspOid, Oid nspOid);
extern Oid QualifiedNameGetCreationNamespace(const List *names, char **objname_p);
extern RangeVar *makeRangeVarFromNameList(const List *names);
+extern RangeVar *makeRangeVarFromNameListSafe(const List *names, Node *escontext);
extern char *NameListToString(const List *names);
extern char *NameListToQuotedString(const List *names);
diff --git a/src/include/utils/varlena.h b/src/include/utils/varlena.h
index db9fdf72941..0cf01ae5281 100644
--- a/src/include/utils/varlena.h
+++ b/src/include/utils/varlena.h
@@ -27,6 +27,7 @@ extern int varstr_levenshtein_less_equal(const char *source, int slen,
int ins_c, int del_c, int sub_c,
int max_d, bool trusted);
extern List *textToQualifiedNameList(text *textval);
+extern List *textToQualifiedNameListSafe(text *textval, Node *escontext);
extern bool SplitIdentifierString(char *rawstring, char separator,
List **namelist);
extern bool SplitDirectoriesString(char *rawstring, char separator,
--
2.34.1
v10-0005-error-safe-for-casting-character-varying-to-other-types-per-pg_c.patchtext/x-patch; charset=US-ASCII; name=v10-0005-error-safe-for-casting-character-varying-to-other-types-per-pg_c.patchDownload
From aabf3932a2ae4a7b6646ab5de19b98522303dd9b Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 19:17:00 +0800
Subject: [PATCH v10 05/20] error safe for casting character varying to other
types per pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc and pc.castfunc > 0 and
(castsource::regtype = 'character varying'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
-------------------+-------------------+----------+-------------+------------+---------------+----------
character varying | regclass | 1079 | i | f | text_regclass | regclass
character varying | "char" | 944 | a | f | text_char | char
character varying | name | 1400 | i | f | text_name | name
character varying | xml | 2896 | e | f | texttoxml | xml
character varying | character varying | 669 | i | f | varchar | varchar
(5 rows)
texttoxml, text_regclass was refactored as error safe in prior patch.
text_char, text_name is already error safe.
so here we only need handle function "varchar".
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/varchar.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c
index a95e4bd094e..fdbef272c39 100644
--- a/src/backend/utils/adt/varchar.c
+++ b/src/backend/utils/adt/varchar.c
@@ -638,10 +638,14 @@ varchar(PG_FUNCTION_ARGS)
{
for (i = maxmblen; i < len; i++)
if (s_data[i] != ' ')
- ereport(ERROR,
+ {
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
errmsg("value too long for type character varying(%d)",
maxlen)));
+
+ PG_RETURN_NULL();
+ }
}
PG_RETURN_VARCHAR_P((VarChar *) cstring_to_text_with_len(s_data,
--
2.34.1
v10-0002-error-safe-for-casting-bit-varbit-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v10-0002-error-safe-for-casting-bit-varbit-to-other-types-per-pg_cast.patchDownload
From 5f7fa88b9e44b7dc684a4989471c27d0017190a7 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 19:09:53 +0800
Subject: [PATCH v10 02/20] error safe for casting bit/varbit to other types
per pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
where pc.castfunc > 0 and (castsource::regtype ='bit'::regtype or
castsource::regtype ='varbit'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
-------------+-------------+----------+-------------+------------+-----------+---------
bit | bigint | 2076 | e | f | bittoint8 | int8
bit | integer | 1684 | e | f | bittoint4 | int4
bit | bit | 1685 | i | f | bit | bit
bit varying | bit varying | 1687 | i | f | varbit | varbit
(4 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/varbit.c | 24 ++++++++++++++++++++----
1 file changed, 20 insertions(+), 4 deletions(-)
diff --git a/src/backend/utils/adt/varbit.c b/src/backend/utils/adt/varbit.c
index 205a67dafc5..bfcea18f4b2 100644
--- a/src/backend/utils/adt/varbit.c
+++ b/src/backend/utils/adt/varbit.c
@@ -401,11 +401,15 @@ bit(PG_FUNCTION_ARGS)
PG_RETURN_VARBIT_P(arg);
if (!isExplicit)
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_STRING_DATA_LENGTH_MISMATCH),
errmsg("bit string length %d does not match type bit(%d)",
VARBITLEN(arg), len)));
+ PG_RETURN_NULL();
+ }
+
rlen = VARBITTOTALLEN(len);
/* set to 0 so that string is zero-padded */
result = (VarBit *) palloc0(rlen);
@@ -752,11 +756,15 @@ varbit(PG_FUNCTION_ARGS)
PG_RETURN_VARBIT_P(arg);
if (!isExplicit)
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
errmsg("bit string too long for type bit varying(%d)",
len)));
+ PG_RETURN_NULL();
+ }
+
rlen = VARBITTOTALLEN(len);
result = (VarBit *) palloc(rlen);
SET_VARSIZE(result, rlen);
@@ -1591,10 +1599,14 @@ bittoint4(PG_FUNCTION_ARGS)
/* Check that the bit string is not too long */
if (VARBITLEN(arg) > sizeof(result) * BITS_PER_BYTE)
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
+ PG_RETURN_NULL();
+ }
+
result = 0;
for (r = VARBITS(arg); r < VARBITEND(arg); r++)
{
@@ -1671,10 +1683,14 @@ bittoint8(PG_FUNCTION_ARGS)
/* Check that the bit string is not too long */
if (VARBITLEN(arg) > sizeof(result) * BITS_PER_BYTE)
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("bigint out of range")));
+ PG_RETURN_NULL();
+ }
+
result = 0;
for (r = VARBITS(arg); r < VARBITEND(arg); r++)
{
--
2.34.1
v10-0003-error-safe-for-casting-character-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v10-0003-error-safe-for-casting-character-to-other-types-per-pg_cast.patchDownload
From ad556d145cf33a3e7a9e6cbb5572b2228b1938be Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Mon, 3 Nov 2025 11:58:46 +0800
Subject: [PATCH v10 03/20] error safe for casting character to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype ='character'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+-------------------+----------+-------------+------------+-------------+---------
character | text | 401 | i | f | rtrim1 | text
character | character varying | 401 | i | f | rtrim1 | text
character | "char" | 944 | a | f | text_char | char
character | name | 409 | i | f | bpchar_name | name
character | xml | 2896 | e | f | texttoxml | xml
character | character | 668 | i | f | bpchar | bpchar
(6 rows)
only texttoxml, bpchar(PG_FUNCTION_ARGS) need take care of error handling.
other functions already error safe.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/executor/execExprInterp.c | 2 +-
src/backend/utils/adt/varchar.c | 6 +++++-
src/backend/utils/adt/xml.c | 17 ++++++++++++-----
src/include/utils/xml.h | 2 +-
4 files changed, 19 insertions(+), 8 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 0e1a74976f7..67f4e00eac4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -4542,7 +4542,7 @@ ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op)
*op->resvalue = PointerGetDatum(xmlparse(data,
xexpr->xmloption,
- preserve_whitespace));
+ preserve_whitespace, NULL));
*op->resnull = false;
}
break;
diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c
index 3f40c9da1a0..a95e4bd094e 100644
--- a/src/backend/utils/adt/varchar.c
+++ b/src/backend/utils/adt/varchar.c
@@ -307,10 +307,14 @@ bpchar(PG_FUNCTION_ARGS)
{
for (i = maxmblen; i < len; i++)
if (s[i] != ' ')
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
errmsg("value too long for type character(%d)",
maxlen)));
+
+ PG_RETURN_NULL();
+ }
}
len = maxmblen;
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 35c915573a1..4723d13928f 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -659,7 +659,7 @@ texttoxml(PG_FUNCTION_ARGS)
{
text *data = PG_GETARG_TEXT_PP(0);
- PG_RETURN_XML_P(xmlparse(data, xmloption, true));
+ PG_RETURN_XML_P(xmlparse(data, xmloption, true, fcinfo->context));
}
@@ -1028,18 +1028,25 @@ xmlelement(XmlExpr *xexpr,
xmltype *
-xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace)
+xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, Node *escontext)
{
#ifdef USE_LIBXML
xmlDocPtr doc;
doc = xml_parse(data, xmloption_arg, preserve_whitespace,
- GetDatabaseEncoding(), NULL, NULL, NULL);
- xmlFreeDoc(doc);
+ GetDatabaseEncoding(), NULL, NULL, escontext);
+ if (doc)
+ xmlFreeDoc(doc);
+
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return NULL;
return (xmltype *) data;
#else
- NO_XML_SUPPORT();
+ errsave(escontext,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unsupported XML feature"),
+ errdetail("This functionality requires the server to be built with libxml support."));
return NULL;
#endif
}
diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h
index 732dac47bc4..b15168c430e 100644
--- a/src/include/utils/xml.h
+++ b/src/include/utils/xml.h
@@ -73,7 +73,7 @@ extern xmltype *xmlconcat(List *args);
extern xmltype *xmlelement(XmlExpr *xexpr,
const Datum *named_argvalue, const bool *named_argnull,
const Datum *argvalue, const bool *argnull);
-extern xmltype *xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace);
+extern xmltype *xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, Node *escontext);
extern xmltype *xmlpi(const char *target, text *arg, bool arg_is_null, bool *result_is_null);
extern xmltype *xmlroot(xmltype *data, text *version, int standalone);
extern bool xml_is_document(xmltype *arg);
--
2.34.1
v10-0001-error-safe-for-casting-bytea-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v10-0001-error-safe-for-casting-bytea-to-other-types-per-pg_cast.patchDownload
From 346525569c67eb8fb9d17057a46746e2a612fc94 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 15:36:35 +0800
Subject: [PATCH v10 01/20] error safe for casting bytea to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc and pc.castfunc > 0 and castsource::regtype ='bytea'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------+----------+-------------+------------+------------+---------
bytea | smallint | 6370 | e | f | bytea_int2 | int2
bytea | integer | 6371 | e | f | bytea_int4 | int4
bytea | bigint | 6372 | e | f | bytea_int8 | int8
(3 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/bytea.c | 18 +++++++++++++++---
1 file changed, 15 insertions(+), 3 deletions(-)
diff --git a/src/backend/utils/adt/bytea.c b/src/backend/utils/adt/bytea.c
index 6e7b914c563..4a8adcb8204 100644
--- a/src/backend/utils/adt/bytea.c
+++ b/src/backend/utils/adt/bytea.c
@@ -1027,10 +1027,14 @@ bytea_int2(PG_FUNCTION_ARGS)
/* Check that the byte array is not too long */
if (len > sizeof(result))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range"));
+ PG_RETURN_NULL();
+ }
+
/* Convert it to an integer; most significant bytes come first */
result = 0;
for (int i = 0; i < len; i++)
@@ -1052,10 +1056,14 @@ bytea_int4(PG_FUNCTION_ARGS)
/* Check that the byte array is not too long */
if (len > sizeof(result))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range"));
+ PG_RETURN_NULL();
+ }
+
/* Convert it to an integer; most significant bytes come first */
result = 0;
for (int i = 0; i < len; i++)
@@ -1077,10 +1085,14 @@ bytea_int8(PG_FUNCTION_ARGS)
/* Check that the byte array is not too long */
if (len > sizeof(result))
- ereport(ERROR,
+ {
+ errsave(fcinfo->context,
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("bigint out of range"));
+ PG_RETURN_NULL();
+ }
+
/* Convert it to an integer; most significant bytes come first */
result = 0;
for (int i = 0; i < len; i++)
--
2.34.1
As mentioned before, to make
CAST(source_expr AS target_type DEFAULT expr ON CONVERSION ERROR);
work,
we cannot just simply replace casting FuncExpr nodes with CoerceViaIO,
since
type modifiers behave differently in these two Nodes.
(e.g., casting numeric 1.11 to integer is not equivalent to casting the
literal
"1.11" to integer).
I wasn't suggesting that. I was suggesting that we move tests that use a
given type's custom cast function into the same patch that makes that cast
safe. This would mean that the initial syntax+nodes+executor patch starts
out only with test cases known to not have custom functions.
Also, are we settled on this new pg_cast column name
(pg_cast.casterrorsafe)?
Overall, I think it's better not to touch pg_cast.dat in each of these
refactoring patches.
I'm fine with it. I can see having 'f' and 's' both mean cast functions,
but 's' means safe, but the extra boolean works too and we'll be fine with
either method.
Without first refactoring pg_cast.castfunc (01 to 18), making CAST ...
DEFAULT as the first patch (0001)
won't work, since pg_cast.castfunc itself is not error safe yet, and we
have no way to test the CAST DEFAULT syntax.So we have to *first* refactor pg_cast.castfunc, make it error safe
then implement CAST DEFAULT.
Makes sense.
However, I found out, to make
SELECT CAST('five'::INTEGER AS INTEGER DEFAULT 6 ON CONVERSION ERROR);
error safe is hard.
That's no problem at all. The CAST () function can't do anything about an
input that's already an error before the CAST node executes. Nor should it.
It should only concern itself with errors related to the actual casting of
a value to the specified type.
('five'::INTEGER) is a TypeCast node, normally it will error out in
transformTypeCast->
coerce_to_target_type->coerce_type ```(if (inputTypeId == UNKNOWNOID
&& IsA(node, Const)))```
If we do not error out, then we need a Node to represent the failed cast,
mainly
for deparse purposes.
We _must_ error out in that case, before the CAST executes.
that means for CAST(source_expr .... DEFAULT defexpr ON CONVERSION ERROR);
The only corner case we handle is when source_expr is a simple Const node.
In all other cases, source_expr is processed through transformExpr,
which does all
the normal parse analysis for a node.
I'll get to reviewing this patchset soon.
On Tue, Nov 11, 2025 at 8:37 AM jian he <jian.universality@gmail.com> wrote:
On Thu, Nov 6, 2025 at 6:00 AM Corey Huinker <corey.huinker@gmail.com> wrote:
[...]
Currently, patches v9-0001 through v9-0018 focus solely on refactoring
pg_cast.castfunc.
I had a quick look but haven't finished the full review due to lack of
time today. Here are a few initial comments:
v10-0003:
- NO_XML_SUPPORT();
+ errsave(escontext,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unsupported XML feature"),
+ errdetail("This functionality requires the server to be
built with libxml support."));
return NULL;
Can use ereturn() instead of errsave() followed by return NULL.
--
10-0004:
+/* error safe version of textToQualifiedNameList */
+List *
+textToQualifiedNameListSafe(text *textval, Node *escontext)
If I am not mistaken, it looks like an exact copy of
textToQualifiedNameList(). I think you can simply keep only
textToQualifiedNameListSafe() and call that from
textToQualifiedNameList() with a NULL value for escontext. This way,
all errsave() or ereturn() calls will behave like ereport().
The same logic applies to RangeVarGetRelidExtendedSafe() and
makeRangeVarFromNameListSafe. These can be called from
RangeVarGetRelidExtended() and makeRangeVarFromNameList(),
respectively.
--
+ {
+ errsave(escontext,
+ errcode(ERRCODE_INVALID_NAME),
+ errmsg("invalid name syntax"));
+ return NIL;
+ }
I prefer ereturn() instead of errsave + return.
--
v10-0017
for (i = 0; i < lengthof(messages); i++)
if (messages[i].type == type)
- ereport(ERROR,
+ {
+ errsave(escontext,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(messages[i].msg, sqltype)));
+ return;
+ }
Here, I think, you can use ereturn() to return void.
--
v10-0018
+ if (unlikely(result == 0.0) && val1 != 0.0 && !isinf(val2))
+ {
+ errsave(escontext,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: underflow"));
+
+ result = 0.0;
+ return result;
+ }
Here, you can use ereturn() to return 0.0. Similar changes are needed
at other places in the same patch.
Regards,
Amul
On Mon, Nov 10, 2025 at 11:36 PM Corey Huinker <corey.huinker@gmail.com>
wrote:
As mentioned before, to make
CAST(source_expr AS target_type DEFAULT expr ON CONVERSION ERROR);
work,
we cannot just simply replace casting FuncExpr nodes with CoerceViaIO,
since
type modifiers behave differently in these two Nodes.
(e.g., casting numeric 1.11 to integer is not equivalent to casting the
literal
"1.11" to integer).I wasn't suggesting that. I was suggesting that we move tests that use a
given type's custom cast function into the same patch that makes that cast
safe. This would mean that the initial syntax+nodes+executor patch starts
out only with test cases known to not have custom functions.
With a second look at the patches, I think the organization is fine as-is.
Also, are we settled on this new pg_cast column name
(pg_cast.casterrorsafe)?
Overall, I think it's better not to touch pg_cast.dat in each of these
refactoring patches.I'm fine with it. I can see having 'f' and 's' both mean cast functions,
but 's' means safe, but the extra boolean works too and we'll be fine with
either method.
I can work on this part if you don't have time.
I'll get to reviewing this patchset soon.
Not as soon as I would have liked, but the patchset still applies without
error, and survives all of my attempts to break it, including user defined
functions that generate errors deterministically as well as
non-deterministically, hoping to see it incorrectly use the default rather
than reporting the error.
On Mon, Nov 17, 2025 at 9:43 PM Amul Sul <sulamul@gmail.com> wrote:
10-0004:
+/* error safe version of textToQualifiedNameList */ +List * +textToQualifiedNameListSafe(text *textval, Node *escontext)If I am not mistaken, it looks like an exact copy of
textToQualifiedNameList(). I think you can simply keep only
textToQualifiedNameListSafe() and call that from
textToQualifiedNameList() with a NULL value for escontext. This way,
all errsave() or ereturn() calls will behave like ereport().The same logic applies to RangeVarGetRelidExtendedSafe() and
makeRangeVarFromNameListSafe. These can be called from
RangeVarGetRelidExtended() and makeRangeVarFromNameList(),
respectively.
--
hi.
List *
textToQualifiedNameList(text *textval)
{
List *namelist;
rawname = text_to_cstring(textval);
if (!SplitIdentifierString(rawname, '.', &namelist))
ereport(ERROR,
(errcode(ERRCODE_INVALID_NAME),
errmsg("invalid name syntax")));
}
I don’t see any way to pass the escontext (ErrorSaveContext) without changing
the textToQualifiedNameList function signature.
There are around 30 occurrences of textToQualifiedNameList.
changing the function textToQualifiedNameList signature is invasive,
so I tend to avoid it.
I think it's easier to just duplicate textToQualifiedNameList
than changing the function textToQualifiedNameList signature.
Am I missing something?
The same logic applies to RangeVarGetRelidExtendedSafe() and
makeRangeVarFromNameListSafe. These can be called from
RangeVarGetRelidExtended() and makeRangeVarFromNameList(),
respectively.
--I don’t see any way to pass the escontext (ErrorSaveContext) without
changing
the textToQualifiedNameList function signature.
...
Am I missing something?
I think we need to keep these separate. The execution paths that don't care
about capturing errors shouldn't be slowed down by minority of paths that
do. That may change in the future, but if it does, we'll be getting rid of
a lot of internal functions with this type of difference.
On Fri, Nov 21, 2025 at 2:11 PM jian he <jian.universality@gmail.com> wrote:
On Mon, Nov 17, 2025 at 9:43 PM Amul Sul <sulamul@gmail.com> wrote:
10-0004:
+/* error safe version of textToQualifiedNameList */ +List * +textToQualifiedNameListSafe(text *textval, Node *escontext)If I am not mistaken, it looks like an exact copy of
textToQualifiedNameList(). I think you can simply keep only
textToQualifiedNameListSafe() and call that from
textToQualifiedNameList() with a NULL value for escontext. This way,
all errsave() or ereturn() calls will behave like ereport().The same logic applies to RangeVarGetRelidExtendedSafe() and
makeRangeVarFromNameListSafe. These can be called from
RangeVarGetRelidExtended() and makeRangeVarFromNameList(),
respectively.
--hi.
List *
textToQualifiedNameList(text *textval)
{
List *namelist;
rawname = text_to_cstring(textval);
if (!SplitIdentifierString(rawname, '.', &namelist))
ereport(ERROR,
(errcode(ERRCODE_INVALID_NAME),
errmsg("invalid name syntax")));
}I don’t see any way to pass the escontext (ErrorSaveContext) without changing
the textToQualifiedNameList function signature.There are around 30 occurrences of textToQualifiedNameList.
changing the function textToQualifiedNameList signature is invasive,
so I tend to avoid it.
I think it's easier to just duplicate textToQualifiedNameList
than changing the function textToQualifiedNameList signature.Am I missing something?
The change I was suggesting will be as below:
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -2684,6 +2684,13 @@ name_text(PG_FUNCTION_ARGS)
*/
List *
textToQualifiedNameList(text *textval)
+{
+ textToQualifiedNameListSafe(textval, NULL);
+}
+
+/* error safe version of textToQualifiedNameList */
+List *
+textToQualifiedNameListSafe(text *textval, Node *escontext)
{
char *rawname;
List *result = NIL;
We must try to avoid duplication whenever possible, as any bug fixes
or enhancements would need to be copied to multiple places, which is
often overlooked.
Regards,
Amul
On Mon, Nov 24, 2025 at 11:38 AM Amul Sul <sulamul@gmail.com> wrote:
The change I was suggesting will be as below:
--- a/src/backend/utils/adt/varlena.c +++ b/src/backend/utils/adt/varlena.c @@ -2684,6 +2684,13 @@ name_text(PG_FUNCTION_ARGS) */ List * textToQualifiedNameList(text *textval) +{ + textToQualifiedNameListSafe(textval, NULL); +} + +/* error safe version of textToQualifiedNameList */ +List * +textToQualifiedNameListSafe(text *textval, Node *escontext) { char *rawname; List *result = NIL;We must try to avoid duplication whenever possible, as any bug fixes
or enhancements would need to be copied to multiple places, which is
often overlooked.
hi.
great idea!
I incorporated all of your ideas into v11.
I replaced all errsave to ereturn.
I aslo simplified T_SafeTypeCastExpr expression initialization, evaluation logic
within execExpr.c, execExprInterp.c.
but one thing I didn't touch: float8_div.
+static inline float8
+float8_div_safe(const float8 val1, const float8 val2, struct Node *escontext)
but we can change float8_div to:
static inline float8
float8_div(const float8 val1, const float8 val2)
{
return float8_div_safe(val1, val2, NULL);
}
I am worried that entering another function would cause a minor performance
degradation. And since these simple functions are so simple, keeping them
separated should not be a big problem. also I placed float8_div,
float8_div_safe together.
Attachments:
v11-0018-error-safe-for-casting-geometry-data-type.patchtext/x-patch; charset=US-ASCII; name=v11-0018-error-safe-for-casting-geometry-data-type.patchDownload
From 78f39bc072a0e228ea225b512d4a7940f9b74e63 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Mon, 24 Nov 2025 16:38:23 +0800
Subject: [PATCH v11 18/20] error safe for casting geometry data type
select castsource::regtype, casttarget::regtype, pp.prosrc
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
join pg_type pt on pt.oid = castsource
join pg_type pt1 on pt1.oid = casttarget
and pc.castfunc > 0 and pt.typarray <> 0
and pt.typnamespace = 'pg_catalog'::regnamespace
and pt1.typnamespace = 'pg_catalog'::regnamespace
and (pt.typcategory = 'G' or pt1.typcategory = 'G')
order by castsource::regtype, casttarget::regtype;
castsource | casttarget | prosrc
------------+------------+---------------
point | box | point_box
lseg | point | lseg_center
path | polygon | path_poly
box | point | box_center
box | lseg | box_diagonal
box | polygon | box_poly
box | circle | box_circle
polygon | point | poly_center
polygon | path | poly_path
polygon | box | poly_box
polygon | circle | poly_circle
circle | point | circle_center
circle | box | circle_box
circle | polygon |
(14 rows)
already error safe: point_box, box_diagonal, box_poly, poly_path, poly_box, circle_center
almost error safe: path_poly
can not error safe: cast circle to polygon, because it's a SQL function
discussion: https://postgr.es/m/CACJufxHCMzrHOW=wRe8L30rMhB3sjwAv1LE928Fa7sxMu1Tx-g@mail.gmail.com
---
src/backend/access/spgist/spgproc.c | 2 +-
src/backend/utils/adt/geo_ops.c | 225 +++++++++++++++++++++++-----
src/backend/utils/adt/geo_spgist.c | 2 +-
src/include/utils/float.h | 77 ++++++++++
src/include/utils/geo_decls.h | 4 +-
5 files changed, 271 insertions(+), 39 deletions(-)
diff --git a/src/backend/access/spgist/spgproc.c b/src/backend/access/spgist/spgproc.c
index 660009291da..b3e0fbc59ba 100644
--- a/src/backend/access/spgist/spgproc.c
+++ b/src/backend/access/spgist/spgproc.c
@@ -51,7 +51,7 @@ point_box_distance(Point *point, BOX *box)
else
dy = 0.0;
- return HYPOT(dx, dy);
+ return HYPOT(dx, dy, NULL);
}
/*
diff --git a/src/backend/utils/adt/geo_ops.c b/src/backend/utils/adt/geo_ops.c
index 377a1b3f3ad..bae8730c347 100644
--- a/src/backend/utils/adt/geo_ops.c
+++ b/src/backend/utils/adt/geo_ops.c
@@ -78,11 +78,14 @@ enum path_delim
/* Routines for points */
static inline void point_construct(Point *result, float8 x, float8 y);
static inline void point_add_point(Point *result, Point *pt1, Point *pt2);
+static inline bool point_add_point_safe(Point *result, Point *pt1, Point *pt2,
+ Node *escontext);
static inline void point_sub_point(Point *result, Point *pt1, Point *pt2);
static inline void point_mul_point(Point *result, Point *pt1, Point *pt2);
static inline void point_div_point(Point *result, Point *pt1, Point *pt2);
static inline bool point_eq_point(Point *pt1, Point *pt2);
static inline float8 point_dt(Point *pt1, Point *pt2);
+static inline float8 point_dt_safe(Point *pt1, Point *pt2, Node *escontext);
static inline float8 point_sl(Point *pt1, Point *pt2);
static int point_inside(Point *p, int npts, Point *plist);
@@ -109,6 +112,7 @@ static float8 lseg_closept_lseg(Point *result, LSEG *on_lseg, LSEG *to_lseg);
/* Routines for boxes */
static inline void box_construct(BOX *result, Point *pt1, Point *pt2);
static void box_cn(Point *center, BOX *box);
+static bool box_cn_safe(Point *center, BOX *box, Node* escontext);
static bool box_ov(BOX *box1, BOX *box2);
static float8 box_ar(BOX *box);
static float8 box_ht(BOX *box);
@@ -125,7 +129,7 @@ static float8 circle_ar(CIRCLE *circle);
/* Routines for polygons */
static void make_bound_box(POLYGON *poly);
-static void poly_to_circle(CIRCLE *result, POLYGON *poly);
+static bool poly_to_circle_safe(CIRCLE *result, POLYGON *poly, Node *escontext);
static bool lseg_inside_poly(Point *a, Point *b, POLYGON *poly, int start);
static bool poly_contain_poly(POLYGON *contains_poly, POLYGON *contained_poly);
static bool plist_same(int npts, Point *p1, Point *p2);
@@ -851,7 +855,8 @@ box_center(PG_FUNCTION_ARGS)
BOX *box = PG_GETARG_BOX_P(0);
Point *result = (Point *) palloc(sizeof(Point));
- box_cn(result, box);
+ if (!box_cn_safe(result, box, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_POINT_P(result);
}
@@ -871,10 +876,33 @@ box_ar(BOX *box)
static void
box_cn(Point *center, BOX *box)
{
- center->x = float8_div(float8_pl(box->high.x, box->low.x), 2.0);
- center->y = float8_div(float8_pl(box->high.y, box->low.y), 2.0);
+ (void) box_cn_safe(center, box, NULL);
}
+static bool
+box_cn_safe(Point *center, BOX *box, Node* escontext)
+{
+ float8 x;
+ float8 y;
+
+ x = float8_pl_safe(box->high.x, box->low.x, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ center->x = float8_div_safe(x, 2.0, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ y = float8_pl_safe(box->high.y, box->low.y, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ center->y = float8_div_safe(y, 2.0, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ return true;
+}
/* box_wd - returns the width (length) of the box
* (horizontal magnitude).
@@ -1276,7 +1304,7 @@ line_distance(PG_FUNCTION_ARGS)
PG_RETURN_FLOAT8(float8_div(fabs(float8_mi(l1->C,
float8_mul(ratio, l2->C))),
- HYPOT(l1->A, l1->B)));
+ HYPOT(l1->A, l1->B, NULL)));
}
/* line_interpt()
@@ -2001,9 +2029,27 @@ point_distance(PG_FUNCTION_ARGS)
static inline float8
point_dt(Point *pt1, Point *pt2)
{
- return HYPOT(float8_mi(pt1->x, pt2->x), float8_mi(pt1->y, pt2->y));
+ return point_dt_safe(pt1, pt2, NULL);
}
+static inline float8
+point_dt_safe(Point *pt1, Point *pt2, Node *escontext)
+{
+ float8 x;
+ float8 y;
+
+ x = float8_mi_safe(pt1->x, pt2->x, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return 0.0;
+
+ y = float8_mi_safe(pt1->y, pt2->y, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return 0.0;
+
+ return HYPOT(x, y, escontext);
+}
+
+
Datum
point_slope(PG_FUNCTION_ARGS)
{
@@ -2317,13 +2363,31 @@ lseg_center(PG_FUNCTION_ARGS)
{
LSEG *lseg = PG_GETARG_LSEG_P(0);
Point *result;
+ float8 x;
+ float8 y;
result = (Point *) palloc(sizeof(Point));
- result->x = float8_div(float8_pl(lseg->p[0].x, lseg->p[1].x), 2.0);
- result->y = float8_div(float8_pl(lseg->p[0].y, lseg->p[1].y), 2.0);
+ x = float8_pl_safe(lseg->p[0].x, lseg->p[1].x, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ result->x = float8_div_safe(x, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ y = float8_pl_safe(lseg->p[0].y, lseg->p[1].y, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ result->y = float8_div_safe(y, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
PG_RETURN_POINT_P(result);
+
+fail:
+ PG_RETURN_NULL();
}
@@ -4110,9 +4174,27 @@ construct_point(PG_FUNCTION_ARGS)
static inline void
point_add_point(Point *result, Point *pt1, Point *pt2)
{
- point_construct(result,
- float8_pl(pt1->x, pt2->x),
- float8_pl(pt1->y, pt2->y));
+ (void) point_add_point_safe(result, pt1, pt2, NULL);
+}
+
+static inline bool
+point_add_point_safe(Point *result, Point *pt1, Point *pt2,
+ Node *escontext)
+{
+ float8 x;
+ float8 y;
+
+ x = float8_pl_safe(pt1->x, pt2->x, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ y = float8_pl_safe(pt1->y, pt2->y, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ point_construct(result, x, y);
+
+ return true;
}
Datum
@@ -4458,7 +4540,7 @@ path_poly(PG_FUNCTION_ARGS)
/* This is not very consistent --- other similar cases return NULL ... */
if (!path->closed)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("open path cannot be converted to polygon")));
@@ -4508,7 +4590,9 @@ poly_center(PG_FUNCTION_ARGS)
result = (Point *) palloc(sizeof(Point));
- poly_to_circle(&circle, poly);
+ if (!poly_to_circle_safe(&circle, poly, fcinfo->context))
+ PG_RETURN_NULL();
+
*result = circle.center;
PG_RETURN_POINT_P(result);
@@ -5005,7 +5089,7 @@ circle_mul_pt(PG_FUNCTION_ARGS)
result = (CIRCLE *) palloc(sizeof(CIRCLE));
point_mul_point(&result->center, &circle->center, point);
- result->radius = float8_mul(circle->radius, HYPOT(point->x, point->y));
+ result->radius = float8_mul(circle->radius, HYPOT(point->x, point->y, NULL));
PG_RETURN_CIRCLE_P(result);
}
@@ -5020,7 +5104,7 @@ circle_div_pt(PG_FUNCTION_ARGS)
result = (CIRCLE *) palloc(sizeof(CIRCLE));
point_div_point(&result->center, &circle->center, point);
- result->radius = float8_div(circle->radius, HYPOT(point->x, point->y));
+ result->radius = float8_div(circle->radius, HYPOT(point->x, point->y, NULL));
PG_RETURN_CIRCLE_P(result);
}
@@ -5191,14 +5275,30 @@ circle_box(PG_FUNCTION_ARGS)
box = (BOX *) palloc(sizeof(BOX));
- delta = float8_div(circle->radius, sqrt(2.0));
+ delta = float8_div_safe(circle->radius, sqrt(2.0), fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
- box->high.x = float8_pl(circle->center.x, delta);
- box->low.x = float8_mi(circle->center.x, delta);
- box->high.y = float8_pl(circle->center.y, delta);
- box->low.y = float8_mi(circle->center.y, delta);
+ box->high.x = float8_pl_safe(circle->center.x, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ box->low.x = float8_mi_safe(circle->center.x, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ box->high.y = float8_pl_safe(circle->center.y, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ box->low.y = float8_mi_safe(circle->center.y, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
PG_RETURN_BOX_P(box);
+
+fail:
+ PG_RETURN_NULL();
}
/* box_circle()
@@ -5209,15 +5309,35 @@ box_circle(PG_FUNCTION_ARGS)
{
BOX *box = PG_GETARG_BOX_P(0);
CIRCLE *circle;
+ float8 x;
+ float8 y;
circle = (CIRCLE *) palloc(sizeof(CIRCLE));
- circle->center.x = float8_div(float8_pl(box->high.x, box->low.x), 2.0);
- circle->center.y = float8_div(float8_pl(box->high.y, box->low.y), 2.0);
+ x = float8_pl_safe(box->high.x, box->low.x, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
- circle->radius = point_dt(&circle->center, &box->high);
+ circle->center.x = float8_div_safe(x, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ y = float8_pl_safe(box->high.y, box->low.y, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ circle->center.y = float8_div_safe(y, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ circle->radius = point_dt_safe(&circle->center, &box->high, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
PG_RETURN_CIRCLE_P(circle);
+
+fail:
+ PG_RETURN_NULL();
}
@@ -5281,10 +5401,11 @@ circle_poly(PG_FUNCTION_ARGS)
* XXX This algorithm should use weighted means of line segments
* rather than straight average values of points - tgl 97/01/21.
*/
-static void
-poly_to_circle(CIRCLE *result, POLYGON *poly)
+static bool
+poly_to_circle_safe(CIRCLE *result, POLYGON *poly, Node *escontext)
{
int i;
+ float8 x;
Assert(poly->npts > 0);
@@ -5293,14 +5414,42 @@ poly_to_circle(CIRCLE *result, POLYGON *poly)
result->radius = 0;
for (i = 0; i < poly->npts; i++)
- point_add_point(&result->center, &result->center, &poly->p[i]);
- result->center.x = float8_div(result->center.x, poly->npts);
- result->center.y = float8_div(result->center.y, poly->npts);
+ {
+ if (!point_add_point_safe(&result->center,
+ &result->center,
+ &poly->p[i],
+ escontext))
+ return false;
+ }
+
+ result->center.x = float8_div_safe(result->center.x,
+ poly->npts,
+ escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ result->center.y = float8_div_safe(result->center.y,
+ poly->npts,
+ escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
for (i = 0; i < poly->npts; i++)
- result->radius = float8_pl(result->radius,
- point_dt(&poly->p[i], &result->center));
- result->radius = float8_div(result->radius, poly->npts);
+ {
+ x = point_dt_safe(&poly->p[i], &result->center, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ result->radius = float8_pl_safe(result->radius, x, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+ }
+
+ result->radius = float8_div_safe(result->radius, poly->npts, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ return true;
}
Datum
@@ -5311,7 +5460,8 @@ poly_circle(PG_FUNCTION_ARGS)
result = (CIRCLE *) palloc(sizeof(CIRCLE));
- poly_to_circle(result, poly);
+ if (!poly_to_circle_safe(result, poly, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_CIRCLE_P(result);
}
@@ -5516,7 +5666,7 @@ plist_same(int npts, Point *p1, Point *p2)
*-----------------------------------------------------------------------
*/
float8
-pg_hypot(float8 x, float8 y)
+pg_hypot(float8 x, float8 y, Node *escontext)
{
float8 yx,
result;
@@ -5554,9 +5704,14 @@ pg_hypot(float8 x, float8 y)
result = x * sqrt(1.0 + (yx * yx));
if (unlikely(isinf(result)))
- float_overflow_error();
+ ereturn(escontext, 0.0,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: overflow"));
+
if (unlikely(result == 0.0))
- float_underflow_error();
+ ereturn(escontext, 0.0,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: underflow"));
return result;
}
diff --git a/src/backend/utils/adt/geo_spgist.c b/src/backend/utils/adt/geo_spgist.c
index fec33e95372..ffffd3cd2de 100644
--- a/src/backend/utils/adt/geo_spgist.c
+++ b/src/backend/utils/adt/geo_spgist.c
@@ -390,7 +390,7 @@ pointToRectBoxDistance(Point *point, RectBox *rect_box)
else
dy = 0;
- return HYPOT(dx, dy);
+ return HYPOT(dx, dy, NULL);
}
diff --git a/src/include/utils/float.h b/src/include/utils/float.h
index fc2a9cf6475..aec376ffc56 100644
--- a/src/include/utils/float.h
+++ b/src/include/utils/float.h
@@ -121,6 +121,21 @@ float8_pl(const float8 val1, const float8 val2)
return result;
}
+/* error safe version of float8_pl */
+static inline float8
+float8_pl_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+ float8 result;
+
+ result = val1 + val2;
+ if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
+ ereturn(escontext, 0,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: overflow"));
+
+ return result;
+}
+
static inline float4
float4_mi(const float4 val1, const float4 val2)
{
@@ -145,6 +160,21 @@ float8_mi(const float8 val1, const float8 val2)
return result;
}
+/* error safe version of float8_mi */
+static inline float8
+float8_mi_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+ float8 result;
+
+ result = val1 - val2;
+ if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
+ ereturn(escontext, 0,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: overflow"));
+
+ return result;
+}
+
static inline float4
float4_mul(const float4 val1, const float4 val2)
{
@@ -173,6 +203,27 @@ float8_mul(const float8 val1, const float8 val2)
return result;
}
+/* error safe version of float8_mul */
+static inline float8
+float8_mul_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+ float8 result;
+
+ result = val1 * val2;
+
+ if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
+ ereturn(escontext, 0,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: overflow"));
+
+ if (unlikely(result == 0.0) && val1 != 0.0 && val2 != 0.0)
+ ereturn(escontext, 0,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: underflow"));
+
+ return result;
+}
+
static inline float4
float4_div(const float4 val1, const float4 val2)
{
@@ -205,6 +256,32 @@ float8_div(const float8 val1, const float8 val2)
return result;
}
+/* error safe version of float8_div */
+static inline float8
+float8_div_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+ float8 result;
+
+ if (unlikely(val2 == 0.0) && !isnan(val1))
+ ereturn(escontext, 0,
+ errcode(ERRCODE_DIVISION_BY_ZERO),
+ errmsg("division by zero"));
+
+ result = val1 / val2;
+
+ if (unlikely(isinf(result)) && !isinf(val1))
+ ereturn(escontext, 0,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: overflow"));
+
+ if (unlikely(result == 0.0) && val1 != 0.0 && !isinf(val2))
+ ereturn(escontext, 0,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: underflow"));
+
+ return result;
+}
+
/*
* Routines for NaN-aware comparisons
*
diff --git a/src/include/utils/geo_decls.h b/src/include/utils/geo_decls.h
index 8a9df75c93c..0056a26639f 100644
--- a/src/include/utils/geo_decls.h
+++ b/src/include/utils/geo_decls.h
@@ -88,7 +88,7 @@ FPge(double A, double B)
#define FPge(A,B) ((A) >= (B))
#endif
-#define HYPOT(A, B) pg_hypot(A, B)
+#define HYPOT(A, B, escontext) pg_hypot(A, B, escontext)
/*---------------------------------------------------------------------
* Point - (x,y)
@@ -280,6 +280,6 @@ CirclePGetDatum(const CIRCLE *X)
* in geo_ops.c
*/
-extern float8 pg_hypot(float8 x, float8 y);
+extern float8 pg_hypot(float8 x, float8 y, Node *escontext);
#endif /* GEO_DECLS_H */
--
2.34.1
v11-0020-CAST-expr-AS-newtype-DEFAULT-ON-ERROR.patchtext/x-patch; charset=UTF-8; name=v11-0020-CAST-expr-AS-newtype-DEFAULT-ON-ERROR.patchDownload
From f1d5dc9962c138323ea2e3d0949fe32663e58800 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Tue, 25 Nov 2025 10:37:33 +0800
Subject: [PATCH v11 20/20] CAST(expr AS newtype DEFAULT ON ERROR)
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.
draft doc also added.
We cannot simply prohibit user-defined functions in pg_cast for safe cast
evaluation because CREATE CAST can also utilize built-in functions. So, to
completely disallow custom casts created via CREATE CAST used in safe cast
evaluation, a new field in pg_cast would unfortunately be necessary.
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}
[0]: https://git.postgresql.org/cgit/postgresql.git/commit/?id=aaaf9449ec6be62cb0d30ed3588dc384f56274b
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
.../pg_stat_statements/expected/select.out | 23 +-
contrib/pg_stat_statements/sql/select.sql | 5 +
doc/src/sgml/catalogs.sgml | 12 +
doc/src/sgml/syntax.sgml | 29 +
src/backend/catalog/pg_cast.c | 1 +
src/backend/executor/execExpr.c | 61 +-
src/backend/executor/execExprInterp.c | 29 +
src/backend/jit/llvm/llvmjit_expr.c | 26 +
src/backend/nodes/nodeFuncs.c | 61 ++
src/backend/optimizer/util/clauses.c | 26 +
src/backend/parser/gram.y | 22 +
src/backend/parser/parse_agg.c | 9 +
src/backend/parser/parse_expr.c | 424 +++++++++-
src/backend/parser/parse_func.c | 3 +
src/backend/parser/parse_target.c | 14 +
src/backend/utils/adt/arrayfuncs.c | 8 +
src/backend/utils/adt/ruleutils.c | 21 +
src/include/catalog/pg_cast.dat | 330 ++++----
src/include/catalog/pg_cast.h | 4 +
src/include/executor/execExpr.h | 7 +
src/include/nodes/execnodes.h | 21 +
src/include/nodes/parsenodes.h | 11 +
src/include/nodes/primnodes.h | 36 +
src/include/parser/parse_node.h | 2 +
src/test/regress/expected/cast.out | 791 ++++++++++++++++++
src/test/regress/expected/create_cast.out | 5 +
src/test/regress/expected/opr_sanity.out | 24 +-
src/test/regress/parallel_schedule | 2 +-
src/test/regress/sql/cast.sql | 340 ++++++++
src/test/regress/sql/create_cast.sql | 1 +
src/tools/pgindent/typedefs.list | 3 +
31 files changed, 2157 insertions(+), 194 deletions(-)
create mode 100644 src/test/regress/expected/cast.out
create mode 100644 src/test/regress/sql/cast.sql
diff --git a/contrib/pg_stat_statements/expected/select.out b/contrib/pg_stat_statements/expected/select.out
index 75c896f3885..6e67997f0a2 100644
--- a/contrib/pg_stat_statements/expected/select.out
+++ b/contrib/pg_stat_statements/expected/select.out
@@ -73,6 +73,25 @@ SELECT 1 AS "int" OFFSET 2 FETCH FIRST 3 ROW ONLY;
-----
(0 rows)
+--error safe type cast
+SELECT CAST('a' AS int DEFAULT 2 ON CONVERSION ERROR);
+ int4
+------
+ 2
+(1 row)
+
+SELECT CAST('11' AS int DEFAULT 2 ON CONVERSION ERROR);
+ int4
+------
+ 11
+(1 row)
+
+SELECT CAST('12' AS numeric DEFAULT 2 ON CONVERSION ERROR);
+ numeric
+---------
+ 12
+(1 row)
+
-- DISTINCT and ORDER BY patterns
-- Try some query permutations which once produced identical query IDs
SELECT DISTINCT 1 AS "int";
@@ -222,6 +241,8 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
2 | 2 | SELECT $1 AS "int" ORDER BY 1
1 | 2 | SELECT $1 AS i UNION SELECT $2 ORDER BY i
1 | 1 | SELECT $1 || $2
+ 2 | 2 | SELECT CAST($1 AS int DEFAULT $2 ON CONVERSION ERROR)
+ 1 | 1 | SELECT CAST($1 AS numeric DEFAULT $2 ON CONVERSION ERROR)
2 | 2 | SELECT DISTINCT $1 AS "int"
0 | 0 | SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"
1 | 1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
@@ -230,7 +251,7 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
| | ) +
| | SELECT f FROM t ORDER BY f
1 | 1 | select $1::jsonb ? $2
-(17 rows)
+(19 rows)
SELECT pg_stat_statements_reset() IS NOT NULL AS t;
t
diff --git a/contrib/pg_stat_statements/sql/select.sql b/contrib/pg_stat_statements/sql/select.sql
index 11662cde08c..7ee8160fd84 100644
--- a/contrib/pg_stat_statements/sql/select.sql
+++ b/contrib/pg_stat_statements/sql/select.sql
@@ -25,6 +25,11 @@ SELECT 1 AS "int" LIMIT 3 OFFSET 3;
SELECT 1 AS "int" OFFSET 1 FETCH FIRST 2 ROW ONLY;
SELECT 1 AS "int" OFFSET 2 FETCH FIRST 3 ROW ONLY;
+--error safe type cast
+SELECT CAST('a' AS int DEFAULT 2 ON CONVERSION ERROR);
+SELECT CAST('11' AS int DEFAULT 2 ON CONVERSION ERROR);
+SELECT CAST('12' AS numeric DEFAULT 2 ON CONVERSION ERROR);
+
-- DISTINCT and ORDER BY patterns
-- Try some query permutations which once produced identical query IDs
SELECT DISTINCT 1 AS "int";
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2fc63442980..ccd1d256003 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1849,6 +1849,18 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<literal>b</literal> means that the types are binary-coercible, thus no conversion is required.
</para></entry>
</row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>casterrorsafe</structfield> <type>bool</type>
+ </para>
+ <para>
+ This indicates whether the <structfield>castfunc</structfield> function is error safe.
+ If the <structfield>castfunc</structfield> function is error safe, it can be used in error safe type cast.
+ For further details see <xref linkend="sql-syntax-type-casts-safe"/>.
+ </para></entry>
+ </row>
+
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml
index 34c83880a66..9b2482e9b99 100644
--- a/doc/src/sgml/syntax.sgml
+++ b/doc/src/sgml/syntax.sgml
@@ -2106,6 +2106,10 @@ CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable>
The <literal>CAST</literal> syntax conforms to SQL; the syntax with
<literal>::</literal> is historical <productname>PostgreSQL</productname>
usage.
+ The equivalent ON CONVERSION ERROR behavior is:
+<synopsis>
+CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable> ERROR ON CONVERSION ERROR )
+</synopsis>
</para>
<para>
@@ -2160,6 +2164,31 @@ CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable>
<xref linkend="sql-createcast"/>.
</para>
</note>
+
+ <sect3 id="sql-syntax-type-casts-safe">
+ <title>Safe Type Cast</title>
+ <para>
+Sometimes a type cast may fail; to handle such cases, an <literal>ON ERROR</literal> clause can be
+specified to avoid potential errors. The syntax is:
+<synopsis>
+CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable> DEFAULT <replaceable>expression</replaceable> ON CONVERSION ERROR )
+</synopsis>
+ If cast the source expression to target type fails, it falls back to
+ evaluating the optionally supplied default <replaceable>expression</replaceable>
+ specified in ON <literal>ON ERROR</literal> clause.
+ Currently, this only support built-in system type casts,
+ casts created using <link linkend="sql-createcast">CREATE CAST</link> are not supported.
+ </para>
+
+ <para>
+ For example, the following query attempts to cast a <type>text</type> type value to <type>integer</type> type,
+ but when the conversion fails, it falls back to evaluate the supplied default expression.
+<programlisting>
+SELECT CAST(TEXT 'error' AS integer DEFAULT NULL ON CONVERSION ERROR) IS NULL; <lineannotation>true</lineannotation>
+</programlisting>
+ </para>
+ </sect3>
+
</sect2>
<sect2 id="sql-syntax-collate-exprs">
diff --git a/src/backend/catalog/pg_cast.c b/src/backend/catalog/pg_cast.c
index 1773c9c5491..6fe65d24d31 100644
--- a/src/backend/catalog/pg_cast.c
+++ b/src/backend/catalog/pg_cast.c
@@ -84,6 +84,7 @@ CastCreate(Oid sourcetypeid, Oid targettypeid,
values[Anum_pg_cast_castfunc - 1] = ObjectIdGetDatum(funcid);
values[Anum_pg_cast_castcontext - 1] = CharGetDatum(castcontext);
values[Anum_pg_cast_castmethod - 1] = CharGetDatum(castmethod);
+ values[Anum_pg_cast_casterrorsafe - 1] = BoolGetDatum(false);
tuple = heap_form_tuple(RelationGetDescr(relation), values, nulls);
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index b302be36f73..1de0d465f56 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,
@@ -1742,6 +1745,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
elemstate->innermost_caseval = (Datum *) palloc(sizeof(Datum));
elemstate->innermost_casenull = (bool *) palloc(sizeof(bool));
+ elemstate->escontext = state->escontext;
ExecInitExprRec(acoerce->elemexpr, elemstate,
&elemstate->resvalue, &elemstate->resnull);
@@ -2218,6 +2222,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;
@@ -2784,7 +2796,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;
@@ -4782,6 +4794,53 @@ ExecBuildParamSetEqual(TupleDesc desc,
return state;
}
+/*
+ * Push steps to evaluate a SafeTypeCastExpr and its various subsidiary
+ * expressions.
+ */
+static void
+ExecInitSafeTypeCastExpr(SafeTypeCastExpr *stcexpr , ExprState *state,
+ Datum *resv, bool *resnull,
+ ExprEvalStep *scratch)
+{
+ /*
+ * If we can not coerce to the target type, fallback to the DEFAULT
+ * expression specified in the ON CONVERSION ERROR clause, and we are done.
+ */
+ if (stcexpr->cast_expr == NULL)
+ {
+ ExecInitExprRec((Expr *) stcexpr->default_expr,
+ state, resv, resnull);
+ return;
+ }
+ else
+ {
+ SafeTypeCastState *stcstate;
+ ErrorSaveContext *saved_escontext;
+
+ stcstate = palloc0(sizeof(SafeTypeCastState));
+ stcstate->stcexpr = stcexpr;
+ stcstate->escontext.type = T_ErrorSaveContext;
+ state->escontext = &stcstate->escontext;
+
+ /* evaluate argument expression into step's result area */
+ ExecInitExprRec((Expr *) stcexpr->cast_expr,
+ state, resv, resnull);
+ scratch->opcode = EEOP_SAFETYPE_CAST;
+ scratch->d.stcexpr.stcstate = stcstate;
+ ExprEvalPushStep(state, scratch);
+
+ /* Steps to evaluate the DEFAULT expression */
+ saved_escontext = state->escontext;
+ state->escontext = NULL;
+ ExecInitExprRec((Expr *) stcstate->stcexpr->default_expr,
+ state, resv, resnull);
+ state->escontext = saved_escontext;
+
+ 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 67f4e00eac4..f1d8df1ac41 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,28 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
EEO_NEXT();
}
+ EEO_CASE(EEOP_SAFETYPE_CAST)
+ {
+ SafeTypeCastState *stcstate = op->d.stcexpr.stcstate;
+
+ if (SOFT_ERROR_OCCURRED(&stcstate->escontext))
+ {
+ *op->resvalue = (Datum) 0;
+ *op->resnull = true;
+
+ /*
+ * Reset for next use such as for catching errors when coercing
+ * a expression.
+ */
+ stcstate->escontext.error_occurred = false;
+ stcstate->escontext.details_wanted = false;
+
+ EEO_NEXT();
+ }
+ else
+ EEO_JUMP(stcstate->jump_end);
+ }
+
EEO_CASE(EEOP_JSONEXPR_PATH)
{
/* too complex for an inline implementation */
@@ -3644,6 +3667,12 @@ 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/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index ac88881e995..bc7cd3283a5 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -2256,6 +2256,32 @@ llvm_compile_expr(ExprState *state)
LLVMBuildBr(b, opblocks[opno + 1]);
break;
+ case EEOP_SAFETYPE_CAST:
+ {
+ SafeTypeCastState *stcstate = op->d.stcexpr.stcstate;
+
+ if (SOFT_ERROR_OCCURRED(&stcstate->escontext))
+ {
+ /*
+ * Reset for next use such as for catching errors when
+ * coercing a expression.
+ */
+ stcstate->escontext.error_occurred = false;
+ stcstate->escontext.details_wanted = false;
+
+ /* set resnull to true */
+ LLVMBuildStore(b, l_sbool_const(1), v_resnullp);
+ /* reset resvalue */
+ LLVMBuildStore(b, l_datum_const(0), v_resvaluep);
+
+ LLVMBuildBr(b, opblocks[opno + 1]);
+ }
+ else
+ 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 ede838cd40c..fe3e6b11a08 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;
@@ -1706,6 +1717,9 @@ exprLocation(const Node *expr)
loc = leftmostLoc(loc, tc->location);
}
break;
+ case T_SafeTypeCastExpr:
+ loc = ((const SafeTypeCastExpr *) expr)->location;
+ break;
case T_CollateClause:
/* just use argument's location */
loc = exprLocation(((const CollateClause *) expr)->arg);
@@ -2321,6 +2335,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:
@@ -3330,6 +3356,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;
@@ -4464,6 +4503,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->raw_default))
+ 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/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 32af0df6c70..b6a9dc2f5fd 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -2951,6 +2951,32 @@ 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);
+
+ /*
+ * We must not reduce any recognizably constant subexpressions
+ * in cast_expr here, since we don’t want it to fail
+ * prematurely.
+ */
+ 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 c3a0a354a9c..8a27e045bc0 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -16013,6 +16013,28 @@ func_expr_common_subexpr:
}
| CAST '(' a_expr AS Typename ')'
{ $$ = makeTypeCast($3, $5, @1); }
+ | CAST '(' a_expr AS Typename ERROR_P ON CONVERSION_P ERROR_P ')'
+ { $$ = makeTypeCast($3, $5, @1); }
+ | CAST '(' a_expr AS Typename NULL_P ON CONVERSION_P ERROR_P ')'
+ {
+ TypeCast *cast = (TypeCast *) makeTypeCast($3, $5, @1);
+
+ SafeTypeCast *safecast = makeNode(SafeTypeCast);
+ safecast->cast = (Node *) cast;
+ safecast->raw_default = makeNullAConst(-1);;
+
+ $$ = (Node *) safecast;
+ }
+ | CAST '(' a_expr AS Typename DEFAULT a_expr ON CONVERSION_P ERROR_P ')'
+ {
+ TypeCast *cast = (TypeCast *) makeTypeCast($3, $5, @1);
+
+ SafeTypeCast *safecast = makeNode(SafeTypeCast);
+ safecast->cast = (Node *) cast;
+ safecast->raw_default = $7;
+
+ $$ = (Node *) safecast;
+ }
| EXTRACT '(' extract_list ')'
{
$$ = (Node *) makeFuncCall(SystemFuncName("extract"),
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index b8340557b34..de77f6e9302 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -490,6 +490,12 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
err = _("grouping operations are not allowed in check constraints");
break;
+ case EXPR_KIND_CAST_DEFAULT:
+ if (isAgg)
+ err = _("aggregate functions are not allowed in CAST DEFAULT expressions");
+ else
+ err = _("grouping operations are not allowed in CAST DEFAULT expressions");
+ break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
@@ -983,6 +989,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_DOMAIN_CHECK:
err = _("window functions are not allowed in check constraints");
break;
+ case EXPR_KIND_CAST_DEFAULT:
+ err = _("window functions are not allowed in CAST DEFAULT expressions");
+ break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
err = _("window functions are not allowed in DEFAULT expressions");
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 32d6ae918ca..ec43fcd18fb 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -17,6 +17,8 @@
#include "access/htup_details.h"
#include "catalog/pg_aggregate.h"
+#include "catalog/pg_cast.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -37,6 +39,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 +63,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 *can_coerce);
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 +80,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);
@@ -164,13 +169,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;
@@ -564,6 +573,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
case EXPR_KIND_VALUES_SINGLE:
case EXPR_KIND_CHECK_CONSTRAINT:
case EXPR_KIND_DOMAIN_CHECK:
+ case EXPR_KIND_CAST_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
case EXPR_KIND_INDEX_EXPRESSION:
case EXPR_KIND_INDEX_PREDICATE:
@@ -1824,6 +1834,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_DOMAIN_CHECK:
err = _("cannot use subquery in check constraint");
break;
+ case EXPR_KIND_CAST_DEFAULT:
+ err = _("cannot use subquery in CAST DEFAULT expression");
+ break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
err = _("cannot use subquery in DEFAULT expression");
@@ -2011,16 +2024,23 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
* 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().
+ *
+ * Most of the time, can_coerce will be NULL.
+ * can_coerce is not NULL only when performing parse analysis for expressions
+ * like CAST(DEFAULT ... ON CONVERSION ERROR). can_coerce should be assumed to
+ * default to true. If coerce array elements to the target type fails,
+ * can_coerce will be set to 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;
List *newcoercedelems = NIL;
ListCell *element;
Oid coerce_type;
+ Oid coerce_type_coll;
bool coerce_hard;
/*
@@ -2045,9 +2065,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
@@ -2088,6 +2109,9 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
}
else
{
+ /* The target type is valid when performing type-safe cast */
+ Assert(can_coerce == NULL);
+
/* Can't handle an empty array without a target type */
if (newelems == NIL)
ereport(ERROR,
@@ -2125,6 +2149,8 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
coerce_hard = false;
}
+ coerce_type_coll = get_typcollation(coerce_type);
+
/*
* Coerce elements to target type
*
@@ -2134,13 +2160,53 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
* If the array's type was merely derived from the common type of its
* elements, then the elements are implicitly coerced to the common type.
* This is consistent with other uses of select_common_type().
+ *
+ * If can_coerce is not NULL, we need to safely test whether each element
+ * can be coerced to the target type, similar to what is done in
+ * transformTypeSafeCast.
*/
foreach(element, newelems)
{
Node *e = (Node *) lfirst(element);
- Node *newe;
+ bool expr_is_const = false;
+ Node *newe = NULL;
- if (coerce_hard)
+ if (can_coerce != NULL && IsA(e, Const))
+ {
+ expr_is_const = true;
+
+ /* coerce UNKNOWN Const to the target type */
+ if (*can_coerce && exprType(e) == UNKNOWNOID)
+ {
+ newe = CoerceUnknownConstSafe(pstate, e, coerce_type, typmod,
+ COERCION_IMPLICIT,
+ COERCE_IMPLICIT_CAST,
+ -1,
+ false);
+ if (newe == NULL)
+ {
+ newe = e;
+ *can_coerce = false;
+ }
+ newcoercedelems = lappend(newcoercedelems, newe);
+
+ /*
+ * Done handling this UNKNOWN Const element; move on to the
+ * next.
+ */
+ continue;
+ }
+ }
+
+ if (can_coerce != NULL && (!*can_coerce))
+ {
+ /*
+ * Can not coerce source expression to the target type, just append
+ * the transformed element expression to the list.
+ */
+ newe = e;
+ }
+ else if (coerce_hard)
{
newe = coerce_to_target_type(pstate, e,
exprType(e),
@@ -2150,12 +2216,74 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
COERCE_EXPLICIT_CAST,
-1);
if (newe == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_CANNOT_COERCE),
- errmsg("cannot cast type %s to %s",
- format_type_be(exprType(e)),
- format_type_be(coerce_type)),
- parser_errposition(pstate, exprLocation(e))));
+ {
+ if (can_coerce == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_CANNOT_COERCE),
+ errmsg("cannot cast type %s to %s",
+ format_type_be(exprType(e)),
+ format_type_be(coerce_type)),
+ parser_errposition(pstate, exprLocation(e))));
+ else
+ {
+ /*
+ * Can not coerce source expression to the target type, just
+ * append the transformed element expression to the list.
+ */
+ newe = e;
+ *can_coerce = false;
+ }
+ }
+ else if (can_coerce != NULL && expr_is_const)
+ {
+ Node *origexpr = newe;
+ Node *result;
+ bool have_collate_expr = false;
+
+ if (IsA(newe, CollateExpr))
+ have_collate_expr = true;
+
+ while (newe && IsA(newe, CollateExpr))
+ newe = (Node *) ((CollateExpr *) newe)->arg;
+
+ if (!IsA(newe, FuncExpr))
+ newe = origexpr;
+ else
+ {
+ /*
+ * pre-evaluate simple constant cast expressions in a way
+ * that tolerate errors.
+ */
+ FuncExpr *newexpr = (FuncExpr *) copyObject(newe);
+
+ assign_expr_collations(pstate, (Node *) newexpr);
+
+ result = (Node *) evaluate_expr_extended((Expr *) newexpr,
+ coerce_type,
+ typmod,
+ coerce_type_coll,
+ true);
+
+ if (result == NULL)
+ {
+ newe = e;
+ *can_coerce = false;
+ }
+ else if (have_collate_expr)
+ {
+ /* Reinstall top CollateExpr */
+ CollateExpr *coll = (CollateExpr *) origexpr;
+ CollateExpr *newcoll = makeNode(CollateExpr);
+
+ newcoll->arg = (Expr *) result;
+ newcoll->collOid = coll->collOid;
+ newcoll->location = coll->location;
+ newe = (Node *) newcoll;
+ }
+ else
+ newe = result;
+ }
+ }
}
else
newe = coerce_to_common_type(pstate, e,
@@ -2743,7 +2871,8 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
(A_ArrayExpr *) arg,
targetBaseType,
elementType,
- targetBaseTypmod);
+ targetBaseTypmod,
+ NULL);
}
else
expr = transformExprRecurse(pstate, arg);
@@ -2780,6 +2909,271 @@ 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;
+ TypeCast *tcast = (TypeCast *) tc->cast;
+ Node *source_expr;
+ Node *def_expr;
+ Node *cast_expr = NULL;
+ Oid inputType;
+ Oid targetType;
+ Oid targetBaseType;
+ Oid typeColl;
+ int32 targetTypmod;
+ int32 targetBaseTypmod;
+ bool can_coerce = true;
+ bool expr_is_const = false;
+ ParseLoc location;
+
+ /* Look up the type name first */
+ typenameTypeIdAndMod(pstate, tcast->typeName, &targetType, &targetTypmod);
+ targetBaseTypmod = targetTypmod;
+
+ typeColl = get_typcollation(targetType);
+
+ targetBaseType = getBaseTypeAndTypmod(targetType, &targetBaseTypmod);
+
+ /* now looking at DEFAULT expression */
+ def_expr = transformExpr(pstate, tc->raw_default, EXPR_KIND_CAST_DEFAULT);
+
+ 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 coerce %s expression to type %s",
+ "CAST DEFAULT",
+ format_type_be(targetType)),
+ parser_coercion_errposition(pstate, exprLocation(tc->raw_default), def_expr));
+
+ assign_expr_collations(pstate, def_expr);
+
+ /*
+ * The collation of DEFAULT expression must match the collation of the
+ * target type.
+ */
+ if (OidIsValid(typeColl))
+ {
+ Oid defColl;
+
+ defColl = exprCollation(def_expr);
+
+ if (OidIsValid(defColl) && typeColl != defColl)
+ ereport(ERROR,
+ errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("the collation of CAST DEFAULT expression conflicts with target type collation"),
+ errdetail("\"%s\" versus \"%s\"",
+ get_collation_name(typeColl),
+ get_collation_name(defColl)),
+ parser_errposition(pstate, exprLocation(def_expr)));
+ }
+
+ /*
+ * If the type cast 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(tcast->arg, A_ArrayExpr))
+ {
+ 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.
+ */
+ elementType = get_element_type(targetBaseType);
+
+ if (OidIsValid(elementType))
+ {
+ source_expr = transformArrayExpr(pstate,
+ (A_ArrayExpr *) tcast->arg,
+ targetBaseType,
+ elementType,
+ targetBaseTypmod,
+ &can_coerce);
+ }
+ else
+ source_expr = transformExprRecurse(pstate, tcast->arg);
+ }
+ else
+ source_expr = transformExprRecurse(pstate, tcast->arg);
+
+ inputType = exprType(source_expr);
+ if (inputType == InvalidOid)
+ return (Node *) NULL; /* return NULL if NULL input */
+
+ /*
+ * Location of the coercion is preferentially the location of 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 && IsA(source_expr, Const))
+ {
+ expr_is_const = true;
+
+ /* coerce UNKNOWN Const to the target type */
+ if (exprType(source_expr) == UNKNOWNOID)
+ {
+ cast_expr = CoerceUnknownConstSafe(pstate, source_expr,
+ targetType, targetTypmod,
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ location, false);
+ if (cast_expr == NULL)
+ can_coerce = false;
+ }
+ }
+
+ if (can_coerce && cast_expr == NULL)
+ {
+ cast_expr = coerce_to_target_type(pstate, source_expr, inputType,
+ targetType, targetTypmod,
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ location);
+ if (cast_expr == NULL)
+ can_coerce = false;
+ }
+
+ if (can_coerce)
+ {
+ Node *origexpr = cast_expr;
+ bool have_collate_expr = false;
+
+ Assert(cast_expr != NULL);
+
+ if (IsA(cast_expr, CollateExpr))
+ have_collate_expr = true;
+
+ while (cast_expr && IsA(cast_expr, CollateExpr))
+ cast_expr = (Node *) ((CollateExpr *) cast_expr)->arg;
+
+ if (IsA(cast_expr, FuncExpr))
+ {
+ HeapTuple tuple;
+ ListCell *lc;
+ Node *sexpr;
+ Node *result;
+ FuncExpr *fexpr = (FuncExpr *) cast_expr;
+
+ lc = list_head(fexpr->args);
+ sexpr = (Node *) lfirst(lc);
+
+ /* Look in pg_cast */
+ tuple = SearchSysCache2(CASTSOURCETARGET,
+ ObjectIdGetDatum(exprType(sexpr)),
+ ObjectIdGetDatum(targetType));
+
+ if (HeapTupleIsValid(tuple))
+ {
+ Form_pg_cast castForm = (Form_pg_cast) GETSTRUCT(tuple);
+ if (!castForm->casterrorsafe)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot cast type %s to %s when %s expression is specified in %s",
+ format_type_be(inputType),
+ format_type_be(targetType),
+ "DEFAULT",
+ "CAST ... ON CONVERSION ERROR"),
+ errhint("Explicit cast is defined but definition is not declared as safe"),
+ parser_errposition(pstate, exprLocation(source_expr)));
+ }
+ else
+ elog(ERROR, "cache lookup failed for pg_cast entry (%s cast to %s)",
+ format_type_be(inputType),
+ format_type_be(targetType));
+ ReleaseSysCache(tuple);
+
+ if (!expr_is_const)
+ cast_expr = origexpr;
+ else
+ {
+ /*
+ * pre-evaluate simple constant cast expressions early in a way
+ * that tolerate errors.
+ *
+ * Rationale:
+ * 1. When deparsing safe cast expressions (or in other cases),
+ * eval_const_expressions might be invoked, but it cannot
+ * handle errors gracefully.
+ * 2. If the cast expression involves only simple constants, we
+ * can safely evaluate it ahead of time. If the evaluation
+ * fails, it indicates that such a cast is not possible, and
+ * we can then fall back to the CAST DEFAULT expression.
+ * 3. Even if FuncExpr (for castfunc) node has three arguments,
+ * the second and third arguments will also be constants per
+ * coerce_to_target_type. Evaluating a FuncExpr whose
+ * arguments are all Const is safe.
+ */
+ FuncExpr *newexpr = (FuncExpr *) copyObject(cast_expr);
+
+ assign_expr_collations(pstate, (Node *) newexpr);
+
+ result = (Node *) evaluate_expr_extended((Expr *) newexpr,
+ targetType,
+ targetTypmod,
+ typeColl,
+ true);
+ if (result == NULL)
+ {
+ /*
+ * source expression can not coerce to the target type.
+ */
+ can_coerce = false;
+ cast_expr = NULL;
+ }
+ else if (have_collate_expr)
+ {
+ /* Reinstall top CollateExpr */
+ CollateExpr *coll = (CollateExpr *) origexpr;
+ CollateExpr *newcoll = makeNode(CollateExpr);
+
+ newcoll->arg = (Expr *) result;
+ newcoll->collOid = coll->collOid;
+ newcoll->location = coll->location;
+ cast_expr = (Node *) newcoll;
+ }
+ else
+ cast_expr = result;
+ }
+ }
+ else
+ /* Nothing to do, restore cast_expr to its original value */
+ cast_expr = origexpr;
+ }
+
+ Assert(can_coerce || cast_expr == NULL);
+
+ result = makeNode(SafeTypeCastExpr);
+ result->source_expr = source_expr;
+ result->cast_expr = cast_expr;
+ result->default_expr = def_expr;
+ result->resulttype = targetType;
+ result->resulttypmod = targetTypmod;
+ result->resultcollid = typeColl;
+ result->location = location;
+
+ return (Node *) result;
+}
+
/*
* Handle an explicit COLLATE clause.
*
@@ -3193,6 +3587,8 @@ ParseExprKindName(ParseExprKind exprKind)
case EXPR_KIND_CHECK_CONSTRAINT:
case EXPR_KIND_DOMAIN_CHECK:
return "CHECK";
+ case EXPR_KIND_CAST_DEFAULT:
+ return "CAST DEFAULT";
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
return "DEFAULT";
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 778d69c6f3c..a90705b9847 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2743,6 +2743,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
case EXPR_KIND_DOMAIN_CHECK:
err = _("set-returning functions are not allowed in check constraints");
break;
+ case EXPR_KIND_CAST_DEFAULT:
+ err = _("set-returning functions are not allowed in CAST DEFAULT expressions");
+ break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
err = _("set-returning functions are not allowed in DEFAULT expressions");
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 905c975d83b..dc03cf4ce74 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1822,6 +1822,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/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index a464349ee33..c1a7031a96c 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -3289,6 +3289,14 @@ array_map(Datum arrayd,
/* Apply the given expression to source element */
values[i] = ExecEvalExpr(exprstate, econtext, &nulls[i]);
+ /* Exit early if the evaluation fails */
+ 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 556ab057e5a..3e41c7b8188 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -10562,6 +10562,27 @@ get_rule_expr(Node *node, deparse_context *context,
}
break;
+ case T_SafeTypeCastExpr:
+ {
+ SafeTypeCastExpr *stcexpr = castNode(SafeTypeCastExpr, node);
+
+ /*
+ * We cannot deparsing cast_expr directly, since
+ * transformTypeSafeCast may have folded it into a simple
+ * constant or NULL. Instead, we use source_expr and
+ * default_expr to reconstruct the CAST DEFAULT clause.
+ */
+ 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/include/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat
index fbfd669587f..ca52cfcd086 100644
--- a/src/include/catalog/pg_cast.dat
+++ b/src/include/catalog/pg_cast.dat
@@ -19,65 +19,65 @@
# int2->int4->int8->numeric->float4->float8, while casts in the
# reverse direction are assignment-only.
{ castsource => 'int8', casttarget => 'int2', castfunc => 'int2(int8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'int4', castfunc => 'int4(int8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'float4', castfunc => 'float4(int8)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'float8', castfunc => 'float8(int8)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'numeric', castfunc => 'numeric(int8)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'int8', castfunc => 'int8(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'int4', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'float4', castfunc => 'float4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'float8', castfunc => 'float8(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'numeric', castfunc => 'numeric(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'int8', castfunc => 'int8(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'int2', castfunc => 'int2(int4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'float4', castfunc => 'float4(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'float8', castfunc => 'float8(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'numeric', castfunc => 'numeric(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'int8', castfunc => 'int8(float4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'int2', castfunc => 'int2(float4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'int4', castfunc => 'int4(float4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'float8', castfunc => 'float8(float4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'numeric',
- castfunc => 'numeric(float4)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'numeric(float4)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'int8', castfunc => 'int8(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'int2', castfunc => 'int2(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'int4', castfunc => 'int4(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'float4', castfunc => 'float4(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'numeric',
- castfunc => 'numeric(float8)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'numeric(float8)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'int8', castfunc => 'int8(numeric)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'int2', castfunc => 'int2(numeric)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'int4', castfunc => 'int4(numeric)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'float4',
- castfunc => 'float4(numeric)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'float4(numeric)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'float8',
- castfunc => 'float8(numeric)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'float8(numeric)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'money', casttarget => 'numeric', castfunc => 'numeric(money)',
castcontext => 'a', castmethod => 'f' },
{ castsource => 'numeric', casttarget => 'money', castfunc => 'money(numeric)',
@@ -89,13 +89,13 @@
# Allow explicit coercions between int4 and bool
{ castsource => 'int4', casttarget => 'bool', castfunc => 'bool(int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'int4', castfunc => 'int4(bool)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Allow explicit coercions between xid8 and xid
{ castsource => 'xid8', casttarget => 'xid', castfunc => 'xid(xid8)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# OID category: allow implicit conversion from any integral type (including
# int8, to support OID literals > 2G) to OID, as well as assignment coercion
@@ -106,13 +106,13 @@
# casts from text and varchar to regclass, which exist mainly to support
# legacy forms of nextval() and related functions.
{ castsource => 'int8', casttarget => 'oid', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'oid', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'oid', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regproc', castfunc => '0',
@@ -120,13 +120,13 @@
{ castsource => 'regproc', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regproc', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regproc', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regproc', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regproc', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regproc', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'regproc', casttarget => 'regprocedure', castfunc => '0',
@@ -138,13 +138,13 @@
{ castsource => 'regprocedure', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regprocedure', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regprocedure', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regprocedure', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regprocedure', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regprocedure', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regoper', castfunc => '0',
@@ -152,13 +152,13 @@
{ castsource => 'regoper', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regoper', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regoper', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regoper', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regoper', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regoper', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'regoper', casttarget => 'regoperator', castfunc => '0',
@@ -170,13 +170,13 @@
{ castsource => 'regoperator', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regoperator', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regoperator', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regoperator', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regoperator', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regoperator', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regclass', castfunc => '0',
@@ -184,13 +184,13 @@
{ castsource => 'regclass', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regclass', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regclass', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regclass', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regclass', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regclass', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regcollation', castfunc => '0',
@@ -198,13 +198,13 @@
{ castsource => 'regcollation', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regcollation', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regcollation', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regcollation', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regcollation', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regcollation', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regtype', castfunc => '0',
@@ -212,13 +212,13 @@
{ castsource => 'regtype', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regtype', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regtype', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regtype', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regtype', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regtype', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regconfig', castfunc => '0',
@@ -226,13 +226,13 @@
{ castsource => 'regconfig', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regconfig', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regconfig', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regconfig', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regconfig', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regconfig', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regdictionary', castfunc => '0',
@@ -240,31 +240,31 @@
{ castsource => 'regdictionary', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regdictionary', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regdictionary', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regdictionary', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regdictionary', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regdictionary', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'text', casttarget => 'regclass', castfunc => 'regclass',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'regclass', castfunc => 'regclass',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'oid', casttarget => 'regrole', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regrole', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regrole', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regrole', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regrole', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regrole', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regrole', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regnamespace', castfunc => '0',
@@ -272,13 +272,13 @@
{ castsource => 'regnamespace', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regnamespace', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regnamespace', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regnamespace', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regnamespace', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regnamespace', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regdatabase', castfunc => '0',
@@ -286,13 +286,13 @@
{ castsource => 'regdatabase', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regdatabase', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regdatabase', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regdatabase', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regdatabase', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regdatabase', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
@@ -302,57 +302,57 @@
{ castsource => 'text', casttarget => 'varchar', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'bpchar', casttarget => 'text', castfunc => 'text(bpchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bpchar', casttarget => 'varchar', castfunc => 'text(bpchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'text', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'varchar', casttarget => 'bpchar', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'char', casttarget => 'text', castfunc => 'text(char)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'char', casttarget => 'bpchar', castfunc => 'bpchar(char)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'char', casttarget => 'varchar', castfunc => 'text(char)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'name', casttarget => 'text', castfunc => 'text(name)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'name', casttarget => 'bpchar', castfunc => 'bpchar(name)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'name', casttarget => 'varchar', castfunc => 'varchar(name)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'text', casttarget => 'char', castfunc => 'char(text)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bpchar', casttarget => 'char', castfunc => 'char(text)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'char', castfunc => 'char(text)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'text', casttarget => 'name', castfunc => 'name(text)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bpchar', casttarget => 'name', castfunc => 'name(bpchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'name', castfunc => 'name(varchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
# Allow explicit coercions between bytea and integer types
{ castsource => 'int2', casttarget => 'bytea', castfunc => 'bytea(int2)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'bytea', castfunc => 'bytea(int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'bytea', castfunc => 'bytea(int8)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bytea', casttarget => 'int2', castfunc => 'int2(bytea)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bytea', casttarget => 'int4', castfunc => 'int4(bytea)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bytea', casttarget => 'int8', castfunc => 'int8(bytea)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Allow explicit coercions between int4 and "char"
{ castsource => 'char', casttarget => 'int4', castfunc => 'int4(char)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'char', castfunc => 'char(int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# pg_node_tree can be coerced to, but not from, text
{ castsource => 'pg_node_tree', casttarget => 'text', castfunc => '0',
@@ -378,73 +378,73 @@
# Datetime category
{ castsource => 'date', casttarget => 'timestamp',
- castfunc => 'timestamp(date)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timestamp(date)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'date', casttarget => 'timestamptz',
- castfunc => 'timestamptz(date)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timestamptz(date)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'time', casttarget => 'interval', castfunc => 'interval(time)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'time', casttarget => 'timetz', castfunc => 'timetz(time)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'date',
- castfunc => 'date(timestamp)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'date(timestamp)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'time',
- castfunc => 'time(timestamp)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'time(timestamp)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'timestamptz',
- castfunc => 'timestamptz(timestamp)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timestamptz(timestamp)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'date',
- castfunc => 'date(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'date(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'time',
- castfunc => 'time(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'time(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'timestamp',
- castfunc => 'timestamp(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'timestamp(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'timetz',
- castfunc => 'timetz(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'timetz(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'interval', casttarget => 'time', castfunc => 'time(interval)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timetz', casttarget => 'time', castfunc => 'time(timetz)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
# Geometric category
{ castsource => 'point', casttarget => 'box', castfunc => 'box(point)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'lseg', casttarget => 'point', castfunc => 'point(lseg)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'path', casttarget => 'polygon', castfunc => 'polygon(path)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'box', casttarget => 'point', castfunc => 'point(box)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'box', casttarget => 'lseg', castfunc => 'lseg(box)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'box', casttarget => 'polygon', castfunc => 'polygon(box)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'box', casttarget => 'circle', castfunc => 'circle(box)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'polygon', casttarget => 'point', castfunc => 'point(polygon)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'polygon', casttarget => 'path', castfunc => 'path',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'polygon', casttarget => 'box', castfunc => 'box(polygon)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'polygon', casttarget => 'circle',
- castfunc => 'circle(polygon)', castcontext => 'e', castmethod => 'f' },
+ castfunc => 'circle(polygon)', castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'circle', casttarget => 'point', castfunc => 'point(circle)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'circle', casttarget => 'box', castfunc => 'box(circle)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'circle', casttarget => 'polygon',
- castfunc => 'polygon(circle)', castcontext => 'e', castmethod => 'f' },
+ castfunc => 'polygon(circle)', castcontext => 'e', castmethod => 'f'},
# MAC address category
{ castsource => 'macaddr', casttarget => 'macaddr8', castfunc => 'macaddr8',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'macaddr8', casttarget => 'macaddr', castfunc => 'macaddr',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
# INET category
{ castsource => 'cidr', casttarget => 'inet', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'inet', casttarget => 'cidr', castfunc => 'cidr',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
# BitString category
{ castsource => 'bit', casttarget => 'varbit', castfunc => '0',
@@ -454,13 +454,13 @@
# Cross-category casts between bit and int4, int8
{ castsource => 'int8', casttarget => 'bit', castfunc => 'bit(int8,int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'bit', castfunc => 'bit(int4,int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bit', casttarget => 'int8', castfunc => 'int8(bit)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bit', casttarget => 'int4', castfunc => 'int4(bit)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Cross-category casts to and from TEXT
# We need entries here only for a few specialized cases where the behavior
@@ -471,68 +471,68 @@
# behavior will ensue when the automatic cast is applied instead of the
# pg_cast entry!
{ castsource => 'cidr', casttarget => 'text', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'inet', casttarget => 'text', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'text', castfunc => 'text(bool)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'xml', casttarget => 'text', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'text', casttarget => 'xml', castfunc => 'xml',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Cross-category casts to and from VARCHAR
# We support all the same casts as for TEXT.
{ castsource => 'cidr', casttarget => 'varchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'inet', casttarget => 'varchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'varchar', castfunc => 'text(bool)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'xml', casttarget => 'varchar', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'varchar', casttarget => 'xml', castfunc => 'xml',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Cross-category casts to and from BPCHAR
# We support all the same casts as for TEXT.
{ castsource => 'cidr', casttarget => 'bpchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'inet', casttarget => 'bpchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'bpchar', castfunc => 'text(bool)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'xml', casttarget => 'bpchar', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'bpchar', casttarget => 'xml', castfunc => 'xml',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Length-coercion functions
{ castsource => 'bpchar', casttarget => 'bpchar',
castfunc => 'bpchar(bpchar,int4,bool)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'varchar',
castfunc => 'varchar(varchar,int4,bool)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'time', casttarget => 'time', castfunc => 'time(time,int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'timestamp',
castfunc => 'timestamp(timestamp,int4)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'timestamptz',
castfunc => 'timestamptz(timestamptz,int4)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'interval', casttarget => 'interval',
castfunc => 'interval(interval,int4)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timetz', casttarget => 'timetz',
- castfunc => 'timetz(timetz,int4)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timetz(timetz,int4)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bit', casttarget => 'bit', castfunc => 'bit(bit,int4,bool)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varbit', casttarget => 'varbit', castfunc => 'varbit',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'numeric',
- castfunc => 'numeric(numeric,int4)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'numeric(numeric,int4)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
# json to/from jsonb
{ castsource => 'json', casttarget => 'jsonb', castfunc => '0',
@@ -542,36 +542,36 @@
# jsonb to numeric and bool types
{ castsource => 'jsonb', casttarget => 'bool', castfunc => 'bool(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'numeric', castfunc => 'numeric(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'int2', castfunc => 'int2(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'int4', castfunc => 'int4(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'int8', castfunc => 'int8(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'float4', castfunc => 'float4(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'float8', castfunc => 'float8(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# range to multirange
{ castsource => 'int4range', casttarget => 'int4multirange',
castfunc => 'int4multirange(int4range)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8range', casttarget => 'int8multirange',
castfunc => 'int8multirange(int8range)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numrange', casttarget => 'nummultirange',
castfunc => 'nummultirange(numrange)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'daterange', casttarget => 'datemultirange',
castfunc => 'datemultirange(daterange)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'tsrange', casttarget => 'tsmultirange',
- castfunc => 'tsmultirange(tsrange)', castcontext => 'e', castmethod => 'f' },
+ castfunc => 'tsmultirange(tsrange)', castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'tstzrange', casttarget => 'tstzmultirange',
castfunc => 'tstzmultirange(tstzrange)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
]
diff --git a/src/include/catalog/pg_cast.h b/src/include/catalog/pg_cast.h
index 6a0ca337153..218d81d535a 100644
--- a/src/include/catalog/pg_cast.h
+++ b/src/include/catalog/pg_cast.h
@@ -47,6 +47,10 @@ CATALOG(pg_cast,2605,CastRelationId)
/* cast method */
char castmethod;
+
+ /* cast function error safe */
+ bool casterrorsafe BKI_DEFAULT(f);
+
} FormData_pg_cast;
/* ----------------
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 75366203706..2f8d163a13e 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_SAFETYPE_CAST */
+ struct
+ {
+ struct SafeTypeCastState *stcstate; /* original expression node */
+ } stcexpr;
+
/* for EEOP_JSONEXPR_PATH */
struct
{
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 18ae8f0d4bb..9bd4cecad63 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1059,6 +1059,27 @@ typedef struct DomainConstraintState
ExprState *check_exprstate; /* check_expr's eval state, or NULL */
} DomainConstraintState;
+typedef struct SafeTypeCastState
+{
+ SafeTypeCastExpr *stcexpr;
+
+ /*
+ * Address to jump to when skipping all the steps to evaulate the default
+ * expression.
+ */
+ int jump_end;
+
+ /*
+ * For error-safe evaluation of coercions. 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 d14294a4ece..4b2eb340f97 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -400,6 +400,17 @@ typedef struct TypeCast
ParseLoc location; /* token location, or -1 if unknown */
} TypeCast;
+/*
+ * SafeTypeCast - a CAST(source_expr AS target_type) DEFAULT ON CONVERSION ERROR
+ * construct
+ */
+typedef struct SafeTypeCast
+{
+ NodeTag type;
+ Node *cast; /* TypeCast expression */
+ Node *raw_default; /* DEFAULT expression */
+} SafeTypeCast;
+
/*
* CollateClause - a COLLATE expression
*/
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 1b4436f2ff6..c3bd722cb2f 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -769,6 +769,42 @@ 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
+{
+ Expr xpr;
+
+ /* transformed expression being casted */
+ Node *source_expr;
+
+ /*
+ * The transformed cast expression; NULL if we can not cocerce source
+ * expression to the target type
+ */
+ Node *cast_expr pg_node_attr(query_jumble_ignore);
+
+ /* Fall back to the default expression if cast evaluation fails */
+ Node *default_expr;
+
+ /* cast result data type */
+ Oid resulttype;
+
+ /* cast result data type typmod (usually -1) */
+ int32 resulttypmod;
+
+ /* cast result data type collation */
+ Oid resultcollid;
+
+ /* Original SafeTypeCastExpr's location */
+ ParseLoc location;
+
+} SafeTypeCastExpr;
+
/*
* FuncExpr - expression node for a function call
*
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index f7d07c84542..9f5b32e0360 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -67,6 +67,8 @@ typedef enum ParseExprKind
EXPR_KIND_VALUES_SINGLE, /* single-row VALUES (in INSERT only) */
EXPR_KIND_CHECK_CONSTRAINT, /* CHECK constraint for a table */
EXPR_KIND_DOMAIN_CHECK, /* CHECK constraint for a domain */
+ EXPR_KIND_CAST_DEFAULT, /* default expression in
+ CAST DEFAULT ON CONVERSION ERROR */
EXPR_KIND_COLUMN_DEFAULT, /* default value for a table column */
EXPR_KIND_FUNCTION_DEFAULT, /* default parameter value for function */
EXPR_KIND_INDEX_EXPRESSION, /* index expression */
diff --git a/src/test/regress/expected/cast.out b/src/test/regress/expected/cast.out
new file mode 100644
index 00000000000..cb40ff0f200
--- /dev/null
+++ b/src/test/regress/expected/cast.out
@@ -0,0 +1,791 @@
+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(B'01' AS date DEFAULT NULL ON CONVERSION ERROR);
+ date
+------
+
+(1 row)
+
+SELECT CAST(BIT'01' AS date DEFAULT NULL ON CONVERSION ERROR);
+ date
+------
+
+(1 row)
+
+SELECT CAST(TRUE AS date DEFAULT NULL ON CONVERSION ERROR);
+ date
+------
+
+(1 row)
+
+SELECT CAST(1.1 AS date DEFAULT NULL ON CONVERSION ERROR);
+ date
+------
+
+(1 row)
+
+SELECT CAST(1 AS date DEFAULT NULL ON CONVERSION ERROR);
+ date
+------
+
+(1 row)
+
+SELECT CAST(1::numeric AS money DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type numeric to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(1::numeric AS money DEFAULT NULL ON CONVERSION E...
+ ^
+HINT: Explicit cast is defined but definition is not declared as safe
+SELECT CAST(1111 AS "char" DEFAULT NULL ON CONVERSION ERROR);
+ char
+------
+
+(1 row)
+
+--the default expression’s collation should match the collation of the cast
+--target type
+VALUES (CAST('error' AS text DEFAULT '1' COLLATE "C" ON CONVERSION ERROR));
+ERROR: the collation of CAST DEFAULT expression conflicts with target type collation
+LINE 1: VALUES (CAST('error' AS text DEFAULT '1' COLLATE "C" ON CONV...
+ ^
+DETAIL: "default" versus "C"
+VALUES (CAST('error' AS int2vector DEFAULT '1 3' ON CONVERSION ERROR));
+ column1
+---------
+ 1 3
+(1 row)
+
+VALUES (CAST('error' AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR));
+ column1
+---------
+ {"1 3"}
+(1 row)
+
+-- test source expression contain subquery
+SELECT CAST((SELECT b FROM generate_series(1, 1), (VALUES('H')) s(b))
+ AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR);
+ b
+---------
+ {"1 3"}
+(1 row)
+
+SELECT CAST((SELECT ARRAY((SELECT b FROM generate_series(1, 2) g, (VALUES('H')) s(b))))
+ AS INT[]
+ DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+CREATE FUNCTION ret_int8() RETURNS BIGINT AS
+$$
+BEGIN RETURN 2147483648; END;
+$$
+LANGUAGE plpgsql IMMUTABLE;
+SELECT CAST('a' as int DEFAULT ret_int8() ON CONVERSION ERROR); --error
+ERROR: integer out of range
+SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERROR); --error
+ERROR: cannot coerce CAST DEFAULT expression to type date
+LINE 1: SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERR...
+ ^
+-- test array coerce
+SELECT CAST(array['a'::text] AS int[] DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR: invalid input syntax for type integer: "a"
+SELECT CAST(array['a'] AS int[] DEFAULT NULL ON CONVERSION ERROR); --ok
+ array
+-------
+
+(1 row)
+
+SELECT CAST(array[11.12] AS date[] DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+SELECT CAST(array[11111111111111111] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+SELECT CAST(array[['abc'],[456]] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+SELECT CAST('{123,abc,456}' AS int[] DEFAULT '{-789}' ON CONVERSION ERROR);
+ int4
+--------
+ {-789}
+(1 row)
+
+SELECT CAST('{234,def,567}'::text[] AS integer[] DEFAULT '{-1011}' ON CONVERSION ERROR);
+ int4
+---------
+ {-1011}
+(1 row)
+
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS int[] DEFAULT '{1,2}' ON CONVERSION ERROR);
+ array
+-------
+ {1,2}
+(1 row)
+
+SELECT CAST(ARRAY[['1', '2'], ['three', 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --ok
+ array
+---------
+ {21,22}
+(1 row)
+
+SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --error
+ERROR: invalid input syntax for type integer: "three"
+LINE 1: SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] ...
+ ^
+SELECT CAST(ARRAY[1, 'three'::int] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --error
+ERROR: invalid input syntax for type integer: "three"
+LINE 1: SELECT CAST(ARRAY[1, 'three'::int] AS int[] DEFAULT '{21,22}...
+ ^
+-- test valid DEFAULT expression for CAST = ON CONVERSION ERROR
+CREATE 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: set-returning functions are not allowed in CAST DEFAULT expressions
+LINE 1: SELECT CAST('a' as int DEFAULT ret_setint() ON CONVERSION ER...
+ ^
+SELECT CAST(c::date::json as date DEFAULT NULL ON CONVERSION ERROR) FROM tcast; --error
+ERROR: cannot cast type date to json
+LINE 1: SELECT CAST(c::date::json as date DEFAULT NULL ON CONVERSION...
+ ^
+SELECT CAST('a'::int as int DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR: invalid input syntax for type integer: "a"
+LINE 1: SELECT CAST('a'::int as int DEFAULT NULL ON CONVERSION ERROR...
+ ^
+SELECT CAST('a' as int DEFAULT NULL ON CONVERSION ERROR); --ok
+ int4
+------
+
+(1 row)
+
+SELECT CAST('a' as int DEFAULT sum(1) ON CONVERSION ERROR); --error
+ERROR: aggregate functions are not allowed in CAST DEFAULT expressions
+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: window functions are not allowed in CAST DEFAULT expressions
+LINE 1: SELECT CAST('a' as int DEFAULT sum(1) over() ON CONVERSION E...
+ ^
+SELECT CAST('a' as int DEFAULT (SELECT NULL) ON CONVERSION ERROR); --error
+ERROR: cannot use subquery in CAST DEFAULT expression
+LINE 1: SELECT CAST('a' as int DEFAULT (SELECT NULL) 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 DOMAIN d_varchar as varchar(3) NOT NULL;
+CREATE TYPE comp_domain_with_typmod AS (a d_char3_not_null, b int);
+CREATE TYPE comp2 AS (a d_varchar);
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION ERROR); --error
+ERROR: value too long for type character varying(3)
+LINE 1: SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION...
+ ^
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(123)' ON CONVERSION ERROR); --ok
+ comp2
+-------
+ (123)
+(1 row)
+
+SELECT CAST(11 AS d_int42 DEFAULT 41 ON CONVERSION ERROR); --error
+ERROR: value for domain d_int42 violates check constraint "d_int42_check"
+SELECT CAST(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)'...
+ ^
+-----safe cast with geometry data type
+SELECT CAST('(1,2)'::point AS box DEFAULT NULL ON CONVERSION ERROR);
+ box
+-------------
+ (1,2),(1,2)
+(1 row)
+
+SELECT CAST('[(NaN,1),(NaN,infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+----------------
+ (NaN,Infinity)
+(1 row)
+
+SELECT CAST('[(1e+300,Infinity),(1e+300,Infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+-------------------
+ (1e+300,Infinity)
+(1 row)
+
+SELECT CAST('[(1,2),(3,4)]'::path as polygon DEFAULT NULL ON CONVERSION ERROR);
+ polygon
+---------
+
+(1 row)
+
+SELECT CAST('(NaN,1.0,NaN,infinity)'::box AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+----------------
+ (NaN,Infinity)
+(1 row)
+
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS lseg DEFAULT NULL ON CONVERSION ERROR);
+ lseg
+---------------
+ [(2,2),(0,0)]
+(1 row)
+
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS polygon DEFAULT NULL ON CONVERSION ERROR);
+ polygon
+---------------------------
+ ((0,0),(0,2),(2,2),(2,0))
+(1 row)
+
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS path DEFAULT NULL ON CONVERSION ERROR);
+ path
+------
+
+(1 row)
+
+SELECT CAST('(2.0,infinity,NaN,infinity)'::box AS circle DEFAULT NULL ON CONVERSION ERROR);
+ circle
+----------------------
+ <(NaN,Infinity),NaN>
+(1 row)
+
+SELECT CAST('(NaN,0.0),(2.0,4.0),(0.0,infinity)'::polygon AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+----------------
+ (NaN,Infinity)
+(1 row)
+
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS path DEFAULT NULL ON CONVERSION ERROR);
+ path
+---------------------
+ ((2,0),(2,4),(0,0))
+(1 row)
+
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS box DEFAULT NULL ON CONVERSION ERROR);
+ box
+-------------
+ (2,4),(0,0)
+(1 row)
+
+SELECT CAST('(NaN,infinity),(2.0,4.0),(0.0,infinity)'::polygon AS circle DEFAULT NULL ON CONVERSION ERROR);
+ circle
+----------------------
+ <(NaN,Infinity),NaN>
+(1 row)
+
+SELECT CAST('<(5,1),3>'::circle AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+-------
+ (5,1)
+(1 row)
+
+SELECT CAST('<(3,5),0>'::circle as box DEFAULT NULL ON CONVERSION ERROR);
+ box
+-------------
+ (3,5),(3,5)
+(1 row)
+
+-- not supported because the cast from circle to polygon is implemented as a SQL
+-- function, which cannot be error-safe.
+SELECT CAST('<(3,5),0>'::circle as polygon DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type circle to polygon when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST('<(3,5),0>'::circle as polygon DEFAULT NULL ON C...
+ ^
+HINT: Explicit cast is defined but definition is not declared as safe
+-----safe cast from/to money type is not supported, all the following would result error
+SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type bigint to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION E...
+ ^
+HINT: Explicit cast is defined but definition is not declared as safe
+SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type integer to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION E...
+ ^
+HINT: Explicit cast is defined but definition is not declared as safe
+SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type numeric to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSIO...
+ ^
+HINT: Explicit cast is defined but definition is not declared as safe
+SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type money to numeric when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSIO...
+ ^
+HINT: Explicit cast is defined but definition is not declared as safe
+-----safe cast from bytea type to other data types
+SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT NULL ON CONVERSION ERROR);
+ int8
+------
+
+(1 row)
+
+SELECT CAST('\x123456789A'::bytea AS int4 DEFAULT NULL ON CONVERSION ERROR);
+ int4
+------
+
+(1 row)
+
+SELECT CAST('\x123456'::bytea AS int2 DEFAULT NULL ON CONVERSION ERROR);
+ int2
+------
+
+(1 row)
+
+-----safe cast from bit type to other data types
+SELECT CAST('111111111100001'::bit(100) AS INT DEFAULT NULL ON CONVERSION ERROR);
+ int4
+------
+
+(1 row)
+
+SELECT CAST ('111111111100001'::bit(100) AS INT8 DEFAULT NULL ON CONVERSION ERROR);
+ int8
+------
+
+(1 row)
+
+-----safe cast from text type to other data types
+select CAST('a.b.c.d'::text as regclass default NULL on conversion error);
+ regclass
+----------
+
+(1 row)
+
+CREATE TABLE test_safecast(col0 text);
+INSERT INTO test_safecast(col0) VALUES ('<value>one</value');
+SELECT col0 as text,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) as to_regclass,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) IS NULL as expect_true,
+ CAST(col0 AS "char" DEFAULT NULL ON CONVERSION ERROR) as to_char,
+ CAST(col0 AS name DEFAULT NULL ON CONVERSION ERROR) as to_name,
+ CAST(col0 AS xml DEFAULT NULL ON CONVERSION ERROR) as to_xml
+FROM test_safecast;
+ text | to_regclass | expect_true | to_char | to_name | to_xml
+-------------------+-------------+-------------+---------+-------------------+--------
+ <value>one</value | | t | < | <value>one</value |
+(1 row)
+
+SELECT CAST('192.168.1.x' as inet DEFAULT NULL ON CONVERSION ERROR);
+ inet
+------
+
+(1 row)
+
+SELECT CAST('192.168.1.2/30' as cidr DEFAULT NULL ON CONVERSION ERROR);
+ cidr
+------
+
+(1 row)
+
+SELECT CAST('22:00:5c:08:55:08:01:02'::macaddr8 as macaddr DEFAULT NULL ON CONVERSION ERROR);
+ macaddr
+---------
+
+(1 row)
+
+-----safe cast betweeen range data types
+SELECT CAST('[1,2]'::int4range AS int4multirange DEFAULT NULL ON CONVERSION ERROR);
+ int4multirange
+----------------
+ {[1,3)}
+(1 row)
+
+SELECT CAST('[1,2]'::int8range AS int8multirange DEFAULT NULL ON CONVERSION ERROR);
+ int8multirange
+----------------
+ {[1,3)}
+(1 row)
+
+SELECT CAST('[1,2]'::numrange AS nummultirange DEFAULT NULL ON CONVERSION ERROR);
+ nummultirange
+---------------
+ {[1,2]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::daterange AS datemultirange DEFAULT NULL ON CONVERSION ERROR);
+ datemultirange
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::tsrange AS tsmultirange DEFAULT NULL ON CONVERSION ERROR);
+ tsmultirange
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::tstzrange AS tstzmultirange DEFAULT NULL ON CONVERSION ERROR);
+ tstzmultirange
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+-----safe cast betweeen numeric data types
+CREATE TABLE test_safecast1(
+ col1 float4, col2 float8, col3 numeric, col4 numeric[],
+ col5 int2 default 32767,
+ col6 int4 default 32768,
+ col7 int8 default 4294967296);
+INSERT INTO test_safecast1 VALUES('11.1234', '11.1234', '9223372036854775808', '{11.1234}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('inf', 'inf', 'inf', '{11.1234, 12, inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('-inf', '-inf', '-inf', '{11.1234, 12, -inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('NaN', 'NaN', 'NaN', '{11.1234, 12, -inf, NaN}'::numeric[]);
+SELECT col5 as int2,
+ CAST(col5 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col5 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col5 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col5 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col5 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col5 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col5 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col5 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ int2 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+(4 rows)
+
+SELECT col6 as int4,
+ CAST(col6 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col6 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col6 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col6 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col6 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col6 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col6 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col6 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ int4 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+(4 rows)
+
+SELECT col7 as int8,
+ CAST(col7 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col7 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col7 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col7 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col7 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col7 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col7 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col7 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ int8 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+------------+---------+---------+--------+------------+-------------+------------+------------+------------------
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+(4 rows)
+
+SELECT col3 as numeric,
+ CAST(col3 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col3 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col3 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col3 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col3 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col3 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col3 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col3 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ numeric | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+---------------------+---------+---------+--------+---------+-------------+----------------------+---------------------+------------------
+ 9223372036854775808 | | | | | 9.22337e+18 | 9.22337203685478e+18 | 9223372036854775808 |
+ Infinity | | | | | Infinity | Infinity | Infinity |
+ -Infinity | | | | | -Infinity | -Infinity | -Infinity |
+ NaN | | | | | NaN | NaN | NaN | NaN
+(4 rows)
+
+SELECT col4 as num_arr,
+ CAST(col4 AS int2[] DEFAULT NULL ON CONVERSION ERROR) as int2arr,
+ CAST(col4 AS int4[] DEFAULT NULL ON CONVERSION ERROR) as int4arr,
+ CAST(col4 as int8[] DEFAULT NULL ON CONVERSION ERROR) as int8arr,
+ CAST(col4 as float4[] DEFAULT NULL ON CONVERSION ERROR) as f4arr,
+ CAST(col4 as float8[] DEFAULT NULL ON CONVERSION ERROR) as f8arr,
+ CAST(col4 as numeric(10,1)[] DEFAULT NULL ON CONVERSION ERROR) as numarr
+FROM test_safecast1;
+ num_arr | int2arr | int4arr | int8arr | f4arr | f8arr | numarr
+----------------------------+---------+---------+---------+----------------------------+----------------------------+--------
+ {11.1234} | {11} | {11} | {11} | {11.1234} | {11.1234} | {11.1}
+ {11.1234,12,Infinity,NaN} | | | | {11.1234,12,Infinity,NaN} | {11.1234,12,Infinity,NaN} |
+ {11.1234,12,-Infinity,NaN} | | | | {11.1234,12,-Infinity,NaN} | {11.1234,12,-Infinity,NaN} |
+ {11.1234,12,-Infinity,NaN} | | | | {11.1234,12,-Infinity,NaN} | {11.1234,12,-Infinity,NaN} |
+(4 rows)
+
+SELECT col1 as float4,
+ CAST(col1 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col1 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col1 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col1 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col1 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col1 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col1 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col1 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ float4 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-----------+---------+---------+--------+---------+-----------+------------------+------------+------------------
+ 11.1234 | 11 | 11 | | 11 | 11.1234 | 11.1233997344971 | 11.1234 | 11.1
+ Infinity | | | | | Infinity | Infinity | Infinity |
+ -Infinity | | | | | -Infinity | -Infinity | -Infinity |
+ NaN | | | | | NaN | NaN | NaN | NaN
+(4 rows)
+
+SELECT col2 as float8,
+ CAST(col2 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col2 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col2 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col2 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col2 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col2 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col2 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col2 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ float8 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-----------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 11.1234 | 11 | 11 | | 11 | 11.1234 | 11.1234 | 11.1234 | 11.1
+ Infinity | | | | | Infinity | Infinity | Infinity |
+ -Infinity | | | | | -Infinity | -Infinity | -Infinity |
+ NaN | | | | | NaN | NaN | NaN | NaN
+(4 rows)
+
+-- date/timestamp/interval related safe type cast
+CREATE TABLE test_safecast2(
+ col0 date, col1 timestamp, col2 timestamptz,
+ col3 interval, col4 time, col5 timetz);
+INSERT INTO test_safecast2 VALUES
+('-infinity', '-infinity', '-infinity', '-infinity',
+ '2003-03-07 15:36:39 America/New_York', '2003-07-07 15:36:39 America/New_York'),
+('-infinity', 'infinity', 'infinity', 'infinity',
+ '11:59:59.99 PM', '11:59:59.99 PM PDT');
+SELECT col0 as date,
+ CAST(col0 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col0 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col0 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col0 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col0 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+ date | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale
+-----------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity | -infinity | | | -infinity
+ -infinity | -infinity | -infinity | | | -infinity
+(2 rows)
+
+SELECT col1 as timestamp,
+ CAST(col1 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col1 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col1 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col1 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col1 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+ timestamp | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale
+-----------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity | -infinity | | | -infinity
+ infinity | infinity | infinity | | | infinity
+(2 rows)
+
+SELECT col2 as timestamptz,
+ CAST(col2 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col2 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col2 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col2 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col2 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+ timestamptz | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale
+-------------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity | -infinity | | | -infinity
+ infinity | infinity | infinity | | | infinity
+(2 rows)
+
+SELECT col3 as interval,
+ CAST(col3 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col3 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col3 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col3 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col3 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale,
+ CAST(col3 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+ interval | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale | to_interval_scale
+-----------+----------------+---------+----------+-----------+--------------------+-------------------
+ -infinity | | | | | | -infinity
+ infinity | | | | | | infinity
+(2 rows)
+
+SELECT col4 as time,
+ CAST(col4 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col4 AS timetz DEFAULT NULL ON CONVERSION ERROR) IS NOT NULL as to_timetz,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+ time | to_times | to_timetz | to_interval | to_interval_scale
+-------------+-------------+-----------+-------------------------------+-------------------------------
+ 15:36:39 | 15:36:39 | t | @ 15 hours 36 mins 39 secs | @ 15 hours 36 mins 39 secs
+ 23:59:59.99 | 23:59:59.99 | t | @ 23 hours 59 mins 59.99 secs | @ 23 hours 59 mins 59.99 secs
+(2 rows)
+
+SELECT col5 as timetz,
+ CAST(col5 AS time DEFAULT NULL ON CONVERSION ERROR) as to_time,
+ CAST(col5 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_time_scale,
+ CAST(col5 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col5 AS timetz(6) DEFAULT NULL ON CONVERSION ERROR) as to_timetz_scale,
+ CAST(col5 AS interval DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col5 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+ timetz | to_time | to_time_scale | to_timetz | to_timetz_scale | to_interval | to_interval_scale
+----------------+-------------+---------------+----------------+-----------------+-------------+-------------------
+ 15:36:39-04 | 15:36:39 | 15:36:39 | 15:36:39-04 | 15:36:39-04 | |
+ 23:59:59.99-07 | 23:59:59.99 | 23:59:59.99 | 23:59:59.99-07 | 23:59:59.99-07 | |
+(2 rows)
+
+CREATE TABLE test_safecast3(col0 jsonb);
+INSERT INTO test_safecast3(col0) VALUES ('"test"');
+SELECT col0 as jsonb,
+ CAST(col0 AS integer DEFAULT NULL ON CONVERSION ERROR) as to_integer,
+ CAST(col0 AS numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col0 AS bigint DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col0 AS float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col0 AS float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col0 AS boolean DEFAULT NULL ON CONVERSION ERROR) as to_bool,
+ CAST(col0 AS smallint DEFAULT NULL ON CONVERSION ERROR) as to_smallint
+FROM test_safecast3;
+ jsonb | to_integer | to_numeric | to_int8 | to_float4 | to_float8 | to_bool | to_smallint
+--------+------------+------------+---------+-----------+-----------+---------+-------------
+ "test" | | | | | | |
+(1 row)
+
+-- test deparse
+CREATE DOMAIN d_int_arr as int[];
+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 test_safecast1,
+ CAST(ARRAY[['1'], ['three'],['a']] AS int[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1,
+ CAST(ARRAY[['1', '2'], ['three', 'a']] AS date[] DEFAULT NULL ON CONVERSION ERROR) as cast2,
+ CAST(ARRAY['three'] AS INT[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast3;
+\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 test_safecast1,
+ CAST(ARRAY[ARRAY[1], ARRAY['three'], ARRAY['a']] AS integer[] DEFAULT '{1,2}'::integer[] ON CONVERSION ERROR) AS cast1,
+ CAST(ARRAY[ARRAY['1', '2'], ARRAY['three', 'a']] AS date[] DEFAULT NULL::date[] ON CONVERSION ERROR) AS cast2,
+ CAST(ARRAY['three'] AS integer[] DEFAULT '{1,2}'::integer[] ON CONVERSION ERROR) AS cast3
+CREATE VIEW safecastview1 AS
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS d_int_arr DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1,
+ CAST(ARRAY[['1', '2'], ['three', 'a']] AS d_int_arr DEFAULT '{21,22}' ON CONVERSION ERROR) as cast2;
+\sv safecastview1
+CREATE OR REPLACE VIEW public.safecastview1 AS
+ SELECT CAST(ARRAY[ARRAY[1], ARRAY['three'], ARRAY['a']] AS d_int_arr DEFAULT '{1,2}'::integer[]::d_int_arr ON CONVERSION ERROR) AS cast1,
+ CAST(ARRAY[ARRAY[1, 2], ARRAY['three', 'a']] AS d_int_arr DEFAULT '{21,22}'::integer[]::d_int_arr ON CONVERSION ERROR) AS cast2
+SELECT * FROM safecastview1;
+ cast1 | cast2
+-------+---------
+ {1,2} | {21,22}
+(1 row)
+
+--error, default expression is mutable
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as int DEFAULT random(min=>1, max=>1) ON CONVERSION ERROR)));
+ERROR: functions in index expression must be marked IMMUTABLE
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as xid DEFAULT NULL ON CONVERSION ERROR)));
+ERROR: data type xid has no default operator class for access method "btree"
+HINT: You must specify an operator class for the index or define a default operator class for the data type.
+CREATE INDEX test_safecast3_idx ON test_safecast3((CAST(col0 as int DEFAULT NULL ON CONVERSION ERROR))); --ok
+SELECT pg_get_indexdef('test_safecast3_idx'::regclass);
+ pg_get_indexdef
+------------------------------------------------------------------------------------------------------------------------------------------
+ CREATE INDEX test_safecast3_idx ON public.test_safecast3 USING btree ((CAST(col0 AS integer DEFAULT NULL::integer ON CONVERSION ERROR)))
+(1 row)
+
+DROP TABLE test_safecast;
+DROP TABLE test_safecast1;
+DROP TABLE test_safecast2;
+DROP TABLE test_safecast3;
+DROP TABLE tcast;
+DROP FUNCTION ret_int8;
+DROP FUNCTION ret_setint;
+DROP TYPE comp_domain_with_typmod;
+DROP TYPE comp2;
+DROP DOMAIN d_varchar;
+DROP DOMAIN d_int42;
+DROP DOMAIN d_char3_not_null;
+RESET extra_float_digits;
diff --git a/src/test/regress/expected/create_cast.out b/src/test/regress/expected/create_cast.out
index 0e69644bca2..f4035c338aa 100644
--- a/src/test/regress/expected/create_cast.out
+++ b/src/test/regress/expected/create_cast.out
@@ -88,6 +88,11 @@ SELECT 1234::int4::casttesttype; -- Should work now
bar1234
(1 row)
+SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- error
+ERROR: cannot cast type integer to casttesttype when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVE...
+ ^
+HINT: Explicit cast is defined but definition is not declared as safe
-- check dependencies generated for that
SELECT pg_describe_object(classid, objid, objsubid) as obj,
pg_describe_object(refclassid, refobjid, refobjsubid) as objref,
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index a357e1d0c0e..81ea244859f 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -943,8 +943,8 @@ SELECT *
FROM pg_cast c
WHERE castsource = 0 OR casttarget = 0 OR castcontext NOT IN ('e', 'a', 'i')
OR castmethod NOT IN ('f', 'b' ,'i');
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Check that castfunc is nonzero only for cast methods that need a function,
@@ -953,8 +953,8 @@ SELECT *
FROM pg_cast c
WHERE (castmethod = 'f' AND castfunc = 0)
OR (castmethod IN ('b', 'i') AND castfunc <> 0);
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Look for casts to/from the same type that aren't length coercion functions.
@@ -963,15 +963,15 @@ WHERE (castmethod = 'f' AND castfunc = 0)
SELECT *
FROM pg_cast c
WHERE castsource = casttarget AND castfunc = 0;
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
SELECT c.*
FROM pg_cast c, pg_proc p
WHERE c.castfunc = p.oid AND p.pronargs < 2 AND castsource = casttarget;
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Look for cast functions that don't have the right signature. The
@@ -989,8 +989,8 @@ WHERE c.castfunc = p.oid AND
OR (c.castsource = 'character'::regtype AND
p.proargtypes[0] = 'text'::regtype))
OR NOT binary_coercible(p.prorettype, c.casttarget));
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
SELECT c.*
@@ -998,8 +998,8 @@ FROM pg_cast c, pg_proc p
WHERE c.castfunc = p.oid AND
((p.pronargs > 1 AND p.proargtypes[1] != 'int4'::regtype) OR
(p.pronargs > 2 AND p.proargtypes[2] != 'bool'::regtype));
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Look for binary compatible casts that do not have the reverse
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index f56482fb9f1..2e086cbc7af 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -81,7 +81,7 @@ test: create_table_like alter_generic alter_operator misc async dbsize merge mis
# collate.linux.utf8 and collate.icu.utf8 tests cannot be run in parallel with each other
# psql depends on create_am
# amutils depends on geometry, create_index_spgist, hash_index, brin
-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 00000000000..905d1f318b5
--- /dev/null
+++ b/src/test/regress/sql/cast.sql
@@ -0,0 +1,340 @@
+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(B'01' AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(BIT'01' AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(TRUE AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(1.1 AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(1 AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(1::numeric AS money DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(1111 AS "char" DEFAULT NULL ON CONVERSION ERROR);
+
+--the default expression’s collation should match the collation of the cast
+--target type
+VALUES (CAST('error' AS text DEFAULT '1' COLLATE "C" ON CONVERSION ERROR));
+
+VALUES (CAST('error' AS int2vector DEFAULT '1 3' ON CONVERSION ERROR));
+VALUES (CAST('error' AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR));
+
+-- test source expression contain subquery
+SELECT CAST((SELECT b FROM generate_series(1, 1), (VALUES('H')) s(b))
+ AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR);
+SELECT CAST((SELECT ARRAY((SELECT b FROM generate_series(1, 2) g, (VALUES('H')) s(b))))
+ AS INT[]
+ DEFAULT NULL ON CONVERSION ERROR);
+
+CREATE FUNCTION ret_int8() RETURNS BIGINT AS
+$$
+BEGIN RETURN 2147483648; END;
+$$
+LANGUAGE plpgsql IMMUTABLE;
+
+SELECT CAST('a' as int DEFAULT ret_int8() ON CONVERSION ERROR); --error
+SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERROR); --error
+
+-- test array coerce
+SELECT CAST(array['a'::text] AS int[] DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(array['a'] AS int[] DEFAULT NULL ON CONVERSION ERROR); --ok
+SELECT CAST(array[11.12] AS date[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(array[11111111111111111] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(array[['abc'],[456]] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('{123,abc,456}' AS int[] 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 int[] DEFAULT '{1,2}' ON CONVERSION ERROR);
+SELECT CAST(ARRAY[['1', '2'], ['three', 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --ok
+SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --error
+SELECT CAST(ARRAY[1, 'three'::int] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --error
+
+-- test valid DEFAULT expression for CAST = ON CONVERSION ERROR
+CREATE 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(c::date::json as date DEFAULT NULL ON CONVERSION ERROR) FROM tcast; --error
+SELECT CAST('a'::int as int DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST('a' as int DEFAULT NULL ON CONVERSION ERROR); --ok
+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 (SELECT NULL) 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 DOMAIN d_varchar as varchar(3) NOT NULL;
+CREATE TYPE comp_domain_with_typmod AS (a d_char3_not_null, b int);
+CREATE TYPE comp2 AS (a d_varchar);
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION ERROR); --error
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(123)' ON CONVERSION ERROR); --ok
+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
+
+-----safe cast with geometry data type
+SELECT CAST('(1,2)'::point AS box DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[(NaN,1),(NaN,infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[(1e+300,Infinity),(1e+300,Infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[(1,2),(3,4)]'::path as polygon DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NaN,1.0,NaN,infinity)'::box AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS lseg DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS polygon DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS path DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,infinity,NaN,infinity)'::box AS circle DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NaN,0.0),(2.0,4.0),(0.0,infinity)'::polygon AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS path DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS box DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NaN,infinity),(2.0,4.0),(0.0,infinity)'::polygon AS circle DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('<(5,1),3>'::circle AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('<(3,5),0>'::circle as box DEFAULT NULL ON CONVERSION ERROR);
+-- not supported because the cast from circle to polygon is implemented as a SQL
+-- function, which cannot be error-safe.
+SELECT CAST('<(3,5),0>'::circle as polygon DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast from/to money type is not supported, all the following would result error
+SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast from bytea type to other data types
+SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('\x123456789A'::bytea AS int4 DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('\x123456'::bytea AS int2 DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast from bit type to other data types
+SELECT CAST('111111111100001'::bit(100) AS INT DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST ('111111111100001'::bit(100) AS INT8 DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast from text type to other data types
+select CAST('a.b.c.d'::text as regclass default NULL on conversion error);
+
+CREATE TABLE test_safecast(col0 text);
+INSERT INTO test_safecast(col0) VALUES ('<value>one</value');
+
+SELECT col0 as text,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) as to_regclass,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) IS NULL as expect_true,
+ CAST(col0 AS "char" DEFAULT NULL ON CONVERSION ERROR) as to_char,
+ CAST(col0 AS name DEFAULT NULL ON CONVERSION ERROR) as to_name,
+ CAST(col0 AS xml DEFAULT NULL ON CONVERSION ERROR) as to_xml
+FROM test_safecast;
+
+SELECT CAST('192.168.1.x' as inet DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('192.168.1.2/30' as cidr DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('22:00:5c:08:55:08:01:02'::macaddr8 as macaddr DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast betweeen range data types
+SELECT CAST('[1,2]'::int4range AS int4multirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[1,2]'::int8range AS int8multirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[1,2]'::numrange AS nummultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::daterange AS datemultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::tsrange AS tsmultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::tstzrange AS tstzmultirange DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast betweeen numeric data types
+CREATE TABLE test_safecast1(
+ col1 float4, col2 float8, col3 numeric, col4 numeric[],
+ col5 int2 default 32767,
+ col6 int4 default 32768,
+ col7 int8 default 4294967296);
+INSERT INTO test_safecast1 VALUES('11.1234', '11.1234', '9223372036854775808', '{11.1234}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('inf', 'inf', 'inf', '{11.1234, 12, inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('-inf', '-inf', '-inf', '{11.1234, 12, -inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('NaN', 'NaN', 'NaN', '{11.1234, 12, -inf, NaN}'::numeric[]);
+
+SELECT col5 as int2,
+ CAST(col5 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col5 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col5 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col5 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col5 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col5 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col5 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col5 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col6 as int4,
+ CAST(col6 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col6 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col6 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col6 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col6 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col6 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col6 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col6 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col7 as int8,
+ CAST(col7 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col7 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col7 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col7 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col7 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col7 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col7 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col7 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col3 as numeric,
+ CAST(col3 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col3 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col3 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col3 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col3 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col3 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col3 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col3 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col4 as num_arr,
+ CAST(col4 AS int2[] DEFAULT NULL ON CONVERSION ERROR) as int2arr,
+ CAST(col4 AS int4[] DEFAULT NULL ON CONVERSION ERROR) as int4arr,
+ CAST(col4 as int8[] DEFAULT NULL ON CONVERSION ERROR) as int8arr,
+ CAST(col4 as float4[] DEFAULT NULL ON CONVERSION ERROR) as f4arr,
+ CAST(col4 as float8[] DEFAULT NULL ON CONVERSION ERROR) as f8arr,
+ CAST(col4 as numeric(10,1)[] DEFAULT NULL ON CONVERSION ERROR) as numarr
+FROM test_safecast1;
+
+SELECT col1 as float4,
+ CAST(col1 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col1 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col1 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col1 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col1 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col1 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col1 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col1 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col2 as float8,
+ CAST(col2 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col2 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col2 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col2 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col2 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col2 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col2 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col2 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+-- date/timestamp/interval related safe type cast
+CREATE TABLE test_safecast2(
+ col0 date, col1 timestamp, col2 timestamptz,
+ col3 interval, col4 time, col5 timetz);
+INSERT INTO test_safecast2 VALUES
+('-infinity', '-infinity', '-infinity', '-infinity',
+ '2003-03-07 15:36:39 America/New_York', '2003-07-07 15:36:39 America/New_York'),
+('-infinity', 'infinity', 'infinity', 'infinity',
+ '11:59:59.99 PM', '11:59:59.99 PM PDT');
+
+SELECT col0 as date,
+ CAST(col0 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col0 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col0 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col0 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col0 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+
+SELECT col1 as timestamp,
+ CAST(col1 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col1 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col1 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col1 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col1 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+
+SELECT col2 as timestamptz,
+ CAST(col2 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col2 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col2 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col2 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col2 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+
+SELECT col3 as interval,
+ CAST(col3 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col3 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col3 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col3 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col3 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale,
+ CAST(col3 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+
+SELECT col4 as time,
+ CAST(col4 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col4 AS timetz DEFAULT NULL ON CONVERSION ERROR) IS NOT NULL as to_timetz,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+
+SELECT col5 as timetz,
+ CAST(col5 AS time DEFAULT NULL ON CONVERSION ERROR) as to_time,
+ CAST(col5 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_time_scale,
+ CAST(col5 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col5 AS timetz(6) DEFAULT NULL ON CONVERSION ERROR) as to_timetz_scale,
+ CAST(col5 AS interval DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col5 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+
+CREATE TABLE test_safecast3(col0 jsonb);
+INSERT INTO test_safecast3(col0) VALUES ('"test"');
+SELECT col0 as jsonb,
+ CAST(col0 AS integer DEFAULT NULL ON CONVERSION ERROR) as to_integer,
+ CAST(col0 AS numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col0 AS bigint DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col0 AS float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col0 AS float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col0 AS boolean DEFAULT NULL ON CONVERSION ERROR) as to_bool,
+ CAST(col0 AS smallint DEFAULT NULL ON CONVERSION ERROR) as to_smallint
+FROM test_safecast3;
+
+-- test deparse
+CREATE DOMAIN d_int_arr as int[];
+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 test_safecast1,
+ CAST(ARRAY[['1'], ['three'],['a']] AS int[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1,
+ CAST(ARRAY[['1', '2'], ['three', 'a']] AS date[] DEFAULT NULL ON CONVERSION ERROR) as cast2,
+ CAST(ARRAY['three'] AS INT[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast3;
+\sv safecastview
+
+CREATE VIEW safecastview1 AS
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS d_int_arr DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1,
+ CAST(ARRAY[['1', '2'], ['three', 'a']] AS d_int_arr DEFAULT '{21,22}' ON CONVERSION ERROR) as cast2;
+\sv safecastview1
+SELECT * FROM safecastview1;
+
+--error, default expression is mutable
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as int DEFAULT random(min=>1, max=>1) ON CONVERSION ERROR)));
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as xid DEFAULT NULL ON CONVERSION ERROR)));
+CREATE INDEX test_safecast3_idx ON test_safecast3((CAST(col0 as int DEFAULT NULL ON CONVERSION ERROR))); --ok
+SELECT pg_get_indexdef('test_safecast3_idx'::regclass);
+
+DROP TABLE test_safecast;
+DROP TABLE test_safecast1;
+DROP TABLE test_safecast2;
+DROP TABLE test_safecast3;
+DROP TABLE tcast;
+DROP FUNCTION ret_int8;
+DROP FUNCTION ret_setint;
+DROP TYPE comp_domain_with_typmod;
+DROP TYPE comp2;
+DROP DOMAIN d_varchar;
+DROP DOMAIN d_int42;
+DROP DOMAIN d_char3_not_null;
+RESET extra_float_digits;
diff --git a/src/test/regress/sql/create_cast.sql b/src/test/regress/sql/create_cast.sql
index 32187853cc7..0a15a795d87 100644
--- a/src/test/regress/sql/create_cast.sql
+++ b/src/test/regress/sql/create_cast.sql
@@ -62,6 +62,7 @@ $$ SELECT ('bar'::text || $1::text); $$;
CREATE CAST (int4 AS casttesttype) WITH FUNCTION bar_int4_text(int4) AS IMPLICIT;
SELECT 1234::int4::casttesttype; -- Should work now
+SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- error
-- check dependencies generated for that
SELECT pg_describe_object(classid, objid, objsubid) as obj,
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 57a8f0366a5..9bbb1e560c7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2668,6 +2668,9 @@ STRLEN
SV
SYNCHRONIZATION_BARRIER
SYSTEM_INFO
+SafeTypeCast
+SafeTypeCastExpr
+SafeTypeCastState
SampleScan
SampleScanGetSampleSize_function
SampleScanState
--
2.34.1
v11-0016-error-safe-for-casting-timestamp-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v11-0016-error-safe-for-casting-timestamp-to-other-types-per-pg_cast.patchDownload
From e4a1a85b15947357ed53ec3382f4ce821ad6257c Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 18:44:35 +0800
Subject: [PATCH v11 16/20] error safe for casting timestamp to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
and pc.castfunc > 0 and (castsource::regtype ='timestamp'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
-----------------------------+-----------------------------+----------+-------------+------------+-----------------------+-------------
timestamp without time zone | date | 2029 | a | f | timestamp_date | date
timestamp without time zone | time without time zone | 1316 | a | f | timestamp_time | time
timestamp without time zone | timestamp with time zone | 2028 | i | f | timestamp_timestamptz | timestamptz
timestamp without time zone | timestamp without time zone | 1961 | i | f | timestamp_scale | timestamp
(4 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 13 +++++++++++--
src/backend/utils/adt/timestamp.c | 17 +++++++++++++++--
2 files changed, 26 insertions(+), 4 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 111bfd8f519..c5562b563e5 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -1373,7 +1373,16 @@ timestamp_date(PG_FUNCTION_ARGS)
Timestamp timestamp = PG_GETARG_TIMESTAMP(0);
DateADT result;
- result = timestamp2date_opt_overflow(timestamp, NULL);
+ if (likely(!fcinfo->context))
+ result = timestamptz2date_opt_overflow(timestamp, NULL);
+ else
+ {
+ int overflow;
+ result = timestamp2date_opt_overflow(timestamp, &overflow);
+
+ if (overflow != 0)
+ PG_RETURN_NULL();
+ }
PG_RETURN_DATEADT(result);
}
@@ -2089,7 +2098,7 @@ timestamp_time(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 116e3ef28fc..7a57ac3eaf6 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -352,7 +352,8 @@ timestamp_scale(PG_FUNCTION_ARGS)
result = timestamp;
- AdjustTimestampForTypmod(&result, typmod, NULL);
+ if (!AdjustTimestampForTypmod(&result, typmod, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_TIMESTAMP(result);
}
@@ -6430,7 +6431,19 @@ timestamp_timestamptz(PG_FUNCTION_ARGS)
{
Timestamp timestamp = PG_GETARG_TIMESTAMP(0);
- PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(timestamp));
+ if (likely(!fcinfo->context))
+ PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(timestamp));
+ else
+ {
+ TimestampTz result;
+ int overflow;
+
+ result = timestamp2timestamptz_opt_overflow(timestamp, &overflow);
+ if (overflow != 0)
+ PG_RETURN_NULL();
+ else
+ PG_RETURN_TIMESTAMPTZ(result);
+ }
}
/*
--
2.34.1
v11-0017-error-safe-for-casting-jsonb-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v11-0017-error-safe-for-casting-jsonb-to-other-types-per-pg_cast.patchDownload
From 9265c7f0533b8f70cbe4eaf452108e3ab69e873a Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Mon, 24 Nov 2025 14:26:53 +0800
Subject: [PATCH v11 17/20] error safe for casting jsonb to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'jsonb'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+---------------+---------
jsonb | boolean | 3556 | e | f | jsonb_bool | bool
jsonb | numeric | 3449 | e | f | jsonb_numeric | numeric
jsonb | smallint | 3450 | e | f | jsonb_int2 | int2
jsonb | integer | 3451 | e | f | jsonb_int4 | int4
jsonb | bigint | 3452 | e | f | jsonb_int8 | int8
jsonb | real | 3453 | e | f | jsonb_float4 | float4
jsonb | double precision | 2580 | e | f | jsonb_float8 | float8
(7 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/jsonb.c | 74 +++++++++++++++++++++++++++--------
1 file changed, 58 insertions(+), 16 deletions(-)
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index 9399cdb491a..ab24d6dc813 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -2006,7 +2006,7 @@ JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res)
* Emit correct, translatable cast error message
*/
static void
-cannotCastJsonbValue(enum jbvType type, const char *sqltype)
+cannotCastJsonbValue(enum jbvType type, const char *sqltype, Node *escontext)
{
static const struct
{
@@ -2027,7 +2027,7 @@ cannotCastJsonbValue(enum jbvType type, const char *sqltype)
for (i = 0; i < lengthof(messages); i++)
if (messages[i].type == type)
- ereport(ERROR,
+ ereturn(escontext,,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(messages[i].msg, sqltype)));
@@ -2042,7 +2042,10 @@ jsonb_bool(PG_FUNCTION_ARGS)
JsonbValue v;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "boolean");
+ {
+ cannotCastJsonbValue(v.type, "boolean", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2051,7 +2054,10 @@ jsonb_bool(PG_FUNCTION_ARGS)
}
if (v.type != jbvBool)
- cannotCastJsonbValue(v.type, "boolean");
+ {
+ cannotCastJsonbValue(v.type, "boolean", fcinfo->context);
+ PG_RETURN_NULL();
+ }
PG_FREE_IF_COPY(in, 0);
@@ -2066,7 +2072,10 @@ jsonb_numeric(PG_FUNCTION_ARGS)
Numeric retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "numeric");
+ {
+ cannotCastJsonbValue(v.type, "numeric", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2075,7 +2084,10 @@ jsonb_numeric(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "numeric");
+ {
+ cannotCastJsonbValue(v.type, "numeric", fcinfo->context);
+ PG_RETURN_NULL();
+ }
/*
* v.val.numeric points into jsonb body, so we need to make a copy to
@@ -2096,7 +2108,10 @@ jsonb_int2(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "smallint");
+ {
+ cannotCastJsonbValue(v.type, "smallint", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2105,7 +2120,10 @@ jsonb_int2(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "smallint");
+ {
+ cannotCastJsonbValue(v.type, "smallint", fcinfo->context);
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_int2,
NumericGetDatum(v.val.numeric));
@@ -2123,7 +2141,10 @@ jsonb_int4(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "integer");
+ {
+ cannotCastJsonbValue(v.type, "integer", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2132,7 +2153,10 @@ jsonb_int4(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "integer");
+ {
+ cannotCastJsonbValue(v.type, "integer", fcinfo->context);
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_int4,
NumericGetDatum(v.val.numeric));
@@ -2150,7 +2174,10 @@ jsonb_int8(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "bigint");
+ {
+ cannotCastJsonbValue(v.type, "bigint", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2159,7 +2186,10 @@ jsonb_int8(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "bigint");
+ {
+ cannotCastJsonbValue(v.type, "bigint", fcinfo->context);
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_int8,
NumericGetDatum(v.val.numeric));
@@ -2177,7 +2207,10 @@ jsonb_float4(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "real");
+ {
+ cannotCastJsonbValue(v.type, "real", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2186,7 +2219,10 @@ jsonb_float4(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "real");
+ {
+ cannotCastJsonbValue(v.type, "real", fcinfo->context);
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_float4,
NumericGetDatum(v.val.numeric));
@@ -2204,7 +2240,10 @@ jsonb_float8(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "double precision");
+ {
+ cannotCastJsonbValue(v.type, "double precision", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2213,7 +2252,10 @@ jsonb_float8(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "double precision");
+ {
+ cannotCastJsonbValue(v.type, "double precision", fcinfo->context);
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_float8,
NumericGetDatum(v.val.numeric));
--
2.34.1
v11-0019-invent-some-error-safe-functions.patchtext/x-patch; charset=US-ASCII; name=v11-0019-invent-some-error-safe-functions.patchDownload
From 1217392db3918ca941e6734b5855f7cf130451cc Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Mon, 24 Nov 2025 17:03:45 +0800
Subject: [PATCH v11 19/20] invent some error safe functions
stringTypeDatumSafe: error safe version of stringTypeDatum
evaluate_expr_extended: If you wish to evaluate a constant expression in a
manner that is safe from potential errors, set the error_safe parameter to true
ExecInitExprSafe: soft error variant of ExecInitExpr
OidInputFunctionCallSafe: soft error variant of OidInputFunctionCall
CoerceUnknownConstSafe: attempts to coerce an UNKNOWN Const to the target type.
Returns NULL if the coercion is not possible.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/executor/execExpr.c | 41 +++++++++++++
src/backend/optimizer/util/clauses.c | 21 ++++++-
src/backend/parser/parse_coerce.c | 92 ++++++++++++++++++++++++++++
src/backend/parser/parse_type.c | 14 +++++
src/backend/utils/fmgr/fmgr.c | 13 ++++
src/include/executor/executor.h | 1 +
src/include/fmgr.h | 3 +
src/include/optimizer/optimizer.h | 3 +
src/include/parser/parse_coerce.h | 4 ++
src/include/parser/parse_type.h | 2 +
10 files changed, 193 insertions(+), 1 deletion(-)
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index f1569879b52..b302be36f73 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -170,6 +170,47 @@ ExecInitExpr(Expr *node, PlanState *parent)
return state;
}
+/*
+ * ExecInitExprSafe: soft error variant of ExecInitExpr.
+ *
+ * use it only for expression nodes support soft errors, not all expression
+ * nodes support it.
+*/
+ExprState *
+ExecInitExprSafe(Expr *node, PlanState *parent)
+{
+ ExprState *state;
+ ExprEvalStep scratch = {0};
+
+ /* Special case: NULL expression produces a NULL ExprState pointer */
+ if (node == NULL)
+ return NULL;
+
+ /* Initialize ExprState with empty step list */
+ state = makeNode(ExprState);
+ state->expr = node;
+ state->parent = parent;
+ state->ext_params = NULL;
+ state->escontext = makeNode(ErrorSaveContext);
+ state->escontext->type = T_ErrorSaveContext;
+ state->escontext->error_occurred = false;
+ state->escontext->details_wanted = false;
+
+ /* Insert setup steps as needed */
+ ExecCreateExprSetupSteps(state, (Node *) node);
+
+ /* Compile the expression proper */
+ ExecInitExprRec(node, state, &state->resvalue, &state->resnull);
+
+ /* Finally, append a DONE step */
+ scratch.opcode = EEOP_DONE_RETURN;
+ ExprEvalPushStep(state, &scratch);
+
+ ExecReadyExpr(state);
+
+ return state;
+}
+
/*
* ExecInitExprWithParams: prepare a standalone expression tree for execution
*
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 202ba8ed4bb..32af0df6c70 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -5081,6 +5081,16 @@ sql_inline_error_callback(void *arg)
Expr *
evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
Oid result_collation)
+{
+ return evaluate_expr_extended(expr,
+ result_type,
+ result_typmod,
+ result_collation,
+ false);
+}
+Expr *
+evaluate_expr_extended(Expr *expr, Oid result_type, int32 result_typmod,
+ Oid result_collation, bool error_safe)
{
EState *estate;
ExprState *exprstate;
@@ -5105,7 +5115,10 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
* Prepare expr for execution. (Note: we can't use ExecPrepareExpr
* because it'd result in recursively invoking eval_const_expressions.)
*/
- exprstate = ExecInitExpr(expr, NULL);
+ if (error_safe)
+ exprstate = ExecInitExprSafe(expr, NULL);
+ else
+ exprstate = ExecInitExpr(expr, NULL);
/*
* And evaluate it.
@@ -5125,6 +5138,12 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
/* Get back to outer memory context */
MemoryContextSwitchTo(oldcontext);
+ if (error_safe && SOFT_ERROR_OCCURRED(exprstate->escontext))
+ {
+ FreeExecutorState(estate);
+ return NULL;
+ }
+
/*
* Must copy result out of sub-context used by expression eval.
*
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 78b1e366ad7..cdcdd44a799 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -657,6 +657,98 @@ can_coerce_type(int nargs, const Oid *input_typeids, const Oid *target_typeids,
return true;
}
+/*
+ * Create an expression tree to represent coercion a UNKNOWN Const node.
+ *
+ * 'node': the input expression
+ * 'baseTypeId': base type of domain
+ * 'baseTypeMod': base type typmod of domain
+ * 'targetType': target type to coerce to
+ * 'targetTypeMod': target type typmod
+ * 'ccontext': context indicator to control coercions
+ * 'cformat': coercion display format
+ * 'location': coercion request location
+ * 'hideInputCoercion': if true, hide the input coercion under this one.
+ */
+Node *
+CoerceUnknownConstSafe(ParseState *pstate, Node *node, Oid targetType, int32 targetTypeMod,
+ CoercionContext ccontext, CoercionForm cformat, int location,
+ bool hideInputCoercion)
+{
+ Oid baseTypeId;
+ int32 baseTypeMod;
+ int32 inputTypeMod;
+ Type baseType;
+ char *string;
+ Datum datum;
+ Const *newcon;
+ Node *result = NULL;
+ Const *con = (Const *) node;
+
+ Assert(IsA(node, Const));
+ Assert(exprType(node) == UNKNOWNOID);
+
+ 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;
+
+ if (!stringTypeDatumSafe(baseType,
+ string,
+ inputTypeMod,
+ &datum))
+ {
+ ReleaseSysCache(baseType);
+ return NULL;
+ }
+
+ newcon = makeNode(Const);
+ newcon->consttype = baseTypeId;
+ newcon->consttypmod = inputTypeMod;
+ newcon->constcollid = typeTypeCollation(baseType);
+ newcon->constlen = typeLen(baseType);
+ newcon->constbyval = typeByVal(baseType);
+ newcon->constisnull = con->constisnull;
+ newcon->constvalue = datum;
+
+ /*
+ * 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)
+ newcon->constvalue =
+ PointerGetDatum(PG_DETOAST_DATUM(newcon->constvalue));
+
+ result = (Node *) newcon;
+
+ /* If target is a domain, apply constraints. */
+ if (baseTypeId != targetType)
+ result = coerce_to_domain(result,
+ baseTypeId, baseTypeMod,
+ targetType,
+ ccontext, cformat, location,
+ false);
+
+ ReleaseSysCache(baseType);
+
+ return result;
+}
+
/*
* Create an expression tree to represent coercion to a domain type.
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index 7713bdc6af0..d260aeec5dc 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,19 @@ stringTypeDatum(Type tp, char *string, int32 atttypmod)
return OidInputFunctionCall(typinput, string, typioparam, atttypmod);
}
+/* error safe version of stringTypeDatum */
+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,
+ (Node *) &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/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index 0fe63c6bb83..aaa4a42b1ea 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, Node *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/executor.h b/src/include/executor/executor.h
index fa2b657fb2f..f99fc26eb1f 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -324,6 +324,7 @@ ExecProcNode(PlanState *node)
* prototypes from functions in execExpr.c
*/
extern ExprState *ExecInitExpr(Expr *node, PlanState *parent);
+extern ExprState *ExecInitExprSafe(Expr *node, PlanState *parent);
extern ExprState *ExecInitExprWithParams(Expr *node, ParamListInfo ext_params);
extern ExprState *ExecInitQual(List *qual, PlanState *parent);
extern ExprState *ExecInitCheck(List *qual, PlanState *parent);
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index 74fe3ea0575..991e14034d3 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, Node *escontext,
+ Datum *result);
extern char *OutputFunctionCall(FmgrInfo *flinfo, Datum val);
extern char *OidOutputFunctionCall(Oid functionId, Datum val);
extern Datum ReceiveFunctionCall(FmgrInfo *flinfo, StringInfo buf,
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index d0aa8ab0c1c..97a0337452c 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -144,6 +144,9 @@ extern Node *estimate_expression_value(PlannerInfo *root, Node *node);
extern Expr *evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
Oid result_collation);
+extern Expr *evaluate_expr_extended(Expr *expr, Oid result_type,
+ int32 result_typmod, Oid result_collation,
+ bool error_safe);
extern bool var_is_nonnullable(PlannerInfo *root, Var *var, bool use_rel_info);
diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h
index 8d775c72c59..ad16cdd7022 100644
--- a/src/include/parser/parse_coerce.h
+++ b/src/include/parser/parse_coerce.h
@@ -48,6 +48,10 @@ extern bool can_coerce_type(int nargs, const Oid *input_typeids, const Oid *targ
extern Node *coerce_type(ParseState *pstate, Node *node,
Oid inputTypeId, Oid targetTypeId, int32 targetTypeMod,
CoercionContext ccontext, CoercionForm cformat, int location);
+extern Node *CoerceUnknownConstSafe(ParseState *pstate, Node *node,
+ Oid targetType, int32 targetTypeMod,
+ CoercionContext ccontext, CoercionForm cformat,
+ int location, bool hideInputCoercion);
extern Node *coerce_to_domain(Node *arg, Oid baseTypeId, int32 baseTypeMod,
Oid typeId,
CoercionContext ccontext, CoercionForm cformat, int location,
diff --git a/src/include/parser/parse_type.h b/src/include/parser/parse_type.h
index 0d919d8bfa2..12381aed64c 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);
--
2.34.1
v11-0012-error-safe-for-casting-float8-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v11-0012-error-safe-for-casting-float8-to-other-types-per-pg_cast.patchDownload
From 5e44ba2f72027bf219412e90b2e8abb72326b5c2 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:31:53 +0800
Subject: [PATCH v11 12/20] error safe for casting float8 to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'float8'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------------+------------+----------+-------------+------------+----------------+---------
double precision | bigint | 483 | a | f | dtoi8 | int8
double precision | smallint | 237 | a | f | dtoi2 | int2
double precision | integer | 317 | a | f | dtoi4 | int4
double precision | real | 312 | a | f | dtof | float4
double precision | numeric | 1743 | a | f | float8_numeric | numeric
(5 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/float.c | 13 +++++++++----
src/backend/utils/adt/int8.c | 2 +-
src/backend/utils/adt/numeric.c | 3 ++-
3 files changed, 12 insertions(+), 6 deletions(-)
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index ab5c38d723d..d746f961fd3 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -1199,9 +1199,14 @@ 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 +1229,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 +1254,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")));
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 8f7c117557a..70e6d89813c 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -1260,7 +1260,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")));
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index b89c35eee65..507c607a799 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -4560,7 +4560,8 @@ float8_numeric(PG_FUNCTION_ARGS)
init_var(&result);
/* Assume we need not worry about leading/trailing spaces */
- (void) set_var_from_str(buf, buf, &result, &endptr, NULL);
+ if (!set_var_from_str(buf, buf, &result, &endptr, fcinfo->context))
+ PG_RETURN_NULL();
res = make_result(&result);
--
2.34.1
v11-0015-error-safe-for-casting-timestamptz-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v11-0015-error-safe-for-casting-timestamptz-to-other-types-per-pg_cast.patchDownload
From fd2c819197756e839eac39198960bea645d931f7 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Mon, 6 Oct 2025 12:39:22 +0800
Subject: [PATCH v11 15/20] error safe for casting timestamptz to other types
per pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
and pc.castfunc > 0 and (castsource::regtype ='timestamptz'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
--------------------------+-----------------------------+----------+-------------+------------+-----------------------+-------------
timestamp with time zone | date | 1178 | a | f | timestamptz_date | date
timestamp with time zone | time without time zone | 2019 | a | f | timestamptz_time | time
timestamp with time zone | timestamp without time zone | 2027 | a | f | timestamptz_timestamp | timestamp
timestamp with time zone | time with time zone | 1388 | a | f | timestamptz_timetz | timetz
timestamp with time zone | timestamp with time zone | 1967 | i | f | timestamptz_scale | timestamptz
(5 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 16 +++++++++++++---
src/backend/utils/adt/timestamp.c | 17 +++++++++++++++--
2 files changed, 28 insertions(+), 5 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 4f0f3d26989..111bfd8f519 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -1468,7 +1468,17 @@ timestamptz_date(PG_FUNCTION_ARGS)
TimestampTz timestamp = PG_GETARG_TIMESTAMP(0);
DateADT result;
- result = timestamptz2date_opt_overflow(timestamp, NULL);
+ if (likely(!fcinfo->context))
+ result = timestamptz2date_opt_overflow(timestamp, NULL);
+ else
+ {
+ int overflow;
+ result = timestamptz2date_opt_overflow(timestamp, &overflow);
+
+ if (overflow != 0)
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_DATEADT(result);
}
@@ -2110,7 +2120,7 @@ timestamptz_time(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
@@ -3029,7 +3039,7 @@ timestamptz_timetz(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 7b565cc6d66..116e3ef28fc 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -875,7 +875,8 @@ timestamptz_scale(PG_FUNCTION_ARGS)
result = timestamp;
- AdjustTimestampForTypmod(&result, typmod, NULL);
+ if (!AdjustTimestampForTypmod(&result, typmod, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_TIMESTAMPTZ(result);
}
@@ -6507,7 +6508,19 @@ timestamptz_timestamp(PG_FUNCTION_ARGS)
{
TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(0);
- PG_RETURN_TIMESTAMP(timestamptz2timestamp(timestamp));
+ if (likely(!fcinfo->context))
+ PG_RETURN_TIMESTAMP(timestamptz2timestamp(timestamp));
+ else
+ {
+ int overflow;
+ Timestamp result;
+
+ result = timestamptz2timestamp_opt_overflow(timestamp, &overflow);
+ if (overflow != 0)
+ PG_RETURN_NULL();
+ else
+ PG_RETURN_TIMESTAMP(result);
+ }
}
/*
--
2.34.1
v11-0014-error-safe-for-casting-interval-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v11-0014-error-safe-for-casting-interval-to-other-types-per-pg_cast.patchDownload
From 1570b865c1f6f745cbadc6b5a52b5779237bb830 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 18:43:29 +0800
Subject: [PATCH v11 14/20] error safe for casting interval to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'interval'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------------+----------+-------------+------------+----------------+----------
interval | time without time zone | 1419 | a | f | interval_time | time
interval | interval | 1200 | i | f | interval_scale | interval
(2 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 2 +-
src/backend/utils/adt/timestamp.c | 3 ++-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index c7a3cde2d81..4f0f3d26989 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2180,7 +2180,7 @@ interval_time(PG_FUNCTION_ARGS)
TimeADT result;
if (INTERVAL_NOT_FINITE(span))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("cannot convert infinite interval to time")));
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 156a4830ffd..7b565cc6d66 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1334,7 +1334,8 @@ interval_scale(PG_FUNCTION_ARGS)
result = palloc(sizeof(Interval));
*result = *interval;
- AdjustIntervalForTypmod(result, typmod, NULL);
+ if (!AdjustIntervalForTypmod(result, typmod, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_INTERVAL_P(result);
}
--
2.34.1
v11-0013-error-safe-for-casting-date-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v11-0013-error-safe-for-casting-date-to-other-types-per-pg_cast.patchDownload
From 10602bb81b53791ce355c732293a42be47be2446 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 18:42:50 +0800
Subject: [PATCH v11 13/20] error safe for casting date to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'date'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+-----------------------------+----------+-------------+------------+------------------+-------------
date | timestamp without time zone | 2024 | i | f | date_timestamp | timestamp
date | timestamp with time zone | 1174 | i | f | date_timestamptz | timestamptz
(2 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 22 ++++++++++++++++++++--
1 file changed, 20 insertions(+), 2 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 344f58b92f7..c7a3cde2d81 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -1350,7 +1350,16 @@ date_timestamp(PG_FUNCTION_ARGS)
DateADT dateVal = PG_GETARG_DATEADT(0);
Timestamp result;
- result = date2timestamp(dateVal);
+ if (likely(!fcinfo->context))
+ result = date2timestamp(dateVal);
+ else
+ {
+ int overflow;
+
+ result = date2timestamp_opt_overflow(dateVal, &overflow);
+ if (overflow != 0)
+ PG_RETURN_NULL();
+ }
PG_RETURN_TIMESTAMP(result);
}
@@ -1435,7 +1444,16 @@ date_timestamptz(PG_FUNCTION_ARGS)
DateADT dateVal = PG_GETARG_DATEADT(0);
TimestampTz result;
- result = date2timestamptz(dateVal);
+ if (likely(!fcinfo->context))
+ result = date2timestamptz(dateVal);
+ else
+ {
+ int overflow;
+ result = date2timestamptz_opt_overflow(dateVal, &overflow);
+
+ if (overflow != 0)
+ PG_RETURN_NULL();
+ }
PG_RETURN_TIMESTAMP(result);
}
--
2.34.1
v11-0011-error-safe-for-casting-float4-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v11-0011-error-safe-for-casting-float4-to-other-types-per-pg_cast.patchDownload
From 6c396ee467a6237134494000c3227f96fc301ee0 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:28:20 +0800
Subject: [PATCH v11 11/20] error safe for casting float4 to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'float4'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+----------------+---------
real | bigint | 653 | a | f | ftoi8 | int8
real | smallint | 238 | a | f | ftoi2 | int2
real | integer | 319 | a | f | ftoi4 | int4
real | double precision | 311 | i | f | ftod | float8
real | numeric | 1742 | a | f | float4_numeric | numeric
(5 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/float.c | 4 ++--
src/backend/utils/adt/int8.c | 2 +-
src/backend/utils/adt/numeric.c | 3 ++-
3 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index 7b97d2be6ca..ab5c38d723d 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -1298,7 +1298,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 +1323,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 3dcbd36bd8e..8f7c117557a 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -1295,7 +1295,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 40d2709b64a..b89c35eee65 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -4658,7 +4658,8 @@ float4_numeric(PG_FUNCTION_ARGS)
init_var(&result);
/* Assume we need not worry about leading/trailing spaces */
- (void) set_var_from_str(buf, buf, &result, &endptr, NULL);
+ if (!set_var_from_str(buf, buf, &result, &endptr, fcinfo->context))
+ PG_RETURN_NULL();
res = make_result(&result);
--
2.34.1
v11-0009-error-safe-for-casting-bigint-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v11-0009-error-safe-for-casting-bigint-to-other-types-per-pg_cast.patchDownload
From 11996d3917e97543cd89393060af0175c8748670 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:22:00 +0800
Subject: [PATCH v11 09/20] error safe for casting bigint to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'bigint'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+--------------+---------
bigint | smallint | 714 | a | f | int82 | int2
bigint | integer | 480 | a | f | int84 | int4
bigint | real | 652 | i | f | i8tof | float4
bigint | double precision | 482 | i | f | i8tod | float8
bigint | numeric | 1781 | i | f | int8_numeric | numeric
bigint | money | 3812 | a | f | int8_cash | money
bigint | oid | 1287 | i | f | i8tooid | oid
bigint | regproc | 1287 | i | f | i8tooid | oid
bigint | regprocedure | 1287 | i | f | i8tooid | oid
bigint | regoper | 1287 | i | f | i8tooid | oid
bigint | regoperator | 1287 | i | f | i8tooid | oid
bigint | regclass | 1287 | i | f | i8tooid | oid
bigint | regcollation | 1287 | i | f | i8tooid | oid
bigint | regtype | 1287 | i | f | i8tooid | oid
bigint | regconfig | 1287 | i | f | i8tooid | oid
bigint | regdictionary | 1287 | i | f | i8tooid | oid
bigint | regrole | 1287 | i | f | i8tooid | oid
bigint | regnamespace | 1287 | i | f | i8tooid | oid
bigint | regdatabase | 1287 | i | f | i8tooid | oid
bigint | bytea | 6369 | e | f | int8_bytea | bytea
bigint | bit | 2075 | e | f | bitfromint8 | bit
(21 rows)
already error safe: i8tof, i8tod, int8_numeric, int8_bytea, bitfromint8
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/int8.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index bdea490202a..3dcbd36bd8e 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -1204,7 +1204,7 @@ int84(PG_FUNCTION_ARGS)
int64 arg = PG_GETARG_INT64(0);
if (unlikely(arg < PG_INT32_MIN) || unlikely(arg > PG_INT32_MAX))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
@@ -1225,7 +1225,7 @@ int82(PG_FUNCTION_ARGS)
int64 arg = PG_GETARG_INT64(0);
if (unlikely(arg < PG_INT16_MIN) || unlikely(arg > PG_INT16_MAX))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
@@ -1308,7 +1308,7 @@ i8tooid(PG_FUNCTION_ARGS)
int64 arg = PG_GETARG_INT64(0);
if (unlikely(arg < 0) || unlikely(arg > PG_UINT32_MAX))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("OID out of range")));
--
2.34.1
v11-0010-error-safe-for-casting-numeric-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v11-0010-error-safe-for-casting-numeric-to-other-types-per-pg_cast.patchDownload
From 4741646c72d5bb8dc9d9c43989173b721d501296 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:25:37 +0800
Subject: [PATCH v11 10/20] error safe for casting numeric to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'numeric'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+----------------+---------
numeric | bigint | 1779 | a | f | numeric_int8 | int8
numeric | smallint | 1783 | a | f | numeric_int2 | int2
numeric | integer | 1744 | a | f | numeric_int4 | int4
numeric | real | 1745 | i | f | numeric_float4 | float4
numeric | double precision | 1746 | i | f | numeric_float8 | float8
numeric | money | 3824 | a | f | numeric_cash | money
numeric | numeric | 1703 | i | f | numeric | numeric
(7 rows)
discussion: https://postgr.es/m/CACJufxHCMzrHOW=wRe8L30rMhB3sjwAv1LE928Fa7sxMu1Tx-g@mail.gmail.com
---
src/backend/utils/adt/numeric.c | 58 ++++++++++++++++++++++++---------
1 file changed, 43 insertions(+), 15 deletions(-)
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 2501007d981..40d2709b64a 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -1244,7 +1244,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));
}
@@ -1295,8 +1296,9 @@ numeric (PG_FUNCTION_ARGS)
init_var(&var);
set_var_from_num(num, &var);
- (void) apply_typmod(&var, typmod, NULL);
- new = make_result(&var);
+ if (!apply_typmod(&var, typmod, fcinfo->context))
+ PG_RETURN_NULL();
+ new = make_result_safe(&var, fcinfo->context);
free_var(&var);
@@ -3019,7 +3021,10 @@ numeric_mul(PG_FUNCTION_ARGS)
Numeric num2 = PG_GETARG_NUMERIC(1);
Numeric res;
- res = numeric_mul_safe(num1, num2, NULL);
+ res = numeric_mul_safe(num1, num2, fcinfo->context);
+
+ if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
PG_RETURN_NUMERIC(res);
}
@@ -4393,9 +4398,15 @@ numeric_int4_safe(Numeric num, Node *escontext)
Datum
numeric_int4(PG_FUNCTION_ARGS)
{
+ int32 result;
Numeric num = PG_GETARG_NUMERIC(0);
- PG_RETURN_INT32(numeric_int4_safe(num, NULL));
+ result = numeric_int4_safe(num, fcinfo->context);
+
+ if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
+
+ PG_RETURN_INT32(result);
}
/*
@@ -4463,9 +4474,15 @@ numeric_int8_safe(Numeric num, Node *escontext)
Datum
numeric_int8(PG_FUNCTION_ARGS)
{
+ int64 result;
Numeric num = PG_GETARG_NUMERIC(0);
- PG_RETURN_INT64(numeric_int8_safe(num, NULL));
+ result = numeric_int8_safe(num, fcinfo->context);
+
+ if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
+
+ PG_RETURN_INT64(result);
}
@@ -4489,11 +4506,11 @@ numeric_int2(PG_FUNCTION_ARGS)
if (NUMERIC_IS_SPECIAL(num))
{
if (NUMERIC_IS_NAN(num))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert NaN to %s", "smallint")));
else
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert infinity to %s", "smallint")));
}
@@ -4502,12 +4519,12 @@ numeric_int2(PG_FUNCTION_ARGS)
init_var_from_num(num, &x);
if (!numericvar_to_int64(&x, &val))
- ereport(ERROR,
+ ereturn(fcinfo->context, (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(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
@@ -4572,10 +4589,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);
}
@@ -4667,7 +4688,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
v11-0007-error-safe-for-casting-macaddr8-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v11-0007-error-safe-for-casting-macaddr8-to-other-types-per-pg_cast.patchDownload
From 53a534b26296beafb2a8c5edd8f3aa5c9ab5cd32 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:17:11 +0800
Subject: [PATCH v11 07/20] error safe for casting macaddr8 to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
and pc.castfunc > 0 and castsource::regtype ='macaddr8'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------+----------+-------------+------------+-------------------+---------
macaddr8 | macaddr | 4124 | i | f | macaddr8tomacaddr | macaddr
(1 row)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/mac8.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/backend/utils/adt/mac8.c b/src/backend/utils/adt/mac8.c
index 08e41ba4eea..1c903f152de 100644
--- a/src/backend/utils/adt/mac8.c
+++ b/src/backend/utils/adt/mac8.c
@@ -550,7 +550,7 @@ macaddr8tomacaddr(PG_FUNCTION_ARGS)
result = (macaddr *) palloc0(sizeof(macaddr));
if ((addr->d != 0xFF) || (addr->e != 0xFE))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("macaddr8 data out of range to convert to macaddr"),
errhint("Only addresses that have FF and FE as values in the "
--
2.34.1
v11-0008-error-safe-for-casting-integer-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v11-0008-error-safe-for-casting-integer-to-other-types-per-pg_cast.patchDownload
From 56ea6746c8c4b11af8ad8342c999f040fc911050 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:20:10 +0800
Subject: [PATCH v11 08/20] error safe for casting integer to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'integer'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+--------------+---------
integer | bigint | 481 | i | f | int48 | int8
integer | smallint | 314 | a | f | i4toi2 | int2
integer | real | 318 | i | f | i4tof | float4
integer | double precision | 316 | i | f | i4tod | float8
integer | numeric | 1740 | i | f | int4_numeric | numeric
integer | money | 3811 | a | f | int4_cash | money
integer | boolean | 2557 | e | f | int4_bool | bool
integer | bytea | 6368 | e | f | int4_bytea | bytea
integer | "char" | 78 | e | f | i4tochar | char
integer | bit | 1683 | e | f | bitfromint4 | bit
(10 rows)
only int4_cash, i4toi2, i4tochar need take care of error handling. but support
for cash data type is not easy, so only i4toi2, i4tochar function refactoring.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/char.c | 2 +-
src/backend/utils/adt/int.c | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/backend/utils/adt/char.c b/src/backend/utils/adt/char.c
index 22dbfc950b1..e90844a29f0 100644
--- a/src/backend/utils/adt/char.c
+++ b/src/backend/utils/adt/char.c
@@ -192,7 +192,7 @@ i4tochar(PG_FUNCTION_ARGS)
int32 arg1 = PG_GETARG_INT32(0);
if (arg1 < SCHAR_MIN || arg1 > SCHAR_MAX)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("\"char\" out of range")));
diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c
index b5781989a64..b45599d402d 100644
--- a/src/backend/utils/adt/int.c
+++ b/src/backend/utils/adt/int.c
@@ -350,7 +350,7 @@ i4toi2(PG_FUNCTION_ARGS)
int32 arg1 = PG_GETARG_INT32(0);
if (unlikely(arg1 < SHRT_MIN) || unlikely(arg1 > SHRT_MAX))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
--
2.34.1
v11-0005-error-safe-for-casting-character-varying-to-other-types-per-pg_c.patchtext/x-patch; charset=US-ASCII; name=v11-0005-error-safe-for-casting-character-varying-to-other-types-per-pg_c.patchDownload
From 723e4abfa7837b1450c29d422f6e0b89831e485b Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:13:45 +0800
Subject: [PATCH v11 05/20] error safe for casting character varying to other
types per pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc and pc.castfunc > 0 and
(castsource::regtype = 'character varying'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
-------------------+-------------------+----------+-------------+------------+---------------+----------
character varying | regclass | 1079 | i | f | text_regclass | regclass
character varying | "char" | 944 | a | f | text_char | char
character varying | name | 1400 | i | f | text_name | name
character varying | xml | 2896 | e | f | texttoxml | xml
character varying | character varying | 669 | i | f | varchar | varchar
(5 rows)
texttoxml, text_regclass was refactored as error safe in prior patch.
text_char, text_name is already error safe.
so here we only need handle function "varchar".
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/varchar.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c
index 5cb5c8c46f9..08f1bf5a24d 100644
--- a/src/backend/utils/adt/varchar.c
+++ b/src/backend/utils/adt/varchar.c
@@ -634,7 +634,7 @@ varchar(PG_FUNCTION_ARGS)
{
for (i = maxmblen; i < len; i++)
if (s_data[i] != ' ')
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
errmsg("value too long for type character varying(%d)",
maxlen)));
--
2.34.1
v11-0006-error-safe-for-casting-inet-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v11-0006-error-safe-for-casting-inet-to-other-types-per-pg_cast.patchDownload
From bf710ed00ac486da8bed9918ececd839c81c61b9 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 10:28:54 +0800
Subject: [PATCH v11 06/20] error safe for casting inet to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'inet'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+-------------------+----------+-------------+------------+--------------+---------
inet | cidr | 1715 | a | f | inet_to_cidr | cidr
inet | text | 730 | a | f | network_show | text
inet | character varying | 730 | a | f | network_show | text
inet | character | 730 | a | f | network_show | text
(4 rows)
inet_to_cidr is already error safe.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/network.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/backend/utils/adt/network.c b/src/backend/utils/adt/network.c
index 3cb0ab6829a..648c8d95f51 100644
--- a/src/backend/utils/adt/network.c
+++ b/src/backend/utils/adt/network.c
@@ -1137,7 +1137,7 @@ network_show(PG_FUNCTION_ARGS)
if (pg_inet_net_ntop(ip_family(ip), ip_addr(ip), ip_maxbits(ip),
tmp, sizeof(tmp)) == NULL)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
errmsg("could not format inet value: %m")));
--
2.34.1
v11-0004-error-safe-for-casting-text-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v11-0004-error-safe-for-casting-text-to-other-types-per-pg_cast.patchDownload
From ba57d49d0c3441a9c7729e6a2c40c98f6c96fdc5 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Mon, 24 Nov 2025 13:45:00 +0800
Subject: [PATCH v11 04/20] error safe for casting text to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'text'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------+----------+-------------+------------+---------------+----------
text | regclass | 1079 | i | f | text_regclass | regclass
text | "char" | 944 | a | f | text_char | char
text | name | 407 | i | f | text_name | name
text | xml | 2896 | e | f | texttoxml | xml
(4 rows)
already error safe: text_name, text_char.
texttoxml is refactored in character type error safe patch.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/catalog/namespace.c | 58 ++++++++++++++++++++++++++-------
src/backend/utils/adt/regproc.c | 13 ++++++--
src/backend/utils/adt/varlena.c | 10 ++++--
src/include/catalog/namespace.h | 6 ++++
src/include/utils/varlena.h | 1 +
5 files changed, 72 insertions(+), 16 deletions(-)
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index d23474da4fb..bef2de5dd39 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -440,6 +440,16 @@ Oid
RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
uint32 flags,
RangeVarGetRelidCallback callback, void *callback_arg)
+{
+ return RangeVarGetRelidExtendedSafe(relation, lockmode, flags,
+ callback, callback_arg,
+ NULL);
+}
+
+Oid
+RangeVarGetRelidExtendedSafe(const RangeVar *relation, LOCKMODE lockmode, uint32 flags,
+ RangeVarGetRelidCallback callback, void *callback_arg,
+ Node *escontext)
{
uint64 inval_count;
Oid relId;
@@ -456,7 +466,7 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
if (relation->catalogname)
{
if (strcmp(relation->catalogname, get_database_name(MyDatabaseId)) != 0)
- ereport(ERROR,
+ ereturn(escontext, InvalidOid,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cross-database references are not implemented: \"%s.%s.%s\"",
relation->catalogname, relation->schemaname,
@@ -513,7 +523,7 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
* return InvalidOid.
*/
if (namespaceId != myTempNamespace)
- ereport(ERROR,
+ ereturn(escontext, InvalidOid,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("temporary tables cannot specify a schema name")));
}
@@ -593,13 +603,23 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
{
int elevel = (flags & RVR_SKIP_LOCKED) ? DEBUG1 : ERROR;
- if (relation->schemaname)
- ereport(elevel,
+ if (relation->schemaname && elevel == DEBUG1)
+ ereport(DEBUG1,
(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
errmsg("could not obtain lock on relation \"%s.%s\"",
relation->schemaname, relation->relname)));
- else
- ereport(elevel,
+ else if (relation->schemaname && elevel == ERROR)
+ ereturn(escontext, InvalidOid,
+ errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s.%s\"",
+ relation->schemaname, relation->relname));
+ else if (elevel == DEBUG1)
+ ereport(DEBUG1,
+ errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s\"",
+ relation->relname));
+ else if (elevel == ERROR)
+ ereturn(escontext, InvalidOid,
(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
errmsg("could not obtain lock on relation \"%s\"",
relation->relname)));
@@ -626,13 +646,23 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
{
int elevel = missing_ok ? DEBUG1 : ERROR;
- if (relation->schemaname)
- ereport(elevel,
+ if (relation->schemaname && elevel == DEBUG1)
+ ereport(DEBUG1,
(errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("relation \"%s.%s\" does not exist",
relation->schemaname, relation->relname)));
- else
- ereport(elevel,
+ else if (relation->schemaname && elevel == ERROR)
+ ereturn(escontext, InvalidOid,
+ errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s.%s\" does not exist",
+ relation->schemaname, relation->relname));
+ else if (elevel == DEBUG1)
+ ereport(DEBUG1,
+ errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" does not exist",
+ relation->relname));
+ else if (elevel == ERROR)
+ ereturn(escontext, InvalidOid,
(errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("relation \"%s\" does not exist",
relation->relname)));
@@ -3622,6 +3652,12 @@ get_namespace_oid(const char *nspname, bool missing_ok)
*/
RangeVar *
makeRangeVarFromNameList(const List *names)
+{
+ return makeRangeVarFromNameListSafe(names, NULL);
+}
+
+RangeVar *
+makeRangeVarFromNameListSafe(const List *names, Node *escontext)
{
RangeVar *rel = makeRangeVar(NULL, NULL, -1);
@@ -3640,7 +3676,7 @@ makeRangeVarFromNameList(const List *names)
rel->relname = strVal(lthird(names));
break;
default:
- ereport(ERROR,
+ ereturn(escontext, NULL,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("improper relation name (too many dotted names): %s",
NameListToString(names))));
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index e5c2246f2c9..59cc508f805 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -1901,12 +1901,19 @@ text_regclass(PG_FUNCTION_ARGS)
text *relname = PG_GETARG_TEXT_PP(0);
Oid result;
RangeVar *rv;
+ List *namelist;
- rv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
+ namelist = textToQualifiedNameListSafe(relname, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
+
+ rv = makeRangeVarFromNameListSafe(namelist, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
/* We might not even have permissions on this relation; don't lock it. */
- result = RangeVarGetRelid(rv, NoLock, false);
-
+ result = RangeVarGetRelidExtendedSafe(rv, NoLock, 0, NULL, NULL,
+ fcinfo->context);
PG_RETURN_OID(result);
}
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index 3894457ab40..f8becbcde51 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -2684,6 +2684,12 @@ name_text(PG_FUNCTION_ARGS)
*/
List *
textToQualifiedNameList(text *textval)
+{
+ return textToQualifiedNameListSafe(textval, NULL);
+}
+
+List *
+textToQualifiedNameListSafe(text *textval, Node *escontext)
{
char *rawname;
List *result = NIL;
@@ -2695,12 +2701,12 @@ textToQualifiedNameList(text *textval)
rawname = text_to_cstring(textval);
if (!SplitIdentifierString(rawname, '.', &namelist))
- ereport(ERROR,
+ ereturn(escontext, NIL,
(errcode(ERRCODE_INVALID_NAME),
errmsg("invalid name syntax")));
if (namelist == NIL)
- ereport(ERROR,
+ ereturn(escontext, NIL,
(errcode(ERRCODE_INVALID_NAME),
errmsg("invalid name syntax")));
diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h
index f1423f28c32..ab61af55ddc 100644
--- a/src/include/catalog/namespace.h
+++ b/src/include/catalog/namespace.h
@@ -103,6 +103,11 @@ extern Oid RangeVarGetRelidExtended(const RangeVar *relation,
LOCKMODE lockmode, uint32 flags,
RangeVarGetRelidCallback callback,
void *callback_arg);
+extern Oid RangeVarGetRelidExtendedSafe(const RangeVar *relation,
+ LOCKMODE lockmode, uint32 flags,
+ RangeVarGetRelidCallback callback,
+ void *callback_arg,
+ Node *escontext);
extern Oid RangeVarGetCreationNamespace(const RangeVar *newRelation);
extern Oid RangeVarGetAndCheckCreationNamespace(RangeVar *relation,
LOCKMODE lockmode,
@@ -168,6 +173,7 @@ extern Oid LookupCreationNamespace(const char *nspname);
extern void CheckSetNamespace(Oid oldNspOid, Oid nspOid);
extern Oid QualifiedNameGetCreationNamespace(const List *names, char **objname_p);
extern RangeVar *makeRangeVarFromNameList(const List *names);
+extern RangeVar *makeRangeVarFromNameListSafe(const List *names, Node *escontext);
extern char *NameListToString(const List *names);
extern char *NameListToQuotedString(const List *names);
diff --git a/src/include/utils/varlena.h b/src/include/utils/varlena.h
index db9fdf72941..0cf01ae5281 100644
--- a/src/include/utils/varlena.h
+++ b/src/include/utils/varlena.h
@@ -27,6 +27,7 @@ extern int varstr_levenshtein_less_equal(const char *source, int slen,
int ins_c, int del_c, int sub_c,
int max_d, bool trusted);
extern List *textToQualifiedNameList(text *textval);
+extern List *textToQualifiedNameListSafe(text *textval, Node *escontext);
extern bool SplitIdentifierString(char *rawstring, char separator,
List **namelist);
extern bool SplitDirectoriesString(char *rawstring, char separator,
--
2.34.1
v11-0002-error-safe-for-casting-bit-varbit-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v11-0002-error-safe-for-casting-bit-varbit-to-other-types-per-pg_cast.patchDownload
From 07da6941a7cf2aa47622e695d582a238e452eb09 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 10:33:08 +0800
Subject: [PATCH v11 02/20] error safe for casting bit/varbit to other types
per pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
where pc.castfunc > 0 and (castsource::regtype ='bit'::regtype or
castsource::regtype ='varbit'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
-------------+-------------+----------+-------------+------------+-----------+---------
bit | bigint | 2076 | e | f | bittoint8 | int8
bit | integer | 1684 | e | f | bittoint4 | int4
bit | bit | 1685 | i | f | bit | bit
bit varying | bit varying | 1687 | i | f | varbit | varbit
(4 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/varbit.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/backend/utils/adt/varbit.c b/src/backend/utils/adt/varbit.c
index 205a67dafc5..6e9b808e20a 100644
--- a/src/backend/utils/adt/varbit.c
+++ b/src/backend/utils/adt/varbit.c
@@ -401,7 +401,7 @@ bit(PG_FUNCTION_ARGS)
PG_RETURN_VARBIT_P(arg);
if (!isExplicit)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_STRING_DATA_LENGTH_MISMATCH),
errmsg("bit string length %d does not match type bit(%d)",
VARBITLEN(arg), len)));
@@ -752,7 +752,7 @@ varbit(PG_FUNCTION_ARGS)
PG_RETURN_VARBIT_P(arg);
if (!isExplicit)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
errmsg("bit string too long for type bit varying(%d)",
len)));
@@ -1591,7 +1591,7 @@ bittoint4(PG_FUNCTION_ARGS)
/* Check that the bit string is not too long */
if (VARBITLEN(arg) > sizeof(result) * BITS_PER_BYTE)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
@@ -1671,7 +1671,7 @@ bittoint8(PG_FUNCTION_ARGS)
/* Check that the bit string is not too long */
if (VARBITLEN(arg) > sizeof(result) * BITS_PER_BYTE)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("bigint out of range")));
--
2.34.1
v11-0003-error-safe-for-casting-character-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v11-0003-error-safe-for-casting-character-to-other-types-per-pg_cast.patchDownload
From ef026d46791c7371cfb31e16d985bafbc25897d2 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Mon, 24 Nov 2025 12:52:16 +0800
Subject: [PATCH v11 03/20] error safe for casting character to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype ='character'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+-------------------+----------+-------------+------------+-------------+---------
character | text | 401 | i | f | rtrim1 | text
character | character varying | 401 | i | f | rtrim1 | text
character | "char" | 944 | a | f | text_char | char
character | name | 409 | i | f | bpchar_name | name
character | xml | 2896 | e | f | texttoxml | xml
character | character | 668 | i | f | bpchar | bpchar
(6 rows)
only texttoxml, bpchar(PG_FUNCTION_ARGS) need take care of error handling.
other functions already error safe.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/executor/execExprInterp.c | 2 +-
src/backend/utils/adt/varchar.c | 2 +-
src/backend/utils/adt/xml.c | 18 ++++++++++++------
src/include/utils/xml.h | 2 +-
4 files changed, 15 insertions(+), 9 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 0e1a74976f7..67f4e00eac4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -4542,7 +4542,7 @@ ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op)
*op->resvalue = PointerGetDatum(xmlparse(data,
xexpr->xmloption,
- preserve_whitespace));
+ preserve_whitespace, NULL));
*op->resnull = false;
}
break;
diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c
index 3f40c9da1a0..5cb5c8c46f9 100644
--- a/src/backend/utils/adt/varchar.c
+++ b/src/backend/utils/adt/varchar.c
@@ -307,7 +307,7 @@ bpchar(PG_FUNCTION_ARGS)
{
for (i = maxmblen; i < len; i++)
if (s[i] != ' ')
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
errmsg("value too long for type character(%d)",
maxlen)));
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 41e775570ec..9e8016456ce 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -659,7 +659,7 @@ texttoxml(PG_FUNCTION_ARGS)
{
text *data = PG_GETARG_TEXT_PP(0);
- PG_RETURN_XML_P(xmlparse(data, xmloption, true));
+ PG_RETURN_XML_P(xmlparse(data, xmloption, true, fcinfo->context));
}
@@ -1028,19 +1028,25 @@ xmlelement(XmlExpr *xexpr,
xmltype *
-xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace)
+xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, Node *escontext)
{
#ifdef USE_LIBXML
xmlDocPtr doc;
doc = xml_parse(data, xmloption_arg, preserve_whitespace,
- GetDatabaseEncoding(), NULL, NULL, NULL);
- xmlFreeDoc(doc);
+ GetDatabaseEncoding(), NULL, NULL, escontext);
+ if (doc)
+ xmlFreeDoc(doc);
+
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return NULL;
return (xmltype *) data;
#else
- NO_XML_SUPPORT();
- return NULL;
+ ereturn(escontext, NULL
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unsupported XML feature"),
+ errdetail("This functionality requires the server to be built with libxml support."));
#endif
}
diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h
index 732dac47bc4..b15168c430e 100644
--- a/src/include/utils/xml.h
+++ b/src/include/utils/xml.h
@@ -73,7 +73,7 @@ extern xmltype *xmlconcat(List *args);
extern xmltype *xmlelement(XmlExpr *xexpr,
const Datum *named_argvalue, const bool *named_argnull,
const Datum *argvalue, const bool *argnull);
-extern xmltype *xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace);
+extern xmltype *xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, Node *escontext);
extern xmltype *xmlpi(const char *target, text *arg, bool arg_is_null, bool *result_is_null);
extern xmltype *xmlroot(xmltype *data, text *version, int standalone);
extern bool xml_is_document(xmltype *arg);
--
2.34.1
v11-0001-error-safe-for-casting-bytea-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v11-0001-error-safe-for-casting-bytea-to-other-types-per-pg_cast.patchDownload
From fda9e35fff9e4f81baaa5508ded48671441b08d3 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:08:00 +0800
Subject: [PATCH v11 01/20] error safe for casting bytea to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc and pc.castfunc > 0 and castsource::regtype ='bytea'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------+----------+-------------+------------+------------+---------
bytea | smallint | 6370 | e | f | bytea_int2 | int2
bytea | integer | 6371 | e | f | bytea_int4 | int4
bytea | bigint | 6372 | e | f | bytea_int8 | int8
(3 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/bytea.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/backend/utils/adt/bytea.c b/src/backend/utils/adt/bytea.c
index 6e7b914c563..f43ad6b4aff 100644
--- a/src/backend/utils/adt/bytea.c
+++ b/src/backend/utils/adt/bytea.c
@@ -1027,7 +1027,7 @@ bytea_int2(PG_FUNCTION_ARGS)
/* Check that the byte array is not too long */
if (len > sizeof(result))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range"));
@@ -1052,7 +1052,7 @@ bytea_int4(PG_FUNCTION_ARGS)
/* Check that the byte array is not too long */
if (len > sizeof(result))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range"));
@@ -1077,7 +1077,7 @@ bytea_int8(PG_FUNCTION_ARGS)
/* Check that the byte array is not too long */
if (len > sizeof(result))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("bigint out of range"));
--
2.34.1
On Fri, Nov 21, 2025 at 3:32 PM Corey Huinker <corey.huinker@gmail.com> wrote:
Also, are we settled on this new pg_cast column name (pg_cast.casterrorsafe)?
Overall, I think it's better not to touch pg_cast.dat in each of these
refactoring patches.I'm fine with it. I can see having 'f' and 's' both mean cast functions, but 's' means safe, but the extra boolean works too and we'll be fine with either method.
I can work on this part if you don't have time.
Do you mean change pg_cast.casterrorsafe from boolean to char?
Currently pg_cast.casterrorsafe works just fine.
if the cast function is not applicable, the castfunc would be InvalidOid.
also the cast function is either error safe or not, I don't see a
usage case for the third value.
attached v12-0004 fixes the xmlparse error, see
https://cirrus-ci.com/task/6181359384788992?logs=build#L1217.
all other patches remain the same as v11.
Attachments:
v12-0019-invent-some-error-safe-functions.patchtext/x-patch; charset=US-ASCII; name=v12-0019-invent-some-error-safe-functions.patchDownload
From 6803dd3fcf06de55544d6e62baa48ad2c7c18d05 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Mon, 24 Nov 2025 17:03:45 +0800
Subject: [PATCH v12 19/20] invent some error safe functions
stringTypeDatumSafe: error safe version of stringTypeDatum
evaluate_expr_extended: If you wish to evaluate a constant expression in a
manner that is safe from potential errors, set the error_safe parameter to true
ExecInitExprSafe: soft error variant of ExecInitExpr
OidInputFunctionCallSafe: soft error variant of OidInputFunctionCall
CoerceUnknownConstSafe: attempts to coerce an UNKNOWN Const to the target type.
Returns NULL if the coercion is not possible.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/executor/execExpr.c | 41 +++++++++++++
src/backend/optimizer/util/clauses.c | 21 ++++++-
src/backend/parser/parse_coerce.c | 92 ++++++++++++++++++++++++++++
src/backend/parser/parse_type.c | 14 +++++
src/backend/utils/fmgr/fmgr.c | 13 ++++
src/include/executor/executor.h | 1 +
src/include/fmgr.h | 3 +
src/include/optimizer/optimizer.h | 3 +
src/include/parser/parse_coerce.h | 4 ++
src/include/parser/parse_type.h | 2 +
10 files changed, 193 insertions(+), 1 deletion(-)
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index f1569879b52..b302be36f73 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -170,6 +170,47 @@ ExecInitExpr(Expr *node, PlanState *parent)
return state;
}
+/*
+ * ExecInitExprSafe: soft error variant of ExecInitExpr.
+ *
+ * use it only for expression nodes support soft errors, not all expression
+ * nodes support it.
+*/
+ExprState *
+ExecInitExprSafe(Expr *node, PlanState *parent)
+{
+ ExprState *state;
+ ExprEvalStep scratch = {0};
+
+ /* Special case: NULL expression produces a NULL ExprState pointer */
+ if (node == NULL)
+ return NULL;
+
+ /* Initialize ExprState with empty step list */
+ state = makeNode(ExprState);
+ state->expr = node;
+ state->parent = parent;
+ state->ext_params = NULL;
+ state->escontext = makeNode(ErrorSaveContext);
+ state->escontext->type = T_ErrorSaveContext;
+ state->escontext->error_occurred = false;
+ state->escontext->details_wanted = false;
+
+ /* Insert setup steps as needed */
+ ExecCreateExprSetupSteps(state, (Node *) node);
+
+ /* Compile the expression proper */
+ ExecInitExprRec(node, state, &state->resvalue, &state->resnull);
+
+ /* Finally, append a DONE step */
+ scratch.opcode = EEOP_DONE_RETURN;
+ ExprEvalPushStep(state, &scratch);
+
+ ExecReadyExpr(state);
+
+ return state;
+}
+
/*
* ExecInitExprWithParams: prepare a standalone expression tree for execution
*
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 202ba8ed4bb..32af0df6c70 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -5081,6 +5081,16 @@ sql_inline_error_callback(void *arg)
Expr *
evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
Oid result_collation)
+{
+ return evaluate_expr_extended(expr,
+ result_type,
+ result_typmod,
+ result_collation,
+ false);
+}
+Expr *
+evaluate_expr_extended(Expr *expr, Oid result_type, int32 result_typmod,
+ Oid result_collation, bool error_safe)
{
EState *estate;
ExprState *exprstate;
@@ -5105,7 +5115,10 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
* Prepare expr for execution. (Note: we can't use ExecPrepareExpr
* because it'd result in recursively invoking eval_const_expressions.)
*/
- exprstate = ExecInitExpr(expr, NULL);
+ if (error_safe)
+ exprstate = ExecInitExprSafe(expr, NULL);
+ else
+ exprstate = ExecInitExpr(expr, NULL);
/*
* And evaluate it.
@@ -5125,6 +5138,12 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
/* Get back to outer memory context */
MemoryContextSwitchTo(oldcontext);
+ if (error_safe && SOFT_ERROR_OCCURRED(exprstate->escontext))
+ {
+ FreeExecutorState(estate);
+ return NULL;
+ }
+
/*
* Must copy result out of sub-context used by expression eval.
*
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 78b1e366ad7..cdcdd44a799 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -657,6 +657,98 @@ can_coerce_type(int nargs, const Oid *input_typeids, const Oid *target_typeids,
return true;
}
+/*
+ * Create an expression tree to represent coercion a UNKNOWN Const node.
+ *
+ * 'node': the input expression
+ * 'baseTypeId': base type of domain
+ * 'baseTypeMod': base type typmod of domain
+ * 'targetType': target type to coerce to
+ * 'targetTypeMod': target type typmod
+ * 'ccontext': context indicator to control coercions
+ * 'cformat': coercion display format
+ * 'location': coercion request location
+ * 'hideInputCoercion': if true, hide the input coercion under this one.
+ */
+Node *
+CoerceUnknownConstSafe(ParseState *pstate, Node *node, Oid targetType, int32 targetTypeMod,
+ CoercionContext ccontext, CoercionForm cformat, int location,
+ bool hideInputCoercion)
+{
+ Oid baseTypeId;
+ int32 baseTypeMod;
+ int32 inputTypeMod;
+ Type baseType;
+ char *string;
+ Datum datum;
+ Const *newcon;
+ Node *result = NULL;
+ Const *con = (Const *) node;
+
+ Assert(IsA(node, Const));
+ Assert(exprType(node) == UNKNOWNOID);
+
+ 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;
+
+ if (!stringTypeDatumSafe(baseType,
+ string,
+ inputTypeMod,
+ &datum))
+ {
+ ReleaseSysCache(baseType);
+ return NULL;
+ }
+
+ newcon = makeNode(Const);
+ newcon->consttype = baseTypeId;
+ newcon->consttypmod = inputTypeMod;
+ newcon->constcollid = typeTypeCollation(baseType);
+ newcon->constlen = typeLen(baseType);
+ newcon->constbyval = typeByVal(baseType);
+ newcon->constisnull = con->constisnull;
+ newcon->constvalue = datum;
+
+ /*
+ * 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)
+ newcon->constvalue =
+ PointerGetDatum(PG_DETOAST_DATUM(newcon->constvalue));
+
+ result = (Node *) newcon;
+
+ /* If target is a domain, apply constraints. */
+ if (baseTypeId != targetType)
+ result = coerce_to_domain(result,
+ baseTypeId, baseTypeMod,
+ targetType,
+ ccontext, cformat, location,
+ false);
+
+ ReleaseSysCache(baseType);
+
+ return result;
+}
+
/*
* Create an expression tree to represent coercion to a domain type.
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index 7713bdc6af0..d260aeec5dc 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,19 @@ stringTypeDatum(Type tp, char *string, int32 atttypmod)
return OidInputFunctionCall(typinput, string, typioparam, atttypmod);
}
+/* error safe version of stringTypeDatum */
+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,
+ (Node *) &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/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index 0fe63c6bb83..aaa4a42b1ea 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, Node *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/executor.h b/src/include/executor/executor.h
index fa2b657fb2f..f99fc26eb1f 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -324,6 +324,7 @@ ExecProcNode(PlanState *node)
* prototypes from functions in execExpr.c
*/
extern ExprState *ExecInitExpr(Expr *node, PlanState *parent);
+extern ExprState *ExecInitExprSafe(Expr *node, PlanState *parent);
extern ExprState *ExecInitExprWithParams(Expr *node, ParamListInfo ext_params);
extern ExprState *ExecInitQual(List *qual, PlanState *parent);
extern ExprState *ExecInitCheck(List *qual, PlanState *parent);
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index 74fe3ea0575..991e14034d3 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, Node *escontext,
+ Datum *result);
extern char *OutputFunctionCall(FmgrInfo *flinfo, Datum val);
extern char *OidOutputFunctionCall(Oid functionId, Datum val);
extern Datum ReceiveFunctionCall(FmgrInfo *flinfo, StringInfo buf,
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index d0aa8ab0c1c..97a0337452c 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -144,6 +144,9 @@ extern Node *estimate_expression_value(PlannerInfo *root, Node *node);
extern Expr *evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
Oid result_collation);
+extern Expr *evaluate_expr_extended(Expr *expr, Oid result_type,
+ int32 result_typmod, Oid result_collation,
+ bool error_safe);
extern bool var_is_nonnullable(PlannerInfo *root, Var *var, bool use_rel_info);
diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h
index 8d775c72c59..ad16cdd7022 100644
--- a/src/include/parser/parse_coerce.h
+++ b/src/include/parser/parse_coerce.h
@@ -48,6 +48,10 @@ extern bool can_coerce_type(int nargs, const Oid *input_typeids, const Oid *targ
extern Node *coerce_type(ParseState *pstate, Node *node,
Oid inputTypeId, Oid targetTypeId, int32 targetTypeMod,
CoercionContext ccontext, CoercionForm cformat, int location);
+extern Node *CoerceUnknownConstSafe(ParseState *pstate, Node *node,
+ Oid targetType, int32 targetTypeMod,
+ CoercionContext ccontext, CoercionForm cformat,
+ int location, bool hideInputCoercion);
extern Node *coerce_to_domain(Node *arg, Oid baseTypeId, int32 baseTypeMod,
Oid typeId,
CoercionContext ccontext, CoercionForm cformat, int location,
diff --git a/src/include/parser/parse_type.h b/src/include/parser/parse_type.h
index 0d919d8bfa2..12381aed64c 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);
--
2.34.1
v12-0018-error-safe-for-casting-geometry-data-type.patchtext/x-patch; charset=US-ASCII; name=v12-0018-error-safe-for-casting-geometry-data-type.patchDownload
From e7032cad38de580b0f6487d1c3be2e6fe817268c Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Mon, 24 Nov 2025 16:38:23 +0800
Subject: [PATCH v12 18/20] error safe for casting geometry data type
select castsource::regtype, casttarget::regtype, pp.prosrc
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
join pg_type pt on pt.oid = castsource
join pg_type pt1 on pt1.oid = casttarget
and pc.castfunc > 0 and pt.typarray <> 0
and pt.typnamespace = 'pg_catalog'::regnamespace
and pt1.typnamespace = 'pg_catalog'::regnamespace
and (pt.typcategory = 'G' or pt1.typcategory = 'G')
order by castsource::regtype, casttarget::regtype;
castsource | casttarget | prosrc
------------+------------+---------------
point | box | point_box
lseg | point | lseg_center
path | polygon | path_poly
box | point | box_center
box | lseg | box_diagonal
box | polygon | box_poly
box | circle | box_circle
polygon | point | poly_center
polygon | path | poly_path
polygon | box | poly_box
polygon | circle | poly_circle
circle | point | circle_center
circle | box | circle_box
circle | polygon |
(14 rows)
already error safe: point_box, box_diagonal, box_poly, poly_path, poly_box, circle_center
almost error safe: path_poly
can not error safe: cast circle to polygon, because it's a SQL function
discussion: https://postgr.es/m/CACJufxHCMzrHOW=wRe8L30rMhB3sjwAv1LE928Fa7sxMu1Tx-g@mail.gmail.com
---
src/backend/access/spgist/spgproc.c | 2 +-
src/backend/utils/adt/geo_ops.c | 225 +++++++++++++++++++++++-----
src/backend/utils/adt/geo_spgist.c | 2 +-
src/include/utils/float.h | 77 ++++++++++
src/include/utils/geo_decls.h | 4 +-
5 files changed, 271 insertions(+), 39 deletions(-)
diff --git a/src/backend/access/spgist/spgproc.c b/src/backend/access/spgist/spgproc.c
index 660009291da..b3e0fbc59ba 100644
--- a/src/backend/access/spgist/spgproc.c
+++ b/src/backend/access/spgist/spgproc.c
@@ -51,7 +51,7 @@ point_box_distance(Point *point, BOX *box)
else
dy = 0.0;
- return HYPOT(dx, dy);
+ return HYPOT(dx, dy, NULL);
}
/*
diff --git a/src/backend/utils/adt/geo_ops.c b/src/backend/utils/adt/geo_ops.c
index 377a1b3f3ad..bae8730c347 100644
--- a/src/backend/utils/adt/geo_ops.c
+++ b/src/backend/utils/adt/geo_ops.c
@@ -78,11 +78,14 @@ enum path_delim
/* Routines for points */
static inline void point_construct(Point *result, float8 x, float8 y);
static inline void point_add_point(Point *result, Point *pt1, Point *pt2);
+static inline bool point_add_point_safe(Point *result, Point *pt1, Point *pt2,
+ Node *escontext);
static inline void point_sub_point(Point *result, Point *pt1, Point *pt2);
static inline void point_mul_point(Point *result, Point *pt1, Point *pt2);
static inline void point_div_point(Point *result, Point *pt1, Point *pt2);
static inline bool point_eq_point(Point *pt1, Point *pt2);
static inline float8 point_dt(Point *pt1, Point *pt2);
+static inline float8 point_dt_safe(Point *pt1, Point *pt2, Node *escontext);
static inline float8 point_sl(Point *pt1, Point *pt2);
static int point_inside(Point *p, int npts, Point *plist);
@@ -109,6 +112,7 @@ static float8 lseg_closept_lseg(Point *result, LSEG *on_lseg, LSEG *to_lseg);
/* Routines for boxes */
static inline void box_construct(BOX *result, Point *pt1, Point *pt2);
static void box_cn(Point *center, BOX *box);
+static bool box_cn_safe(Point *center, BOX *box, Node* escontext);
static bool box_ov(BOX *box1, BOX *box2);
static float8 box_ar(BOX *box);
static float8 box_ht(BOX *box);
@@ -125,7 +129,7 @@ static float8 circle_ar(CIRCLE *circle);
/* Routines for polygons */
static void make_bound_box(POLYGON *poly);
-static void poly_to_circle(CIRCLE *result, POLYGON *poly);
+static bool poly_to_circle_safe(CIRCLE *result, POLYGON *poly, Node *escontext);
static bool lseg_inside_poly(Point *a, Point *b, POLYGON *poly, int start);
static bool poly_contain_poly(POLYGON *contains_poly, POLYGON *contained_poly);
static bool plist_same(int npts, Point *p1, Point *p2);
@@ -851,7 +855,8 @@ box_center(PG_FUNCTION_ARGS)
BOX *box = PG_GETARG_BOX_P(0);
Point *result = (Point *) palloc(sizeof(Point));
- box_cn(result, box);
+ if (!box_cn_safe(result, box, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_POINT_P(result);
}
@@ -871,10 +876,33 @@ box_ar(BOX *box)
static void
box_cn(Point *center, BOX *box)
{
- center->x = float8_div(float8_pl(box->high.x, box->low.x), 2.0);
- center->y = float8_div(float8_pl(box->high.y, box->low.y), 2.0);
+ (void) box_cn_safe(center, box, NULL);
}
+static bool
+box_cn_safe(Point *center, BOX *box, Node* escontext)
+{
+ float8 x;
+ float8 y;
+
+ x = float8_pl_safe(box->high.x, box->low.x, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ center->x = float8_div_safe(x, 2.0, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ y = float8_pl_safe(box->high.y, box->low.y, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ center->y = float8_div_safe(y, 2.0, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ return true;
+}
/* box_wd - returns the width (length) of the box
* (horizontal magnitude).
@@ -1276,7 +1304,7 @@ line_distance(PG_FUNCTION_ARGS)
PG_RETURN_FLOAT8(float8_div(fabs(float8_mi(l1->C,
float8_mul(ratio, l2->C))),
- HYPOT(l1->A, l1->B)));
+ HYPOT(l1->A, l1->B, NULL)));
}
/* line_interpt()
@@ -2001,9 +2029,27 @@ point_distance(PG_FUNCTION_ARGS)
static inline float8
point_dt(Point *pt1, Point *pt2)
{
- return HYPOT(float8_mi(pt1->x, pt2->x), float8_mi(pt1->y, pt2->y));
+ return point_dt_safe(pt1, pt2, NULL);
}
+static inline float8
+point_dt_safe(Point *pt1, Point *pt2, Node *escontext)
+{
+ float8 x;
+ float8 y;
+
+ x = float8_mi_safe(pt1->x, pt2->x, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return 0.0;
+
+ y = float8_mi_safe(pt1->y, pt2->y, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return 0.0;
+
+ return HYPOT(x, y, escontext);
+}
+
+
Datum
point_slope(PG_FUNCTION_ARGS)
{
@@ -2317,13 +2363,31 @@ lseg_center(PG_FUNCTION_ARGS)
{
LSEG *lseg = PG_GETARG_LSEG_P(0);
Point *result;
+ float8 x;
+ float8 y;
result = (Point *) palloc(sizeof(Point));
- result->x = float8_div(float8_pl(lseg->p[0].x, lseg->p[1].x), 2.0);
- result->y = float8_div(float8_pl(lseg->p[0].y, lseg->p[1].y), 2.0);
+ x = float8_pl_safe(lseg->p[0].x, lseg->p[1].x, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ result->x = float8_div_safe(x, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ y = float8_pl_safe(lseg->p[0].y, lseg->p[1].y, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ result->y = float8_div_safe(y, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
PG_RETURN_POINT_P(result);
+
+fail:
+ PG_RETURN_NULL();
}
@@ -4110,9 +4174,27 @@ construct_point(PG_FUNCTION_ARGS)
static inline void
point_add_point(Point *result, Point *pt1, Point *pt2)
{
- point_construct(result,
- float8_pl(pt1->x, pt2->x),
- float8_pl(pt1->y, pt2->y));
+ (void) point_add_point_safe(result, pt1, pt2, NULL);
+}
+
+static inline bool
+point_add_point_safe(Point *result, Point *pt1, Point *pt2,
+ Node *escontext)
+{
+ float8 x;
+ float8 y;
+
+ x = float8_pl_safe(pt1->x, pt2->x, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ y = float8_pl_safe(pt1->y, pt2->y, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ point_construct(result, x, y);
+
+ return true;
}
Datum
@@ -4458,7 +4540,7 @@ path_poly(PG_FUNCTION_ARGS)
/* This is not very consistent --- other similar cases return NULL ... */
if (!path->closed)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("open path cannot be converted to polygon")));
@@ -4508,7 +4590,9 @@ poly_center(PG_FUNCTION_ARGS)
result = (Point *) palloc(sizeof(Point));
- poly_to_circle(&circle, poly);
+ if (!poly_to_circle_safe(&circle, poly, fcinfo->context))
+ PG_RETURN_NULL();
+
*result = circle.center;
PG_RETURN_POINT_P(result);
@@ -5005,7 +5089,7 @@ circle_mul_pt(PG_FUNCTION_ARGS)
result = (CIRCLE *) palloc(sizeof(CIRCLE));
point_mul_point(&result->center, &circle->center, point);
- result->radius = float8_mul(circle->radius, HYPOT(point->x, point->y));
+ result->radius = float8_mul(circle->radius, HYPOT(point->x, point->y, NULL));
PG_RETURN_CIRCLE_P(result);
}
@@ -5020,7 +5104,7 @@ circle_div_pt(PG_FUNCTION_ARGS)
result = (CIRCLE *) palloc(sizeof(CIRCLE));
point_div_point(&result->center, &circle->center, point);
- result->radius = float8_div(circle->radius, HYPOT(point->x, point->y));
+ result->radius = float8_div(circle->radius, HYPOT(point->x, point->y, NULL));
PG_RETURN_CIRCLE_P(result);
}
@@ -5191,14 +5275,30 @@ circle_box(PG_FUNCTION_ARGS)
box = (BOX *) palloc(sizeof(BOX));
- delta = float8_div(circle->radius, sqrt(2.0));
+ delta = float8_div_safe(circle->radius, sqrt(2.0), fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
- box->high.x = float8_pl(circle->center.x, delta);
- box->low.x = float8_mi(circle->center.x, delta);
- box->high.y = float8_pl(circle->center.y, delta);
- box->low.y = float8_mi(circle->center.y, delta);
+ box->high.x = float8_pl_safe(circle->center.x, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ box->low.x = float8_mi_safe(circle->center.x, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ box->high.y = float8_pl_safe(circle->center.y, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ box->low.y = float8_mi_safe(circle->center.y, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
PG_RETURN_BOX_P(box);
+
+fail:
+ PG_RETURN_NULL();
}
/* box_circle()
@@ -5209,15 +5309,35 @@ box_circle(PG_FUNCTION_ARGS)
{
BOX *box = PG_GETARG_BOX_P(0);
CIRCLE *circle;
+ float8 x;
+ float8 y;
circle = (CIRCLE *) palloc(sizeof(CIRCLE));
- circle->center.x = float8_div(float8_pl(box->high.x, box->low.x), 2.0);
- circle->center.y = float8_div(float8_pl(box->high.y, box->low.y), 2.0);
+ x = float8_pl_safe(box->high.x, box->low.x, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
- circle->radius = point_dt(&circle->center, &box->high);
+ circle->center.x = float8_div_safe(x, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ y = float8_pl_safe(box->high.y, box->low.y, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ circle->center.y = float8_div_safe(y, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ circle->radius = point_dt_safe(&circle->center, &box->high, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
PG_RETURN_CIRCLE_P(circle);
+
+fail:
+ PG_RETURN_NULL();
}
@@ -5281,10 +5401,11 @@ circle_poly(PG_FUNCTION_ARGS)
* XXX This algorithm should use weighted means of line segments
* rather than straight average values of points - tgl 97/01/21.
*/
-static void
-poly_to_circle(CIRCLE *result, POLYGON *poly)
+static bool
+poly_to_circle_safe(CIRCLE *result, POLYGON *poly, Node *escontext)
{
int i;
+ float8 x;
Assert(poly->npts > 0);
@@ -5293,14 +5414,42 @@ poly_to_circle(CIRCLE *result, POLYGON *poly)
result->radius = 0;
for (i = 0; i < poly->npts; i++)
- point_add_point(&result->center, &result->center, &poly->p[i]);
- result->center.x = float8_div(result->center.x, poly->npts);
- result->center.y = float8_div(result->center.y, poly->npts);
+ {
+ if (!point_add_point_safe(&result->center,
+ &result->center,
+ &poly->p[i],
+ escontext))
+ return false;
+ }
+
+ result->center.x = float8_div_safe(result->center.x,
+ poly->npts,
+ escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ result->center.y = float8_div_safe(result->center.y,
+ poly->npts,
+ escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
for (i = 0; i < poly->npts; i++)
- result->radius = float8_pl(result->radius,
- point_dt(&poly->p[i], &result->center));
- result->radius = float8_div(result->radius, poly->npts);
+ {
+ x = point_dt_safe(&poly->p[i], &result->center, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ result->radius = float8_pl_safe(result->radius, x, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+ }
+
+ result->radius = float8_div_safe(result->radius, poly->npts, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ return true;
}
Datum
@@ -5311,7 +5460,8 @@ poly_circle(PG_FUNCTION_ARGS)
result = (CIRCLE *) palloc(sizeof(CIRCLE));
- poly_to_circle(result, poly);
+ if (!poly_to_circle_safe(result, poly, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_CIRCLE_P(result);
}
@@ -5516,7 +5666,7 @@ plist_same(int npts, Point *p1, Point *p2)
*-----------------------------------------------------------------------
*/
float8
-pg_hypot(float8 x, float8 y)
+pg_hypot(float8 x, float8 y, Node *escontext)
{
float8 yx,
result;
@@ -5554,9 +5704,14 @@ pg_hypot(float8 x, float8 y)
result = x * sqrt(1.0 + (yx * yx));
if (unlikely(isinf(result)))
- float_overflow_error();
+ ereturn(escontext, 0.0,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: overflow"));
+
if (unlikely(result == 0.0))
- float_underflow_error();
+ ereturn(escontext, 0.0,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: underflow"));
return result;
}
diff --git a/src/backend/utils/adt/geo_spgist.c b/src/backend/utils/adt/geo_spgist.c
index fec33e95372..ffffd3cd2de 100644
--- a/src/backend/utils/adt/geo_spgist.c
+++ b/src/backend/utils/adt/geo_spgist.c
@@ -390,7 +390,7 @@ pointToRectBoxDistance(Point *point, RectBox *rect_box)
else
dy = 0;
- return HYPOT(dx, dy);
+ return HYPOT(dx, dy, NULL);
}
diff --git a/src/include/utils/float.h b/src/include/utils/float.h
index fc2a9cf6475..aec376ffc56 100644
--- a/src/include/utils/float.h
+++ b/src/include/utils/float.h
@@ -121,6 +121,21 @@ float8_pl(const float8 val1, const float8 val2)
return result;
}
+/* error safe version of float8_pl */
+static inline float8
+float8_pl_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+ float8 result;
+
+ result = val1 + val2;
+ if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
+ ereturn(escontext, 0,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: overflow"));
+
+ return result;
+}
+
static inline float4
float4_mi(const float4 val1, const float4 val2)
{
@@ -145,6 +160,21 @@ float8_mi(const float8 val1, const float8 val2)
return result;
}
+/* error safe version of float8_mi */
+static inline float8
+float8_mi_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+ float8 result;
+
+ result = val1 - val2;
+ if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
+ ereturn(escontext, 0,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: overflow"));
+
+ return result;
+}
+
static inline float4
float4_mul(const float4 val1, const float4 val2)
{
@@ -173,6 +203,27 @@ float8_mul(const float8 val1, const float8 val2)
return result;
}
+/* error safe version of float8_mul */
+static inline float8
+float8_mul_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+ float8 result;
+
+ result = val1 * val2;
+
+ if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
+ ereturn(escontext, 0,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: overflow"));
+
+ if (unlikely(result == 0.0) && val1 != 0.0 && val2 != 0.0)
+ ereturn(escontext, 0,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: underflow"));
+
+ return result;
+}
+
static inline float4
float4_div(const float4 val1, const float4 val2)
{
@@ -205,6 +256,32 @@ float8_div(const float8 val1, const float8 val2)
return result;
}
+/* error safe version of float8_div */
+static inline float8
+float8_div_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+ float8 result;
+
+ if (unlikely(val2 == 0.0) && !isnan(val1))
+ ereturn(escontext, 0,
+ errcode(ERRCODE_DIVISION_BY_ZERO),
+ errmsg("division by zero"));
+
+ result = val1 / val2;
+
+ if (unlikely(isinf(result)) && !isinf(val1))
+ ereturn(escontext, 0,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: overflow"));
+
+ if (unlikely(result == 0.0) && val1 != 0.0 && !isinf(val2))
+ ereturn(escontext, 0,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: underflow"));
+
+ return result;
+}
+
/*
* Routines for NaN-aware comparisons
*
diff --git a/src/include/utils/geo_decls.h b/src/include/utils/geo_decls.h
index 8a9df75c93c..0056a26639f 100644
--- a/src/include/utils/geo_decls.h
+++ b/src/include/utils/geo_decls.h
@@ -88,7 +88,7 @@ FPge(double A, double B)
#define FPge(A,B) ((A) >= (B))
#endif
-#define HYPOT(A, B) pg_hypot(A, B)
+#define HYPOT(A, B, escontext) pg_hypot(A, B, escontext)
/*---------------------------------------------------------------------
* Point - (x,y)
@@ -280,6 +280,6 @@ CirclePGetDatum(const CIRCLE *X)
* in geo_ops.c
*/
-extern float8 pg_hypot(float8 x, float8 y);
+extern float8 pg_hypot(float8 x, float8 y, Node *escontext);
#endif /* GEO_DECLS_H */
--
2.34.1
v12-0017-error-safe-for-casting-jsonb-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v12-0017-error-safe-for-casting-jsonb-to-other-types-per-pg_cast.patchDownload
From bafd37051cf8a39e2fa5f5c4bc81ae94d5f951e0 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Mon, 24 Nov 2025 14:26:53 +0800
Subject: [PATCH v12 17/20] error safe for casting jsonb to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'jsonb'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+---------------+---------
jsonb | boolean | 3556 | e | f | jsonb_bool | bool
jsonb | numeric | 3449 | e | f | jsonb_numeric | numeric
jsonb | smallint | 3450 | e | f | jsonb_int2 | int2
jsonb | integer | 3451 | e | f | jsonb_int4 | int4
jsonb | bigint | 3452 | e | f | jsonb_int8 | int8
jsonb | real | 3453 | e | f | jsonb_float4 | float4
jsonb | double precision | 2580 | e | f | jsonb_float8 | float8
(7 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/jsonb.c | 74 +++++++++++++++++++++++++++--------
1 file changed, 58 insertions(+), 16 deletions(-)
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index 9399cdb491a..ab24d6dc813 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -2006,7 +2006,7 @@ JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res)
* Emit correct, translatable cast error message
*/
static void
-cannotCastJsonbValue(enum jbvType type, const char *sqltype)
+cannotCastJsonbValue(enum jbvType type, const char *sqltype, Node *escontext)
{
static const struct
{
@@ -2027,7 +2027,7 @@ cannotCastJsonbValue(enum jbvType type, const char *sqltype)
for (i = 0; i < lengthof(messages); i++)
if (messages[i].type == type)
- ereport(ERROR,
+ ereturn(escontext,,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(messages[i].msg, sqltype)));
@@ -2042,7 +2042,10 @@ jsonb_bool(PG_FUNCTION_ARGS)
JsonbValue v;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "boolean");
+ {
+ cannotCastJsonbValue(v.type, "boolean", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2051,7 +2054,10 @@ jsonb_bool(PG_FUNCTION_ARGS)
}
if (v.type != jbvBool)
- cannotCastJsonbValue(v.type, "boolean");
+ {
+ cannotCastJsonbValue(v.type, "boolean", fcinfo->context);
+ PG_RETURN_NULL();
+ }
PG_FREE_IF_COPY(in, 0);
@@ -2066,7 +2072,10 @@ jsonb_numeric(PG_FUNCTION_ARGS)
Numeric retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "numeric");
+ {
+ cannotCastJsonbValue(v.type, "numeric", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2075,7 +2084,10 @@ jsonb_numeric(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "numeric");
+ {
+ cannotCastJsonbValue(v.type, "numeric", fcinfo->context);
+ PG_RETURN_NULL();
+ }
/*
* v.val.numeric points into jsonb body, so we need to make a copy to
@@ -2096,7 +2108,10 @@ jsonb_int2(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "smallint");
+ {
+ cannotCastJsonbValue(v.type, "smallint", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2105,7 +2120,10 @@ jsonb_int2(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "smallint");
+ {
+ cannotCastJsonbValue(v.type, "smallint", fcinfo->context);
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_int2,
NumericGetDatum(v.val.numeric));
@@ -2123,7 +2141,10 @@ jsonb_int4(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "integer");
+ {
+ cannotCastJsonbValue(v.type, "integer", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2132,7 +2153,10 @@ jsonb_int4(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "integer");
+ {
+ cannotCastJsonbValue(v.type, "integer", fcinfo->context);
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_int4,
NumericGetDatum(v.val.numeric));
@@ -2150,7 +2174,10 @@ jsonb_int8(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "bigint");
+ {
+ cannotCastJsonbValue(v.type, "bigint", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2159,7 +2186,10 @@ jsonb_int8(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "bigint");
+ {
+ cannotCastJsonbValue(v.type, "bigint", fcinfo->context);
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_int8,
NumericGetDatum(v.val.numeric));
@@ -2177,7 +2207,10 @@ jsonb_float4(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "real");
+ {
+ cannotCastJsonbValue(v.type, "real", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2186,7 +2219,10 @@ jsonb_float4(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "real");
+ {
+ cannotCastJsonbValue(v.type, "real", fcinfo->context);
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_float4,
NumericGetDatum(v.val.numeric));
@@ -2204,7 +2240,10 @@ jsonb_float8(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "double precision");
+ {
+ cannotCastJsonbValue(v.type, "double precision", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2213,7 +2252,10 @@ jsonb_float8(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "double precision");
+ {
+ cannotCastJsonbValue(v.type, "double precision", fcinfo->context);
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_float8,
NumericGetDatum(v.val.numeric));
--
2.34.1
v12-0015-error-safe-for-casting-timestamptz-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v12-0015-error-safe-for-casting-timestamptz-to-other-types-per-pg_cast.patchDownload
From 30fb4299bcc66aa90755fbac42e43d6d8a66b642 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Mon, 6 Oct 2025 12:39:22 +0800
Subject: [PATCH v12 15/20] error safe for casting timestamptz to other types
per pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
and pc.castfunc > 0 and (castsource::regtype ='timestamptz'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
--------------------------+-----------------------------+----------+-------------+------------+-----------------------+-------------
timestamp with time zone | date | 1178 | a | f | timestamptz_date | date
timestamp with time zone | time without time zone | 2019 | a | f | timestamptz_time | time
timestamp with time zone | timestamp without time zone | 2027 | a | f | timestamptz_timestamp | timestamp
timestamp with time zone | time with time zone | 1388 | a | f | timestamptz_timetz | timetz
timestamp with time zone | timestamp with time zone | 1967 | i | f | timestamptz_scale | timestamptz
(5 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 16 +++++++++++++---
src/backend/utils/adt/timestamp.c | 17 +++++++++++++++--
2 files changed, 28 insertions(+), 5 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 4f0f3d26989..111bfd8f519 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -1468,7 +1468,17 @@ timestamptz_date(PG_FUNCTION_ARGS)
TimestampTz timestamp = PG_GETARG_TIMESTAMP(0);
DateADT result;
- result = timestamptz2date_opt_overflow(timestamp, NULL);
+ if (likely(!fcinfo->context))
+ result = timestamptz2date_opt_overflow(timestamp, NULL);
+ else
+ {
+ int overflow;
+ result = timestamptz2date_opt_overflow(timestamp, &overflow);
+
+ if (overflow != 0)
+ PG_RETURN_NULL();
+ }
+
PG_RETURN_DATEADT(result);
}
@@ -2110,7 +2120,7 @@ timestamptz_time(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
@@ -3029,7 +3039,7 @@ timestamptz_timetz(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 7b565cc6d66..116e3ef28fc 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -875,7 +875,8 @@ timestamptz_scale(PG_FUNCTION_ARGS)
result = timestamp;
- AdjustTimestampForTypmod(&result, typmod, NULL);
+ if (!AdjustTimestampForTypmod(&result, typmod, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_TIMESTAMPTZ(result);
}
@@ -6507,7 +6508,19 @@ timestamptz_timestamp(PG_FUNCTION_ARGS)
{
TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(0);
- PG_RETURN_TIMESTAMP(timestamptz2timestamp(timestamp));
+ if (likely(!fcinfo->context))
+ PG_RETURN_TIMESTAMP(timestamptz2timestamp(timestamp));
+ else
+ {
+ int overflow;
+ Timestamp result;
+
+ result = timestamptz2timestamp_opt_overflow(timestamp, &overflow);
+ if (overflow != 0)
+ PG_RETURN_NULL();
+ else
+ PG_RETURN_TIMESTAMP(result);
+ }
}
/*
--
2.34.1
v12-0016-error-safe-for-casting-timestamp-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v12-0016-error-safe-for-casting-timestamp-to-other-types-per-pg_cast.patchDownload
From 675190ed0721396503fa3a79d562c72066c9464d Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 18:44:35 +0800
Subject: [PATCH v12 16/20] error safe for casting timestamp to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
and pc.castfunc > 0 and (castsource::regtype ='timestamp'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
-----------------------------+-----------------------------+----------+-------------+------------+-----------------------+-------------
timestamp without time zone | date | 2029 | a | f | timestamp_date | date
timestamp without time zone | time without time zone | 1316 | a | f | timestamp_time | time
timestamp without time zone | timestamp with time zone | 2028 | i | f | timestamp_timestamptz | timestamptz
timestamp without time zone | timestamp without time zone | 1961 | i | f | timestamp_scale | timestamp
(4 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 13 +++++++++++--
src/backend/utils/adt/timestamp.c | 17 +++++++++++++++--
2 files changed, 26 insertions(+), 4 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 111bfd8f519..c5562b563e5 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -1373,7 +1373,16 @@ timestamp_date(PG_FUNCTION_ARGS)
Timestamp timestamp = PG_GETARG_TIMESTAMP(0);
DateADT result;
- result = timestamp2date_opt_overflow(timestamp, NULL);
+ if (likely(!fcinfo->context))
+ result = timestamptz2date_opt_overflow(timestamp, NULL);
+ else
+ {
+ int overflow;
+ result = timestamp2date_opt_overflow(timestamp, &overflow);
+
+ if (overflow != 0)
+ PG_RETURN_NULL();
+ }
PG_RETURN_DATEADT(result);
}
@@ -2089,7 +2098,7 @@ timestamp_time(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 116e3ef28fc..7a57ac3eaf6 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -352,7 +352,8 @@ timestamp_scale(PG_FUNCTION_ARGS)
result = timestamp;
- AdjustTimestampForTypmod(&result, typmod, NULL);
+ if (!AdjustTimestampForTypmod(&result, typmod, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_TIMESTAMP(result);
}
@@ -6430,7 +6431,19 @@ timestamp_timestamptz(PG_FUNCTION_ARGS)
{
Timestamp timestamp = PG_GETARG_TIMESTAMP(0);
- PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(timestamp));
+ if (likely(!fcinfo->context))
+ PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(timestamp));
+ else
+ {
+ TimestampTz result;
+ int overflow;
+
+ result = timestamp2timestamptz_opt_overflow(timestamp, &overflow);
+ if (overflow != 0)
+ PG_RETURN_NULL();
+ else
+ PG_RETURN_TIMESTAMPTZ(result);
+ }
}
/*
--
2.34.1
v12-0014-error-safe-for-casting-interval-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v12-0014-error-safe-for-casting-interval-to-other-types-per-pg_cast.patchDownload
From 61ef3a3c9bd59a83c893fcdb51d0865e43c1fe3a Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 18:43:29 +0800
Subject: [PATCH v12 14/20] error safe for casting interval to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'interval'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------------+----------+-------------+------------+----------------+----------
interval | time without time zone | 1419 | a | f | interval_time | time
interval | interval | 1200 | i | f | interval_scale | interval
(2 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 2 +-
src/backend/utils/adt/timestamp.c | 3 ++-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index c7a3cde2d81..4f0f3d26989 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2180,7 +2180,7 @@ interval_time(PG_FUNCTION_ARGS)
TimeADT result;
if (INTERVAL_NOT_FINITE(span))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("cannot convert infinite interval to time")));
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 156a4830ffd..7b565cc6d66 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1334,7 +1334,8 @@ interval_scale(PG_FUNCTION_ARGS)
result = palloc(sizeof(Interval));
*result = *interval;
- AdjustIntervalForTypmod(result, typmod, NULL);
+ if (!AdjustIntervalForTypmod(result, typmod, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_INTERVAL_P(result);
}
--
2.34.1
v12-0012-error-safe-for-casting-float8-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v12-0012-error-safe-for-casting-float8-to-other-types-per-pg_cast.patchDownload
From 35664fc4f1aafd5f0058bc796cb83ce9d37c2881 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:31:53 +0800
Subject: [PATCH v12 12/20] error safe for casting float8 to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'float8'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------------+------------+----------+-------------+------------+----------------+---------
double precision | bigint | 483 | a | f | dtoi8 | int8
double precision | smallint | 237 | a | f | dtoi2 | int2
double precision | integer | 317 | a | f | dtoi4 | int4
double precision | real | 312 | a | f | dtof | float4
double precision | numeric | 1743 | a | f | float8_numeric | numeric
(5 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/float.c | 13 +++++++++----
src/backend/utils/adt/int8.c | 2 +-
src/backend/utils/adt/numeric.c | 3 ++-
3 files changed, 12 insertions(+), 6 deletions(-)
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index ab5c38d723d..d746f961fd3 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -1199,9 +1199,14 @@ 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 +1229,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 +1254,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")));
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 8f7c117557a..70e6d89813c 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -1260,7 +1260,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")));
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index b89c35eee65..507c607a799 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -4560,7 +4560,8 @@ float8_numeric(PG_FUNCTION_ARGS)
init_var(&result);
/* Assume we need not worry about leading/trailing spaces */
- (void) set_var_from_str(buf, buf, &result, &endptr, NULL);
+ if (!set_var_from_str(buf, buf, &result, &endptr, fcinfo->context))
+ PG_RETURN_NULL();
res = make_result(&result);
--
2.34.1
v12-0013-error-safe-for-casting-date-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v12-0013-error-safe-for-casting-date-to-other-types-per-pg_cast.patchDownload
From 819f444d9cf10248c4a29fc89aab9aa62913ebbb Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 18:42:50 +0800
Subject: [PATCH v12 13/20] error safe for casting date to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'date'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+-----------------------------+----------+-------------+------------+------------------+-------------
date | timestamp without time zone | 2024 | i | f | date_timestamp | timestamp
date | timestamp with time zone | 1174 | i | f | date_timestamptz | timestamptz
(2 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 22 ++++++++++++++++++++--
1 file changed, 20 insertions(+), 2 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 344f58b92f7..c7a3cde2d81 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -1350,7 +1350,16 @@ date_timestamp(PG_FUNCTION_ARGS)
DateADT dateVal = PG_GETARG_DATEADT(0);
Timestamp result;
- result = date2timestamp(dateVal);
+ if (likely(!fcinfo->context))
+ result = date2timestamp(dateVal);
+ else
+ {
+ int overflow;
+
+ result = date2timestamp_opt_overflow(dateVal, &overflow);
+ if (overflow != 0)
+ PG_RETURN_NULL();
+ }
PG_RETURN_TIMESTAMP(result);
}
@@ -1435,7 +1444,16 @@ date_timestamptz(PG_FUNCTION_ARGS)
DateADT dateVal = PG_GETARG_DATEADT(0);
TimestampTz result;
- result = date2timestamptz(dateVal);
+ if (likely(!fcinfo->context))
+ result = date2timestamptz(dateVal);
+ else
+ {
+ int overflow;
+ result = date2timestamptz_opt_overflow(dateVal, &overflow);
+
+ if (overflow != 0)
+ PG_RETURN_NULL();
+ }
PG_RETURN_TIMESTAMP(result);
}
--
2.34.1
v12-0011-error-safe-for-casting-float4-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v12-0011-error-safe-for-casting-float4-to-other-types-per-pg_cast.patchDownload
From 2d5488319fb708212e8d44a4226c013ad8bf2401 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:28:20 +0800
Subject: [PATCH v12 11/20] error safe for casting float4 to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'float4'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+----------------+---------
real | bigint | 653 | a | f | ftoi8 | int8
real | smallint | 238 | a | f | ftoi2 | int2
real | integer | 319 | a | f | ftoi4 | int4
real | double precision | 311 | i | f | ftod | float8
real | numeric | 1742 | a | f | float4_numeric | numeric
(5 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/float.c | 4 ++--
src/backend/utils/adt/int8.c | 2 +-
src/backend/utils/adt/numeric.c | 3 ++-
3 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index 7b97d2be6ca..ab5c38d723d 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -1298,7 +1298,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 +1323,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 3dcbd36bd8e..8f7c117557a 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -1295,7 +1295,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 40d2709b64a..b89c35eee65 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -4658,7 +4658,8 @@ float4_numeric(PG_FUNCTION_ARGS)
init_var(&result);
/* Assume we need not worry about leading/trailing spaces */
- (void) set_var_from_str(buf, buf, &result, &endptr, NULL);
+ if (!set_var_from_str(buf, buf, &result, &endptr, fcinfo->context))
+ PG_RETURN_NULL();
res = make_result(&result);
--
2.34.1
v12-0010-error-safe-for-casting-numeric-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v12-0010-error-safe-for-casting-numeric-to-other-types-per-pg_cast.patchDownload
From a4c0b5c782ead8aa18301657bd070b9df0354f2e Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:25:37 +0800
Subject: [PATCH v12 10/20] error safe for casting numeric to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'numeric'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+----------------+---------
numeric | bigint | 1779 | a | f | numeric_int8 | int8
numeric | smallint | 1783 | a | f | numeric_int2 | int2
numeric | integer | 1744 | a | f | numeric_int4 | int4
numeric | real | 1745 | i | f | numeric_float4 | float4
numeric | double precision | 1746 | i | f | numeric_float8 | float8
numeric | money | 3824 | a | f | numeric_cash | money
numeric | numeric | 1703 | i | f | numeric | numeric
(7 rows)
discussion: https://postgr.es/m/CACJufxHCMzrHOW=wRe8L30rMhB3sjwAv1LE928Fa7sxMu1Tx-g@mail.gmail.com
---
src/backend/utils/adt/numeric.c | 58 ++++++++++++++++++++++++---------
1 file changed, 43 insertions(+), 15 deletions(-)
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 2501007d981..40d2709b64a 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -1244,7 +1244,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));
}
@@ -1295,8 +1296,9 @@ numeric (PG_FUNCTION_ARGS)
init_var(&var);
set_var_from_num(num, &var);
- (void) apply_typmod(&var, typmod, NULL);
- new = make_result(&var);
+ if (!apply_typmod(&var, typmod, fcinfo->context))
+ PG_RETURN_NULL();
+ new = make_result_safe(&var, fcinfo->context);
free_var(&var);
@@ -3019,7 +3021,10 @@ numeric_mul(PG_FUNCTION_ARGS)
Numeric num2 = PG_GETARG_NUMERIC(1);
Numeric res;
- res = numeric_mul_safe(num1, num2, NULL);
+ res = numeric_mul_safe(num1, num2, fcinfo->context);
+
+ if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
PG_RETURN_NUMERIC(res);
}
@@ -4393,9 +4398,15 @@ numeric_int4_safe(Numeric num, Node *escontext)
Datum
numeric_int4(PG_FUNCTION_ARGS)
{
+ int32 result;
Numeric num = PG_GETARG_NUMERIC(0);
- PG_RETURN_INT32(numeric_int4_safe(num, NULL));
+ result = numeric_int4_safe(num, fcinfo->context);
+
+ if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
+
+ PG_RETURN_INT32(result);
}
/*
@@ -4463,9 +4474,15 @@ numeric_int8_safe(Numeric num, Node *escontext)
Datum
numeric_int8(PG_FUNCTION_ARGS)
{
+ int64 result;
Numeric num = PG_GETARG_NUMERIC(0);
- PG_RETURN_INT64(numeric_int8_safe(num, NULL));
+ result = numeric_int8_safe(num, fcinfo->context);
+
+ if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
+
+ PG_RETURN_INT64(result);
}
@@ -4489,11 +4506,11 @@ numeric_int2(PG_FUNCTION_ARGS)
if (NUMERIC_IS_SPECIAL(num))
{
if (NUMERIC_IS_NAN(num))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert NaN to %s", "smallint")));
else
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert infinity to %s", "smallint")));
}
@@ -4502,12 +4519,12 @@ numeric_int2(PG_FUNCTION_ARGS)
init_var_from_num(num, &x);
if (!numericvar_to_int64(&x, &val))
- ereport(ERROR,
+ ereturn(fcinfo->context, (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(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
@@ -4572,10 +4589,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);
}
@@ -4667,7 +4688,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
v12-0009-error-safe-for-casting-bigint-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v12-0009-error-safe-for-casting-bigint-to-other-types-per-pg_cast.patchDownload
From 67e46c5a4ec3964e66b186ead03edcd046b03c68 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:22:00 +0800
Subject: [PATCH v12 09/20] error safe for casting bigint to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'bigint'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+--------------+---------
bigint | smallint | 714 | a | f | int82 | int2
bigint | integer | 480 | a | f | int84 | int4
bigint | real | 652 | i | f | i8tof | float4
bigint | double precision | 482 | i | f | i8tod | float8
bigint | numeric | 1781 | i | f | int8_numeric | numeric
bigint | money | 3812 | a | f | int8_cash | money
bigint | oid | 1287 | i | f | i8tooid | oid
bigint | regproc | 1287 | i | f | i8tooid | oid
bigint | regprocedure | 1287 | i | f | i8tooid | oid
bigint | regoper | 1287 | i | f | i8tooid | oid
bigint | regoperator | 1287 | i | f | i8tooid | oid
bigint | regclass | 1287 | i | f | i8tooid | oid
bigint | regcollation | 1287 | i | f | i8tooid | oid
bigint | regtype | 1287 | i | f | i8tooid | oid
bigint | regconfig | 1287 | i | f | i8tooid | oid
bigint | regdictionary | 1287 | i | f | i8tooid | oid
bigint | regrole | 1287 | i | f | i8tooid | oid
bigint | regnamespace | 1287 | i | f | i8tooid | oid
bigint | regdatabase | 1287 | i | f | i8tooid | oid
bigint | bytea | 6369 | e | f | int8_bytea | bytea
bigint | bit | 2075 | e | f | bitfromint8 | bit
(21 rows)
already error safe: i8tof, i8tod, int8_numeric, int8_bytea, bitfromint8
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/int8.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index bdea490202a..3dcbd36bd8e 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -1204,7 +1204,7 @@ int84(PG_FUNCTION_ARGS)
int64 arg = PG_GETARG_INT64(0);
if (unlikely(arg < PG_INT32_MIN) || unlikely(arg > PG_INT32_MAX))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
@@ -1225,7 +1225,7 @@ int82(PG_FUNCTION_ARGS)
int64 arg = PG_GETARG_INT64(0);
if (unlikely(arg < PG_INT16_MIN) || unlikely(arg > PG_INT16_MAX))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
@@ -1308,7 +1308,7 @@ i8tooid(PG_FUNCTION_ARGS)
int64 arg = PG_GETARG_INT64(0);
if (unlikely(arg < 0) || unlikely(arg > PG_UINT32_MAX))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("OID out of range")));
--
2.34.1
v12-0008-error-safe-for-casting-integer-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v12-0008-error-safe-for-casting-integer-to-other-types-per-pg_cast.patchDownload
From 2a3fce700e11e1a9bf0eb9f7fa5c48e0d4ec10d6 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:20:10 +0800
Subject: [PATCH v12 08/20] error safe for casting integer to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'integer'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+--------------+---------
integer | bigint | 481 | i | f | int48 | int8
integer | smallint | 314 | a | f | i4toi2 | int2
integer | real | 318 | i | f | i4tof | float4
integer | double precision | 316 | i | f | i4tod | float8
integer | numeric | 1740 | i | f | int4_numeric | numeric
integer | money | 3811 | a | f | int4_cash | money
integer | boolean | 2557 | e | f | int4_bool | bool
integer | bytea | 6368 | e | f | int4_bytea | bytea
integer | "char" | 78 | e | f | i4tochar | char
integer | bit | 1683 | e | f | bitfromint4 | bit
(10 rows)
only int4_cash, i4toi2, i4tochar need take care of error handling. but support
for cash data type is not easy, so only i4toi2, i4tochar function refactoring.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/char.c | 2 +-
src/backend/utils/adt/int.c | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/backend/utils/adt/char.c b/src/backend/utils/adt/char.c
index 22dbfc950b1..e90844a29f0 100644
--- a/src/backend/utils/adt/char.c
+++ b/src/backend/utils/adt/char.c
@@ -192,7 +192,7 @@ i4tochar(PG_FUNCTION_ARGS)
int32 arg1 = PG_GETARG_INT32(0);
if (arg1 < SCHAR_MIN || arg1 > SCHAR_MAX)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("\"char\" out of range")));
diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c
index b5781989a64..b45599d402d 100644
--- a/src/backend/utils/adt/int.c
+++ b/src/backend/utils/adt/int.c
@@ -350,7 +350,7 @@ i4toi2(PG_FUNCTION_ARGS)
int32 arg1 = PG_GETARG_INT32(0);
if (unlikely(arg1 < SHRT_MIN) || unlikely(arg1 > SHRT_MAX))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
--
2.34.1
v12-0007-error-safe-for-casting-macaddr8-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v12-0007-error-safe-for-casting-macaddr8-to-other-types-per-pg_cast.patchDownload
From 7db397a55d6ff5e1b96176b704556649b86630c2 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:17:11 +0800
Subject: [PATCH v12 07/20] error safe for casting macaddr8 to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
and pc.castfunc > 0 and castsource::regtype ='macaddr8'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------+----------+-------------+------------+-------------------+---------
macaddr8 | macaddr | 4124 | i | f | macaddr8tomacaddr | macaddr
(1 row)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/mac8.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/backend/utils/adt/mac8.c b/src/backend/utils/adt/mac8.c
index 08e41ba4eea..1c903f152de 100644
--- a/src/backend/utils/adt/mac8.c
+++ b/src/backend/utils/adt/mac8.c
@@ -550,7 +550,7 @@ macaddr8tomacaddr(PG_FUNCTION_ARGS)
result = (macaddr *) palloc0(sizeof(macaddr));
if ((addr->d != 0xFF) || (addr->e != 0xFE))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("macaddr8 data out of range to convert to macaddr"),
errhint("Only addresses that have FF and FE as values in the "
--
2.34.1
v12-0006-error-safe-for-casting-inet-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v12-0006-error-safe-for-casting-inet-to-other-types-per-pg_cast.patchDownload
From 4ec193ef249c61a71f0ecb7c7601e73399f9aaf9 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 10:28:54 +0800
Subject: [PATCH v12 06/20] error safe for casting inet to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'inet'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+-------------------+----------+-------------+------------+--------------+---------
inet | cidr | 1715 | a | f | inet_to_cidr | cidr
inet | text | 730 | a | f | network_show | text
inet | character varying | 730 | a | f | network_show | text
inet | character | 730 | a | f | network_show | text
(4 rows)
inet_to_cidr is already error safe.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/network.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/backend/utils/adt/network.c b/src/backend/utils/adt/network.c
index 3cb0ab6829a..648c8d95f51 100644
--- a/src/backend/utils/adt/network.c
+++ b/src/backend/utils/adt/network.c
@@ -1137,7 +1137,7 @@ network_show(PG_FUNCTION_ARGS)
if (pg_inet_net_ntop(ip_family(ip), ip_addr(ip), ip_maxbits(ip),
tmp, sizeof(tmp)) == NULL)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
errmsg("could not format inet value: %m")));
--
2.34.1
v12-0005-error-safe-for-casting-character-varying-to-other-types-per-pg_c.patchtext/x-patch; charset=US-ASCII; name=v12-0005-error-safe-for-casting-character-varying-to-other-types-per-pg_c.patchDownload
From 5a31cec8bce660f0f323a7842c27330cb4cb7d60 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:13:45 +0800
Subject: [PATCH v12 05/20] error safe for casting character varying to other
types per pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc and pc.castfunc > 0 and
(castsource::regtype = 'character varying'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
-------------------+-------------------+----------+-------------+------------+---------------+----------
character varying | regclass | 1079 | i | f | text_regclass | regclass
character varying | "char" | 944 | a | f | text_char | char
character varying | name | 1400 | i | f | text_name | name
character varying | xml | 2896 | e | f | texttoxml | xml
character varying | character varying | 669 | i | f | varchar | varchar
(5 rows)
texttoxml, text_regclass was refactored as error safe in prior patch.
text_char, text_name is already error safe.
so here we only need handle function "varchar".
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/varchar.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c
index 5cb5c8c46f9..08f1bf5a24d 100644
--- a/src/backend/utils/adt/varchar.c
+++ b/src/backend/utils/adt/varchar.c
@@ -634,7 +634,7 @@ varchar(PG_FUNCTION_ARGS)
{
for (i = maxmblen; i < len; i++)
if (s_data[i] != ' ')
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
errmsg("value too long for type character varying(%d)",
maxlen)));
--
2.34.1
v12-0003-error-safe-for-casting-character-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v12-0003-error-safe-for-casting-character-to-other-types-per-pg_cast.patchDownload
From d81dc3b37242eaa9e8e4a018343c15e4753c8e91 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Mon, 24 Nov 2025 12:52:16 +0800
Subject: [PATCH v12 03/20] error safe for casting character to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype ='character'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+-------------------+----------+-------------+------------+-------------+---------
character | text | 401 | i | f | rtrim1 | text
character | character varying | 401 | i | f | rtrim1 | text
character | "char" | 944 | a | f | text_char | char
character | name | 409 | i | f | bpchar_name | name
character | xml | 2896 | e | f | texttoxml | xml
character | character | 668 | i | f | bpchar | bpchar
(6 rows)
only texttoxml, bpchar(PG_FUNCTION_ARGS) need take care of error handling.
other functions already error safe.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/executor/execExprInterp.c | 2 +-
src/backend/utils/adt/varchar.c | 2 +-
src/backend/utils/adt/xml.c | 18 ++++++++++++------
src/include/utils/xml.h | 2 +-
4 files changed, 15 insertions(+), 9 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 0e1a74976f7..67f4e00eac4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -4542,7 +4542,7 @@ ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op)
*op->resvalue = PointerGetDatum(xmlparse(data,
xexpr->xmloption,
- preserve_whitespace));
+ preserve_whitespace, NULL));
*op->resnull = false;
}
break;
diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c
index 3f40c9da1a0..5cb5c8c46f9 100644
--- a/src/backend/utils/adt/varchar.c
+++ b/src/backend/utils/adt/varchar.c
@@ -307,7 +307,7 @@ bpchar(PG_FUNCTION_ARGS)
{
for (i = maxmblen; i < len; i++)
if (s[i] != ' ')
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
errmsg("value too long for type character(%d)",
maxlen)));
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 41e775570ec..9e8016456ce 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -659,7 +659,7 @@ texttoxml(PG_FUNCTION_ARGS)
{
text *data = PG_GETARG_TEXT_PP(0);
- PG_RETURN_XML_P(xmlparse(data, xmloption, true));
+ PG_RETURN_XML_P(xmlparse(data, xmloption, true, fcinfo->context));
}
@@ -1028,19 +1028,25 @@ xmlelement(XmlExpr *xexpr,
xmltype *
-xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace)
+xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, Node *escontext)
{
#ifdef USE_LIBXML
xmlDocPtr doc;
doc = xml_parse(data, xmloption_arg, preserve_whitespace,
- GetDatabaseEncoding(), NULL, NULL, NULL);
- xmlFreeDoc(doc);
+ GetDatabaseEncoding(), NULL, NULL, escontext);
+ if (doc)
+ xmlFreeDoc(doc);
+
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return NULL;
return (xmltype *) data;
#else
- NO_XML_SUPPORT();
- return NULL;
+ ereturn(escontext, NULL
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unsupported XML feature"),
+ errdetail("This functionality requires the server to be built with libxml support."));
#endif
}
diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h
index 732dac47bc4..b15168c430e 100644
--- a/src/include/utils/xml.h
+++ b/src/include/utils/xml.h
@@ -73,7 +73,7 @@ extern xmltype *xmlconcat(List *args);
extern xmltype *xmlelement(XmlExpr *xexpr,
const Datum *named_argvalue, const bool *named_argnull,
const Datum *argvalue, const bool *argnull);
-extern xmltype *xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace);
+extern xmltype *xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, Node *escontext);
extern xmltype *xmlpi(const char *target, text *arg, bool arg_is_null, bool *result_is_null);
extern xmltype *xmlroot(xmltype *data, text *version, int standalone);
extern bool xml_is_document(xmltype *arg);
--
2.34.1
v12-0002-error-safe-for-casting-bit-varbit-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v12-0002-error-safe-for-casting-bit-varbit-to-other-types-per-pg_cast.patchDownload
From c35febdcc1b540e80cd6b3d779498cbac7de0f99 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 10:33:08 +0800
Subject: [PATCH v12 02/20] error safe for casting bit/varbit to other types
per pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
where pc.castfunc > 0 and (castsource::regtype ='bit'::regtype or
castsource::regtype ='varbit'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
-------------+-------------+----------+-------------+------------+-----------+---------
bit | bigint | 2076 | e | f | bittoint8 | int8
bit | integer | 1684 | e | f | bittoint4 | int4
bit | bit | 1685 | i | f | bit | bit
bit varying | bit varying | 1687 | i | f | varbit | varbit
(4 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/varbit.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/backend/utils/adt/varbit.c b/src/backend/utils/adt/varbit.c
index 205a67dafc5..6e9b808e20a 100644
--- a/src/backend/utils/adt/varbit.c
+++ b/src/backend/utils/adt/varbit.c
@@ -401,7 +401,7 @@ bit(PG_FUNCTION_ARGS)
PG_RETURN_VARBIT_P(arg);
if (!isExplicit)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_STRING_DATA_LENGTH_MISMATCH),
errmsg("bit string length %d does not match type bit(%d)",
VARBITLEN(arg), len)));
@@ -752,7 +752,7 @@ varbit(PG_FUNCTION_ARGS)
PG_RETURN_VARBIT_P(arg);
if (!isExplicit)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
errmsg("bit string too long for type bit varying(%d)",
len)));
@@ -1591,7 +1591,7 @@ bittoint4(PG_FUNCTION_ARGS)
/* Check that the bit string is not too long */
if (VARBITLEN(arg) > sizeof(result) * BITS_PER_BYTE)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
@@ -1671,7 +1671,7 @@ bittoint8(PG_FUNCTION_ARGS)
/* Check that the bit string is not too long */
if (VARBITLEN(arg) > sizeof(result) * BITS_PER_BYTE)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("bigint out of range")));
--
2.34.1
v12-0004-error-safe-for-casting-character-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v12-0004-error-safe-for-casting-character-to-other-types-per-pg_cast.patchDownload
From 0120c7f31e3b37247ecdc9945fbba850856d626e Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Tue, 25 Nov 2025 20:07:28 +0800
Subject: [PATCH v12 04/20] error safe for casting character to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype ='character'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+-------------------+----------+-------------+------------+-------------+---------
character | text | 401 | i | f | rtrim1 | text
character | character varying | 401 | i | f | rtrim1 | text
character | "char" | 944 | a | f | text_char | char
character | name | 409 | i | f | bpchar_name | name
character | xml | 2896 | e | f | texttoxml | xml
character | character | 668 | i | f | bpchar | bpchar
(6 rows)
only texttoxml, bpchar(PG_FUNCTION_ARGS) need take care of error handling.
other functions already error safe.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/catalog/namespace.c | 58 ++++++++++++++++++++++++++-------
src/backend/utils/adt/regproc.c | 13 ++++++--
src/backend/utils/adt/varlena.c | 10 ++++--
src/backend/utils/adt/xml.c | 2 +-
src/include/catalog/namespace.h | 6 ++++
src/include/utils/varlena.h | 1 +
6 files changed, 73 insertions(+), 17 deletions(-)
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index d23474da4fb..bef2de5dd39 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -440,6 +440,16 @@ Oid
RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
uint32 flags,
RangeVarGetRelidCallback callback, void *callback_arg)
+{
+ return RangeVarGetRelidExtendedSafe(relation, lockmode, flags,
+ callback, callback_arg,
+ NULL);
+}
+
+Oid
+RangeVarGetRelidExtendedSafe(const RangeVar *relation, LOCKMODE lockmode, uint32 flags,
+ RangeVarGetRelidCallback callback, void *callback_arg,
+ Node *escontext)
{
uint64 inval_count;
Oid relId;
@@ -456,7 +466,7 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
if (relation->catalogname)
{
if (strcmp(relation->catalogname, get_database_name(MyDatabaseId)) != 0)
- ereport(ERROR,
+ ereturn(escontext, InvalidOid,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cross-database references are not implemented: \"%s.%s.%s\"",
relation->catalogname, relation->schemaname,
@@ -513,7 +523,7 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
* return InvalidOid.
*/
if (namespaceId != myTempNamespace)
- ereport(ERROR,
+ ereturn(escontext, InvalidOid,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("temporary tables cannot specify a schema name")));
}
@@ -593,13 +603,23 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
{
int elevel = (flags & RVR_SKIP_LOCKED) ? DEBUG1 : ERROR;
- if (relation->schemaname)
- ereport(elevel,
+ if (relation->schemaname && elevel == DEBUG1)
+ ereport(DEBUG1,
(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
errmsg("could not obtain lock on relation \"%s.%s\"",
relation->schemaname, relation->relname)));
- else
- ereport(elevel,
+ else if (relation->schemaname && elevel == ERROR)
+ ereturn(escontext, InvalidOid,
+ errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s.%s\"",
+ relation->schemaname, relation->relname));
+ else if (elevel == DEBUG1)
+ ereport(DEBUG1,
+ errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s\"",
+ relation->relname));
+ else if (elevel == ERROR)
+ ereturn(escontext, InvalidOid,
(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
errmsg("could not obtain lock on relation \"%s\"",
relation->relname)));
@@ -626,13 +646,23 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
{
int elevel = missing_ok ? DEBUG1 : ERROR;
- if (relation->schemaname)
- ereport(elevel,
+ if (relation->schemaname && elevel == DEBUG1)
+ ereport(DEBUG1,
(errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("relation \"%s.%s\" does not exist",
relation->schemaname, relation->relname)));
- else
- ereport(elevel,
+ else if (relation->schemaname && elevel == ERROR)
+ ereturn(escontext, InvalidOid,
+ errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s.%s\" does not exist",
+ relation->schemaname, relation->relname));
+ else if (elevel == DEBUG1)
+ ereport(DEBUG1,
+ errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" does not exist",
+ relation->relname));
+ else if (elevel == ERROR)
+ ereturn(escontext, InvalidOid,
(errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("relation \"%s\" does not exist",
relation->relname)));
@@ -3622,6 +3652,12 @@ get_namespace_oid(const char *nspname, bool missing_ok)
*/
RangeVar *
makeRangeVarFromNameList(const List *names)
+{
+ return makeRangeVarFromNameListSafe(names, NULL);
+}
+
+RangeVar *
+makeRangeVarFromNameListSafe(const List *names, Node *escontext)
{
RangeVar *rel = makeRangeVar(NULL, NULL, -1);
@@ -3640,7 +3676,7 @@ makeRangeVarFromNameList(const List *names)
rel->relname = strVal(lthird(names));
break;
default:
- ereport(ERROR,
+ ereturn(escontext, NULL,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("improper relation name (too many dotted names): %s",
NameListToString(names))));
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index e5c2246f2c9..59cc508f805 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -1901,12 +1901,19 @@ text_regclass(PG_FUNCTION_ARGS)
text *relname = PG_GETARG_TEXT_PP(0);
Oid result;
RangeVar *rv;
+ List *namelist;
- rv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
+ namelist = textToQualifiedNameListSafe(relname, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
+
+ rv = makeRangeVarFromNameListSafe(namelist, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
/* We might not even have permissions on this relation; don't lock it. */
- result = RangeVarGetRelid(rv, NoLock, false);
-
+ result = RangeVarGetRelidExtendedSafe(rv, NoLock, 0, NULL, NULL,
+ fcinfo->context);
PG_RETURN_OID(result);
}
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index 3894457ab40..f8becbcde51 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -2684,6 +2684,12 @@ name_text(PG_FUNCTION_ARGS)
*/
List *
textToQualifiedNameList(text *textval)
+{
+ return textToQualifiedNameListSafe(textval, NULL);
+}
+
+List *
+textToQualifiedNameListSafe(text *textval, Node *escontext)
{
char *rawname;
List *result = NIL;
@@ -2695,12 +2701,12 @@ textToQualifiedNameList(text *textval)
rawname = text_to_cstring(textval);
if (!SplitIdentifierString(rawname, '.', &namelist))
- ereport(ERROR,
+ ereturn(escontext, NIL,
(errcode(ERRCODE_INVALID_NAME),
errmsg("invalid name syntax")));
if (namelist == NIL)
- ereport(ERROR,
+ ereturn(escontext, NIL,
(errcode(ERRCODE_INVALID_NAME),
errmsg("invalid name syntax")));
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 9e8016456ce..8de1f1fc741 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -1043,7 +1043,7 @@ xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, Node
return (xmltype *) data;
#else
- ereturn(escontext, NULL
+ ereturn(escontext, NULL,
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("unsupported XML feature"),
errdetail("This functionality requires the server to be built with libxml support."));
diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h
index f1423f28c32..ab61af55ddc 100644
--- a/src/include/catalog/namespace.h
+++ b/src/include/catalog/namespace.h
@@ -103,6 +103,11 @@ extern Oid RangeVarGetRelidExtended(const RangeVar *relation,
LOCKMODE lockmode, uint32 flags,
RangeVarGetRelidCallback callback,
void *callback_arg);
+extern Oid RangeVarGetRelidExtendedSafe(const RangeVar *relation,
+ LOCKMODE lockmode, uint32 flags,
+ RangeVarGetRelidCallback callback,
+ void *callback_arg,
+ Node *escontext);
extern Oid RangeVarGetCreationNamespace(const RangeVar *newRelation);
extern Oid RangeVarGetAndCheckCreationNamespace(RangeVar *relation,
LOCKMODE lockmode,
@@ -168,6 +173,7 @@ extern Oid LookupCreationNamespace(const char *nspname);
extern void CheckSetNamespace(Oid oldNspOid, Oid nspOid);
extern Oid QualifiedNameGetCreationNamespace(const List *names, char **objname_p);
extern RangeVar *makeRangeVarFromNameList(const List *names);
+extern RangeVar *makeRangeVarFromNameListSafe(const List *names, Node *escontext);
extern char *NameListToString(const List *names);
extern char *NameListToQuotedString(const List *names);
diff --git a/src/include/utils/varlena.h b/src/include/utils/varlena.h
index db9fdf72941..0cf01ae5281 100644
--- a/src/include/utils/varlena.h
+++ b/src/include/utils/varlena.h
@@ -27,6 +27,7 @@ extern int varstr_levenshtein_less_equal(const char *source, int slen,
int ins_c, int del_c, int sub_c,
int max_d, bool trusted);
extern List *textToQualifiedNameList(text *textval);
+extern List *textToQualifiedNameListSafe(text *textval, Node *escontext);
extern bool SplitIdentifierString(char *rawstring, char separator,
List **namelist);
extern bool SplitDirectoriesString(char *rawstring, char separator,
--
2.34.1
v12-0020-CAST-expr-AS-newtype-DEFAULT-ON-ERROR.patchtext/x-patch; charset=UTF-8; name=v12-0020-CAST-expr-AS-newtype-DEFAULT-ON-ERROR.patchDownload
From 60228e5c831f170b122da208641061e5ae07b06b Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Tue, 25 Nov 2025 10:37:33 +0800
Subject: [PATCH v12 20/20] CAST(expr AS newtype DEFAULT ON ERROR)
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.
draft doc also added.
We cannot simply prohibit user-defined functions in pg_cast for safe cast
evaluation because CREATE CAST can also utilize built-in functions. So, to
completely disallow custom casts created via CREATE CAST used in safe cast
evaluation, a new field in pg_cast would unfortunately be necessary.
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}
[0]: https://git.postgresql.org/cgit/postgresql.git/commit/?id=aaaf9449ec6be62cb0d30ed3588dc384f56274b
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
.../pg_stat_statements/expected/select.out | 23 +-
contrib/pg_stat_statements/sql/select.sql | 5 +
doc/src/sgml/catalogs.sgml | 12 +
doc/src/sgml/syntax.sgml | 29 +
src/backend/catalog/pg_cast.c | 1 +
src/backend/executor/execExpr.c | 61 +-
src/backend/executor/execExprInterp.c | 29 +
src/backend/jit/llvm/llvmjit_expr.c | 26 +
src/backend/nodes/nodeFuncs.c | 61 ++
src/backend/optimizer/util/clauses.c | 26 +
src/backend/parser/gram.y | 22 +
src/backend/parser/parse_agg.c | 9 +
src/backend/parser/parse_expr.c | 424 +++++++++-
src/backend/parser/parse_func.c | 3 +
src/backend/parser/parse_target.c | 14 +
src/backend/utils/adt/arrayfuncs.c | 8 +
src/backend/utils/adt/ruleutils.c | 21 +
src/include/catalog/pg_cast.dat | 330 ++++----
src/include/catalog/pg_cast.h | 4 +
src/include/executor/execExpr.h | 7 +
src/include/nodes/execnodes.h | 21 +
src/include/nodes/parsenodes.h | 11 +
src/include/nodes/primnodes.h | 36 +
src/include/parser/parse_node.h | 2 +
src/test/regress/expected/cast.out | 791 ++++++++++++++++++
src/test/regress/expected/create_cast.out | 5 +
src/test/regress/expected/opr_sanity.out | 24 +-
src/test/regress/parallel_schedule | 2 +-
src/test/regress/sql/cast.sql | 340 ++++++++
src/test/regress/sql/create_cast.sql | 1 +
src/tools/pgindent/typedefs.list | 3 +
31 files changed, 2157 insertions(+), 194 deletions(-)
create mode 100644 src/test/regress/expected/cast.out
create mode 100644 src/test/regress/sql/cast.sql
diff --git a/contrib/pg_stat_statements/expected/select.out b/contrib/pg_stat_statements/expected/select.out
index 75c896f3885..6e67997f0a2 100644
--- a/contrib/pg_stat_statements/expected/select.out
+++ b/contrib/pg_stat_statements/expected/select.out
@@ -73,6 +73,25 @@ SELECT 1 AS "int" OFFSET 2 FETCH FIRST 3 ROW ONLY;
-----
(0 rows)
+--error safe type cast
+SELECT CAST('a' AS int DEFAULT 2 ON CONVERSION ERROR);
+ int4
+------
+ 2
+(1 row)
+
+SELECT CAST('11' AS int DEFAULT 2 ON CONVERSION ERROR);
+ int4
+------
+ 11
+(1 row)
+
+SELECT CAST('12' AS numeric DEFAULT 2 ON CONVERSION ERROR);
+ numeric
+---------
+ 12
+(1 row)
+
-- DISTINCT and ORDER BY patterns
-- Try some query permutations which once produced identical query IDs
SELECT DISTINCT 1 AS "int";
@@ -222,6 +241,8 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
2 | 2 | SELECT $1 AS "int" ORDER BY 1
1 | 2 | SELECT $1 AS i UNION SELECT $2 ORDER BY i
1 | 1 | SELECT $1 || $2
+ 2 | 2 | SELECT CAST($1 AS int DEFAULT $2 ON CONVERSION ERROR)
+ 1 | 1 | SELECT CAST($1 AS numeric DEFAULT $2 ON CONVERSION ERROR)
2 | 2 | SELECT DISTINCT $1 AS "int"
0 | 0 | SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"
1 | 1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
@@ -230,7 +251,7 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
| | ) +
| | SELECT f FROM t ORDER BY f
1 | 1 | select $1::jsonb ? $2
-(17 rows)
+(19 rows)
SELECT pg_stat_statements_reset() IS NOT NULL AS t;
t
diff --git a/contrib/pg_stat_statements/sql/select.sql b/contrib/pg_stat_statements/sql/select.sql
index 11662cde08c..7ee8160fd84 100644
--- a/contrib/pg_stat_statements/sql/select.sql
+++ b/contrib/pg_stat_statements/sql/select.sql
@@ -25,6 +25,11 @@ SELECT 1 AS "int" LIMIT 3 OFFSET 3;
SELECT 1 AS "int" OFFSET 1 FETCH FIRST 2 ROW ONLY;
SELECT 1 AS "int" OFFSET 2 FETCH FIRST 3 ROW ONLY;
+--error safe type cast
+SELECT CAST('a' AS int DEFAULT 2 ON CONVERSION ERROR);
+SELECT CAST('11' AS int DEFAULT 2 ON CONVERSION ERROR);
+SELECT CAST('12' AS numeric DEFAULT 2 ON CONVERSION ERROR);
+
-- DISTINCT and ORDER BY patterns
-- Try some query permutations which once produced identical query IDs
SELECT DISTINCT 1 AS "int";
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2fc63442980..ccd1d256003 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1849,6 +1849,18 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<literal>b</literal> means that the types are binary-coercible, thus no conversion is required.
</para></entry>
</row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>casterrorsafe</structfield> <type>bool</type>
+ </para>
+ <para>
+ This indicates whether the <structfield>castfunc</structfield> function is error safe.
+ If the <structfield>castfunc</structfield> function is error safe, it can be used in error safe type cast.
+ For further details see <xref linkend="sql-syntax-type-casts-safe"/>.
+ </para></entry>
+ </row>
+
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml
index 34c83880a66..9b2482e9b99 100644
--- a/doc/src/sgml/syntax.sgml
+++ b/doc/src/sgml/syntax.sgml
@@ -2106,6 +2106,10 @@ CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable>
The <literal>CAST</literal> syntax conforms to SQL; the syntax with
<literal>::</literal> is historical <productname>PostgreSQL</productname>
usage.
+ The equivalent ON CONVERSION ERROR behavior is:
+<synopsis>
+CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable> ERROR ON CONVERSION ERROR )
+</synopsis>
</para>
<para>
@@ -2160,6 +2164,31 @@ CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable>
<xref linkend="sql-createcast"/>.
</para>
</note>
+
+ <sect3 id="sql-syntax-type-casts-safe">
+ <title>Safe Type Cast</title>
+ <para>
+Sometimes a type cast may fail; to handle such cases, an <literal>ON ERROR</literal> clause can be
+specified to avoid potential errors. The syntax is:
+<synopsis>
+CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable> DEFAULT <replaceable>expression</replaceable> ON CONVERSION ERROR )
+</synopsis>
+ If cast the source expression to target type fails, it falls back to
+ evaluating the optionally supplied default <replaceable>expression</replaceable>
+ specified in ON <literal>ON ERROR</literal> clause.
+ Currently, this only support built-in system type casts,
+ casts created using <link linkend="sql-createcast">CREATE CAST</link> are not supported.
+ </para>
+
+ <para>
+ For example, the following query attempts to cast a <type>text</type> type value to <type>integer</type> type,
+ but when the conversion fails, it falls back to evaluate the supplied default expression.
+<programlisting>
+SELECT CAST(TEXT 'error' AS integer DEFAULT NULL ON CONVERSION ERROR) IS NULL; <lineannotation>true</lineannotation>
+</programlisting>
+ </para>
+ </sect3>
+
</sect2>
<sect2 id="sql-syntax-collate-exprs">
diff --git a/src/backend/catalog/pg_cast.c b/src/backend/catalog/pg_cast.c
index 1773c9c5491..6fe65d24d31 100644
--- a/src/backend/catalog/pg_cast.c
+++ b/src/backend/catalog/pg_cast.c
@@ -84,6 +84,7 @@ CastCreate(Oid sourcetypeid, Oid targettypeid,
values[Anum_pg_cast_castfunc - 1] = ObjectIdGetDatum(funcid);
values[Anum_pg_cast_castcontext - 1] = CharGetDatum(castcontext);
values[Anum_pg_cast_castmethod - 1] = CharGetDatum(castmethod);
+ values[Anum_pg_cast_casterrorsafe - 1] = BoolGetDatum(false);
tuple = heap_form_tuple(RelationGetDescr(relation), values, nulls);
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index b302be36f73..1de0d465f56 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,
@@ -1742,6 +1745,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
elemstate->innermost_caseval = (Datum *) palloc(sizeof(Datum));
elemstate->innermost_casenull = (bool *) palloc(sizeof(bool));
+ elemstate->escontext = state->escontext;
ExecInitExprRec(acoerce->elemexpr, elemstate,
&elemstate->resvalue, &elemstate->resnull);
@@ -2218,6 +2222,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;
@@ -2784,7 +2796,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;
@@ -4782,6 +4794,53 @@ ExecBuildParamSetEqual(TupleDesc desc,
return state;
}
+/*
+ * Push steps to evaluate a SafeTypeCastExpr and its various subsidiary
+ * expressions.
+ */
+static void
+ExecInitSafeTypeCastExpr(SafeTypeCastExpr *stcexpr , ExprState *state,
+ Datum *resv, bool *resnull,
+ ExprEvalStep *scratch)
+{
+ /*
+ * If we can not coerce to the target type, fallback to the DEFAULT
+ * expression specified in the ON CONVERSION ERROR clause, and we are done.
+ */
+ if (stcexpr->cast_expr == NULL)
+ {
+ ExecInitExprRec((Expr *) stcexpr->default_expr,
+ state, resv, resnull);
+ return;
+ }
+ else
+ {
+ SafeTypeCastState *stcstate;
+ ErrorSaveContext *saved_escontext;
+
+ stcstate = palloc0(sizeof(SafeTypeCastState));
+ stcstate->stcexpr = stcexpr;
+ stcstate->escontext.type = T_ErrorSaveContext;
+ state->escontext = &stcstate->escontext;
+
+ /* evaluate argument expression into step's result area */
+ ExecInitExprRec((Expr *) stcexpr->cast_expr,
+ state, resv, resnull);
+ scratch->opcode = EEOP_SAFETYPE_CAST;
+ scratch->d.stcexpr.stcstate = stcstate;
+ ExprEvalPushStep(state, scratch);
+
+ /* Steps to evaluate the DEFAULT expression */
+ saved_escontext = state->escontext;
+ state->escontext = NULL;
+ ExecInitExprRec((Expr *) stcstate->stcexpr->default_expr,
+ state, resv, resnull);
+ state->escontext = saved_escontext;
+
+ 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 67f4e00eac4..f1d8df1ac41 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,28 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
EEO_NEXT();
}
+ EEO_CASE(EEOP_SAFETYPE_CAST)
+ {
+ SafeTypeCastState *stcstate = op->d.stcexpr.stcstate;
+
+ if (SOFT_ERROR_OCCURRED(&stcstate->escontext))
+ {
+ *op->resvalue = (Datum) 0;
+ *op->resnull = true;
+
+ /*
+ * Reset for next use such as for catching errors when coercing
+ * a expression.
+ */
+ stcstate->escontext.error_occurred = false;
+ stcstate->escontext.details_wanted = false;
+
+ EEO_NEXT();
+ }
+ else
+ EEO_JUMP(stcstate->jump_end);
+ }
+
EEO_CASE(EEOP_JSONEXPR_PATH)
{
/* too complex for an inline implementation */
@@ -3644,6 +3667,12 @@ 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/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index ac88881e995..bc7cd3283a5 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -2256,6 +2256,32 @@ llvm_compile_expr(ExprState *state)
LLVMBuildBr(b, opblocks[opno + 1]);
break;
+ case EEOP_SAFETYPE_CAST:
+ {
+ SafeTypeCastState *stcstate = op->d.stcexpr.stcstate;
+
+ if (SOFT_ERROR_OCCURRED(&stcstate->escontext))
+ {
+ /*
+ * Reset for next use such as for catching errors when
+ * coercing a expression.
+ */
+ stcstate->escontext.error_occurred = false;
+ stcstate->escontext.details_wanted = false;
+
+ /* set resnull to true */
+ LLVMBuildStore(b, l_sbool_const(1), v_resnullp);
+ /* reset resvalue */
+ LLVMBuildStore(b, l_datum_const(0), v_resvaluep);
+
+ LLVMBuildBr(b, opblocks[opno + 1]);
+ }
+ else
+ 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 ede838cd40c..fe3e6b11a08 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;
@@ -1706,6 +1717,9 @@ exprLocation(const Node *expr)
loc = leftmostLoc(loc, tc->location);
}
break;
+ case T_SafeTypeCastExpr:
+ loc = ((const SafeTypeCastExpr *) expr)->location;
+ break;
case T_CollateClause:
/* just use argument's location */
loc = exprLocation(((const CollateClause *) expr)->arg);
@@ -2321,6 +2335,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:
@@ -3330,6 +3356,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;
@@ -4464,6 +4503,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->raw_default))
+ 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/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 32af0df6c70..b6a9dc2f5fd 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -2951,6 +2951,32 @@ 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);
+
+ /*
+ * We must not reduce any recognizably constant subexpressions
+ * in cast_expr here, since we don’t want it to fail
+ * prematurely.
+ */
+ 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 c3a0a354a9c..8a27e045bc0 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -16013,6 +16013,28 @@ func_expr_common_subexpr:
}
| CAST '(' a_expr AS Typename ')'
{ $$ = makeTypeCast($3, $5, @1); }
+ | CAST '(' a_expr AS Typename ERROR_P ON CONVERSION_P ERROR_P ')'
+ { $$ = makeTypeCast($3, $5, @1); }
+ | CAST '(' a_expr AS Typename NULL_P ON CONVERSION_P ERROR_P ')'
+ {
+ TypeCast *cast = (TypeCast *) makeTypeCast($3, $5, @1);
+
+ SafeTypeCast *safecast = makeNode(SafeTypeCast);
+ safecast->cast = (Node *) cast;
+ safecast->raw_default = makeNullAConst(-1);;
+
+ $$ = (Node *) safecast;
+ }
+ | CAST '(' a_expr AS Typename DEFAULT a_expr ON CONVERSION_P ERROR_P ')'
+ {
+ TypeCast *cast = (TypeCast *) makeTypeCast($3, $5, @1);
+
+ SafeTypeCast *safecast = makeNode(SafeTypeCast);
+ safecast->cast = (Node *) cast;
+ safecast->raw_default = $7;
+
+ $$ = (Node *) safecast;
+ }
| EXTRACT '(' extract_list ')'
{
$$ = (Node *) makeFuncCall(SystemFuncName("extract"),
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index b8340557b34..de77f6e9302 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -490,6 +490,12 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
err = _("grouping operations are not allowed in check constraints");
break;
+ case EXPR_KIND_CAST_DEFAULT:
+ if (isAgg)
+ err = _("aggregate functions are not allowed in CAST DEFAULT expressions");
+ else
+ err = _("grouping operations are not allowed in CAST DEFAULT expressions");
+ break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
@@ -983,6 +989,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_DOMAIN_CHECK:
err = _("window functions are not allowed in check constraints");
break;
+ case EXPR_KIND_CAST_DEFAULT:
+ err = _("window functions are not allowed in CAST DEFAULT expressions");
+ break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
err = _("window functions are not allowed in DEFAULT expressions");
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 32d6ae918ca..ec43fcd18fb 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -17,6 +17,8 @@
#include "access/htup_details.h"
#include "catalog/pg_aggregate.h"
+#include "catalog/pg_cast.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -37,6 +39,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 +63,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 *can_coerce);
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 +80,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);
@@ -164,13 +169,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;
@@ -564,6 +573,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
case EXPR_KIND_VALUES_SINGLE:
case EXPR_KIND_CHECK_CONSTRAINT:
case EXPR_KIND_DOMAIN_CHECK:
+ case EXPR_KIND_CAST_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
case EXPR_KIND_INDEX_EXPRESSION:
case EXPR_KIND_INDEX_PREDICATE:
@@ -1824,6 +1834,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_DOMAIN_CHECK:
err = _("cannot use subquery in check constraint");
break;
+ case EXPR_KIND_CAST_DEFAULT:
+ err = _("cannot use subquery in CAST DEFAULT expression");
+ break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
err = _("cannot use subquery in DEFAULT expression");
@@ -2011,16 +2024,23 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
* 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().
+ *
+ * Most of the time, can_coerce will be NULL.
+ * can_coerce is not NULL only when performing parse analysis for expressions
+ * like CAST(DEFAULT ... ON CONVERSION ERROR). can_coerce should be assumed to
+ * default to true. If coerce array elements to the target type fails,
+ * can_coerce will be set to 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;
List *newcoercedelems = NIL;
ListCell *element;
Oid coerce_type;
+ Oid coerce_type_coll;
bool coerce_hard;
/*
@@ -2045,9 +2065,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
@@ -2088,6 +2109,9 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
}
else
{
+ /* The target type is valid when performing type-safe cast */
+ Assert(can_coerce == NULL);
+
/* Can't handle an empty array without a target type */
if (newelems == NIL)
ereport(ERROR,
@@ -2125,6 +2149,8 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
coerce_hard = false;
}
+ coerce_type_coll = get_typcollation(coerce_type);
+
/*
* Coerce elements to target type
*
@@ -2134,13 +2160,53 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
* If the array's type was merely derived from the common type of its
* elements, then the elements are implicitly coerced to the common type.
* This is consistent with other uses of select_common_type().
+ *
+ * If can_coerce is not NULL, we need to safely test whether each element
+ * can be coerced to the target type, similar to what is done in
+ * transformTypeSafeCast.
*/
foreach(element, newelems)
{
Node *e = (Node *) lfirst(element);
- Node *newe;
+ bool expr_is_const = false;
+ Node *newe = NULL;
- if (coerce_hard)
+ if (can_coerce != NULL && IsA(e, Const))
+ {
+ expr_is_const = true;
+
+ /* coerce UNKNOWN Const to the target type */
+ if (*can_coerce && exprType(e) == UNKNOWNOID)
+ {
+ newe = CoerceUnknownConstSafe(pstate, e, coerce_type, typmod,
+ COERCION_IMPLICIT,
+ COERCE_IMPLICIT_CAST,
+ -1,
+ false);
+ if (newe == NULL)
+ {
+ newe = e;
+ *can_coerce = false;
+ }
+ newcoercedelems = lappend(newcoercedelems, newe);
+
+ /*
+ * Done handling this UNKNOWN Const element; move on to the
+ * next.
+ */
+ continue;
+ }
+ }
+
+ if (can_coerce != NULL && (!*can_coerce))
+ {
+ /*
+ * Can not coerce source expression to the target type, just append
+ * the transformed element expression to the list.
+ */
+ newe = e;
+ }
+ else if (coerce_hard)
{
newe = coerce_to_target_type(pstate, e,
exprType(e),
@@ -2150,12 +2216,74 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
COERCE_EXPLICIT_CAST,
-1);
if (newe == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_CANNOT_COERCE),
- errmsg("cannot cast type %s to %s",
- format_type_be(exprType(e)),
- format_type_be(coerce_type)),
- parser_errposition(pstate, exprLocation(e))));
+ {
+ if (can_coerce == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_CANNOT_COERCE),
+ errmsg("cannot cast type %s to %s",
+ format_type_be(exprType(e)),
+ format_type_be(coerce_type)),
+ parser_errposition(pstate, exprLocation(e))));
+ else
+ {
+ /*
+ * Can not coerce source expression to the target type, just
+ * append the transformed element expression to the list.
+ */
+ newe = e;
+ *can_coerce = false;
+ }
+ }
+ else if (can_coerce != NULL && expr_is_const)
+ {
+ Node *origexpr = newe;
+ Node *result;
+ bool have_collate_expr = false;
+
+ if (IsA(newe, CollateExpr))
+ have_collate_expr = true;
+
+ while (newe && IsA(newe, CollateExpr))
+ newe = (Node *) ((CollateExpr *) newe)->arg;
+
+ if (!IsA(newe, FuncExpr))
+ newe = origexpr;
+ else
+ {
+ /*
+ * pre-evaluate simple constant cast expressions in a way
+ * that tolerate errors.
+ */
+ FuncExpr *newexpr = (FuncExpr *) copyObject(newe);
+
+ assign_expr_collations(pstate, (Node *) newexpr);
+
+ result = (Node *) evaluate_expr_extended((Expr *) newexpr,
+ coerce_type,
+ typmod,
+ coerce_type_coll,
+ true);
+
+ if (result == NULL)
+ {
+ newe = e;
+ *can_coerce = false;
+ }
+ else if (have_collate_expr)
+ {
+ /* Reinstall top CollateExpr */
+ CollateExpr *coll = (CollateExpr *) origexpr;
+ CollateExpr *newcoll = makeNode(CollateExpr);
+
+ newcoll->arg = (Expr *) result;
+ newcoll->collOid = coll->collOid;
+ newcoll->location = coll->location;
+ newe = (Node *) newcoll;
+ }
+ else
+ newe = result;
+ }
+ }
}
else
newe = coerce_to_common_type(pstate, e,
@@ -2743,7 +2871,8 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
(A_ArrayExpr *) arg,
targetBaseType,
elementType,
- targetBaseTypmod);
+ targetBaseTypmod,
+ NULL);
}
else
expr = transformExprRecurse(pstate, arg);
@@ -2780,6 +2909,271 @@ 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;
+ TypeCast *tcast = (TypeCast *) tc->cast;
+ Node *source_expr;
+ Node *def_expr;
+ Node *cast_expr = NULL;
+ Oid inputType;
+ Oid targetType;
+ Oid targetBaseType;
+ Oid typeColl;
+ int32 targetTypmod;
+ int32 targetBaseTypmod;
+ bool can_coerce = true;
+ bool expr_is_const = false;
+ ParseLoc location;
+
+ /* Look up the type name first */
+ typenameTypeIdAndMod(pstate, tcast->typeName, &targetType, &targetTypmod);
+ targetBaseTypmod = targetTypmod;
+
+ typeColl = get_typcollation(targetType);
+
+ targetBaseType = getBaseTypeAndTypmod(targetType, &targetBaseTypmod);
+
+ /* now looking at DEFAULT expression */
+ def_expr = transformExpr(pstate, tc->raw_default, EXPR_KIND_CAST_DEFAULT);
+
+ 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 coerce %s expression to type %s",
+ "CAST DEFAULT",
+ format_type_be(targetType)),
+ parser_coercion_errposition(pstate, exprLocation(tc->raw_default), def_expr));
+
+ assign_expr_collations(pstate, def_expr);
+
+ /*
+ * The collation of DEFAULT expression must match the collation of the
+ * target type.
+ */
+ if (OidIsValid(typeColl))
+ {
+ Oid defColl;
+
+ defColl = exprCollation(def_expr);
+
+ if (OidIsValid(defColl) && typeColl != defColl)
+ ereport(ERROR,
+ errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("the collation of CAST DEFAULT expression conflicts with target type collation"),
+ errdetail("\"%s\" versus \"%s\"",
+ get_collation_name(typeColl),
+ get_collation_name(defColl)),
+ parser_errposition(pstate, exprLocation(def_expr)));
+ }
+
+ /*
+ * If the type cast 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(tcast->arg, A_ArrayExpr))
+ {
+ 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.
+ */
+ elementType = get_element_type(targetBaseType);
+
+ if (OidIsValid(elementType))
+ {
+ source_expr = transformArrayExpr(pstate,
+ (A_ArrayExpr *) tcast->arg,
+ targetBaseType,
+ elementType,
+ targetBaseTypmod,
+ &can_coerce);
+ }
+ else
+ source_expr = transformExprRecurse(pstate, tcast->arg);
+ }
+ else
+ source_expr = transformExprRecurse(pstate, tcast->arg);
+
+ inputType = exprType(source_expr);
+ if (inputType == InvalidOid)
+ return (Node *) NULL; /* return NULL if NULL input */
+
+ /*
+ * Location of the coercion is preferentially the location of 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 && IsA(source_expr, Const))
+ {
+ expr_is_const = true;
+
+ /* coerce UNKNOWN Const to the target type */
+ if (exprType(source_expr) == UNKNOWNOID)
+ {
+ cast_expr = CoerceUnknownConstSafe(pstate, source_expr,
+ targetType, targetTypmod,
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ location, false);
+ if (cast_expr == NULL)
+ can_coerce = false;
+ }
+ }
+
+ if (can_coerce && cast_expr == NULL)
+ {
+ cast_expr = coerce_to_target_type(pstate, source_expr, inputType,
+ targetType, targetTypmod,
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ location);
+ if (cast_expr == NULL)
+ can_coerce = false;
+ }
+
+ if (can_coerce)
+ {
+ Node *origexpr = cast_expr;
+ bool have_collate_expr = false;
+
+ Assert(cast_expr != NULL);
+
+ if (IsA(cast_expr, CollateExpr))
+ have_collate_expr = true;
+
+ while (cast_expr && IsA(cast_expr, CollateExpr))
+ cast_expr = (Node *) ((CollateExpr *) cast_expr)->arg;
+
+ if (IsA(cast_expr, FuncExpr))
+ {
+ HeapTuple tuple;
+ ListCell *lc;
+ Node *sexpr;
+ Node *result;
+ FuncExpr *fexpr = (FuncExpr *) cast_expr;
+
+ lc = list_head(fexpr->args);
+ sexpr = (Node *) lfirst(lc);
+
+ /* Look in pg_cast */
+ tuple = SearchSysCache2(CASTSOURCETARGET,
+ ObjectIdGetDatum(exprType(sexpr)),
+ ObjectIdGetDatum(targetType));
+
+ if (HeapTupleIsValid(tuple))
+ {
+ Form_pg_cast castForm = (Form_pg_cast) GETSTRUCT(tuple);
+ if (!castForm->casterrorsafe)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot cast type %s to %s when %s expression is specified in %s",
+ format_type_be(inputType),
+ format_type_be(targetType),
+ "DEFAULT",
+ "CAST ... ON CONVERSION ERROR"),
+ errhint("Explicit cast is defined but definition is not declared as safe"),
+ parser_errposition(pstate, exprLocation(source_expr)));
+ }
+ else
+ elog(ERROR, "cache lookup failed for pg_cast entry (%s cast to %s)",
+ format_type_be(inputType),
+ format_type_be(targetType));
+ ReleaseSysCache(tuple);
+
+ if (!expr_is_const)
+ cast_expr = origexpr;
+ else
+ {
+ /*
+ * pre-evaluate simple constant cast expressions early in a way
+ * that tolerate errors.
+ *
+ * Rationale:
+ * 1. When deparsing safe cast expressions (or in other cases),
+ * eval_const_expressions might be invoked, but it cannot
+ * handle errors gracefully.
+ * 2. If the cast expression involves only simple constants, we
+ * can safely evaluate it ahead of time. If the evaluation
+ * fails, it indicates that such a cast is not possible, and
+ * we can then fall back to the CAST DEFAULT expression.
+ * 3. Even if FuncExpr (for castfunc) node has three arguments,
+ * the second and third arguments will also be constants per
+ * coerce_to_target_type. Evaluating a FuncExpr whose
+ * arguments are all Const is safe.
+ */
+ FuncExpr *newexpr = (FuncExpr *) copyObject(cast_expr);
+
+ assign_expr_collations(pstate, (Node *) newexpr);
+
+ result = (Node *) evaluate_expr_extended((Expr *) newexpr,
+ targetType,
+ targetTypmod,
+ typeColl,
+ true);
+ if (result == NULL)
+ {
+ /*
+ * source expression can not coerce to the target type.
+ */
+ can_coerce = false;
+ cast_expr = NULL;
+ }
+ else if (have_collate_expr)
+ {
+ /* Reinstall top CollateExpr */
+ CollateExpr *coll = (CollateExpr *) origexpr;
+ CollateExpr *newcoll = makeNode(CollateExpr);
+
+ newcoll->arg = (Expr *) result;
+ newcoll->collOid = coll->collOid;
+ newcoll->location = coll->location;
+ cast_expr = (Node *) newcoll;
+ }
+ else
+ cast_expr = result;
+ }
+ }
+ else
+ /* Nothing to do, restore cast_expr to its original value */
+ cast_expr = origexpr;
+ }
+
+ Assert(can_coerce || cast_expr == NULL);
+
+ result = makeNode(SafeTypeCastExpr);
+ result->source_expr = source_expr;
+ result->cast_expr = cast_expr;
+ result->default_expr = def_expr;
+ result->resulttype = targetType;
+ result->resulttypmod = targetTypmod;
+ result->resultcollid = typeColl;
+ result->location = location;
+
+ return (Node *) result;
+}
+
/*
* Handle an explicit COLLATE clause.
*
@@ -3193,6 +3587,8 @@ ParseExprKindName(ParseExprKind exprKind)
case EXPR_KIND_CHECK_CONSTRAINT:
case EXPR_KIND_DOMAIN_CHECK:
return "CHECK";
+ case EXPR_KIND_CAST_DEFAULT:
+ return "CAST DEFAULT";
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
return "DEFAULT";
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 778d69c6f3c..a90705b9847 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2743,6 +2743,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
case EXPR_KIND_DOMAIN_CHECK:
err = _("set-returning functions are not allowed in check constraints");
break;
+ case EXPR_KIND_CAST_DEFAULT:
+ err = _("set-returning functions are not allowed in CAST DEFAULT expressions");
+ break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
err = _("set-returning functions are not allowed in DEFAULT expressions");
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 905c975d83b..dc03cf4ce74 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1822,6 +1822,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/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index a464349ee33..c1a7031a96c 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -3289,6 +3289,14 @@ array_map(Datum arrayd,
/* Apply the given expression to source element */
values[i] = ExecEvalExpr(exprstate, econtext, &nulls[i]);
+ /* Exit early if the evaluation fails */
+ 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 556ab057e5a..3e41c7b8188 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -10562,6 +10562,27 @@ get_rule_expr(Node *node, deparse_context *context,
}
break;
+ case T_SafeTypeCastExpr:
+ {
+ SafeTypeCastExpr *stcexpr = castNode(SafeTypeCastExpr, node);
+
+ /*
+ * We cannot deparsing cast_expr directly, since
+ * transformTypeSafeCast may have folded it into a simple
+ * constant or NULL. Instead, we use source_expr and
+ * default_expr to reconstruct the CAST DEFAULT clause.
+ */
+ 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/include/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat
index fbfd669587f..ca52cfcd086 100644
--- a/src/include/catalog/pg_cast.dat
+++ b/src/include/catalog/pg_cast.dat
@@ -19,65 +19,65 @@
# int2->int4->int8->numeric->float4->float8, while casts in the
# reverse direction are assignment-only.
{ castsource => 'int8', casttarget => 'int2', castfunc => 'int2(int8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'int4', castfunc => 'int4(int8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'float4', castfunc => 'float4(int8)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'float8', castfunc => 'float8(int8)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'numeric', castfunc => 'numeric(int8)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'int8', castfunc => 'int8(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'int4', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'float4', castfunc => 'float4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'float8', castfunc => 'float8(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'numeric', castfunc => 'numeric(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'int8', castfunc => 'int8(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'int2', castfunc => 'int2(int4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'float4', castfunc => 'float4(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'float8', castfunc => 'float8(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'numeric', castfunc => 'numeric(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'int8', castfunc => 'int8(float4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'int2', castfunc => 'int2(float4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'int4', castfunc => 'int4(float4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'float8', castfunc => 'float8(float4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'numeric',
- castfunc => 'numeric(float4)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'numeric(float4)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'int8', castfunc => 'int8(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'int2', castfunc => 'int2(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'int4', castfunc => 'int4(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'float4', castfunc => 'float4(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'numeric',
- castfunc => 'numeric(float8)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'numeric(float8)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'int8', castfunc => 'int8(numeric)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'int2', castfunc => 'int2(numeric)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'int4', castfunc => 'int4(numeric)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'float4',
- castfunc => 'float4(numeric)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'float4(numeric)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'float8',
- castfunc => 'float8(numeric)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'float8(numeric)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'money', casttarget => 'numeric', castfunc => 'numeric(money)',
castcontext => 'a', castmethod => 'f' },
{ castsource => 'numeric', casttarget => 'money', castfunc => 'money(numeric)',
@@ -89,13 +89,13 @@
# Allow explicit coercions between int4 and bool
{ castsource => 'int4', casttarget => 'bool', castfunc => 'bool(int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'int4', castfunc => 'int4(bool)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Allow explicit coercions between xid8 and xid
{ castsource => 'xid8', casttarget => 'xid', castfunc => 'xid(xid8)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# OID category: allow implicit conversion from any integral type (including
# int8, to support OID literals > 2G) to OID, as well as assignment coercion
@@ -106,13 +106,13 @@
# casts from text and varchar to regclass, which exist mainly to support
# legacy forms of nextval() and related functions.
{ castsource => 'int8', casttarget => 'oid', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'oid', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'oid', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regproc', castfunc => '0',
@@ -120,13 +120,13 @@
{ castsource => 'regproc', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regproc', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regproc', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regproc', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regproc', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regproc', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'regproc', casttarget => 'regprocedure', castfunc => '0',
@@ -138,13 +138,13 @@
{ castsource => 'regprocedure', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regprocedure', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regprocedure', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regprocedure', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regprocedure', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regprocedure', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regoper', castfunc => '0',
@@ -152,13 +152,13 @@
{ castsource => 'regoper', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regoper', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regoper', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regoper', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regoper', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regoper', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'regoper', casttarget => 'regoperator', castfunc => '0',
@@ -170,13 +170,13 @@
{ castsource => 'regoperator', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regoperator', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regoperator', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regoperator', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regoperator', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regoperator', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regclass', castfunc => '0',
@@ -184,13 +184,13 @@
{ castsource => 'regclass', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regclass', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regclass', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regclass', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regclass', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regclass', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regcollation', castfunc => '0',
@@ -198,13 +198,13 @@
{ castsource => 'regcollation', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regcollation', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regcollation', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regcollation', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regcollation', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regcollation', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regtype', castfunc => '0',
@@ -212,13 +212,13 @@
{ castsource => 'regtype', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regtype', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regtype', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regtype', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regtype', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regtype', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regconfig', castfunc => '0',
@@ -226,13 +226,13 @@
{ castsource => 'regconfig', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regconfig', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regconfig', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regconfig', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regconfig', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regconfig', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regdictionary', castfunc => '0',
@@ -240,31 +240,31 @@
{ castsource => 'regdictionary', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regdictionary', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regdictionary', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regdictionary', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regdictionary', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regdictionary', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'text', casttarget => 'regclass', castfunc => 'regclass',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'regclass', castfunc => 'regclass',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'oid', casttarget => 'regrole', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regrole', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regrole', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regrole', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regrole', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regrole', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regrole', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regnamespace', castfunc => '0',
@@ -272,13 +272,13 @@
{ castsource => 'regnamespace', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regnamespace', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regnamespace', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regnamespace', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regnamespace', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regnamespace', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regdatabase', castfunc => '0',
@@ -286,13 +286,13 @@
{ castsource => 'regdatabase', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regdatabase', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regdatabase', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regdatabase', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regdatabase', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regdatabase', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
@@ -302,57 +302,57 @@
{ castsource => 'text', casttarget => 'varchar', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'bpchar', casttarget => 'text', castfunc => 'text(bpchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bpchar', casttarget => 'varchar', castfunc => 'text(bpchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'text', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'varchar', casttarget => 'bpchar', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'char', casttarget => 'text', castfunc => 'text(char)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'char', casttarget => 'bpchar', castfunc => 'bpchar(char)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'char', casttarget => 'varchar', castfunc => 'text(char)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'name', casttarget => 'text', castfunc => 'text(name)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'name', casttarget => 'bpchar', castfunc => 'bpchar(name)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'name', casttarget => 'varchar', castfunc => 'varchar(name)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'text', casttarget => 'char', castfunc => 'char(text)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bpchar', casttarget => 'char', castfunc => 'char(text)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'char', castfunc => 'char(text)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'text', casttarget => 'name', castfunc => 'name(text)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bpchar', casttarget => 'name', castfunc => 'name(bpchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'name', castfunc => 'name(varchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
# Allow explicit coercions between bytea and integer types
{ castsource => 'int2', casttarget => 'bytea', castfunc => 'bytea(int2)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'bytea', castfunc => 'bytea(int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'bytea', castfunc => 'bytea(int8)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bytea', casttarget => 'int2', castfunc => 'int2(bytea)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bytea', casttarget => 'int4', castfunc => 'int4(bytea)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bytea', casttarget => 'int8', castfunc => 'int8(bytea)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Allow explicit coercions between int4 and "char"
{ castsource => 'char', casttarget => 'int4', castfunc => 'int4(char)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'char', castfunc => 'char(int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# pg_node_tree can be coerced to, but not from, text
{ castsource => 'pg_node_tree', casttarget => 'text', castfunc => '0',
@@ -378,73 +378,73 @@
# Datetime category
{ castsource => 'date', casttarget => 'timestamp',
- castfunc => 'timestamp(date)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timestamp(date)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'date', casttarget => 'timestamptz',
- castfunc => 'timestamptz(date)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timestamptz(date)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'time', casttarget => 'interval', castfunc => 'interval(time)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'time', casttarget => 'timetz', castfunc => 'timetz(time)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'date',
- castfunc => 'date(timestamp)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'date(timestamp)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'time',
- castfunc => 'time(timestamp)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'time(timestamp)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'timestamptz',
- castfunc => 'timestamptz(timestamp)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timestamptz(timestamp)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'date',
- castfunc => 'date(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'date(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'time',
- castfunc => 'time(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'time(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'timestamp',
- castfunc => 'timestamp(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'timestamp(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'timetz',
- castfunc => 'timetz(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'timetz(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'interval', casttarget => 'time', castfunc => 'time(interval)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timetz', casttarget => 'time', castfunc => 'time(timetz)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
# Geometric category
{ castsource => 'point', casttarget => 'box', castfunc => 'box(point)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'lseg', casttarget => 'point', castfunc => 'point(lseg)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'path', casttarget => 'polygon', castfunc => 'polygon(path)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'box', casttarget => 'point', castfunc => 'point(box)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'box', casttarget => 'lseg', castfunc => 'lseg(box)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'box', casttarget => 'polygon', castfunc => 'polygon(box)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'box', casttarget => 'circle', castfunc => 'circle(box)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'polygon', casttarget => 'point', castfunc => 'point(polygon)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'polygon', casttarget => 'path', castfunc => 'path',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'polygon', casttarget => 'box', castfunc => 'box(polygon)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'polygon', casttarget => 'circle',
- castfunc => 'circle(polygon)', castcontext => 'e', castmethod => 'f' },
+ castfunc => 'circle(polygon)', castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'circle', casttarget => 'point', castfunc => 'point(circle)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'circle', casttarget => 'box', castfunc => 'box(circle)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'circle', casttarget => 'polygon',
- castfunc => 'polygon(circle)', castcontext => 'e', castmethod => 'f' },
+ castfunc => 'polygon(circle)', castcontext => 'e', castmethod => 'f'},
# MAC address category
{ castsource => 'macaddr', casttarget => 'macaddr8', castfunc => 'macaddr8',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'macaddr8', casttarget => 'macaddr', castfunc => 'macaddr',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
# INET category
{ castsource => 'cidr', casttarget => 'inet', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'inet', casttarget => 'cidr', castfunc => 'cidr',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
# BitString category
{ castsource => 'bit', casttarget => 'varbit', castfunc => '0',
@@ -454,13 +454,13 @@
# Cross-category casts between bit and int4, int8
{ castsource => 'int8', casttarget => 'bit', castfunc => 'bit(int8,int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'bit', castfunc => 'bit(int4,int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bit', casttarget => 'int8', castfunc => 'int8(bit)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bit', casttarget => 'int4', castfunc => 'int4(bit)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Cross-category casts to and from TEXT
# We need entries here only for a few specialized cases where the behavior
@@ -471,68 +471,68 @@
# behavior will ensue when the automatic cast is applied instead of the
# pg_cast entry!
{ castsource => 'cidr', casttarget => 'text', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'inet', casttarget => 'text', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'text', castfunc => 'text(bool)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'xml', casttarget => 'text', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'text', casttarget => 'xml', castfunc => 'xml',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Cross-category casts to and from VARCHAR
# We support all the same casts as for TEXT.
{ castsource => 'cidr', casttarget => 'varchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'inet', casttarget => 'varchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'varchar', castfunc => 'text(bool)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'xml', casttarget => 'varchar', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'varchar', casttarget => 'xml', castfunc => 'xml',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Cross-category casts to and from BPCHAR
# We support all the same casts as for TEXT.
{ castsource => 'cidr', casttarget => 'bpchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'inet', casttarget => 'bpchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'bpchar', castfunc => 'text(bool)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'xml', casttarget => 'bpchar', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'bpchar', casttarget => 'xml', castfunc => 'xml',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Length-coercion functions
{ castsource => 'bpchar', casttarget => 'bpchar',
castfunc => 'bpchar(bpchar,int4,bool)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'varchar',
castfunc => 'varchar(varchar,int4,bool)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'time', casttarget => 'time', castfunc => 'time(time,int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'timestamp',
castfunc => 'timestamp(timestamp,int4)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'timestamptz',
castfunc => 'timestamptz(timestamptz,int4)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'interval', casttarget => 'interval',
castfunc => 'interval(interval,int4)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timetz', casttarget => 'timetz',
- castfunc => 'timetz(timetz,int4)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timetz(timetz,int4)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bit', casttarget => 'bit', castfunc => 'bit(bit,int4,bool)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varbit', casttarget => 'varbit', castfunc => 'varbit',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'numeric',
- castfunc => 'numeric(numeric,int4)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'numeric(numeric,int4)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
# json to/from jsonb
{ castsource => 'json', casttarget => 'jsonb', castfunc => '0',
@@ -542,36 +542,36 @@
# jsonb to numeric and bool types
{ castsource => 'jsonb', casttarget => 'bool', castfunc => 'bool(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'numeric', castfunc => 'numeric(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'int2', castfunc => 'int2(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'int4', castfunc => 'int4(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'int8', castfunc => 'int8(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'float4', castfunc => 'float4(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'float8', castfunc => 'float8(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# range to multirange
{ castsource => 'int4range', casttarget => 'int4multirange',
castfunc => 'int4multirange(int4range)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8range', casttarget => 'int8multirange',
castfunc => 'int8multirange(int8range)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numrange', casttarget => 'nummultirange',
castfunc => 'nummultirange(numrange)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'daterange', casttarget => 'datemultirange',
castfunc => 'datemultirange(daterange)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'tsrange', casttarget => 'tsmultirange',
- castfunc => 'tsmultirange(tsrange)', castcontext => 'e', castmethod => 'f' },
+ castfunc => 'tsmultirange(tsrange)', castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'tstzrange', casttarget => 'tstzmultirange',
castfunc => 'tstzmultirange(tstzrange)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
]
diff --git a/src/include/catalog/pg_cast.h b/src/include/catalog/pg_cast.h
index 6a0ca337153..218d81d535a 100644
--- a/src/include/catalog/pg_cast.h
+++ b/src/include/catalog/pg_cast.h
@@ -47,6 +47,10 @@ CATALOG(pg_cast,2605,CastRelationId)
/* cast method */
char castmethod;
+
+ /* cast function error safe */
+ bool casterrorsafe BKI_DEFAULT(f);
+
} FormData_pg_cast;
/* ----------------
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 75366203706..2f8d163a13e 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_SAFETYPE_CAST */
+ struct
+ {
+ struct SafeTypeCastState *stcstate; /* original expression node */
+ } stcexpr;
+
/* for EEOP_JSONEXPR_PATH */
struct
{
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 18ae8f0d4bb..9bd4cecad63 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1059,6 +1059,27 @@ typedef struct DomainConstraintState
ExprState *check_exprstate; /* check_expr's eval state, or NULL */
} DomainConstraintState;
+typedef struct SafeTypeCastState
+{
+ SafeTypeCastExpr *stcexpr;
+
+ /*
+ * Address to jump to when skipping all the steps to evaulate the default
+ * expression.
+ */
+ int jump_end;
+
+ /*
+ * For error-safe evaluation of coercions. 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 d14294a4ece..4b2eb340f97 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -400,6 +400,17 @@ typedef struct TypeCast
ParseLoc location; /* token location, or -1 if unknown */
} TypeCast;
+/*
+ * SafeTypeCast - a CAST(source_expr AS target_type) DEFAULT ON CONVERSION ERROR
+ * construct
+ */
+typedef struct SafeTypeCast
+{
+ NodeTag type;
+ Node *cast; /* TypeCast expression */
+ Node *raw_default; /* DEFAULT expression */
+} SafeTypeCast;
+
/*
* CollateClause - a COLLATE expression
*/
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 1b4436f2ff6..c3bd722cb2f 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -769,6 +769,42 @@ 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
+{
+ Expr xpr;
+
+ /* transformed expression being casted */
+ Node *source_expr;
+
+ /*
+ * The transformed cast expression; NULL if we can not cocerce source
+ * expression to the target type
+ */
+ Node *cast_expr pg_node_attr(query_jumble_ignore);
+
+ /* Fall back to the default expression if cast evaluation fails */
+ Node *default_expr;
+
+ /* cast result data type */
+ Oid resulttype;
+
+ /* cast result data type typmod (usually -1) */
+ int32 resulttypmod;
+
+ /* cast result data type collation */
+ Oid resultcollid;
+
+ /* Original SafeTypeCastExpr's location */
+ ParseLoc location;
+
+} SafeTypeCastExpr;
+
/*
* FuncExpr - expression node for a function call
*
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index f7d07c84542..9f5b32e0360 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -67,6 +67,8 @@ typedef enum ParseExprKind
EXPR_KIND_VALUES_SINGLE, /* single-row VALUES (in INSERT only) */
EXPR_KIND_CHECK_CONSTRAINT, /* CHECK constraint for a table */
EXPR_KIND_DOMAIN_CHECK, /* CHECK constraint for a domain */
+ EXPR_KIND_CAST_DEFAULT, /* default expression in
+ CAST DEFAULT ON CONVERSION ERROR */
EXPR_KIND_COLUMN_DEFAULT, /* default value for a table column */
EXPR_KIND_FUNCTION_DEFAULT, /* default parameter value for function */
EXPR_KIND_INDEX_EXPRESSION, /* index expression */
diff --git a/src/test/regress/expected/cast.out b/src/test/regress/expected/cast.out
new file mode 100644
index 00000000000..cb40ff0f200
--- /dev/null
+++ b/src/test/regress/expected/cast.out
@@ -0,0 +1,791 @@
+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(B'01' AS date DEFAULT NULL ON CONVERSION ERROR);
+ date
+------
+
+(1 row)
+
+SELECT CAST(BIT'01' AS date DEFAULT NULL ON CONVERSION ERROR);
+ date
+------
+
+(1 row)
+
+SELECT CAST(TRUE AS date DEFAULT NULL ON CONVERSION ERROR);
+ date
+------
+
+(1 row)
+
+SELECT CAST(1.1 AS date DEFAULT NULL ON CONVERSION ERROR);
+ date
+------
+
+(1 row)
+
+SELECT CAST(1 AS date DEFAULT NULL ON CONVERSION ERROR);
+ date
+------
+
+(1 row)
+
+SELECT CAST(1::numeric AS money DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type numeric to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(1::numeric AS money DEFAULT NULL ON CONVERSION E...
+ ^
+HINT: Explicit cast is defined but definition is not declared as safe
+SELECT CAST(1111 AS "char" DEFAULT NULL ON CONVERSION ERROR);
+ char
+------
+
+(1 row)
+
+--the default expression’s collation should match the collation of the cast
+--target type
+VALUES (CAST('error' AS text DEFAULT '1' COLLATE "C" ON CONVERSION ERROR));
+ERROR: the collation of CAST DEFAULT expression conflicts with target type collation
+LINE 1: VALUES (CAST('error' AS text DEFAULT '1' COLLATE "C" ON CONV...
+ ^
+DETAIL: "default" versus "C"
+VALUES (CAST('error' AS int2vector DEFAULT '1 3' ON CONVERSION ERROR));
+ column1
+---------
+ 1 3
+(1 row)
+
+VALUES (CAST('error' AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR));
+ column1
+---------
+ {"1 3"}
+(1 row)
+
+-- test source expression contain subquery
+SELECT CAST((SELECT b FROM generate_series(1, 1), (VALUES('H')) s(b))
+ AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR);
+ b
+---------
+ {"1 3"}
+(1 row)
+
+SELECT CAST((SELECT ARRAY((SELECT b FROM generate_series(1, 2) g, (VALUES('H')) s(b))))
+ AS INT[]
+ DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+CREATE FUNCTION ret_int8() RETURNS BIGINT AS
+$$
+BEGIN RETURN 2147483648; END;
+$$
+LANGUAGE plpgsql IMMUTABLE;
+SELECT CAST('a' as int DEFAULT ret_int8() ON CONVERSION ERROR); --error
+ERROR: integer out of range
+SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERROR); --error
+ERROR: cannot coerce CAST DEFAULT expression to type date
+LINE 1: SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERR...
+ ^
+-- test array coerce
+SELECT CAST(array['a'::text] AS int[] DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR: invalid input syntax for type integer: "a"
+SELECT CAST(array['a'] AS int[] DEFAULT NULL ON CONVERSION ERROR); --ok
+ array
+-------
+
+(1 row)
+
+SELECT CAST(array[11.12] AS date[] DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+SELECT CAST(array[11111111111111111] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+SELECT CAST(array[['abc'],[456]] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+SELECT CAST('{123,abc,456}' AS int[] DEFAULT '{-789}' ON CONVERSION ERROR);
+ int4
+--------
+ {-789}
+(1 row)
+
+SELECT CAST('{234,def,567}'::text[] AS integer[] DEFAULT '{-1011}' ON CONVERSION ERROR);
+ int4
+---------
+ {-1011}
+(1 row)
+
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS int[] DEFAULT '{1,2}' ON CONVERSION ERROR);
+ array
+-------
+ {1,2}
+(1 row)
+
+SELECT CAST(ARRAY[['1', '2'], ['three', 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --ok
+ array
+---------
+ {21,22}
+(1 row)
+
+SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --error
+ERROR: invalid input syntax for type integer: "three"
+LINE 1: SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] ...
+ ^
+SELECT CAST(ARRAY[1, 'three'::int] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --error
+ERROR: invalid input syntax for type integer: "three"
+LINE 1: SELECT CAST(ARRAY[1, 'three'::int] AS int[] DEFAULT '{21,22}...
+ ^
+-- test valid DEFAULT expression for CAST = ON CONVERSION ERROR
+CREATE 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: set-returning functions are not allowed in CAST DEFAULT expressions
+LINE 1: SELECT CAST('a' as int DEFAULT ret_setint() ON CONVERSION ER...
+ ^
+SELECT CAST(c::date::json as date DEFAULT NULL ON CONVERSION ERROR) FROM tcast; --error
+ERROR: cannot cast type date to json
+LINE 1: SELECT CAST(c::date::json as date DEFAULT NULL ON CONVERSION...
+ ^
+SELECT CAST('a'::int as int DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR: invalid input syntax for type integer: "a"
+LINE 1: SELECT CAST('a'::int as int DEFAULT NULL ON CONVERSION ERROR...
+ ^
+SELECT CAST('a' as int DEFAULT NULL ON CONVERSION ERROR); --ok
+ int4
+------
+
+(1 row)
+
+SELECT CAST('a' as int DEFAULT sum(1) ON CONVERSION ERROR); --error
+ERROR: aggregate functions are not allowed in CAST DEFAULT expressions
+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: window functions are not allowed in CAST DEFAULT expressions
+LINE 1: SELECT CAST('a' as int DEFAULT sum(1) over() ON CONVERSION E...
+ ^
+SELECT CAST('a' as int DEFAULT (SELECT NULL) ON CONVERSION ERROR); --error
+ERROR: cannot use subquery in CAST DEFAULT expression
+LINE 1: SELECT CAST('a' as int DEFAULT (SELECT NULL) 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 DOMAIN d_varchar as varchar(3) NOT NULL;
+CREATE TYPE comp_domain_with_typmod AS (a d_char3_not_null, b int);
+CREATE TYPE comp2 AS (a d_varchar);
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION ERROR); --error
+ERROR: value too long for type character varying(3)
+LINE 1: SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION...
+ ^
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(123)' ON CONVERSION ERROR); --ok
+ comp2
+-------
+ (123)
+(1 row)
+
+SELECT CAST(11 AS d_int42 DEFAULT 41 ON CONVERSION ERROR); --error
+ERROR: value for domain d_int42 violates check constraint "d_int42_check"
+SELECT CAST(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)'...
+ ^
+-----safe cast with geometry data type
+SELECT CAST('(1,2)'::point AS box DEFAULT NULL ON CONVERSION ERROR);
+ box
+-------------
+ (1,2),(1,2)
+(1 row)
+
+SELECT CAST('[(NaN,1),(NaN,infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+----------------
+ (NaN,Infinity)
+(1 row)
+
+SELECT CAST('[(1e+300,Infinity),(1e+300,Infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+-------------------
+ (1e+300,Infinity)
+(1 row)
+
+SELECT CAST('[(1,2),(3,4)]'::path as polygon DEFAULT NULL ON CONVERSION ERROR);
+ polygon
+---------
+
+(1 row)
+
+SELECT CAST('(NaN,1.0,NaN,infinity)'::box AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+----------------
+ (NaN,Infinity)
+(1 row)
+
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS lseg DEFAULT NULL ON CONVERSION ERROR);
+ lseg
+---------------
+ [(2,2),(0,0)]
+(1 row)
+
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS polygon DEFAULT NULL ON CONVERSION ERROR);
+ polygon
+---------------------------
+ ((0,0),(0,2),(2,2),(2,0))
+(1 row)
+
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS path DEFAULT NULL ON CONVERSION ERROR);
+ path
+------
+
+(1 row)
+
+SELECT CAST('(2.0,infinity,NaN,infinity)'::box AS circle DEFAULT NULL ON CONVERSION ERROR);
+ circle
+----------------------
+ <(NaN,Infinity),NaN>
+(1 row)
+
+SELECT CAST('(NaN,0.0),(2.0,4.0),(0.0,infinity)'::polygon AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+----------------
+ (NaN,Infinity)
+(1 row)
+
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS path DEFAULT NULL ON CONVERSION ERROR);
+ path
+---------------------
+ ((2,0),(2,4),(0,0))
+(1 row)
+
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS box DEFAULT NULL ON CONVERSION ERROR);
+ box
+-------------
+ (2,4),(0,0)
+(1 row)
+
+SELECT CAST('(NaN,infinity),(2.0,4.0),(0.0,infinity)'::polygon AS circle DEFAULT NULL ON CONVERSION ERROR);
+ circle
+----------------------
+ <(NaN,Infinity),NaN>
+(1 row)
+
+SELECT CAST('<(5,1),3>'::circle AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+-------
+ (5,1)
+(1 row)
+
+SELECT CAST('<(3,5),0>'::circle as box DEFAULT NULL ON CONVERSION ERROR);
+ box
+-------------
+ (3,5),(3,5)
+(1 row)
+
+-- not supported because the cast from circle to polygon is implemented as a SQL
+-- function, which cannot be error-safe.
+SELECT CAST('<(3,5),0>'::circle as polygon DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type circle to polygon when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST('<(3,5),0>'::circle as polygon DEFAULT NULL ON C...
+ ^
+HINT: Explicit cast is defined but definition is not declared as safe
+-----safe cast from/to money type is not supported, all the following would result error
+SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type bigint to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION E...
+ ^
+HINT: Explicit cast is defined but definition is not declared as safe
+SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type integer to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION E...
+ ^
+HINT: Explicit cast is defined but definition is not declared as safe
+SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type numeric to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSIO...
+ ^
+HINT: Explicit cast is defined but definition is not declared as safe
+SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type money to numeric when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSIO...
+ ^
+HINT: Explicit cast is defined but definition is not declared as safe
+-----safe cast from bytea type to other data types
+SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT NULL ON CONVERSION ERROR);
+ int8
+------
+
+(1 row)
+
+SELECT CAST('\x123456789A'::bytea AS int4 DEFAULT NULL ON CONVERSION ERROR);
+ int4
+------
+
+(1 row)
+
+SELECT CAST('\x123456'::bytea AS int2 DEFAULT NULL ON CONVERSION ERROR);
+ int2
+------
+
+(1 row)
+
+-----safe cast from bit type to other data types
+SELECT CAST('111111111100001'::bit(100) AS INT DEFAULT NULL ON CONVERSION ERROR);
+ int4
+------
+
+(1 row)
+
+SELECT CAST ('111111111100001'::bit(100) AS INT8 DEFAULT NULL ON CONVERSION ERROR);
+ int8
+------
+
+(1 row)
+
+-----safe cast from text type to other data types
+select CAST('a.b.c.d'::text as regclass default NULL on conversion error);
+ regclass
+----------
+
+(1 row)
+
+CREATE TABLE test_safecast(col0 text);
+INSERT INTO test_safecast(col0) VALUES ('<value>one</value');
+SELECT col0 as text,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) as to_regclass,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) IS NULL as expect_true,
+ CAST(col0 AS "char" DEFAULT NULL ON CONVERSION ERROR) as to_char,
+ CAST(col0 AS name DEFAULT NULL ON CONVERSION ERROR) as to_name,
+ CAST(col0 AS xml DEFAULT NULL ON CONVERSION ERROR) as to_xml
+FROM test_safecast;
+ text | to_regclass | expect_true | to_char | to_name | to_xml
+-------------------+-------------+-------------+---------+-------------------+--------
+ <value>one</value | | t | < | <value>one</value |
+(1 row)
+
+SELECT CAST('192.168.1.x' as inet DEFAULT NULL ON CONVERSION ERROR);
+ inet
+------
+
+(1 row)
+
+SELECT CAST('192.168.1.2/30' as cidr DEFAULT NULL ON CONVERSION ERROR);
+ cidr
+------
+
+(1 row)
+
+SELECT CAST('22:00:5c:08:55:08:01:02'::macaddr8 as macaddr DEFAULT NULL ON CONVERSION ERROR);
+ macaddr
+---------
+
+(1 row)
+
+-----safe cast betweeen range data types
+SELECT CAST('[1,2]'::int4range AS int4multirange DEFAULT NULL ON CONVERSION ERROR);
+ int4multirange
+----------------
+ {[1,3)}
+(1 row)
+
+SELECT CAST('[1,2]'::int8range AS int8multirange DEFAULT NULL ON CONVERSION ERROR);
+ int8multirange
+----------------
+ {[1,3)}
+(1 row)
+
+SELECT CAST('[1,2]'::numrange AS nummultirange DEFAULT NULL ON CONVERSION ERROR);
+ nummultirange
+---------------
+ {[1,2]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::daterange AS datemultirange DEFAULT NULL ON CONVERSION ERROR);
+ datemultirange
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::tsrange AS tsmultirange DEFAULT NULL ON CONVERSION ERROR);
+ tsmultirange
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::tstzrange AS tstzmultirange DEFAULT NULL ON CONVERSION ERROR);
+ tstzmultirange
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+-----safe cast betweeen numeric data types
+CREATE TABLE test_safecast1(
+ col1 float4, col2 float8, col3 numeric, col4 numeric[],
+ col5 int2 default 32767,
+ col6 int4 default 32768,
+ col7 int8 default 4294967296);
+INSERT INTO test_safecast1 VALUES('11.1234', '11.1234', '9223372036854775808', '{11.1234}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('inf', 'inf', 'inf', '{11.1234, 12, inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('-inf', '-inf', '-inf', '{11.1234, 12, -inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('NaN', 'NaN', 'NaN', '{11.1234, 12, -inf, NaN}'::numeric[]);
+SELECT col5 as int2,
+ CAST(col5 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col5 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col5 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col5 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col5 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col5 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col5 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col5 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ int2 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+(4 rows)
+
+SELECT col6 as int4,
+ CAST(col6 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col6 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col6 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col6 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col6 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col6 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col6 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col6 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ int4 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+(4 rows)
+
+SELECT col7 as int8,
+ CAST(col7 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col7 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col7 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col7 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col7 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col7 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col7 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col7 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ int8 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+------------+---------+---------+--------+------------+-------------+------------+------------+------------------
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+(4 rows)
+
+SELECT col3 as numeric,
+ CAST(col3 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col3 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col3 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col3 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col3 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col3 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col3 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col3 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ numeric | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+---------------------+---------+---------+--------+---------+-------------+----------------------+---------------------+------------------
+ 9223372036854775808 | | | | | 9.22337e+18 | 9.22337203685478e+18 | 9223372036854775808 |
+ Infinity | | | | | Infinity | Infinity | Infinity |
+ -Infinity | | | | | -Infinity | -Infinity | -Infinity |
+ NaN | | | | | NaN | NaN | NaN | NaN
+(4 rows)
+
+SELECT col4 as num_arr,
+ CAST(col4 AS int2[] DEFAULT NULL ON CONVERSION ERROR) as int2arr,
+ CAST(col4 AS int4[] DEFAULT NULL ON CONVERSION ERROR) as int4arr,
+ CAST(col4 as int8[] DEFAULT NULL ON CONVERSION ERROR) as int8arr,
+ CAST(col4 as float4[] DEFAULT NULL ON CONVERSION ERROR) as f4arr,
+ CAST(col4 as float8[] DEFAULT NULL ON CONVERSION ERROR) as f8arr,
+ CAST(col4 as numeric(10,1)[] DEFAULT NULL ON CONVERSION ERROR) as numarr
+FROM test_safecast1;
+ num_arr | int2arr | int4arr | int8arr | f4arr | f8arr | numarr
+----------------------------+---------+---------+---------+----------------------------+----------------------------+--------
+ {11.1234} | {11} | {11} | {11} | {11.1234} | {11.1234} | {11.1}
+ {11.1234,12,Infinity,NaN} | | | | {11.1234,12,Infinity,NaN} | {11.1234,12,Infinity,NaN} |
+ {11.1234,12,-Infinity,NaN} | | | | {11.1234,12,-Infinity,NaN} | {11.1234,12,-Infinity,NaN} |
+ {11.1234,12,-Infinity,NaN} | | | | {11.1234,12,-Infinity,NaN} | {11.1234,12,-Infinity,NaN} |
+(4 rows)
+
+SELECT col1 as float4,
+ CAST(col1 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col1 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col1 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col1 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col1 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col1 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col1 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col1 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ float4 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-----------+---------+---------+--------+---------+-----------+------------------+------------+------------------
+ 11.1234 | 11 | 11 | | 11 | 11.1234 | 11.1233997344971 | 11.1234 | 11.1
+ Infinity | | | | | Infinity | Infinity | Infinity |
+ -Infinity | | | | | -Infinity | -Infinity | -Infinity |
+ NaN | | | | | NaN | NaN | NaN | NaN
+(4 rows)
+
+SELECT col2 as float8,
+ CAST(col2 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col2 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col2 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col2 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col2 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col2 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col2 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col2 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ float8 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-----------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 11.1234 | 11 | 11 | | 11 | 11.1234 | 11.1234 | 11.1234 | 11.1
+ Infinity | | | | | Infinity | Infinity | Infinity |
+ -Infinity | | | | | -Infinity | -Infinity | -Infinity |
+ NaN | | | | | NaN | NaN | NaN | NaN
+(4 rows)
+
+-- date/timestamp/interval related safe type cast
+CREATE TABLE test_safecast2(
+ col0 date, col1 timestamp, col2 timestamptz,
+ col3 interval, col4 time, col5 timetz);
+INSERT INTO test_safecast2 VALUES
+('-infinity', '-infinity', '-infinity', '-infinity',
+ '2003-03-07 15:36:39 America/New_York', '2003-07-07 15:36:39 America/New_York'),
+('-infinity', 'infinity', 'infinity', 'infinity',
+ '11:59:59.99 PM', '11:59:59.99 PM PDT');
+SELECT col0 as date,
+ CAST(col0 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col0 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col0 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col0 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col0 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+ date | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale
+-----------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity | -infinity | | | -infinity
+ -infinity | -infinity | -infinity | | | -infinity
+(2 rows)
+
+SELECT col1 as timestamp,
+ CAST(col1 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col1 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col1 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col1 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col1 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+ timestamp | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale
+-----------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity | -infinity | | | -infinity
+ infinity | infinity | infinity | | | infinity
+(2 rows)
+
+SELECT col2 as timestamptz,
+ CAST(col2 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col2 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col2 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col2 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col2 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+ timestamptz | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale
+-------------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity | -infinity | | | -infinity
+ infinity | infinity | infinity | | | infinity
+(2 rows)
+
+SELECT col3 as interval,
+ CAST(col3 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col3 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col3 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col3 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col3 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale,
+ CAST(col3 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+ interval | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale | to_interval_scale
+-----------+----------------+---------+----------+-----------+--------------------+-------------------
+ -infinity | | | | | | -infinity
+ infinity | | | | | | infinity
+(2 rows)
+
+SELECT col4 as time,
+ CAST(col4 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col4 AS timetz DEFAULT NULL ON CONVERSION ERROR) IS NOT NULL as to_timetz,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+ time | to_times | to_timetz | to_interval | to_interval_scale
+-------------+-------------+-----------+-------------------------------+-------------------------------
+ 15:36:39 | 15:36:39 | t | @ 15 hours 36 mins 39 secs | @ 15 hours 36 mins 39 secs
+ 23:59:59.99 | 23:59:59.99 | t | @ 23 hours 59 mins 59.99 secs | @ 23 hours 59 mins 59.99 secs
+(2 rows)
+
+SELECT col5 as timetz,
+ CAST(col5 AS time DEFAULT NULL ON CONVERSION ERROR) as to_time,
+ CAST(col5 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_time_scale,
+ CAST(col5 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col5 AS timetz(6) DEFAULT NULL ON CONVERSION ERROR) as to_timetz_scale,
+ CAST(col5 AS interval DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col5 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+ timetz | to_time | to_time_scale | to_timetz | to_timetz_scale | to_interval | to_interval_scale
+----------------+-------------+---------------+----------------+-----------------+-------------+-------------------
+ 15:36:39-04 | 15:36:39 | 15:36:39 | 15:36:39-04 | 15:36:39-04 | |
+ 23:59:59.99-07 | 23:59:59.99 | 23:59:59.99 | 23:59:59.99-07 | 23:59:59.99-07 | |
+(2 rows)
+
+CREATE TABLE test_safecast3(col0 jsonb);
+INSERT INTO test_safecast3(col0) VALUES ('"test"');
+SELECT col0 as jsonb,
+ CAST(col0 AS integer DEFAULT NULL ON CONVERSION ERROR) as to_integer,
+ CAST(col0 AS numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col0 AS bigint DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col0 AS float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col0 AS float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col0 AS boolean DEFAULT NULL ON CONVERSION ERROR) as to_bool,
+ CAST(col0 AS smallint DEFAULT NULL ON CONVERSION ERROR) as to_smallint
+FROM test_safecast3;
+ jsonb | to_integer | to_numeric | to_int8 | to_float4 | to_float8 | to_bool | to_smallint
+--------+------------+------------+---------+-----------+-----------+---------+-------------
+ "test" | | | | | | |
+(1 row)
+
+-- test deparse
+CREATE DOMAIN d_int_arr as int[];
+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 test_safecast1,
+ CAST(ARRAY[['1'], ['three'],['a']] AS int[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1,
+ CAST(ARRAY[['1', '2'], ['three', 'a']] AS date[] DEFAULT NULL ON CONVERSION ERROR) as cast2,
+ CAST(ARRAY['three'] AS INT[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast3;
+\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 test_safecast1,
+ CAST(ARRAY[ARRAY[1], ARRAY['three'], ARRAY['a']] AS integer[] DEFAULT '{1,2}'::integer[] ON CONVERSION ERROR) AS cast1,
+ CAST(ARRAY[ARRAY['1', '2'], ARRAY['three', 'a']] AS date[] DEFAULT NULL::date[] ON CONVERSION ERROR) AS cast2,
+ CAST(ARRAY['three'] AS integer[] DEFAULT '{1,2}'::integer[] ON CONVERSION ERROR) AS cast3
+CREATE VIEW safecastview1 AS
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS d_int_arr DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1,
+ CAST(ARRAY[['1', '2'], ['three', 'a']] AS d_int_arr DEFAULT '{21,22}' ON CONVERSION ERROR) as cast2;
+\sv safecastview1
+CREATE OR REPLACE VIEW public.safecastview1 AS
+ SELECT CAST(ARRAY[ARRAY[1], ARRAY['three'], ARRAY['a']] AS d_int_arr DEFAULT '{1,2}'::integer[]::d_int_arr ON CONVERSION ERROR) AS cast1,
+ CAST(ARRAY[ARRAY[1, 2], ARRAY['three', 'a']] AS d_int_arr DEFAULT '{21,22}'::integer[]::d_int_arr ON CONVERSION ERROR) AS cast2
+SELECT * FROM safecastview1;
+ cast1 | cast2
+-------+---------
+ {1,2} | {21,22}
+(1 row)
+
+--error, default expression is mutable
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as int DEFAULT random(min=>1, max=>1) ON CONVERSION ERROR)));
+ERROR: functions in index expression must be marked IMMUTABLE
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as xid DEFAULT NULL ON CONVERSION ERROR)));
+ERROR: data type xid has no default operator class for access method "btree"
+HINT: You must specify an operator class for the index or define a default operator class for the data type.
+CREATE INDEX test_safecast3_idx ON test_safecast3((CAST(col0 as int DEFAULT NULL ON CONVERSION ERROR))); --ok
+SELECT pg_get_indexdef('test_safecast3_idx'::regclass);
+ pg_get_indexdef
+------------------------------------------------------------------------------------------------------------------------------------------
+ CREATE INDEX test_safecast3_idx ON public.test_safecast3 USING btree ((CAST(col0 AS integer DEFAULT NULL::integer ON CONVERSION ERROR)))
+(1 row)
+
+DROP TABLE test_safecast;
+DROP TABLE test_safecast1;
+DROP TABLE test_safecast2;
+DROP TABLE test_safecast3;
+DROP TABLE tcast;
+DROP FUNCTION ret_int8;
+DROP FUNCTION ret_setint;
+DROP TYPE comp_domain_with_typmod;
+DROP TYPE comp2;
+DROP DOMAIN d_varchar;
+DROP DOMAIN d_int42;
+DROP DOMAIN d_char3_not_null;
+RESET extra_float_digits;
diff --git a/src/test/regress/expected/create_cast.out b/src/test/regress/expected/create_cast.out
index 0e69644bca2..f4035c338aa 100644
--- a/src/test/regress/expected/create_cast.out
+++ b/src/test/regress/expected/create_cast.out
@@ -88,6 +88,11 @@ SELECT 1234::int4::casttesttype; -- Should work now
bar1234
(1 row)
+SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- error
+ERROR: cannot cast type integer to casttesttype when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVE...
+ ^
+HINT: Explicit cast is defined but definition is not declared as safe
-- check dependencies generated for that
SELECT pg_describe_object(classid, objid, objsubid) as obj,
pg_describe_object(refclassid, refobjid, refobjsubid) as objref,
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index a357e1d0c0e..81ea244859f 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -943,8 +943,8 @@ SELECT *
FROM pg_cast c
WHERE castsource = 0 OR casttarget = 0 OR castcontext NOT IN ('e', 'a', 'i')
OR castmethod NOT IN ('f', 'b' ,'i');
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Check that castfunc is nonzero only for cast methods that need a function,
@@ -953,8 +953,8 @@ SELECT *
FROM pg_cast c
WHERE (castmethod = 'f' AND castfunc = 0)
OR (castmethod IN ('b', 'i') AND castfunc <> 0);
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Look for casts to/from the same type that aren't length coercion functions.
@@ -963,15 +963,15 @@ WHERE (castmethod = 'f' AND castfunc = 0)
SELECT *
FROM pg_cast c
WHERE castsource = casttarget AND castfunc = 0;
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
SELECT c.*
FROM pg_cast c, pg_proc p
WHERE c.castfunc = p.oid AND p.pronargs < 2 AND castsource = casttarget;
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Look for cast functions that don't have the right signature. The
@@ -989,8 +989,8 @@ WHERE c.castfunc = p.oid AND
OR (c.castsource = 'character'::regtype AND
p.proargtypes[0] = 'text'::regtype))
OR NOT binary_coercible(p.prorettype, c.casttarget));
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
SELECT c.*
@@ -998,8 +998,8 @@ FROM pg_cast c, pg_proc p
WHERE c.castfunc = p.oid AND
((p.pronargs > 1 AND p.proargtypes[1] != 'int4'::regtype) OR
(p.pronargs > 2 AND p.proargtypes[2] != 'bool'::regtype));
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Look for binary compatible casts that do not have the reverse
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index f56482fb9f1..2e086cbc7af 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -81,7 +81,7 @@ test: create_table_like alter_generic alter_operator misc async dbsize merge mis
# collate.linux.utf8 and collate.icu.utf8 tests cannot be run in parallel with each other
# psql depends on create_am
# amutils depends on geometry, create_index_spgist, hash_index, brin
-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 00000000000..905d1f318b5
--- /dev/null
+++ b/src/test/regress/sql/cast.sql
@@ -0,0 +1,340 @@
+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(B'01' AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(BIT'01' AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(TRUE AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(1.1 AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(1 AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(1::numeric AS money DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(1111 AS "char" DEFAULT NULL ON CONVERSION ERROR);
+
+--the default expression’s collation should match the collation of the cast
+--target type
+VALUES (CAST('error' AS text DEFAULT '1' COLLATE "C" ON CONVERSION ERROR));
+
+VALUES (CAST('error' AS int2vector DEFAULT '1 3' ON CONVERSION ERROR));
+VALUES (CAST('error' AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR));
+
+-- test source expression contain subquery
+SELECT CAST((SELECT b FROM generate_series(1, 1), (VALUES('H')) s(b))
+ AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR);
+SELECT CAST((SELECT ARRAY((SELECT b FROM generate_series(1, 2) g, (VALUES('H')) s(b))))
+ AS INT[]
+ DEFAULT NULL ON CONVERSION ERROR);
+
+CREATE FUNCTION ret_int8() RETURNS BIGINT AS
+$$
+BEGIN RETURN 2147483648; END;
+$$
+LANGUAGE plpgsql IMMUTABLE;
+
+SELECT CAST('a' as int DEFAULT ret_int8() ON CONVERSION ERROR); --error
+SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERROR); --error
+
+-- test array coerce
+SELECT CAST(array['a'::text] AS int[] DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(array['a'] AS int[] DEFAULT NULL ON CONVERSION ERROR); --ok
+SELECT CAST(array[11.12] AS date[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(array[11111111111111111] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(array[['abc'],[456]] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('{123,abc,456}' AS int[] 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 int[] DEFAULT '{1,2}' ON CONVERSION ERROR);
+SELECT CAST(ARRAY[['1', '2'], ['three', 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --ok
+SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --error
+SELECT CAST(ARRAY[1, 'three'::int] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --error
+
+-- test valid DEFAULT expression for CAST = ON CONVERSION ERROR
+CREATE 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(c::date::json as date DEFAULT NULL ON CONVERSION ERROR) FROM tcast; --error
+SELECT CAST('a'::int as int DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST('a' as int DEFAULT NULL ON CONVERSION ERROR); --ok
+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 (SELECT NULL) 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 DOMAIN d_varchar as varchar(3) NOT NULL;
+CREATE TYPE comp_domain_with_typmod AS (a d_char3_not_null, b int);
+CREATE TYPE comp2 AS (a d_varchar);
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION ERROR); --error
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(123)' ON CONVERSION ERROR); --ok
+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
+
+-----safe cast with geometry data type
+SELECT CAST('(1,2)'::point AS box DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[(NaN,1),(NaN,infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[(1e+300,Infinity),(1e+300,Infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[(1,2),(3,4)]'::path as polygon DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NaN,1.0,NaN,infinity)'::box AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS lseg DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS polygon DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS path DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,infinity,NaN,infinity)'::box AS circle DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NaN,0.0),(2.0,4.0),(0.0,infinity)'::polygon AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS path DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS box DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NaN,infinity),(2.0,4.0),(0.0,infinity)'::polygon AS circle DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('<(5,1),3>'::circle AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('<(3,5),0>'::circle as box DEFAULT NULL ON CONVERSION ERROR);
+-- not supported because the cast from circle to polygon is implemented as a SQL
+-- function, which cannot be error-safe.
+SELECT CAST('<(3,5),0>'::circle as polygon DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast from/to money type is not supported, all the following would result error
+SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast from bytea type to other data types
+SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('\x123456789A'::bytea AS int4 DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('\x123456'::bytea AS int2 DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast from bit type to other data types
+SELECT CAST('111111111100001'::bit(100) AS INT DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST ('111111111100001'::bit(100) AS INT8 DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast from text type to other data types
+select CAST('a.b.c.d'::text as regclass default NULL on conversion error);
+
+CREATE TABLE test_safecast(col0 text);
+INSERT INTO test_safecast(col0) VALUES ('<value>one</value');
+
+SELECT col0 as text,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) as to_regclass,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) IS NULL as expect_true,
+ CAST(col0 AS "char" DEFAULT NULL ON CONVERSION ERROR) as to_char,
+ CAST(col0 AS name DEFAULT NULL ON CONVERSION ERROR) as to_name,
+ CAST(col0 AS xml DEFAULT NULL ON CONVERSION ERROR) as to_xml
+FROM test_safecast;
+
+SELECT CAST('192.168.1.x' as inet DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('192.168.1.2/30' as cidr DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('22:00:5c:08:55:08:01:02'::macaddr8 as macaddr DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast betweeen range data types
+SELECT CAST('[1,2]'::int4range AS int4multirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[1,2]'::int8range AS int8multirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[1,2]'::numrange AS nummultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::daterange AS datemultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::tsrange AS tsmultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::tstzrange AS tstzmultirange DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast betweeen numeric data types
+CREATE TABLE test_safecast1(
+ col1 float4, col2 float8, col3 numeric, col4 numeric[],
+ col5 int2 default 32767,
+ col6 int4 default 32768,
+ col7 int8 default 4294967296);
+INSERT INTO test_safecast1 VALUES('11.1234', '11.1234', '9223372036854775808', '{11.1234}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('inf', 'inf', 'inf', '{11.1234, 12, inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('-inf', '-inf', '-inf', '{11.1234, 12, -inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('NaN', 'NaN', 'NaN', '{11.1234, 12, -inf, NaN}'::numeric[]);
+
+SELECT col5 as int2,
+ CAST(col5 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col5 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col5 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col5 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col5 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col5 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col5 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col5 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col6 as int4,
+ CAST(col6 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col6 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col6 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col6 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col6 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col6 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col6 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col6 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col7 as int8,
+ CAST(col7 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col7 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col7 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col7 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col7 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col7 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col7 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col7 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col3 as numeric,
+ CAST(col3 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col3 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col3 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col3 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col3 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col3 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col3 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col3 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col4 as num_arr,
+ CAST(col4 AS int2[] DEFAULT NULL ON CONVERSION ERROR) as int2arr,
+ CAST(col4 AS int4[] DEFAULT NULL ON CONVERSION ERROR) as int4arr,
+ CAST(col4 as int8[] DEFAULT NULL ON CONVERSION ERROR) as int8arr,
+ CAST(col4 as float4[] DEFAULT NULL ON CONVERSION ERROR) as f4arr,
+ CAST(col4 as float8[] DEFAULT NULL ON CONVERSION ERROR) as f8arr,
+ CAST(col4 as numeric(10,1)[] DEFAULT NULL ON CONVERSION ERROR) as numarr
+FROM test_safecast1;
+
+SELECT col1 as float4,
+ CAST(col1 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col1 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col1 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col1 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col1 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col1 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col1 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col1 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col2 as float8,
+ CAST(col2 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col2 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col2 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col2 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col2 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col2 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col2 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col2 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+-- date/timestamp/interval related safe type cast
+CREATE TABLE test_safecast2(
+ col0 date, col1 timestamp, col2 timestamptz,
+ col3 interval, col4 time, col5 timetz);
+INSERT INTO test_safecast2 VALUES
+('-infinity', '-infinity', '-infinity', '-infinity',
+ '2003-03-07 15:36:39 America/New_York', '2003-07-07 15:36:39 America/New_York'),
+('-infinity', 'infinity', 'infinity', 'infinity',
+ '11:59:59.99 PM', '11:59:59.99 PM PDT');
+
+SELECT col0 as date,
+ CAST(col0 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col0 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col0 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col0 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col0 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+
+SELECT col1 as timestamp,
+ CAST(col1 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col1 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col1 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col1 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col1 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+
+SELECT col2 as timestamptz,
+ CAST(col2 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col2 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col2 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col2 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col2 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+
+SELECT col3 as interval,
+ CAST(col3 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col3 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col3 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col3 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col3 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale,
+ CAST(col3 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+
+SELECT col4 as time,
+ CAST(col4 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col4 AS timetz DEFAULT NULL ON CONVERSION ERROR) IS NOT NULL as to_timetz,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+
+SELECT col5 as timetz,
+ CAST(col5 AS time DEFAULT NULL ON CONVERSION ERROR) as to_time,
+ CAST(col5 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_time_scale,
+ CAST(col5 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col5 AS timetz(6) DEFAULT NULL ON CONVERSION ERROR) as to_timetz_scale,
+ CAST(col5 AS interval DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col5 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+
+CREATE TABLE test_safecast3(col0 jsonb);
+INSERT INTO test_safecast3(col0) VALUES ('"test"');
+SELECT col0 as jsonb,
+ CAST(col0 AS integer DEFAULT NULL ON CONVERSION ERROR) as to_integer,
+ CAST(col0 AS numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col0 AS bigint DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col0 AS float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col0 AS float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col0 AS boolean DEFAULT NULL ON CONVERSION ERROR) as to_bool,
+ CAST(col0 AS smallint DEFAULT NULL ON CONVERSION ERROR) as to_smallint
+FROM test_safecast3;
+
+-- test deparse
+CREATE DOMAIN d_int_arr as int[];
+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 test_safecast1,
+ CAST(ARRAY[['1'], ['three'],['a']] AS int[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1,
+ CAST(ARRAY[['1', '2'], ['three', 'a']] AS date[] DEFAULT NULL ON CONVERSION ERROR) as cast2,
+ CAST(ARRAY['three'] AS INT[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast3;
+\sv safecastview
+
+CREATE VIEW safecastview1 AS
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS d_int_arr DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1,
+ CAST(ARRAY[['1', '2'], ['three', 'a']] AS d_int_arr DEFAULT '{21,22}' ON CONVERSION ERROR) as cast2;
+\sv safecastview1
+SELECT * FROM safecastview1;
+
+--error, default expression is mutable
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as int DEFAULT random(min=>1, max=>1) ON CONVERSION ERROR)));
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as xid DEFAULT NULL ON CONVERSION ERROR)));
+CREATE INDEX test_safecast3_idx ON test_safecast3((CAST(col0 as int DEFAULT NULL ON CONVERSION ERROR))); --ok
+SELECT pg_get_indexdef('test_safecast3_idx'::regclass);
+
+DROP TABLE test_safecast;
+DROP TABLE test_safecast1;
+DROP TABLE test_safecast2;
+DROP TABLE test_safecast3;
+DROP TABLE tcast;
+DROP FUNCTION ret_int8;
+DROP FUNCTION ret_setint;
+DROP TYPE comp_domain_with_typmod;
+DROP TYPE comp2;
+DROP DOMAIN d_varchar;
+DROP DOMAIN d_int42;
+DROP DOMAIN d_char3_not_null;
+RESET extra_float_digits;
diff --git a/src/test/regress/sql/create_cast.sql b/src/test/regress/sql/create_cast.sql
index 32187853cc7..0a15a795d87 100644
--- a/src/test/regress/sql/create_cast.sql
+++ b/src/test/regress/sql/create_cast.sql
@@ -62,6 +62,7 @@ $$ SELECT ('bar'::text || $1::text); $$;
CREATE CAST (int4 AS casttesttype) WITH FUNCTION bar_int4_text(int4) AS IMPLICIT;
SELECT 1234::int4::casttesttype; -- Should work now
+SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- error
-- check dependencies generated for that
SELECT pg_describe_object(classid, objid, objsubid) as obj,
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 57a8f0366a5..9bbb1e560c7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2668,6 +2668,9 @@ STRLEN
SV
SYNCHRONIZATION_BARRIER
SYSTEM_INFO
+SafeTypeCast
+SafeTypeCastExpr
+SafeTypeCastState
SampleScan
SampleScanGetSampleSize_function
SampleScanState
--
2.34.1
v12-0001-error-safe-for-casting-bytea-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v12-0001-error-safe-for-casting-bytea-to-other-types-per-pg_cast.patchDownload
From 5875ac4fdbc6393286aeabaa293a192524400eeb Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:08:00 +0800
Subject: [PATCH v12 01/20] error safe for casting bytea to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc and pc.castfunc > 0 and castsource::regtype ='bytea'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------+----------+-------------+------------+------------+---------
bytea | smallint | 6370 | e | f | bytea_int2 | int2
bytea | integer | 6371 | e | f | bytea_int4 | int4
bytea | bigint | 6372 | e | f | bytea_int8 | int8
(3 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/bytea.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/backend/utils/adt/bytea.c b/src/backend/utils/adt/bytea.c
index 6e7b914c563..f43ad6b4aff 100644
--- a/src/backend/utils/adt/bytea.c
+++ b/src/backend/utils/adt/bytea.c
@@ -1027,7 +1027,7 @@ bytea_int2(PG_FUNCTION_ARGS)
/* Check that the byte array is not too long */
if (len > sizeof(result))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range"));
@@ -1052,7 +1052,7 @@ bytea_int4(PG_FUNCTION_ARGS)
/* Check that the byte array is not too long */
if (len > sizeof(result))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range"));
@@ -1077,7 +1077,7 @@ bytea_int8(PG_FUNCTION_ARGS)
/* Check that the byte array is not too long */
if (len > sizeof(result))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("bigint out of range"));
--
2.34.1
I'm fine with it. I can see having 'f' and 's' both mean cast
functions, but 's' means safe, but the extra boolean works too and we'll be
fine with either method.I can work on this part if you don't have time.
Do you mean change pg_cast.casterrorsafe from boolean to char?
No, I meant implementing the syntax for being able to declare a custom CAST
function as safe (or not). Basically adding the [SAFE] to
CREATE CAST (*source_type* AS *target_type*)
WITH [SAFE] FUNCTION *function_name* [ (*argument_type* [, ...]) ]
I'm not tied to this syntax choice, but this one seemed the most obvious
and least invasive.
But this brings up an interesting point: if a cast is declared as WITHOUT
FUNCTION aka COERCION_METHOD_BINARY, then the cast can never fail, and we
should probably check for that because a cast that cannot fail can ignore
the DEFAULT clause altogether and fall back to being an ordinary CAST().
Currently pg_cast.casterrorsafe works just fine.
if the cast function is not applicable, the castfunc would be InvalidOid.
also the cast function is either error safe or not, I don't see a
usage case for the third value.
Agreed, it's fine. A committer may want a char with 's'/'u' values to keep
all the options char types, but even if they do that's a very minor change.
On Tue, Nov 25, 2025 at 8:30 AM jian he <jian.universality@gmail.com> wrote:
On Mon, Nov 24, 2025 at 11:38 AM Amul Sul <sulamul@gmail.com> wrote:
[...]
+static inline float8 +float8_div_safe(const float8 val1, const float8 val2, struct Node *escontext)but we can change float8_div to:
static inline float8
float8_div(const float8 val1, const float8 val2)
{
return float8_div_safe(val1, val2, NULL);
}
I am worried that entering another function would cause a minor performance
degradation. And since these simple functions are so simple, keeping them
separated should not be a big problem. also I placed float8_div,
float8_div_safe together.
Since you declared float8_div_safe() as static inline, I believe it
wouldn't have any performance degradation since most compilers
optimize it. Also, I suggest you pass the ErrorSafeContext to
float_overflow_error(), float_underflow_error(), and
float_zero_divide_error() so that you can avoid duplicating error
messages.
Regards,
Amul
On Mon, Dec 1, 2025 at 8:09 PM Amul Sul <sulamul@gmail.com> wrote:
Since you declared float8_div_safe() as static inline, I believe it
wouldn't have any performance degradation since most compilers
optimize it. Also, I suggest you pass the ErrorSafeContext to
float_overflow_error(), float_underflow_error(), and
float_zero_divide_error() so that you can avoid duplicating error
messages.
hi.
First I want to use ereturn, then I found out
float_overflow_error, float_underflow_error, float_zero_divide_error
used both in float4, float8.
ereturn would not be appropriate for both types.
so I choose errsave.
for these 3 functions, now it looks like:
pg_noinline void
float_overflow_error(struct Node *escontext)
{
errsave(escontext,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("value out of range: overflow")));
}
pg_noinline void
float_underflow_error(struct Node *escontext)
{
errsave(escontext,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("value out of range: underflow")));
}
pg_noinline void
float_zero_divide_error(struct Node *escontext)
{
errsave(escontext,
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
}
Attachments:
v13-0018-error-safe-for-casting-geometry-data-type.patchtext/x-patch; charset=US-ASCII; name=v13-0018-error-safe-for-casting-geometry-data-type.patchDownload
From 494a2c19a623104ac7e5d1bcc7d4f4fb57b178e1 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Tue, 2 Dec 2025 15:25:29 +0800
Subject: [PATCH v13 18/20] error safe for casting geometry data type
select castsource::regtype, casttarget::regtype, pp.prosrc
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
join pg_type pt on pt.oid = castsource
join pg_type pt1 on pt1.oid = casttarget
and pc.castfunc > 0 and pt.typarray <> 0
and pt.typnamespace = 'pg_catalog'::regnamespace
and pt1.typnamespace = 'pg_catalog'::regnamespace
and (pt.typcategory = 'G' or pt1.typcategory = 'G')
order by castsource::regtype, casttarget::regtype;
castsource | casttarget | prosrc
------------+------------+---------------
point | box | point_box
lseg | point | lseg_center
path | polygon | path_poly
box | point | box_center
box | lseg | box_diagonal
box | polygon | box_poly
box | circle | box_circle
polygon | point | poly_center
polygon | path | poly_path
polygon | box | poly_box
polygon | circle | poly_circle
circle | point | circle_center
circle | box | circle_box
circle | polygon |
(14 rows)
already error safe: point_box, box_diagonal, box_poly, poly_path, poly_box, circle_center
almost error safe: path_poly
patch make these functions error safe: lseg_center, box_center, box_circle, poly_center, poly_circle
circle_box
can not error safe: cast circle to polygon, because it's a SQL function
discussion: https://postgr.es/m/CACJufxHCMzrHOW=wRe8L30rMhB3sjwAv1LE928Fa7sxMu1Tx-g@mail.gmail.com
---
contrib/btree_gist/btree_float4.c | 2 +-
contrib/btree_gist/btree_float8.c | 4 +-
src/backend/utils/adt/float.c | 104 +++++++--------
src/backend/utils/adt/geo_ops.c | 205 ++++++++++++++++++++++++++----
src/include/utils/float.h | 112 +++++++++++-----
5 files changed, 313 insertions(+), 114 deletions(-)
diff --git a/contrib/btree_gist/btree_float4.c b/contrib/btree_gist/btree_float4.c
index d9c859835da..a7325a7bb29 100644
--- a/contrib/btree_gist/btree_float4.c
+++ b/contrib/btree_gist/btree_float4.c
@@ -101,7 +101,7 @@ float4_dist(PG_FUNCTION_ARGS)
r = a - b;
if (unlikely(isinf(r)) && !isinf(a) && !isinf(b))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT4(fabsf(r));
}
diff --git a/contrib/btree_gist/btree_float8.c b/contrib/btree_gist/btree_float8.c
index 567beede178..7c99b84de35 100644
--- a/contrib/btree_gist/btree_float8.c
+++ b/contrib/btree_gist/btree_float8.c
@@ -79,7 +79,7 @@ gbt_float8_dist(const void *a, const void *b, FmgrInfo *flinfo)
r = arg1 - arg2;
if (unlikely(isinf(r)) && !isinf(arg1) && !isinf(arg2))
- float_overflow_error();
+ float_overflow_error(NULL);
return fabs(r);
}
@@ -109,7 +109,7 @@ float8_dist(PG_FUNCTION_ARGS)
r = a - b;
if (unlikely(isinf(r)) && !isinf(a) && !isinf(b))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(fabs(r));
}
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index d746f961fd3..4a7623b1456 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -83,25 +83,25 @@ static void init_degree_constants(void);
* This does mean that you don't get a useful error location indicator.
*/
pg_noinline void
-float_overflow_error(void)
+float_overflow_error(struct Node *escontext)
{
- ereport(ERROR,
+ errsave(escontext,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("value out of range: overflow")));
}
pg_noinline void
-float_underflow_error(void)
+float_underflow_error(struct Node *escontext)
{
- ereport(ERROR,
+ errsave(escontext,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("value out of range: underflow")));
}
pg_noinline void
-float_zero_divide_error(void)
+float_zero_divide_error(struct Node *escontext)
{
- ereport(ERROR,
+ errsave(escontext,
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
}
@@ -1460,9 +1460,9 @@ dsqrt(PG_FUNCTION_ARGS)
result = sqrt(arg1);
if (unlikely(isinf(result)) && !isinf(arg1))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0) && arg1 != 0.0)
- float_underflow_error();
+ float_underflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1479,9 +1479,9 @@ dcbrt(PG_FUNCTION_ARGS)
result = cbrt(arg1);
if (unlikely(isinf(result)) && !isinf(arg1))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0) && arg1 != 0.0)
- float_underflow_error();
+ float_underflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1617,24 +1617,24 @@ dpow(PG_FUNCTION_ARGS)
if (absx == 1.0)
result = 1.0;
else if (arg2 >= 0.0 ? (absx > 1.0) : (absx < 1.0))
- float_overflow_error();
+ float_overflow_error(NULL);
else
- float_underflow_error();
+ float_underflow_error(NULL);
}
}
else if (errno == ERANGE)
{
if (result != 0.0)
- float_overflow_error();
+ float_overflow_error(NULL);
else
- float_underflow_error();
+ float_underflow_error(NULL);
}
else
{
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0) && arg1 != 0.0)
- float_underflow_error();
+ float_underflow_error(NULL);
}
}
@@ -1674,14 +1674,14 @@ dexp(PG_FUNCTION_ARGS)
if (unlikely(errno == ERANGE))
{
if (result != 0.0)
- float_overflow_error();
+ float_overflow_error(NULL);
else
- float_underflow_error();
+ float_underflow_error(NULL);
}
else if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
else if (unlikely(result == 0.0))
- float_underflow_error();
+ float_underflow_error(NULL);
}
PG_RETURN_FLOAT8(result);
@@ -1712,9 +1712,9 @@ dlog1(PG_FUNCTION_ARGS)
result = log(arg1);
if (unlikely(isinf(result)) && !isinf(arg1))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0) && arg1 != 1.0)
- float_underflow_error();
+ float_underflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1745,9 +1745,9 @@ dlog10(PG_FUNCTION_ARGS)
result = log10(arg1);
if (unlikely(isinf(result)) && !isinf(arg1))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0) && arg1 != 1.0)
- float_underflow_error();
+ float_underflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1778,7 +1778,7 @@ dacos(PG_FUNCTION_ARGS)
result = acos(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1809,7 +1809,7 @@ dasin(PG_FUNCTION_ARGS)
result = asin(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1835,7 +1835,7 @@ datan(PG_FUNCTION_ARGS)
*/
result = atan(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1861,7 +1861,7 @@ datan2(PG_FUNCTION_ARGS)
*/
result = atan2(arg1, arg2);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1902,7 +1902,7 @@ dcos(PG_FUNCTION_ARGS)
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("input is out of range")));
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1957,7 +1957,7 @@ dsin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("input is out of range")));
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2137,7 +2137,7 @@ dacosd(PG_FUNCTION_ARGS)
result = 90.0 + asind_q1(-arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2174,7 +2174,7 @@ dasind(PG_FUNCTION_ARGS)
result = -asind_q1(-arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2206,7 +2206,7 @@ datand(PG_FUNCTION_ARGS)
result = (atan_arg1 / atan_1_0) * 45.0;
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2242,7 +2242,7 @@ datan2d(PG_FUNCTION_ARGS)
result = (atan2_arg1_arg2 / atan_1_0) * 45.0;
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2365,7 +2365,7 @@ dcosd(PG_FUNCTION_ARGS)
result = sign * cosd_q1(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2487,7 +2487,7 @@ dsind(PG_FUNCTION_ARGS)
result = sign * sind_q1(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2645,7 +2645,7 @@ dcosh(PG_FUNCTION_ARGS)
result = get_float8_infinity();
if (unlikely(result == 0.0))
- float_underflow_error();
+ float_underflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2665,7 +2665,7 @@ dtanh(PG_FUNCTION_ARGS)
result = tanh(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2765,7 +2765,7 @@ derf(PG_FUNCTION_ARGS)
result = erf(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2785,7 +2785,7 @@ derfc(PG_FUNCTION_ARGS)
result = erfc(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2814,7 +2814,7 @@ dgamma(PG_FUNCTION_ARGS)
/* Per POSIX, an input of -Inf causes a domain error */
if (arg1 < 0)
{
- float_overflow_error();
+ float_overflow_error(NULL);
result = get_float8_nan(); /* keep compiler quiet */
}
else
@@ -2836,12 +2836,12 @@ dgamma(PG_FUNCTION_ARGS)
if (errno != 0 || isinf(result) || isnan(result))
{
if (result != 0.0)
- float_overflow_error();
+ float_overflow_error(NULL);
else
- float_underflow_error();
+ float_underflow_error(NULL);
}
else if (result == 0.0)
- float_underflow_error();
+ float_underflow_error(NULL);
}
PG_RETURN_FLOAT8(result);
@@ -2873,7 +2873,7 @@ dlgamma(PG_FUNCTION_ARGS)
* to report overflow, but it should never underflow.
*/
if (errno == ERANGE || (isinf(result) && !isinf(arg1)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -3013,7 +3013,7 @@ float8_combine(PG_FUNCTION_ARGS)
tmp = Sx1 / N1 - Sx2 / N2;
Sxx = Sxx1 + Sxx2 + N1 * N2 * tmp * tmp / N;
if (unlikely(isinf(Sxx)) && !isinf(Sxx1) && !isinf(Sxx2))
- float_overflow_error();
+ float_overflow_error(NULL);
}
/*
@@ -3080,7 +3080,7 @@ float8_accum(PG_FUNCTION_ARGS)
if (isinf(Sx) || isinf(Sxx))
{
if (!isinf(transvalues[1]) && !isinf(newval))
- float_overflow_error();
+ float_overflow_error(NULL);
Sxx = get_float8_nan();
}
@@ -3163,7 +3163,7 @@ float4_accum(PG_FUNCTION_ARGS)
if (isinf(Sx) || isinf(Sxx))
{
if (!isinf(transvalues[1]) && !isinf(newval))
- float_overflow_error();
+ float_overflow_error(NULL);
Sxx = get_float8_nan();
}
@@ -3393,7 +3393,7 @@ float8_regr_accum(PG_FUNCTION_ARGS)
(isinf(Sxy) &&
!isinf(transvalues[1]) && !isinf(newvalX) &&
!isinf(transvalues[3]) && !isinf(newvalY)))
- float_overflow_error();
+ float_overflow_error(NULL);
if (isinf(Sxx))
Sxx = get_float8_nan();
@@ -3545,15 +3545,15 @@ float8_regr_combine(PG_FUNCTION_ARGS)
tmp1 = Sx1 / N1 - Sx2 / N2;
Sxx = Sxx1 + Sxx2 + N1 * N2 * tmp1 * tmp1 / N;
if (unlikely(isinf(Sxx)) && !isinf(Sxx1) && !isinf(Sxx2))
- float_overflow_error();
+ float_overflow_error(NULL);
Sy = float8_pl(Sy1, Sy2);
tmp2 = Sy1 / N1 - Sy2 / N2;
Syy = Syy1 + Syy2 + N1 * N2 * tmp2 * tmp2 / N;
if (unlikely(isinf(Syy)) && !isinf(Syy1) && !isinf(Syy2))
- float_overflow_error();
+ float_overflow_error(NULL);
Sxy = Sxy1 + Sxy2 + N1 * N2 * tmp1 * tmp2 / N;
if (unlikely(isinf(Sxy)) && !isinf(Sxy1) && !isinf(Sxy2))
- float_overflow_error();
+ float_overflow_error(NULL);
}
/*
diff --git a/src/backend/utils/adt/geo_ops.c b/src/backend/utils/adt/geo_ops.c
index 9101a720744..0c792c17e83 100644
--- a/src/backend/utils/adt/geo_ops.c
+++ b/src/backend/utils/adt/geo_ops.c
@@ -78,11 +78,14 @@ enum path_delim
/* Routines for points */
static inline void point_construct(Point *result, float8 x, float8 y);
static inline void point_add_point(Point *result, Point *pt1, Point *pt2);
+static inline bool point_add_point_safe(Point *result, Point *pt1, Point *pt2,
+ Node *escontext);
static inline void point_sub_point(Point *result, Point *pt1, Point *pt2);
static inline void point_mul_point(Point *result, Point *pt1, Point *pt2);
static inline void point_div_point(Point *result, Point *pt1, Point *pt2);
static inline bool point_eq_point(Point *pt1, Point *pt2);
static inline float8 point_dt(Point *pt1, Point *pt2);
+static inline float8 point_dt_safe(Point *pt1, Point *pt2, Node *escontext);
static inline float8 point_sl(Point *pt1, Point *pt2);
static int point_inside(Point *p, int npts, Point *plist);
@@ -109,6 +112,7 @@ static float8 lseg_closept_lseg(Point *result, LSEG *on_lseg, LSEG *to_lseg);
/* Routines for boxes */
static inline void box_construct(BOX *result, Point *pt1, Point *pt2);
static void box_cn(Point *center, BOX *box);
+static bool box_cn_safe(Point *center, BOX *box, Node* escontext);
static bool box_ov(BOX *box1, BOX *box2);
static float8 box_ar(BOX *box);
static float8 box_ht(BOX *box);
@@ -125,7 +129,7 @@ static float8 circle_ar(CIRCLE *circle);
/* Routines for polygons */
static void make_bound_box(POLYGON *poly);
-static void poly_to_circle(CIRCLE *result, POLYGON *poly);
+static bool poly_to_circle_safe(CIRCLE *result, POLYGON *poly, Node *escontext);
static bool lseg_inside_poly(Point *a, Point *b, POLYGON *poly, int start);
static bool poly_contain_poly(POLYGON *contains_poly, POLYGON *contained_poly);
static bool plist_same(int npts, Point *p1, Point *p2);
@@ -851,7 +855,8 @@ box_center(PG_FUNCTION_ARGS)
BOX *box = PG_GETARG_BOX_P(0);
Point *result = (Point *) palloc(sizeof(Point));
- box_cn(result, box);
+ if (!box_cn_safe(result, box, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_POINT_P(result);
}
@@ -871,10 +876,33 @@ box_ar(BOX *box)
static void
box_cn(Point *center, BOX *box)
{
- center->x = float8_div(float8_pl(box->high.x, box->low.x), 2.0);
- center->y = float8_div(float8_pl(box->high.y, box->low.y), 2.0);
+ (void) box_cn_safe(center, box, NULL);
}
+static bool
+box_cn_safe(Point *center, BOX *box, Node* escontext)
+{
+ float8 x;
+ float8 y;
+
+ x = float8_pl_safe(box->high.x, box->low.x, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ center->x = float8_div_safe(x, 2.0, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ y = float8_pl_safe(box->high.y, box->low.y, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ center->y = float8_div_safe(y, 2.0, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ return true;
+}
/* box_wd - returns the width (length) of the box
* (horizontal magnitude).
@@ -2004,6 +2032,23 @@ point_dt(Point *pt1, Point *pt2)
return hypot(float8_mi(pt1->x, pt2->x), float8_mi(pt1->y, pt2->y));
}
+static inline float8
+point_dt_safe(Point *pt1, Point *pt2, Node *escontext)
+{
+ float8 x;
+ float8 y;
+
+ x = float8_mi_safe(pt1->x, pt2->x, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return 0.0;
+
+ y = float8_mi_safe(pt1->y, pt2->y, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return 0.0;
+
+ return hypot(x, y);
+}
+
Datum
point_slope(PG_FUNCTION_ARGS)
{
@@ -2317,13 +2362,31 @@ lseg_center(PG_FUNCTION_ARGS)
{
LSEG *lseg = PG_GETARG_LSEG_P(0);
Point *result;
+ float8 x;
+ float8 y;
result = (Point *) palloc(sizeof(Point));
- result->x = float8_div(float8_pl(lseg->p[0].x, lseg->p[1].x), 2.0);
- result->y = float8_div(float8_pl(lseg->p[0].y, lseg->p[1].y), 2.0);
+ x = float8_pl_safe(lseg->p[0].x, lseg->p[1].x, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ result->x = float8_div_safe(x, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ y = float8_pl_safe(lseg->p[0].y, lseg->p[1].y, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ result->y = float8_div_safe(y, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
PG_RETURN_POINT_P(result);
+
+fail:
+ PG_RETURN_NULL();
}
@@ -4110,9 +4173,27 @@ construct_point(PG_FUNCTION_ARGS)
static inline void
point_add_point(Point *result, Point *pt1, Point *pt2)
{
- point_construct(result,
- float8_pl(pt1->x, pt2->x),
- float8_pl(pt1->y, pt2->y));
+ (void) point_add_point_safe(result, pt1, pt2, NULL);
+}
+
+static inline bool
+point_add_point_safe(Point *result, Point *pt1, Point *pt2,
+ Node *escontext)
+{
+ float8 x;
+ float8 y;
+
+ x = float8_pl_safe(pt1->x, pt2->x, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ y = float8_pl_safe(pt1->y, pt2->y, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ point_construct(result, x, y);
+
+ return true;
}
Datum
@@ -4458,7 +4539,7 @@ path_poly(PG_FUNCTION_ARGS)
/* This is not very consistent --- other similar cases return NULL ... */
if (!path->closed)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("open path cannot be converted to polygon")));
@@ -4508,7 +4589,9 @@ poly_center(PG_FUNCTION_ARGS)
result = (Point *) palloc(sizeof(Point));
- poly_to_circle(&circle, poly);
+ if (!poly_to_circle_safe(&circle, poly, fcinfo->context))
+ PG_RETURN_NULL();
+
*result = circle.center;
PG_RETURN_POINT_P(result);
@@ -5191,14 +5274,30 @@ circle_box(PG_FUNCTION_ARGS)
box = (BOX *) palloc(sizeof(BOX));
- delta = float8_div(circle->radius, sqrt(2.0));
+ delta = float8_div_safe(circle->radius, sqrt(2.0), fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
- box->high.x = float8_pl(circle->center.x, delta);
- box->low.x = float8_mi(circle->center.x, delta);
- box->high.y = float8_pl(circle->center.y, delta);
- box->low.y = float8_mi(circle->center.y, delta);
+ box->high.x = float8_pl_safe(circle->center.x, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ box->low.x = float8_mi_safe(circle->center.x, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ box->high.y = float8_pl_safe(circle->center.y, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ box->low.y = float8_mi_safe(circle->center.y, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
PG_RETURN_BOX_P(box);
+
+fail:
+ PG_RETURN_NULL();
}
/* box_circle()
@@ -5209,15 +5308,35 @@ box_circle(PG_FUNCTION_ARGS)
{
BOX *box = PG_GETARG_BOX_P(0);
CIRCLE *circle;
+ float8 x;
+ float8 y;
circle = (CIRCLE *) palloc(sizeof(CIRCLE));
- circle->center.x = float8_div(float8_pl(box->high.x, box->low.x), 2.0);
- circle->center.y = float8_div(float8_pl(box->high.y, box->low.y), 2.0);
+ x = float8_pl_safe(box->high.x, box->low.x, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
- circle->radius = point_dt(&circle->center, &box->high);
+ circle->center.x = float8_div_safe(x, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ y = float8_pl_safe(box->high.y, box->low.y, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ circle->center.y = float8_div_safe(y, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ circle->radius = point_dt_safe(&circle->center, &box->high, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
PG_RETURN_CIRCLE_P(circle);
+
+fail:
+ PG_RETURN_NULL();
}
@@ -5281,10 +5400,11 @@ circle_poly(PG_FUNCTION_ARGS)
* XXX This algorithm should use weighted means of line segments
* rather than straight average values of points - tgl 97/01/21.
*/
-static void
-poly_to_circle(CIRCLE *result, POLYGON *poly)
+static bool
+poly_to_circle_safe(CIRCLE *result, POLYGON *poly, Node *escontext)
{
int i;
+ float8 x;
Assert(poly->npts > 0);
@@ -5293,14 +5413,42 @@ poly_to_circle(CIRCLE *result, POLYGON *poly)
result->radius = 0;
for (i = 0; i < poly->npts; i++)
- point_add_point(&result->center, &result->center, &poly->p[i]);
- result->center.x = float8_div(result->center.x, poly->npts);
- result->center.y = float8_div(result->center.y, poly->npts);
+ {
+ if (!point_add_point_safe(&result->center,
+ &result->center,
+ &poly->p[i],
+ escontext))
+ return false;
+ }
+
+ result->center.x = float8_div_safe(result->center.x,
+ poly->npts,
+ escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ result->center.y = float8_div_safe(result->center.y,
+ poly->npts,
+ escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
for (i = 0; i < poly->npts; i++)
- result->radius = float8_pl(result->radius,
- point_dt(&poly->p[i], &result->center));
- result->radius = float8_div(result->radius, poly->npts);
+ {
+ x = point_dt_safe(&poly->p[i], &result->center, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ result->radius = float8_pl_safe(result->radius, x, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+ }
+
+ result->radius = float8_div_safe(result->radius, poly->npts, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ return true;
}
Datum
@@ -5311,7 +5459,8 @@ poly_circle(PG_FUNCTION_ARGS)
result = (CIRCLE *) palloc(sizeof(CIRCLE));
- poly_to_circle(result, poly);
+ if (!poly_to_circle_safe(result, poly, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_CIRCLE_P(result);
}
diff --git a/src/include/utils/float.h b/src/include/utils/float.h
index fc2a9cf6475..c8abc7b3d78 100644
--- a/src/include/utils/float.h
+++ b/src/include/utils/float.h
@@ -30,9 +30,9 @@ extern PGDLLIMPORT int extra_float_digits;
/*
* Utility functions in float.c
*/
-pg_noreturn extern void float_overflow_error(void);
-pg_noreturn extern void float_underflow_error(void);
-pg_noreturn extern void float_zero_divide_error(void);
+extern void float_overflow_error(struct Node *escontext);
+extern void float_underflow_error(struct Node *escontext);
+extern void float_zero_divide_error(struct Node *escontext);
extern int is_infinite(float8 val);
extern float8 float8in_internal(char *num, char **endptr_p,
const char *type_name, const char *orig_string,
@@ -104,7 +104,22 @@ float4_pl(const float4 val1, const float4 val2)
result = val1 + val2;
if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error();
+ float_overflow_error(NULL);
+
+ return result;
+}
+
+static inline float8
+float8_pl_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+ float8 result;
+
+ result = val1 + val2;
+ if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
+ {
+ float_overflow_error(escontext);
+ return 0.0;
+ }
return result;
}
@@ -112,15 +127,10 @@ float4_pl(const float4 val1, const float4 val2)
static inline float8
float8_pl(const float8 val1, const float8 val2)
{
- float8 result;
-
- result = val1 + val2;
- if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error();
-
- return result;
+ return float8_pl_safe(val1, val2, NULL);
}
+
static inline float4
float4_mi(const float4 val1, const float4 val2)
{
@@ -128,7 +138,22 @@ float4_mi(const float4 val1, const float4 val2)
result = val1 - val2;
if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error();
+ float_overflow_error(NULL);
+
+ return result;
+}
+
+static inline float8
+float8_mi_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+ float8 result;
+
+ result = val1 - val2;
+ if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
+ {
+ float_overflow_error(escontext);
+ return 0.0;
+ }
return result;
}
@@ -136,15 +161,10 @@ float4_mi(const float4 val1, const float4 val2)
static inline float8
float8_mi(const float8 val1, const float8 val2)
{
- float8 result;
-
- result = val1 - val2;
- if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error();
-
- return result;
+ return float8_mi_safe(val1, val2, NULL);
}
+
static inline float4
float4_mul(const float4 val1, const float4 val2)
{
@@ -152,59 +172,89 @@ float4_mul(const float4 val1, const float4 val2)
result = val1 * val2;
if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0f) && val1 != 0.0f && val2 != 0.0f)
- float_underflow_error();
+ float_underflow_error(NULL);
return result;
}
static inline float8
-float8_mul(const float8 val1, const float8 val2)
+float8_mul_safe(const float8 val1, const float8 val2, struct Node *escontext)
{
float8 result;
result = val1 * val2;
if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error();
+ {
+ float_overflow_error(escontext);
+ return 0.0;
+ }
+
if (unlikely(result == 0.0) && val1 != 0.0 && val2 != 0.0)
- float_underflow_error();
+ {
+ float_underflow_error(escontext);
+ return 0.0;
+ }
return result;
}
+static inline float8
+float8_mul(const float8 val1, const float8 val2)
+{
+ return float8_mul_safe(val1, val2, NULL);
+}
+
static inline float4
float4_div(const float4 val1, const float4 val2)
{
float4 result;
if (unlikely(val2 == 0.0f) && !isnan(val1))
- float_zero_divide_error();
+ float_zero_divide_error(NULL);
result = val1 / val2;
if (unlikely(isinf(result)) && !isinf(val1))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0f) && val1 != 0.0f && !isinf(val2))
- float_underflow_error();
+ float_underflow_error(NULL);
return result;
}
static inline float8
-float8_div(const float8 val1, const float8 val2)
+float8_div_safe(const float8 val1, const float8 val2, struct Node *escontext)
{
float8 result;
if (unlikely(val2 == 0.0) && !isnan(val1))
- float_zero_divide_error();
+ {
+ float_zero_divide_error(escontext);
+ return 0.0;
+ }
+
result = val1 / val2;
if (unlikely(isinf(result)) && !isinf(val1))
- float_overflow_error();
+ {
+ float_overflow_error(escontext);
+ return 0.0;
+ }
+
if (unlikely(result == 0.0) && val1 != 0.0 && !isinf(val2))
- float_underflow_error();
+ {
+ float_underflow_error(escontext);
+ return 0.0;
+ }
return result;
}
+static inline float8
+float8_div(const float8 val1, const float8 val2)
+{
+ return float8_div_safe(val1, val2, NULL);
+}
+
/*
* Routines for NaN-aware comparisons
*
--
2.34.1
v13-0020-CAST-expr-AS-newtype-DEFAULT-ON-ERROR.patchtext/x-patch; charset=UTF-8; name=v13-0020-CAST-expr-AS-newtype-DEFAULT-ON-ERROR.patchDownload
From 75671a9e79e2be40218812a9754b6efc8ad0cb06 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Tue, 2 Dec 2025 15:47:42 +0800
Subject: [PATCH v13 20/20] CAST(expr AS newtype DEFAULT ON ERROR)
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.
draft doc also added.
We cannot simply prohibit user-defined functions in pg_cast for safe cast
evaluation because CREATE CAST can also utilize built-in functions. So, to
completely disallow custom casts created via CREATE CAST used in safe cast
evaluation, a new field in pg_cast would unfortunately be necessary.
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}
[0]: https://git.postgresql.org/cgit/postgresql.git/commit/?id=aaaf9449ec6be62cb0d30ed3588dc384f56274b
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
.../pg_stat_statements/expected/select.out | 23 +-
contrib/pg_stat_statements/sql/select.sql | 5 +
doc/src/sgml/catalogs.sgml | 12 +
doc/src/sgml/syntax.sgml | 29 +
src/backend/catalog/pg_cast.c | 1 +
src/backend/executor/execExpr.c | 61 +-
src/backend/executor/execExprInterp.c | 29 +
src/backend/jit/llvm/llvmjit_expr.c | 26 +
src/backend/nodes/nodeFuncs.c | 61 ++
src/backend/optimizer/util/clauses.c | 26 +
src/backend/parser/gram.y | 22 +
src/backend/parser/parse_agg.c | 9 +
src/backend/parser/parse_expr.c | 417 ++++++++-
src/backend/parser/parse_func.c | 3 +
src/backend/parser/parse_target.c | 14 +
src/backend/utils/adt/arrayfuncs.c | 8 +
src/backend/utils/adt/ruleutils.c | 21 +
src/include/catalog/pg_cast.dat | 330 ++++----
src/include/catalog/pg_cast.h | 4 +
src/include/executor/execExpr.h | 7 +
src/include/nodes/execnodes.h | 21 +
src/include/nodes/parsenodes.h | 11 +
src/include/nodes/primnodes.h | 36 +
src/include/parser/parse_node.h | 2 +
src/test/regress/expected/cast.out | 791 ++++++++++++++++++
src/test/regress/expected/create_cast.out | 5 +
src/test/regress/expected/opr_sanity.out | 24 +-
src/test/regress/parallel_schedule | 2 +-
src/test/regress/sql/cast.sql | 340 ++++++++
src/test/regress/sql/create_cast.sql | 1 +
src/tools/pgindent/typedefs.list | 3 +
31 files changed, 2150 insertions(+), 194 deletions(-)
create mode 100644 src/test/regress/expected/cast.out
create mode 100644 src/test/regress/sql/cast.sql
diff --git a/contrib/pg_stat_statements/expected/select.out b/contrib/pg_stat_statements/expected/select.out
index 75c896f3885..6e67997f0a2 100644
--- a/contrib/pg_stat_statements/expected/select.out
+++ b/contrib/pg_stat_statements/expected/select.out
@@ -73,6 +73,25 @@ SELECT 1 AS "int" OFFSET 2 FETCH FIRST 3 ROW ONLY;
-----
(0 rows)
+--error safe type cast
+SELECT CAST('a' AS int DEFAULT 2 ON CONVERSION ERROR);
+ int4
+------
+ 2
+(1 row)
+
+SELECT CAST('11' AS int DEFAULT 2 ON CONVERSION ERROR);
+ int4
+------
+ 11
+(1 row)
+
+SELECT CAST('12' AS numeric DEFAULT 2 ON CONVERSION ERROR);
+ numeric
+---------
+ 12
+(1 row)
+
-- DISTINCT and ORDER BY patterns
-- Try some query permutations which once produced identical query IDs
SELECT DISTINCT 1 AS "int";
@@ -222,6 +241,8 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
2 | 2 | SELECT $1 AS "int" ORDER BY 1
1 | 2 | SELECT $1 AS i UNION SELECT $2 ORDER BY i
1 | 1 | SELECT $1 || $2
+ 2 | 2 | SELECT CAST($1 AS int DEFAULT $2 ON CONVERSION ERROR)
+ 1 | 1 | SELECT CAST($1 AS numeric DEFAULT $2 ON CONVERSION ERROR)
2 | 2 | SELECT DISTINCT $1 AS "int"
0 | 0 | SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"
1 | 1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
@@ -230,7 +251,7 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
| | ) +
| | SELECT f FROM t ORDER BY f
1 | 1 | select $1::jsonb ? $2
-(17 rows)
+(19 rows)
SELECT pg_stat_statements_reset() IS NOT NULL AS t;
t
diff --git a/contrib/pg_stat_statements/sql/select.sql b/contrib/pg_stat_statements/sql/select.sql
index 11662cde08c..7ee8160fd84 100644
--- a/contrib/pg_stat_statements/sql/select.sql
+++ b/contrib/pg_stat_statements/sql/select.sql
@@ -25,6 +25,11 @@ SELECT 1 AS "int" LIMIT 3 OFFSET 3;
SELECT 1 AS "int" OFFSET 1 FETCH FIRST 2 ROW ONLY;
SELECT 1 AS "int" OFFSET 2 FETCH FIRST 3 ROW ONLY;
+--error safe type cast
+SELECT CAST('a' AS int DEFAULT 2 ON CONVERSION ERROR);
+SELECT CAST('11' AS int DEFAULT 2 ON CONVERSION ERROR);
+SELECT CAST('12' AS numeric DEFAULT 2 ON CONVERSION ERROR);
+
-- DISTINCT and ORDER BY patterns
-- Try some query permutations which once produced identical query IDs
SELECT DISTINCT 1 AS "int";
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2fc63442980..ccd1d256003 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1849,6 +1849,18 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<literal>b</literal> means that the types are binary-coercible, thus no conversion is required.
</para></entry>
</row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>casterrorsafe</structfield> <type>bool</type>
+ </para>
+ <para>
+ This indicates whether the <structfield>castfunc</structfield> function is error safe.
+ If the <structfield>castfunc</structfield> function is error safe, it can be used in error safe type cast.
+ For further details see <xref linkend="sql-syntax-type-casts-safe"/>.
+ </para></entry>
+ </row>
+
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml
index 34c83880a66..9b2482e9b99 100644
--- a/doc/src/sgml/syntax.sgml
+++ b/doc/src/sgml/syntax.sgml
@@ -2106,6 +2106,10 @@ CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable>
The <literal>CAST</literal> syntax conforms to SQL; the syntax with
<literal>::</literal> is historical <productname>PostgreSQL</productname>
usage.
+ The equivalent ON CONVERSION ERROR behavior is:
+<synopsis>
+CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable> ERROR ON CONVERSION ERROR )
+</synopsis>
</para>
<para>
@@ -2160,6 +2164,31 @@ CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable>
<xref linkend="sql-createcast"/>.
</para>
</note>
+
+ <sect3 id="sql-syntax-type-casts-safe">
+ <title>Safe Type Cast</title>
+ <para>
+Sometimes a type cast may fail; to handle such cases, an <literal>ON ERROR</literal> clause can be
+specified to avoid potential errors. The syntax is:
+<synopsis>
+CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable> DEFAULT <replaceable>expression</replaceable> ON CONVERSION ERROR )
+</synopsis>
+ If cast the source expression to target type fails, it falls back to
+ evaluating the optionally supplied default <replaceable>expression</replaceable>
+ specified in ON <literal>ON ERROR</literal> clause.
+ Currently, this only support built-in system type casts,
+ casts created using <link linkend="sql-createcast">CREATE CAST</link> are not supported.
+ </para>
+
+ <para>
+ For example, the following query attempts to cast a <type>text</type> type value to <type>integer</type> type,
+ but when the conversion fails, it falls back to evaluate the supplied default expression.
+<programlisting>
+SELECT CAST(TEXT 'error' AS integer DEFAULT NULL ON CONVERSION ERROR) IS NULL; <lineannotation>true</lineannotation>
+</programlisting>
+ </para>
+ </sect3>
+
</sect2>
<sect2 id="sql-syntax-collate-exprs">
diff --git a/src/backend/catalog/pg_cast.c b/src/backend/catalog/pg_cast.c
index 1773c9c5491..6fe65d24d31 100644
--- a/src/backend/catalog/pg_cast.c
+++ b/src/backend/catalog/pg_cast.c
@@ -84,6 +84,7 @@ CastCreate(Oid sourcetypeid, Oid targettypeid,
values[Anum_pg_cast_castfunc - 1] = ObjectIdGetDatum(funcid);
values[Anum_pg_cast_castcontext - 1] = CharGetDatum(castcontext);
values[Anum_pg_cast_castmethod - 1] = CharGetDatum(castmethod);
+ values[Anum_pg_cast_casterrorsafe - 1] = BoolGetDatum(false);
tuple = heap_form_tuple(RelationGetDescr(relation), values, nulls);
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index b302be36f73..1de0d465f56 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,
@@ -1742,6 +1745,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
elemstate->innermost_caseval = (Datum *) palloc(sizeof(Datum));
elemstate->innermost_casenull = (bool *) palloc(sizeof(bool));
+ elemstate->escontext = state->escontext;
ExecInitExprRec(acoerce->elemexpr, elemstate,
&elemstate->resvalue, &elemstate->resnull);
@@ -2218,6 +2222,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;
@@ -2784,7 +2796,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;
@@ -4782,6 +4794,53 @@ ExecBuildParamSetEqual(TupleDesc desc,
return state;
}
+/*
+ * Push steps to evaluate a SafeTypeCastExpr and its various subsidiary
+ * expressions.
+ */
+static void
+ExecInitSafeTypeCastExpr(SafeTypeCastExpr *stcexpr , ExprState *state,
+ Datum *resv, bool *resnull,
+ ExprEvalStep *scratch)
+{
+ /*
+ * If we can not coerce to the target type, fallback to the DEFAULT
+ * expression specified in the ON CONVERSION ERROR clause, and we are done.
+ */
+ if (stcexpr->cast_expr == NULL)
+ {
+ ExecInitExprRec((Expr *) stcexpr->default_expr,
+ state, resv, resnull);
+ return;
+ }
+ else
+ {
+ SafeTypeCastState *stcstate;
+ ErrorSaveContext *saved_escontext;
+
+ stcstate = palloc0(sizeof(SafeTypeCastState));
+ stcstate->stcexpr = stcexpr;
+ stcstate->escontext.type = T_ErrorSaveContext;
+ state->escontext = &stcstate->escontext;
+
+ /* evaluate argument expression into step's result area */
+ ExecInitExprRec((Expr *) stcexpr->cast_expr,
+ state, resv, resnull);
+ scratch->opcode = EEOP_SAFETYPE_CAST;
+ scratch->d.stcexpr.stcstate = stcstate;
+ ExprEvalPushStep(state, scratch);
+
+ /* Steps to evaluate the DEFAULT expression */
+ saved_escontext = state->escontext;
+ state->escontext = NULL;
+ ExecInitExprRec((Expr *) stcstate->stcexpr->default_expr,
+ state, resv, resnull);
+ state->escontext = saved_escontext;
+
+ 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 67f4e00eac4..f1d8df1ac41 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,28 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
EEO_NEXT();
}
+ EEO_CASE(EEOP_SAFETYPE_CAST)
+ {
+ SafeTypeCastState *stcstate = op->d.stcexpr.stcstate;
+
+ if (SOFT_ERROR_OCCURRED(&stcstate->escontext))
+ {
+ *op->resvalue = (Datum) 0;
+ *op->resnull = true;
+
+ /*
+ * Reset for next use such as for catching errors when coercing
+ * a expression.
+ */
+ stcstate->escontext.error_occurred = false;
+ stcstate->escontext.details_wanted = false;
+
+ EEO_NEXT();
+ }
+ else
+ EEO_JUMP(stcstate->jump_end);
+ }
+
EEO_CASE(EEOP_JSONEXPR_PATH)
{
/* too complex for an inline implementation */
@@ -3644,6 +3667,12 @@ 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/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index ac88881e995..bc7cd3283a5 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -2256,6 +2256,32 @@ llvm_compile_expr(ExprState *state)
LLVMBuildBr(b, opblocks[opno + 1]);
break;
+ case EEOP_SAFETYPE_CAST:
+ {
+ SafeTypeCastState *stcstate = op->d.stcexpr.stcstate;
+
+ if (SOFT_ERROR_OCCURRED(&stcstate->escontext))
+ {
+ /*
+ * Reset for next use such as for catching errors when
+ * coercing a expression.
+ */
+ stcstate->escontext.error_occurred = false;
+ stcstate->escontext.details_wanted = false;
+
+ /* set resnull to true */
+ LLVMBuildStore(b, l_sbool_const(1), v_resnullp);
+ /* reset resvalue */
+ LLVMBuildStore(b, l_datum_const(0), v_resvaluep);
+
+ LLVMBuildBr(b, opblocks[opno + 1]);
+ }
+ else
+ 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 d228318dc72..b3ae69068d0 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;
@@ -1550,6 +1561,9 @@ exprLocation(const Node *expr)
/* just use leftmost argument's location */
loc = exprLocation((Node *) ((const RowCompareExpr *) expr)->largs);
break;
+ case T_SafeTypeCastExpr:
+ loc = ((const SafeTypeCastExpr *) expr)->location;
+ break;
case T_CoalesceExpr:
/* COALESCE keyword should always be the first thing */
loc = ((const CoalesceExpr *) expr)->location;
@@ -2321,6 +2335,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:
@@ -3330,6 +3356,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;
@@ -4464,6 +4503,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->raw_default))
+ 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/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index e5dbd40cda2..04a8ca63459 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -2958,6 +2958,32 @@ 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);
+
+ /*
+ * We must not reduce any recognizably constant subexpressions
+ * in cast_expr here, since we don’t want it to fail
+ * prematurely.
+ */
+ 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 c3a0a354a9c..8a27e045bc0 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -16013,6 +16013,28 @@ func_expr_common_subexpr:
}
| CAST '(' a_expr AS Typename ')'
{ $$ = makeTypeCast($3, $5, @1); }
+ | CAST '(' a_expr AS Typename ERROR_P ON CONVERSION_P ERROR_P ')'
+ { $$ = makeTypeCast($3, $5, @1); }
+ | CAST '(' a_expr AS Typename NULL_P ON CONVERSION_P ERROR_P ')'
+ {
+ TypeCast *cast = (TypeCast *) makeTypeCast($3, $5, @1);
+
+ SafeTypeCast *safecast = makeNode(SafeTypeCast);
+ safecast->cast = (Node *) cast;
+ safecast->raw_default = makeNullAConst(-1);;
+
+ $$ = (Node *) safecast;
+ }
+ | CAST '(' a_expr AS Typename DEFAULT a_expr ON CONVERSION_P ERROR_P ')'
+ {
+ TypeCast *cast = (TypeCast *) makeTypeCast($3, $5, @1);
+
+ SafeTypeCast *safecast = makeNode(SafeTypeCast);
+ safecast->cast = (Node *) cast;
+ safecast->raw_default = $7;
+
+ $$ = (Node *) safecast;
+ }
| EXTRACT '(' extract_list ')'
{
$$ = (Node *) makeFuncCall(SystemFuncName("extract"),
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index b8340557b34..de77f6e9302 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -490,6 +490,12 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
err = _("grouping operations are not allowed in check constraints");
break;
+ case EXPR_KIND_CAST_DEFAULT:
+ if (isAgg)
+ err = _("aggregate functions are not allowed in CAST DEFAULT expressions");
+ else
+ err = _("grouping operations are not allowed in CAST DEFAULT expressions");
+ break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
@@ -983,6 +989,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_DOMAIN_CHECK:
err = _("window functions are not allowed in check constraints");
break;
+ case EXPR_KIND_CAST_DEFAULT:
+ err = _("window functions are not allowed in CAST DEFAULT expressions");
+ break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
err = _("window functions are not allowed in DEFAULT expressions");
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 44fd1385f8c..57010d6434e 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -17,6 +17,8 @@
#include "access/htup_details.h"
#include "catalog/pg_aggregate.h"
+#include "catalog/pg_cast.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -37,6 +39,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 +63,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 *can_coerce);
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 +80,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);
@@ -164,13 +169,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;
@@ -564,6 +573,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
case EXPR_KIND_VALUES_SINGLE:
case EXPR_KIND_CHECK_CONSTRAINT:
case EXPR_KIND_DOMAIN_CHECK:
+ case EXPR_KIND_CAST_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
case EXPR_KIND_INDEX_EXPRESSION:
case EXPR_KIND_INDEX_PREDICATE:
@@ -1824,6 +1834,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_DOMAIN_CHECK:
err = _("cannot use subquery in check constraint");
break;
+ case EXPR_KIND_CAST_DEFAULT:
+ err = _("cannot use subquery in CAST DEFAULT expression");
+ break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
err = _("cannot use subquery in DEFAULT expression");
@@ -2011,16 +2024,22 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
* 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().
+ *
+ * Most of the time, can_coerce will be NULL. It is not NULL only when
+ * performing parse analysis for CAST(DEFAULT ... ON CONVERSION ERROR)
+ * expression. It's default to true. If coercing array elements fails, it will
+ * be set to 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;
List *newcoercedelems = NIL;
ListCell *element;
Oid coerce_type;
+ Oid coerce_type_coll;
bool coerce_hard;
/*
@@ -2045,9 +2064,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
@@ -2088,6 +2108,9 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
}
else
{
+ /* Target type must valid for CAST DEFAULT */
+ Assert(can_coerce == NULL);
+
/* Can't handle an empty array without a target type */
if (newelems == NIL)
ereport(ERROR,
@@ -2125,6 +2148,8 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
coerce_hard = false;
}
+ coerce_type_coll = get_typcollation(coerce_type);
+
/*
* Coerce elements to target type
*
@@ -2134,13 +2159,55 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
* If the array's type was merely derived from the common type of its
* elements, then the elements are implicitly coerced to the common type.
* This is consistent with other uses of select_common_type().
+ *
+ * If can_coerce is not NULL, we need to safely test whether each element
+ * can be coerced to the target type, similar to what is done in
+ * transformTypeSafeCast.
*/
foreach(element, newelems)
{
Node *e = (Node *) lfirst(element);
- Node *newe;
+ bool expr_is_const = false;
+ Node *newe = NULL;
- if (coerce_hard)
+ if (can_coerce != NULL && IsA(e, Const))
+ expr_is_const = true;
+
+ if (expr_is_const)
+ {
+ /*
+ * We have to to use CoerceUnknownConstSafe rather than
+ * coerce_to_target_type. because coerce_to_target_type is not error
+ * safe.
+ */
+ if (*can_coerce && exprType(e) == UNKNOWNOID)
+ {
+ newe = CoerceUnknownConstSafe(pstate, e, coerce_type, typmod,
+ COERCION_IMPLICIT,
+ COERCE_IMPLICIT_CAST,
+ -1,
+ false);
+ if (newe == NULL)
+ {
+ newe = e;
+ *can_coerce = false;
+ }
+
+ newcoercedelems = lappend(newcoercedelems, newe);
+
+ continue;
+ }
+ }
+
+ if (can_coerce != NULL && (!*can_coerce))
+ {
+ /*
+ * Can not coerce, just append the transformed element expression to
+ * the list.
+ */
+ newe = e;
+ }
+ else if (coerce_hard)
{
newe = coerce_to_target_type(pstate, e,
exprType(e),
@@ -2150,12 +2217,74 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
COERCE_EXPLICIT_CAST,
-1);
if (newe == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_CANNOT_COERCE),
- errmsg("cannot cast type %s to %s",
- format_type_be(exprType(e)),
- format_type_be(coerce_type)),
- parser_errposition(pstate, exprLocation(e))));
+ {
+ if (can_coerce == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_CANNOT_COERCE),
+ errmsg("cannot cast type %s to %s",
+ format_type_be(exprType(e)),
+ format_type_be(coerce_type)),
+ parser_errposition(pstate, exprLocation(e))));
+ else
+ {
+ /*
+ * Can not coerce, just append the transformed element
+ * expression to the list.
+ */
+ newe = e;
+ *can_coerce = false;
+ }
+ }
+ else if (expr_is_const)
+ {
+ Node *origexpr = newe;
+ Node *result;
+ bool have_collate_expr = false;
+
+ if (IsA(newe, CollateExpr))
+ have_collate_expr = true;
+
+ while (newe && IsA(newe, CollateExpr))
+ newe = (Node *) ((CollateExpr *) newe)->arg;
+
+ if (!IsA(newe, FuncExpr))
+ newe = origexpr;
+ else
+ {
+ /*
+ * pre-evaluate simple constant cast expressions in a way
+ * that tolerate errors.
+ */
+ FuncExpr *newexpr = (FuncExpr *) copyObject(newe);
+
+ assign_expr_collations(pstate, (Node *) newexpr);
+
+ result = (Node *) evaluate_expr_extended((Expr *) newexpr,
+ coerce_type,
+ typmod,
+ coerce_type_coll,
+ true);
+
+ if (result == NULL)
+ {
+ newe = e;
+ *can_coerce = false;
+ }
+ else if (have_collate_expr)
+ {
+ /* Reinstall top CollateExpr */
+ CollateExpr *coll = (CollateExpr *) origexpr;
+ CollateExpr *newcoll = makeNode(CollateExpr);
+
+ newcoll->arg = (Expr *) result;
+ newcoll->collOid = coll->collOid;
+ newcoll->location = coll->location;
+ newe = (Node *) newcoll;
+ }
+ else
+ newe = result;
+ }
+ }
}
else
newe = coerce_to_common_type(pstate, e,
@@ -2743,7 +2872,8 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
(A_ArrayExpr *) arg,
targetBaseType,
elementType,
- targetBaseTypmod);
+ targetBaseTypmod,
+ NULL);
}
else
expr = transformExprRecurse(pstate, arg);
@@ -2780,6 +2910,263 @@ 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;
+ TypeCast *tcast = (TypeCast *) tc->cast;
+ Node *source_expr;
+ Node *def_expr;
+ Node *cast_expr = NULL;
+ Oid inputType;
+ Oid targetType;
+ Oid targetBaseType;
+ Oid typeColl;
+ int32 targetTypmod;
+ int32 targetBaseTypmod;
+ bool can_coerce = true;
+ bool expr_is_const = false;
+ ParseLoc location;
+
+ /* Look up the type name first */
+ typenameTypeIdAndMod(pstate, tcast->typeName, &targetType, &targetTypmod);
+ targetBaseTypmod = targetTypmod;
+
+ typeColl = get_typcollation(targetType);
+
+ targetBaseType = getBaseTypeAndTypmod(targetType, &targetBaseTypmod);
+
+ /* now looking at DEFAULT expression */
+ def_expr = transformExpr(pstate, tc->raw_default, EXPR_KIND_CAST_DEFAULT);
+
+ 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 coerce %s expression to type %s",
+ "CAST DEFAULT",
+ format_type_be(targetType)),
+ parser_coercion_errposition(pstate, exprLocation(tc->raw_default), def_expr));
+
+ assign_expr_collations(pstate, def_expr);
+
+ /*
+ * The collation of DEFAULT expression must match the collation of the
+ * target type.
+ */
+ if (OidIsValid(typeColl))
+ {
+ Oid defColl;
+
+ defColl = exprCollation(def_expr);
+
+ if (OidIsValid(defColl) && typeColl != defColl)
+ ereport(ERROR,
+ errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("the collation of CAST DEFAULT expression conflicts with target type collation"),
+ errdetail("\"%s\" versus \"%s\"",
+ get_collation_name(typeColl),
+ get_collation_name(defColl)),
+ parser_errposition(pstate, exprLocation(def_expr)));
+ }
+
+ /*
+ * If the type cast 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(tcast->arg, A_ArrayExpr))
+ {
+ 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.
+ */
+ elementType = get_element_type(targetBaseType);
+
+ if (OidIsValid(elementType))
+ {
+ source_expr = transformArrayExpr(pstate,
+ (A_ArrayExpr *) tcast->arg,
+ targetBaseType,
+ elementType,
+ targetBaseTypmod,
+ &can_coerce);
+ }
+ else
+ source_expr = transformExprRecurse(pstate, tcast->arg);
+ }
+ else
+ source_expr = transformExprRecurse(pstate, tcast->arg);
+
+ inputType = exprType(source_expr);
+ if (inputType == InvalidOid)
+ return (Node *) NULL; /* return NULL if NULL input */
+
+ /* Location of the coercion is the location of CAST symbol. */
+ location = tcast->location;
+
+ if (can_coerce && IsA(source_expr, Const))
+ {
+ expr_is_const = true;
+
+ /* coerce UNKNOWN Const to the target type */
+ if (exprType(source_expr) == UNKNOWNOID)
+ {
+ cast_expr = CoerceUnknownConstSafe(pstate, source_expr,
+ targetType, targetTypmod,
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ location, false);
+ if (cast_expr == NULL)
+ can_coerce = false;
+ }
+ }
+
+ if (can_coerce && cast_expr == NULL)
+ {
+ cast_expr = coerce_to_target_type(pstate, source_expr, inputType,
+ targetType, targetTypmod,
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ location);
+ if (cast_expr == NULL)
+ can_coerce = false;
+ }
+
+ if (can_coerce)
+ {
+ Node *origexpr = cast_expr;
+ bool have_collate_expr = false;
+
+ Assert(cast_expr != NULL);
+
+ if (IsA(cast_expr, CollateExpr))
+ have_collate_expr = true;
+
+ while (cast_expr && IsA(cast_expr, CollateExpr))
+ cast_expr = (Node *) ((CollateExpr *) cast_expr)->arg;
+
+ if (IsA(cast_expr, FuncExpr))
+ {
+ HeapTuple tuple;
+ ListCell *lc;
+ Node *sexpr;
+ Node *result;
+ FuncExpr *fexpr = (FuncExpr *) cast_expr;
+
+ lc = list_head(fexpr->args);
+ sexpr = (Node *) lfirst(lc);
+
+ /* Look in pg_cast */
+ tuple = SearchSysCache2(CASTSOURCETARGET,
+ ObjectIdGetDatum(exprType(sexpr)),
+ ObjectIdGetDatum(targetType));
+
+ if (HeapTupleIsValid(tuple))
+ {
+ Form_pg_cast castForm = (Form_pg_cast) GETSTRUCT(tuple);
+ if (!castForm->casterrorsafe)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot cast type %s to %s when %s expression is specified in %s",
+ format_type_be(inputType),
+ format_type_be(targetType),
+ "DEFAULT",
+ "CAST ... ON CONVERSION ERROR"),
+ errhint("Explicit cast is defined but definition is not declared as safe"),
+ parser_errposition(pstate, exprLocation(source_expr)));
+ }
+ else
+ elog(ERROR, "cache lookup failed for pg_cast entry (%s cast to %s)",
+ format_type_be(inputType),
+ format_type_be(targetType));
+ ReleaseSysCache(tuple);
+
+ if (!expr_is_const)
+ cast_expr = origexpr;
+ else
+ {
+ /*
+ * pre-evaluate simple constant cast expressions in a way that
+ * tolerate errors.
+ *
+ * Rationale:
+ * 1. When deparsing safe cast expressions (or in other cases),
+ * eval_const_expressions might be invoked, but it cannot
+ * handle errors gracefully.
+ * 2. If the cast expression involves only simple constants, we
+ * can safely evaluate it ahead of time. If the evaluation
+ * fails, it indicates that such a cast is not possible, and
+ * we can then fall back to the CAST DEFAULT expression.
+ * 3. Even if FuncExpr (for castfunc) node has three arguments,
+ * the second and third arguments will also be constants per
+ * coerce_to_target_type. Evaluating a FuncExpr whose
+ * arguments are all Const is safe.
+ */
+ FuncExpr *newexpr = (FuncExpr *) copyObject(cast_expr);
+
+ assign_expr_collations(pstate, (Node *) newexpr);
+
+ result = (Node *) evaluate_expr_extended((Expr *) newexpr,
+ targetType,
+ targetTypmod,
+ typeColl,
+ true);
+ if (result == NULL)
+ {
+ /* can not coerce, set can_coerce to false */
+ can_coerce = false;
+ cast_expr = NULL;
+ }
+ else if (have_collate_expr)
+ {
+ /* Reinstall top CollateExpr */
+ CollateExpr *coll = (CollateExpr *) origexpr;
+ CollateExpr *newcoll = makeNode(CollateExpr);
+
+ newcoll->arg = (Expr *) result;
+ newcoll->collOid = coll->collOid;
+ newcoll->location = coll->location;
+ cast_expr = (Node *) newcoll;
+ }
+ else
+ cast_expr = result;
+ }
+ }
+ else
+ /* Nothing to do, restore cast_expr to its original value */
+ cast_expr = origexpr;
+ }
+
+ Assert(can_coerce || cast_expr == NULL);
+
+ result = makeNode(SafeTypeCastExpr);
+ result->source_expr = source_expr;
+ result->cast_expr = cast_expr;
+ result->default_expr = def_expr;
+ result->resulttype = targetType;
+ result->resulttypmod = targetTypmod;
+ result->resultcollid = typeColl;
+ result->location = location;
+
+ return (Node *) result;
+}
+
/*
* Handle an explicit COLLATE clause.
*
@@ -3193,6 +3580,8 @@ ParseExprKindName(ParseExprKind exprKind)
case EXPR_KIND_CHECK_CONSTRAINT:
case EXPR_KIND_DOMAIN_CHECK:
return "CHECK";
+ case EXPR_KIND_CAST_DEFAULT:
+ return "CAST DEFAULT";
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
return "DEFAULT";
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 778d69c6f3c..a90705b9847 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2743,6 +2743,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
case EXPR_KIND_DOMAIN_CHECK:
err = _("set-returning functions are not allowed in check constraints");
break;
+ case EXPR_KIND_CAST_DEFAULT:
+ err = _("set-returning functions are not allowed in CAST DEFAULT expressions");
+ break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
err = _("set-returning functions are not allowed in DEFAULT expressions");
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 905c975d83b..dc03cf4ce74 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1822,6 +1822,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/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index a464349ee33..c1a7031a96c 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -3289,6 +3289,14 @@ array_map(Datum arrayd,
/* Apply the given expression to source element */
values[i] = ExecEvalExpr(exprstate, econtext, &nulls[i]);
+ /* Exit early if the evaluation fails */
+ 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 556ab057e5a..24820a739f3 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -10562,6 +10562,27 @@ get_rule_expr(Node *node, deparse_context *context,
}
break;
+ case T_SafeTypeCastExpr:
+ {
+ SafeTypeCastExpr *stcexpr = castNode(SafeTypeCastExpr, node);
+
+ /*
+ * Here, we cannot deparsing cast_expr directly, since
+ * transformTypeSafeCast may have folded it into a simple
+ * constant or NULL. Instead, we use source_expr and
+ * default_expr to reconstruct the CAST DEFAULT clause.
+ */
+ 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/include/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat
index fbfd669587f..ca52cfcd086 100644
--- a/src/include/catalog/pg_cast.dat
+++ b/src/include/catalog/pg_cast.dat
@@ -19,65 +19,65 @@
# int2->int4->int8->numeric->float4->float8, while casts in the
# reverse direction are assignment-only.
{ castsource => 'int8', casttarget => 'int2', castfunc => 'int2(int8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'int4', castfunc => 'int4(int8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'float4', castfunc => 'float4(int8)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'float8', castfunc => 'float8(int8)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'numeric', castfunc => 'numeric(int8)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'int8', castfunc => 'int8(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'int4', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'float4', castfunc => 'float4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'float8', castfunc => 'float8(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'numeric', castfunc => 'numeric(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'int8', castfunc => 'int8(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'int2', castfunc => 'int2(int4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'float4', castfunc => 'float4(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'float8', castfunc => 'float8(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'numeric', castfunc => 'numeric(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'int8', castfunc => 'int8(float4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'int2', castfunc => 'int2(float4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'int4', castfunc => 'int4(float4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'float8', castfunc => 'float8(float4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'numeric',
- castfunc => 'numeric(float4)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'numeric(float4)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'int8', castfunc => 'int8(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'int2', castfunc => 'int2(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'int4', castfunc => 'int4(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'float4', castfunc => 'float4(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'numeric',
- castfunc => 'numeric(float8)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'numeric(float8)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'int8', castfunc => 'int8(numeric)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'int2', castfunc => 'int2(numeric)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'int4', castfunc => 'int4(numeric)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'float4',
- castfunc => 'float4(numeric)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'float4(numeric)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'float8',
- castfunc => 'float8(numeric)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'float8(numeric)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'money', casttarget => 'numeric', castfunc => 'numeric(money)',
castcontext => 'a', castmethod => 'f' },
{ castsource => 'numeric', casttarget => 'money', castfunc => 'money(numeric)',
@@ -89,13 +89,13 @@
# Allow explicit coercions between int4 and bool
{ castsource => 'int4', casttarget => 'bool', castfunc => 'bool(int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'int4', castfunc => 'int4(bool)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Allow explicit coercions between xid8 and xid
{ castsource => 'xid8', casttarget => 'xid', castfunc => 'xid(xid8)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# OID category: allow implicit conversion from any integral type (including
# int8, to support OID literals > 2G) to OID, as well as assignment coercion
@@ -106,13 +106,13 @@
# casts from text and varchar to regclass, which exist mainly to support
# legacy forms of nextval() and related functions.
{ castsource => 'int8', casttarget => 'oid', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'oid', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'oid', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regproc', castfunc => '0',
@@ -120,13 +120,13 @@
{ castsource => 'regproc', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regproc', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regproc', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regproc', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regproc', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regproc', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'regproc', casttarget => 'regprocedure', castfunc => '0',
@@ -138,13 +138,13 @@
{ castsource => 'regprocedure', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regprocedure', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regprocedure', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regprocedure', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regprocedure', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regprocedure', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regoper', castfunc => '0',
@@ -152,13 +152,13 @@
{ castsource => 'regoper', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regoper', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regoper', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regoper', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regoper', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regoper', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'regoper', casttarget => 'regoperator', castfunc => '0',
@@ -170,13 +170,13 @@
{ castsource => 'regoperator', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regoperator', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regoperator', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regoperator', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regoperator', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regoperator', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regclass', castfunc => '0',
@@ -184,13 +184,13 @@
{ castsource => 'regclass', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regclass', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regclass', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regclass', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regclass', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regclass', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regcollation', castfunc => '0',
@@ -198,13 +198,13 @@
{ castsource => 'regcollation', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regcollation', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regcollation', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regcollation', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regcollation', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regcollation', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regtype', castfunc => '0',
@@ -212,13 +212,13 @@
{ castsource => 'regtype', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regtype', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regtype', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regtype', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regtype', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regtype', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regconfig', castfunc => '0',
@@ -226,13 +226,13 @@
{ castsource => 'regconfig', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regconfig', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regconfig', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regconfig', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regconfig', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regconfig', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regdictionary', castfunc => '0',
@@ -240,31 +240,31 @@
{ castsource => 'regdictionary', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regdictionary', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regdictionary', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regdictionary', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regdictionary', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regdictionary', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'text', casttarget => 'regclass', castfunc => 'regclass',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'regclass', castfunc => 'regclass',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'oid', casttarget => 'regrole', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regrole', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regrole', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regrole', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regrole', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regrole', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regrole', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regnamespace', castfunc => '0',
@@ -272,13 +272,13 @@
{ castsource => 'regnamespace', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regnamespace', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regnamespace', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regnamespace', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regnamespace', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regnamespace', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regdatabase', castfunc => '0',
@@ -286,13 +286,13 @@
{ castsource => 'regdatabase', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regdatabase', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regdatabase', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regdatabase', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regdatabase', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regdatabase', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
@@ -302,57 +302,57 @@
{ castsource => 'text', casttarget => 'varchar', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'bpchar', casttarget => 'text', castfunc => 'text(bpchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bpchar', casttarget => 'varchar', castfunc => 'text(bpchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'text', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'varchar', casttarget => 'bpchar', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'char', casttarget => 'text', castfunc => 'text(char)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'char', casttarget => 'bpchar', castfunc => 'bpchar(char)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'char', casttarget => 'varchar', castfunc => 'text(char)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'name', casttarget => 'text', castfunc => 'text(name)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'name', casttarget => 'bpchar', castfunc => 'bpchar(name)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'name', casttarget => 'varchar', castfunc => 'varchar(name)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'text', casttarget => 'char', castfunc => 'char(text)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bpchar', casttarget => 'char', castfunc => 'char(text)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'char', castfunc => 'char(text)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'text', casttarget => 'name', castfunc => 'name(text)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bpchar', casttarget => 'name', castfunc => 'name(bpchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'name', castfunc => 'name(varchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
# Allow explicit coercions between bytea and integer types
{ castsource => 'int2', casttarget => 'bytea', castfunc => 'bytea(int2)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'bytea', castfunc => 'bytea(int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'bytea', castfunc => 'bytea(int8)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bytea', casttarget => 'int2', castfunc => 'int2(bytea)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bytea', casttarget => 'int4', castfunc => 'int4(bytea)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bytea', casttarget => 'int8', castfunc => 'int8(bytea)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Allow explicit coercions between int4 and "char"
{ castsource => 'char', casttarget => 'int4', castfunc => 'int4(char)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'char', castfunc => 'char(int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# pg_node_tree can be coerced to, but not from, text
{ castsource => 'pg_node_tree', casttarget => 'text', castfunc => '0',
@@ -378,73 +378,73 @@
# Datetime category
{ castsource => 'date', casttarget => 'timestamp',
- castfunc => 'timestamp(date)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timestamp(date)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'date', casttarget => 'timestamptz',
- castfunc => 'timestamptz(date)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timestamptz(date)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'time', casttarget => 'interval', castfunc => 'interval(time)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'time', casttarget => 'timetz', castfunc => 'timetz(time)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'date',
- castfunc => 'date(timestamp)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'date(timestamp)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'time',
- castfunc => 'time(timestamp)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'time(timestamp)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'timestamptz',
- castfunc => 'timestamptz(timestamp)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timestamptz(timestamp)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'date',
- castfunc => 'date(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'date(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'time',
- castfunc => 'time(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'time(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'timestamp',
- castfunc => 'timestamp(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'timestamp(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'timetz',
- castfunc => 'timetz(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'timetz(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'interval', casttarget => 'time', castfunc => 'time(interval)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timetz', casttarget => 'time', castfunc => 'time(timetz)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
# Geometric category
{ castsource => 'point', casttarget => 'box', castfunc => 'box(point)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'lseg', casttarget => 'point', castfunc => 'point(lseg)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'path', casttarget => 'polygon', castfunc => 'polygon(path)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'box', casttarget => 'point', castfunc => 'point(box)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'box', casttarget => 'lseg', castfunc => 'lseg(box)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'box', casttarget => 'polygon', castfunc => 'polygon(box)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'box', casttarget => 'circle', castfunc => 'circle(box)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'polygon', casttarget => 'point', castfunc => 'point(polygon)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'polygon', casttarget => 'path', castfunc => 'path',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'polygon', casttarget => 'box', castfunc => 'box(polygon)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'polygon', casttarget => 'circle',
- castfunc => 'circle(polygon)', castcontext => 'e', castmethod => 'f' },
+ castfunc => 'circle(polygon)', castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'circle', casttarget => 'point', castfunc => 'point(circle)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'circle', casttarget => 'box', castfunc => 'box(circle)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'circle', casttarget => 'polygon',
- castfunc => 'polygon(circle)', castcontext => 'e', castmethod => 'f' },
+ castfunc => 'polygon(circle)', castcontext => 'e', castmethod => 'f'},
# MAC address category
{ castsource => 'macaddr', casttarget => 'macaddr8', castfunc => 'macaddr8',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'macaddr8', casttarget => 'macaddr', castfunc => 'macaddr',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
# INET category
{ castsource => 'cidr', casttarget => 'inet', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'inet', casttarget => 'cidr', castfunc => 'cidr',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
# BitString category
{ castsource => 'bit', casttarget => 'varbit', castfunc => '0',
@@ -454,13 +454,13 @@
# Cross-category casts between bit and int4, int8
{ castsource => 'int8', casttarget => 'bit', castfunc => 'bit(int8,int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'bit', castfunc => 'bit(int4,int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bit', casttarget => 'int8', castfunc => 'int8(bit)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bit', casttarget => 'int4', castfunc => 'int4(bit)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Cross-category casts to and from TEXT
# We need entries here only for a few specialized cases where the behavior
@@ -471,68 +471,68 @@
# behavior will ensue when the automatic cast is applied instead of the
# pg_cast entry!
{ castsource => 'cidr', casttarget => 'text', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'inet', casttarget => 'text', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'text', castfunc => 'text(bool)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'xml', casttarget => 'text', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'text', casttarget => 'xml', castfunc => 'xml',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Cross-category casts to and from VARCHAR
# We support all the same casts as for TEXT.
{ castsource => 'cidr', casttarget => 'varchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'inet', casttarget => 'varchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'varchar', castfunc => 'text(bool)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'xml', casttarget => 'varchar', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'varchar', casttarget => 'xml', castfunc => 'xml',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Cross-category casts to and from BPCHAR
# We support all the same casts as for TEXT.
{ castsource => 'cidr', casttarget => 'bpchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'inet', casttarget => 'bpchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'bpchar', castfunc => 'text(bool)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'xml', casttarget => 'bpchar', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'bpchar', casttarget => 'xml', castfunc => 'xml',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Length-coercion functions
{ castsource => 'bpchar', casttarget => 'bpchar',
castfunc => 'bpchar(bpchar,int4,bool)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'varchar',
castfunc => 'varchar(varchar,int4,bool)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'time', casttarget => 'time', castfunc => 'time(time,int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'timestamp',
castfunc => 'timestamp(timestamp,int4)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'timestamptz',
castfunc => 'timestamptz(timestamptz,int4)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'interval', casttarget => 'interval',
castfunc => 'interval(interval,int4)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timetz', casttarget => 'timetz',
- castfunc => 'timetz(timetz,int4)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timetz(timetz,int4)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bit', casttarget => 'bit', castfunc => 'bit(bit,int4,bool)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varbit', casttarget => 'varbit', castfunc => 'varbit',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'numeric',
- castfunc => 'numeric(numeric,int4)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'numeric(numeric,int4)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
# json to/from jsonb
{ castsource => 'json', casttarget => 'jsonb', castfunc => '0',
@@ -542,36 +542,36 @@
# jsonb to numeric and bool types
{ castsource => 'jsonb', casttarget => 'bool', castfunc => 'bool(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'numeric', castfunc => 'numeric(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'int2', castfunc => 'int2(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'int4', castfunc => 'int4(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'int8', castfunc => 'int8(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'float4', castfunc => 'float4(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'float8', castfunc => 'float8(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# range to multirange
{ castsource => 'int4range', casttarget => 'int4multirange',
castfunc => 'int4multirange(int4range)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8range', casttarget => 'int8multirange',
castfunc => 'int8multirange(int8range)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numrange', casttarget => 'nummultirange',
castfunc => 'nummultirange(numrange)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'daterange', casttarget => 'datemultirange',
castfunc => 'datemultirange(daterange)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'tsrange', casttarget => 'tsmultirange',
- castfunc => 'tsmultirange(tsrange)', castcontext => 'e', castmethod => 'f' },
+ castfunc => 'tsmultirange(tsrange)', castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'tstzrange', casttarget => 'tstzmultirange',
castfunc => 'tstzmultirange(tstzrange)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
]
diff --git a/src/include/catalog/pg_cast.h b/src/include/catalog/pg_cast.h
index 6a0ca337153..218d81d535a 100644
--- a/src/include/catalog/pg_cast.h
+++ b/src/include/catalog/pg_cast.h
@@ -47,6 +47,10 @@ CATALOG(pg_cast,2605,CastRelationId)
/* cast method */
char castmethod;
+
+ /* cast function error safe */
+ bool casterrorsafe BKI_DEFAULT(f);
+
} FormData_pg_cast;
/* ----------------
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 75366203706..937cbf3bc9b 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_SAFETYPE_CAST */
+ struct
+ {
+ struct SafeTypeCastState *stcstate;
+ } stcexpr;
+
/* for EEOP_JSONEXPR_PATH */
struct
{
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 64ff6996431..9018e190cc7 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1059,6 +1059,27 @@ typedef struct DomainConstraintState
ExprState *check_exprstate; /* check_expr's eval state, or NULL */
} DomainConstraintState;
+typedef struct SafeTypeCastState
+{
+ SafeTypeCastExpr *stcexpr;
+
+ /*
+ * Address to jump to when skipping all the steps to evaulate the default
+ * expression.
+ */
+ int jump_end;
+
+ /*
+ * For error-safe evaluation of coercions. 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 d14294a4ece..4b2eb340f97 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -400,6 +400,17 @@ typedef struct TypeCast
ParseLoc location; /* token location, or -1 if unknown */
} TypeCast;
+/*
+ * SafeTypeCast - a CAST(source_expr AS target_type) DEFAULT ON CONVERSION ERROR
+ * construct
+ */
+typedef struct SafeTypeCast
+{
+ NodeTag type;
+ Node *cast; /* TypeCast expression */
+ Node *raw_default; /* DEFAULT expression */
+} SafeTypeCast;
+
/*
* CollateClause - a COLLATE expression
*/
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 1b4436f2ff6..c3bd722cb2f 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -769,6 +769,42 @@ 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
+{
+ Expr xpr;
+
+ /* transformed expression being casted */
+ Node *source_expr;
+
+ /*
+ * The transformed cast expression; NULL if we can not cocerce source
+ * expression to the target type
+ */
+ Node *cast_expr pg_node_attr(query_jumble_ignore);
+
+ /* Fall back to the default expression if cast evaluation fails */
+ Node *default_expr;
+
+ /* cast result data type */
+ Oid resulttype;
+
+ /* cast result data type typmod (usually -1) */
+ int32 resulttypmod;
+
+ /* cast result data type collation */
+ Oid resultcollid;
+
+ /* Original SafeTypeCastExpr's location */
+ ParseLoc location;
+
+} SafeTypeCastExpr;
+
/*
* FuncExpr - expression node for a function call
*
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index f7d07c84542..9f5b32e0360 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -67,6 +67,8 @@ typedef enum ParseExprKind
EXPR_KIND_VALUES_SINGLE, /* single-row VALUES (in INSERT only) */
EXPR_KIND_CHECK_CONSTRAINT, /* CHECK constraint for a table */
EXPR_KIND_DOMAIN_CHECK, /* CHECK constraint for a domain */
+ EXPR_KIND_CAST_DEFAULT, /* default expression in
+ CAST DEFAULT ON CONVERSION ERROR */
EXPR_KIND_COLUMN_DEFAULT, /* default value for a table column */
EXPR_KIND_FUNCTION_DEFAULT, /* default parameter value for function */
EXPR_KIND_INDEX_EXPRESSION, /* index expression */
diff --git a/src/test/regress/expected/cast.out b/src/test/regress/expected/cast.out
new file mode 100644
index 00000000000..cb40ff0f200
--- /dev/null
+++ b/src/test/regress/expected/cast.out
@@ -0,0 +1,791 @@
+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(B'01' AS date DEFAULT NULL ON CONVERSION ERROR);
+ date
+------
+
+(1 row)
+
+SELECT CAST(BIT'01' AS date DEFAULT NULL ON CONVERSION ERROR);
+ date
+------
+
+(1 row)
+
+SELECT CAST(TRUE AS date DEFAULT NULL ON CONVERSION ERROR);
+ date
+------
+
+(1 row)
+
+SELECT CAST(1.1 AS date DEFAULT NULL ON CONVERSION ERROR);
+ date
+------
+
+(1 row)
+
+SELECT CAST(1 AS date DEFAULT NULL ON CONVERSION ERROR);
+ date
+------
+
+(1 row)
+
+SELECT CAST(1::numeric AS money DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type numeric to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(1::numeric AS money DEFAULT NULL ON CONVERSION E...
+ ^
+HINT: Explicit cast is defined but definition is not declared as safe
+SELECT CAST(1111 AS "char" DEFAULT NULL ON CONVERSION ERROR);
+ char
+------
+
+(1 row)
+
+--the default expression’s collation should match the collation of the cast
+--target type
+VALUES (CAST('error' AS text DEFAULT '1' COLLATE "C" ON CONVERSION ERROR));
+ERROR: the collation of CAST DEFAULT expression conflicts with target type collation
+LINE 1: VALUES (CAST('error' AS text DEFAULT '1' COLLATE "C" ON CONV...
+ ^
+DETAIL: "default" versus "C"
+VALUES (CAST('error' AS int2vector DEFAULT '1 3' ON CONVERSION ERROR));
+ column1
+---------
+ 1 3
+(1 row)
+
+VALUES (CAST('error' AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR));
+ column1
+---------
+ {"1 3"}
+(1 row)
+
+-- test source expression contain subquery
+SELECT CAST((SELECT b FROM generate_series(1, 1), (VALUES('H')) s(b))
+ AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR);
+ b
+---------
+ {"1 3"}
+(1 row)
+
+SELECT CAST((SELECT ARRAY((SELECT b FROM generate_series(1, 2) g, (VALUES('H')) s(b))))
+ AS INT[]
+ DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+CREATE FUNCTION ret_int8() RETURNS BIGINT AS
+$$
+BEGIN RETURN 2147483648; END;
+$$
+LANGUAGE plpgsql IMMUTABLE;
+SELECT CAST('a' as int DEFAULT ret_int8() ON CONVERSION ERROR); --error
+ERROR: integer out of range
+SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERROR); --error
+ERROR: cannot coerce CAST DEFAULT expression to type date
+LINE 1: SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERR...
+ ^
+-- test array coerce
+SELECT CAST(array['a'::text] AS int[] DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR: invalid input syntax for type integer: "a"
+SELECT CAST(array['a'] AS int[] DEFAULT NULL ON CONVERSION ERROR); --ok
+ array
+-------
+
+(1 row)
+
+SELECT CAST(array[11.12] AS date[] DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+SELECT CAST(array[11111111111111111] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+SELECT CAST(array[['abc'],[456]] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+SELECT CAST('{123,abc,456}' AS int[] DEFAULT '{-789}' ON CONVERSION ERROR);
+ int4
+--------
+ {-789}
+(1 row)
+
+SELECT CAST('{234,def,567}'::text[] AS integer[] DEFAULT '{-1011}' ON CONVERSION ERROR);
+ int4
+---------
+ {-1011}
+(1 row)
+
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS int[] DEFAULT '{1,2}' ON CONVERSION ERROR);
+ array
+-------
+ {1,2}
+(1 row)
+
+SELECT CAST(ARRAY[['1', '2'], ['three', 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --ok
+ array
+---------
+ {21,22}
+(1 row)
+
+SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --error
+ERROR: invalid input syntax for type integer: "three"
+LINE 1: SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] ...
+ ^
+SELECT CAST(ARRAY[1, 'three'::int] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --error
+ERROR: invalid input syntax for type integer: "three"
+LINE 1: SELECT CAST(ARRAY[1, 'three'::int] AS int[] DEFAULT '{21,22}...
+ ^
+-- test valid DEFAULT expression for CAST = ON CONVERSION ERROR
+CREATE 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: set-returning functions are not allowed in CAST DEFAULT expressions
+LINE 1: SELECT CAST('a' as int DEFAULT ret_setint() ON CONVERSION ER...
+ ^
+SELECT CAST(c::date::json as date DEFAULT NULL ON CONVERSION ERROR) FROM tcast; --error
+ERROR: cannot cast type date to json
+LINE 1: SELECT CAST(c::date::json as date DEFAULT NULL ON CONVERSION...
+ ^
+SELECT CAST('a'::int as int DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR: invalid input syntax for type integer: "a"
+LINE 1: SELECT CAST('a'::int as int DEFAULT NULL ON CONVERSION ERROR...
+ ^
+SELECT CAST('a' as int DEFAULT NULL ON CONVERSION ERROR); --ok
+ int4
+------
+
+(1 row)
+
+SELECT CAST('a' as int DEFAULT sum(1) ON CONVERSION ERROR); --error
+ERROR: aggregate functions are not allowed in CAST DEFAULT expressions
+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: window functions are not allowed in CAST DEFAULT expressions
+LINE 1: SELECT CAST('a' as int DEFAULT sum(1) over() ON CONVERSION E...
+ ^
+SELECT CAST('a' as int DEFAULT (SELECT NULL) ON CONVERSION ERROR); --error
+ERROR: cannot use subquery in CAST DEFAULT expression
+LINE 1: SELECT CAST('a' as int DEFAULT (SELECT NULL) 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 DOMAIN d_varchar as varchar(3) NOT NULL;
+CREATE TYPE comp_domain_with_typmod AS (a d_char3_not_null, b int);
+CREATE TYPE comp2 AS (a d_varchar);
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION ERROR); --error
+ERROR: value too long for type character varying(3)
+LINE 1: SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION...
+ ^
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(123)' ON CONVERSION ERROR); --ok
+ comp2
+-------
+ (123)
+(1 row)
+
+SELECT CAST(11 AS d_int42 DEFAULT 41 ON CONVERSION ERROR); --error
+ERROR: value for domain d_int42 violates check constraint "d_int42_check"
+SELECT CAST(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)'...
+ ^
+-----safe cast with geometry data type
+SELECT CAST('(1,2)'::point AS box DEFAULT NULL ON CONVERSION ERROR);
+ box
+-------------
+ (1,2),(1,2)
+(1 row)
+
+SELECT CAST('[(NaN,1),(NaN,infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+----------------
+ (NaN,Infinity)
+(1 row)
+
+SELECT CAST('[(1e+300,Infinity),(1e+300,Infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+-------------------
+ (1e+300,Infinity)
+(1 row)
+
+SELECT CAST('[(1,2),(3,4)]'::path as polygon DEFAULT NULL ON CONVERSION ERROR);
+ polygon
+---------
+
+(1 row)
+
+SELECT CAST('(NaN,1.0,NaN,infinity)'::box AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+----------------
+ (NaN,Infinity)
+(1 row)
+
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS lseg DEFAULT NULL ON CONVERSION ERROR);
+ lseg
+---------------
+ [(2,2),(0,0)]
+(1 row)
+
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS polygon DEFAULT NULL ON CONVERSION ERROR);
+ polygon
+---------------------------
+ ((0,0),(0,2),(2,2),(2,0))
+(1 row)
+
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS path DEFAULT NULL ON CONVERSION ERROR);
+ path
+------
+
+(1 row)
+
+SELECT CAST('(2.0,infinity,NaN,infinity)'::box AS circle DEFAULT NULL ON CONVERSION ERROR);
+ circle
+----------------------
+ <(NaN,Infinity),NaN>
+(1 row)
+
+SELECT CAST('(NaN,0.0),(2.0,4.0),(0.0,infinity)'::polygon AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+----------------
+ (NaN,Infinity)
+(1 row)
+
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS path DEFAULT NULL ON CONVERSION ERROR);
+ path
+---------------------
+ ((2,0),(2,4),(0,0))
+(1 row)
+
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS box DEFAULT NULL ON CONVERSION ERROR);
+ box
+-------------
+ (2,4),(0,0)
+(1 row)
+
+SELECT CAST('(NaN,infinity),(2.0,4.0),(0.0,infinity)'::polygon AS circle DEFAULT NULL ON CONVERSION ERROR);
+ circle
+----------------------
+ <(NaN,Infinity),NaN>
+(1 row)
+
+SELECT CAST('<(5,1),3>'::circle AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+-------
+ (5,1)
+(1 row)
+
+SELECT CAST('<(3,5),0>'::circle as box DEFAULT NULL ON CONVERSION ERROR);
+ box
+-------------
+ (3,5),(3,5)
+(1 row)
+
+-- not supported because the cast from circle to polygon is implemented as a SQL
+-- function, which cannot be error-safe.
+SELECT CAST('<(3,5),0>'::circle as polygon DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type circle to polygon when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST('<(3,5),0>'::circle as polygon DEFAULT NULL ON C...
+ ^
+HINT: Explicit cast is defined but definition is not declared as safe
+-----safe cast from/to money type is not supported, all the following would result error
+SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type bigint to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION E...
+ ^
+HINT: Explicit cast is defined but definition is not declared as safe
+SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type integer to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION E...
+ ^
+HINT: Explicit cast is defined but definition is not declared as safe
+SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type numeric to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSIO...
+ ^
+HINT: Explicit cast is defined but definition is not declared as safe
+SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type money to numeric when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSIO...
+ ^
+HINT: Explicit cast is defined but definition is not declared as safe
+-----safe cast from bytea type to other data types
+SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT NULL ON CONVERSION ERROR);
+ int8
+------
+
+(1 row)
+
+SELECT CAST('\x123456789A'::bytea AS int4 DEFAULT NULL ON CONVERSION ERROR);
+ int4
+------
+
+(1 row)
+
+SELECT CAST('\x123456'::bytea AS int2 DEFAULT NULL ON CONVERSION ERROR);
+ int2
+------
+
+(1 row)
+
+-----safe cast from bit type to other data types
+SELECT CAST('111111111100001'::bit(100) AS INT DEFAULT NULL ON CONVERSION ERROR);
+ int4
+------
+
+(1 row)
+
+SELECT CAST ('111111111100001'::bit(100) AS INT8 DEFAULT NULL ON CONVERSION ERROR);
+ int8
+------
+
+(1 row)
+
+-----safe cast from text type to other data types
+select CAST('a.b.c.d'::text as regclass default NULL on conversion error);
+ regclass
+----------
+
+(1 row)
+
+CREATE TABLE test_safecast(col0 text);
+INSERT INTO test_safecast(col0) VALUES ('<value>one</value');
+SELECT col0 as text,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) as to_regclass,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) IS NULL as expect_true,
+ CAST(col0 AS "char" DEFAULT NULL ON CONVERSION ERROR) as to_char,
+ CAST(col0 AS name DEFAULT NULL ON CONVERSION ERROR) as to_name,
+ CAST(col0 AS xml DEFAULT NULL ON CONVERSION ERROR) as to_xml
+FROM test_safecast;
+ text | to_regclass | expect_true | to_char | to_name | to_xml
+-------------------+-------------+-------------+---------+-------------------+--------
+ <value>one</value | | t | < | <value>one</value |
+(1 row)
+
+SELECT CAST('192.168.1.x' as inet DEFAULT NULL ON CONVERSION ERROR);
+ inet
+------
+
+(1 row)
+
+SELECT CAST('192.168.1.2/30' as cidr DEFAULT NULL ON CONVERSION ERROR);
+ cidr
+------
+
+(1 row)
+
+SELECT CAST('22:00:5c:08:55:08:01:02'::macaddr8 as macaddr DEFAULT NULL ON CONVERSION ERROR);
+ macaddr
+---------
+
+(1 row)
+
+-----safe cast betweeen range data types
+SELECT CAST('[1,2]'::int4range AS int4multirange DEFAULT NULL ON CONVERSION ERROR);
+ int4multirange
+----------------
+ {[1,3)}
+(1 row)
+
+SELECT CAST('[1,2]'::int8range AS int8multirange DEFAULT NULL ON CONVERSION ERROR);
+ int8multirange
+----------------
+ {[1,3)}
+(1 row)
+
+SELECT CAST('[1,2]'::numrange AS nummultirange DEFAULT NULL ON CONVERSION ERROR);
+ nummultirange
+---------------
+ {[1,2]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::daterange AS datemultirange DEFAULT NULL ON CONVERSION ERROR);
+ datemultirange
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::tsrange AS tsmultirange DEFAULT NULL ON CONVERSION ERROR);
+ tsmultirange
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::tstzrange AS tstzmultirange DEFAULT NULL ON CONVERSION ERROR);
+ tstzmultirange
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+-----safe cast betweeen numeric data types
+CREATE TABLE test_safecast1(
+ col1 float4, col2 float8, col3 numeric, col4 numeric[],
+ col5 int2 default 32767,
+ col6 int4 default 32768,
+ col7 int8 default 4294967296);
+INSERT INTO test_safecast1 VALUES('11.1234', '11.1234', '9223372036854775808', '{11.1234}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('inf', 'inf', 'inf', '{11.1234, 12, inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('-inf', '-inf', '-inf', '{11.1234, 12, -inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('NaN', 'NaN', 'NaN', '{11.1234, 12, -inf, NaN}'::numeric[]);
+SELECT col5 as int2,
+ CAST(col5 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col5 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col5 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col5 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col5 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col5 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col5 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col5 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ int2 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+(4 rows)
+
+SELECT col6 as int4,
+ CAST(col6 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col6 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col6 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col6 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col6 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col6 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col6 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col6 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ int4 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+(4 rows)
+
+SELECT col7 as int8,
+ CAST(col7 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col7 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col7 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col7 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col7 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col7 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col7 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col7 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ int8 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+------------+---------+---------+--------+------------+-------------+------------+------------+------------------
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+(4 rows)
+
+SELECT col3 as numeric,
+ CAST(col3 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col3 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col3 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col3 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col3 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col3 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col3 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col3 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ numeric | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+---------------------+---------+---------+--------+---------+-------------+----------------------+---------------------+------------------
+ 9223372036854775808 | | | | | 9.22337e+18 | 9.22337203685478e+18 | 9223372036854775808 |
+ Infinity | | | | | Infinity | Infinity | Infinity |
+ -Infinity | | | | | -Infinity | -Infinity | -Infinity |
+ NaN | | | | | NaN | NaN | NaN | NaN
+(4 rows)
+
+SELECT col4 as num_arr,
+ CAST(col4 AS int2[] DEFAULT NULL ON CONVERSION ERROR) as int2arr,
+ CAST(col4 AS int4[] DEFAULT NULL ON CONVERSION ERROR) as int4arr,
+ CAST(col4 as int8[] DEFAULT NULL ON CONVERSION ERROR) as int8arr,
+ CAST(col4 as float4[] DEFAULT NULL ON CONVERSION ERROR) as f4arr,
+ CAST(col4 as float8[] DEFAULT NULL ON CONVERSION ERROR) as f8arr,
+ CAST(col4 as numeric(10,1)[] DEFAULT NULL ON CONVERSION ERROR) as numarr
+FROM test_safecast1;
+ num_arr | int2arr | int4arr | int8arr | f4arr | f8arr | numarr
+----------------------------+---------+---------+---------+----------------------------+----------------------------+--------
+ {11.1234} | {11} | {11} | {11} | {11.1234} | {11.1234} | {11.1}
+ {11.1234,12,Infinity,NaN} | | | | {11.1234,12,Infinity,NaN} | {11.1234,12,Infinity,NaN} |
+ {11.1234,12,-Infinity,NaN} | | | | {11.1234,12,-Infinity,NaN} | {11.1234,12,-Infinity,NaN} |
+ {11.1234,12,-Infinity,NaN} | | | | {11.1234,12,-Infinity,NaN} | {11.1234,12,-Infinity,NaN} |
+(4 rows)
+
+SELECT col1 as float4,
+ CAST(col1 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col1 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col1 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col1 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col1 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col1 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col1 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col1 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ float4 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-----------+---------+---------+--------+---------+-----------+------------------+------------+------------------
+ 11.1234 | 11 | 11 | | 11 | 11.1234 | 11.1233997344971 | 11.1234 | 11.1
+ Infinity | | | | | Infinity | Infinity | Infinity |
+ -Infinity | | | | | -Infinity | -Infinity | -Infinity |
+ NaN | | | | | NaN | NaN | NaN | NaN
+(4 rows)
+
+SELECT col2 as float8,
+ CAST(col2 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col2 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col2 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col2 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col2 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col2 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col2 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col2 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ float8 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-----------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 11.1234 | 11 | 11 | | 11 | 11.1234 | 11.1234 | 11.1234 | 11.1
+ Infinity | | | | | Infinity | Infinity | Infinity |
+ -Infinity | | | | | -Infinity | -Infinity | -Infinity |
+ NaN | | | | | NaN | NaN | NaN | NaN
+(4 rows)
+
+-- date/timestamp/interval related safe type cast
+CREATE TABLE test_safecast2(
+ col0 date, col1 timestamp, col2 timestamptz,
+ col3 interval, col4 time, col5 timetz);
+INSERT INTO test_safecast2 VALUES
+('-infinity', '-infinity', '-infinity', '-infinity',
+ '2003-03-07 15:36:39 America/New_York', '2003-07-07 15:36:39 America/New_York'),
+('-infinity', 'infinity', 'infinity', 'infinity',
+ '11:59:59.99 PM', '11:59:59.99 PM PDT');
+SELECT col0 as date,
+ CAST(col0 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col0 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col0 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col0 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col0 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+ date | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale
+-----------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity | -infinity | | | -infinity
+ -infinity | -infinity | -infinity | | | -infinity
+(2 rows)
+
+SELECT col1 as timestamp,
+ CAST(col1 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col1 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col1 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col1 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col1 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+ timestamp | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale
+-----------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity | -infinity | | | -infinity
+ infinity | infinity | infinity | | | infinity
+(2 rows)
+
+SELECT col2 as timestamptz,
+ CAST(col2 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col2 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col2 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col2 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col2 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+ timestamptz | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale
+-------------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity | -infinity | | | -infinity
+ infinity | infinity | infinity | | | infinity
+(2 rows)
+
+SELECT col3 as interval,
+ CAST(col3 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col3 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col3 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col3 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col3 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale,
+ CAST(col3 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+ interval | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale | to_interval_scale
+-----------+----------------+---------+----------+-----------+--------------------+-------------------
+ -infinity | | | | | | -infinity
+ infinity | | | | | | infinity
+(2 rows)
+
+SELECT col4 as time,
+ CAST(col4 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col4 AS timetz DEFAULT NULL ON CONVERSION ERROR) IS NOT NULL as to_timetz,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+ time | to_times | to_timetz | to_interval | to_interval_scale
+-------------+-------------+-----------+-------------------------------+-------------------------------
+ 15:36:39 | 15:36:39 | t | @ 15 hours 36 mins 39 secs | @ 15 hours 36 mins 39 secs
+ 23:59:59.99 | 23:59:59.99 | t | @ 23 hours 59 mins 59.99 secs | @ 23 hours 59 mins 59.99 secs
+(2 rows)
+
+SELECT col5 as timetz,
+ CAST(col5 AS time DEFAULT NULL ON CONVERSION ERROR) as to_time,
+ CAST(col5 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_time_scale,
+ CAST(col5 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col5 AS timetz(6) DEFAULT NULL ON CONVERSION ERROR) as to_timetz_scale,
+ CAST(col5 AS interval DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col5 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+ timetz | to_time | to_time_scale | to_timetz | to_timetz_scale | to_interval | to_interval_scale
+----------------+-------------+---------------+----------------+-----------------+-------------+-------------------
+ 15:36:39-04 | 15:36:39 | 15:36:39 | 15:36:39-04 | 15:36:39-04 | |
+ 23:59:59.99-07 | 23:59:59.99 | 23:59:59.99 | 23:59:59.99-07 | 23:59:59.99-07 | |
+(2 rows)
+
+CREATE TABLE test_safecast3(col0 jsonb);
+INSERT INTO test_safecast3(col0) VALUES ('"test"');
+SELECT col0 as jsonb,
+ CAST(col0 AS integer DEFAULT NULL ON CONVERSION ERROR) as to_integer,
+ CAST(col0 AS numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col0 AS bigint DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col0 AS float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col0 AS float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col0 AS boolean DEFAULT NULL ON CONVERSION ERROR) as to_bool,
+ CAST(col0 AS smallint DEFAULT NULL ON CONVERSION ERROR) as to_smallint
+FROM test_safecast3;
+ jsonb | to_integer | to_numeric | to_int8 | to_float4 | to_float8 | to_bool | to_smallint
+--------+------------+------------+---------+-----------+-----------+---------+-------------
+ "test" | | | | | | |
+(1 row)
+
+-- test deparse
+CREATE DOMAIN d_int_arr as int[];
+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 test_safecast1,
+ CAST(ARRAY[['1'], ['three'],['a']] AS int[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1,
+ CAST(ARRAY[['1', '2'], ['three', 'a']] AS date[] DEFAULT NULL ON CONVERSION ERROR) as cast2,
+ CAST(ARRAY['three'] AS INT[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast3;
+\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 test_safecast1,
+ CAST(ARRAY[ARRAY[1], ARRAY['three'], ARRAY['a']] AS integer[] DEFAULT '{1,2}'::integer[] ON CONVERSION ERROR) AS cast1,
+ CAST(ARRAY[ARRAY['1', '2'], ARRAY['three', 'a']] AS date[] DEFAULT NULL::date[] ON CONVERSION ERROR) AS cast2,
+ CAST(ARRAY['three'] AS integer[] DEFAULT '{1,2}'::integer[] ON CONVERSION ERROR) AS cast3
+CREATE VIEW safecastview1 AS
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS d_int_arr DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1,
+ CAST(ARRAY[['1', '2'], ['three', 'a']] AS d_int_arr DEFAULT '{21,22}' ON CONVERSION ERROR) as cast2;
+\sv safecastview1
+CREATE OR REPLACE VIEW public.safecastview1 AS
+ SELECT CAST(ARRAY[ARRAY[1], ARRAY['three'], ARRAY['a']] AS d_int_arr DEFAULT '{1,2}'::integer[]::d_int_arr ON CONVERSION ERROR) AS cast1,
+ CAST(ARRAY[ARRAY[1, 2], ARRAY['three', 'a']] AS d_int_arr DEFAULT '{21,22}'::integer[]::d_int_arr ON CONVERSION ERROR) AS cast2
+SELECT * FROM safecastview1;
+ cast1 | cast2
+-------+---------
+ {1,2} | {21,22}
+(1 row)
+
+--error, default expression is mutable
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as int DEFAULT random(min=>1, max=>1) ON CONVERSION ERROR)));
+ERROR: functions in index expression must be marked IMMUTABLE
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as xid DEFAULT NULL ON CONVERSION ERROR)));
+ERROR: data type xid has no default operator class for access method "btree"
+HINT: You must specify an operator class for the index or define a default operator class for the data type.
+CREATE INDEX test_safecast3_idx ON test_safecast3((CAST(col0 as int DEFAULT NULL ON CONVERSION ERROR))); --ok
+SELECT pg_get_indexdef('test_safecast3_idx'::regclass);
+ pg_get_indexdef
+------------------------------------------------------------------------------------------------------------------------------------------
+ CREATE INDEX test_safecast3_idx ON public.test_safecast3 USING btree ((CAST(col0 AS integer DEFAULT NULL::integer ON CONVERSION ERROR)))
+(1 row)
+
+DROP TABLE test_safecast;
+DROP TABLE test_safecast1;
+DROP TABLE test_safecast2;
+DROP TABLE test_safecast3;
+DROP TABLE tcast;
+DROP FUNCTION ret_int8;
+DROP FUNCTION ret_setint;
+DROP TYPE comp_domain_with_typmod;
+DROP TYPE comp2;
+DROP DOMAIN d_varchar;
+DROP DOMAIN d_int42;
+DROP DOMAIN d_char3_not_null;
+RESET extra_float_digits;
diff --git a/src/test/regress/expected/create_cast.out b/src/test/regress/expected/create_cast.out
index 0e69644bca2..f4035c338aa 100644
--- a/src/test/regress/expected/create_cast.out
+++ b/src/test/regress/expected/create_cast.out
@@ -88,6 +88,11 @@ SELECT 1234::int4::casttesttype; -- Should work now
bar1234
(1 row)
+SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- error
+ERROR: cannot cast type integer to casttesttype when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVE...
+ ^
+HINT: Explicit cast is defined but definition is not declared as safe
-- check dependencies generated for that
SELECT pg_describe_object(classid, objid, objsubid) as obj,
pg_describe_object(refclassid, refobjid, refobjsubid) as objref,
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index a357e1d0c0e..81ea244859f 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -943,8 +943,8 @@ SELECT *
FROM pg_cast c
WHERE castsource = 0 OR casttarget = 0 OR castcontext NOT IN ('e', 'a', 'i')
OR castmethod NOT IN ('f', 'b' ,'i');
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Check that castfunc is nonzero only for cast methods that need a function,
@@ -953,8 +953,8 @@ SELECT *
FROM pg_cast c
WHERE (castmethod = 'f' AND castfunc = 0)
OR (castmethod IN ('b', 'i') AND castfunc <> 0);
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Look for casts to/from the same type that aren't length coercion functions.
@@ -963,15 +963,15 @@ WHERE (castmethod = 'f' AND castfunc = 0)
SELECT *
FROM pg_cast c
WHERE castsource = casttarget AND castfunc = 0;
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
SELECT c.*
FROM pg_cast c, pg_proc p
WHERE c.castfunc = p.oid AND p.pronargs < 2 AND castsource = casttarget;
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Look for cast functions that don't have the right signature. The
@@ -989,8 +989,8 @@ WHERE c.castfunc = p.oid AND
OR (c.castsource = 'character'::regtype AND
p.proargtypes[0] = 'text'::regtype))
OR NOT binary_coercible(p.prorettype, c.casttarget));
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
SELECT c.*
@@ -998,8 +998,8 @@ FROM pg_cast c, pg_proc p
WHERE c.castfunc = p.oid AND
((p.pronargs > 1 AND p.proargtypes[1] != 'int4'::regtype) OR
(p.pronargs > 2 AND p.proargtypes[2] != 'bool'::regtype));
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Look for binary compatible casts that do not have the reverse
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index cc6d799bcea..0b031a37c36 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -81,7 +81,7 @@ test: create_table_like alter_generic alter_operator misc async dbsize merge mis
# collate.linux.utf8 and collate.icu.utf8 tests cannot be run in parallel with each other
# psql depends on create_am
# amutils depends on geometry, create_index_spgist, hash_index, brin
-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 00000000000..905d1f318b5
--- /dev/null
+++ b/src/test/regress/sql/cast.sql
@@ -0,0 +1,340 @@
+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(B'01' AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(BIT'01' AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(TRUE AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(1.1 AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(1 AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(1::numeric AS money DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(1111 AS "char" DEFAULT NULL ON CONVERSION ERROR);
+
+--the default expression’s collation should match the collation of the cast
+--target type
+VALUES (CAST('error' AS text DEFAULT '1' COLLATE "C" ON CONVERSION ERROR));
+
+VALUES (CAST('error' AS int2vector DEFAULT '1 3' ON CONVERSION ERROR));
+VALUES (CAST('error' AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR));
+
+-- test source expression contain subquery
+SELECT CAST((SELECT b FROM generate_series(1, 1), (VALUES('H')) s(b))
+ AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR);
+SELECT CAST((SELECT ARRAY((SELECT b FROM generate_series(1, 2) g, (VALUES('H')) s(b))))
+ AS INT[]
+ DEFAULT NULL ON CONVERSION ERROR);
+
+CREATE FUNCTION ret_int8() RETURNS BIGINT AS
+$$
+BEGIN RETURN 2147483648; END;
+$$
+LANGUAGE plpgsql IMMUTABLE;
+
+SELECT CAST('a' as int DEFAULT ret_int8() ON CONVERSION ERROR); --error
+SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERROR); --error
+
+-- test array coerce
+SELECT CAST(array['a'::text] AS int[] DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(array['a'] AS int[] DEFAULT NULL ON CONVERSION ERROR); --ok
+SELECT CAST(array[11.12] AS date[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(array[11111111111111111] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(array[['abc'],[456]] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('{123,abc,456}' AS int[] 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 int[] DEFAULT '{1,2}' ON CONVERSION ERROR);
+SELECT CAST(ARRAY[['1', '2'], ['three', 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --ok
+SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --error
+SELECT CAST(ARRAY[1, 'three'::int] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --error
+
+-- test valid DEFAULT expression for CAST = ON CONVERSION ERROR
+CREATE 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(c::date::json as date DEFAULT NULL ON CONVERSION ERROR) FROM tcast; --error
+SELECT CAST('a'::int as int DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST('a' as int DEFAULT NULL ON CONVERSION ERROR); --ok
+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 (SELECT NULL) 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 DOMAIN d_varchar as varchar(3) NOT NULL;
+CREATE TYPE comp_domain_with_typmod AS (a d_char3_not_null, b int);
+CREATE TYPE comp2 AS (a d_varchar);
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION ERROR); --error
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(123)' ON CONVERSION ERROR); --ok
+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
+
+-----safe cast with geometry data type
+SELECT CAST('(1,2)'::point AS box DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[(NaN,1),(NaN,infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[(1e+300,Infinity),(1e+300,Infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[(1,2),(3,4)]'::path as polygon DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NaN,1.0,NaN,infinity)'::box AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS lseg DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS polygon DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS path DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,infinity,NaN,infinity)'::box AS circle DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NaN,0.0),(2.0,4.0),(0.0,infinity)'::polygon AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS path DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS box DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NaN,infinity),(2.0,4.0),(0.0,infinity)'::polygon AS circle DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('<(5,1),3>'::circle AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('<(3,5),0>'::circle as box DEFAULT NULL ON CONVERSION ERROR);
+-- not supported because the cast from circle to polygon is implemented as a SQL
+-- function, which cannot be error-safe.
+SELECT CAST('<(3,5),0>'::circle as polygon DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast from/to money type is not supported, all the following would result error
+SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast from bytea type to other data types
+SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('\x123456789A'::bytea AS int4 DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('\x123456'::bytea AS int2 DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast from bit type to other data types
+SELECT CAST('111111111100001'::bit(100) AS INT DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST ('111111111100001'::bit(100) AS INT8 DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast from text type to other data types
+select CAST('a.b.c.d'::text as regclass default NULL on conversion error);
+
+CREATE TABLE test_safecast(col0 text);
+INSERT INTO test_safecast(col0) VALUES ('<value>one</value');
+
+SELECT col0 as text,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) as to_regclass,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) IS NULL as expect_true,
+ CAST(col0 AS "char" DEFAULT NULL ON CONVERSION ERROR) as to_char,
+ CAST(col0 AS name DEFAULT NULL ON CONVERSION ERROR) as to_name,
+ CAST(col0 AS xml DEFAULT NULL ON CONVERSION ERROR) as to_xml
+FROM test_safecast;
+
+SELECT CAST('192.168.1.x' as inet DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('192.168.1.2/30' as cidr DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('22:00:5c:08:55:08:01:02'::macaddr8 as macaddr DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast betweeen range data types
+SELECT CAST('[1,2]'::int4range AS int4multirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[1,2]'::int8range AS int8multirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[1,2]'::numrange AS nummultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::daterange AS datemultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::tsrange AS tsmultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::tstzrange AS tstzmultirange DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast betweeen numeric data types
+CREATE TABLE test_safecast1(
+ col1 float4, col2 float8, col3 numeric, col4 numeric[],
+ col5 int2 default 32767,
+ col6 int4 default 32768,
+ col7 int8 default 4294967296);
+INSERT INTO test_safecast1 VALUES('11.1234', '11.1234', '9223372036854775808', '{11.1234}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('inf', 'inf', 'inf', '{11.1234, 12, inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('-inf', '-inf', '-inf', '{11.1234, 12, -inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('NaN', 'NaN', 'NaN', '{11.1234, 12, -inf, NaN}'::numeric[]);
+
+SELECT col5 as int2,
+ CAST(col5 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col5 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col5 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col5 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col5 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col5 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col5 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col5 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col6 as int4,
+ CAST(col6 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col6 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col6 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col6 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col6 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col6 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col6 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col6 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col7 as int8,
+ CAST(col7 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col7 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col7 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col7 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col7 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col7 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col7 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col7 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col3 as numeric,
+ CAST(col3 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col3 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col3 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col3 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col3 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col3 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col3 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col3 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col4 as num_arr,
+ CAST(col4 AS int2[] DEFAULT NULL ON CONVERSION ERROR) as int2arr,
+ CAST(col4 AS int4[] DEFAULT NULL ON CONVERSION ERROR) as int4arr,
+ CAST(col4 as int8[] DEFAULT NULL ON CONVERSION ERROR) as int8arr,
+ CAST(col4 as float4[] DEFAULT NULL ON CONVERSION ERROR) as f4arr,
+ CAST(col4 as float8[] DEFAULT NULL ON CONVERSION ERROR) as f8arr,
+ CAST(col4 as numeric(10,1)[] DEFAULT NULL ON CONVERSION ERROR) as numarr
+FROM test_safecast1;
+
+SELECT col1 as float4,
+ CAST(col1 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col1 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col1 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col1 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col1 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col1 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col1 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col1 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col2 as float8,
+ CAST(col2 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col2 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col2 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col2 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col2 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col2 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col2 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col2 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+-- date/timestamp/interval related safe type cast
+CREATE TABLE test_safecast2(
+ col0 date, col1 timestamp, col2 timestamptz,
+ col3 interval, col4 time, col5 timetz);
+INSERT INTO test_safecast2 VALUES
+('-infinity', '-infinity', '-infinity', '-infinity',
+ '2003-03-07 15:36:39 America/New_York', '2003-07-07 15:36:39 America/New_York'),
+('-infinity', 'infinity', 'infinity', 'infinity',
+ '11:59:59.99 PM', '11:59:59.99 PM PDT');
+
+SELECT col0 as date,
+ CAST(col0 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col0 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col0 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col0 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col0 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+
+SELECT col1 as timestamp,
+ CAST(col1 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col1 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col1 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col1 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col1 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+
+SELECT col2 as timestamptz,
+ CAST(col2 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col2 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col2 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col2 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col2 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+
+SELECT col3 as interval,
+ CAST(col3 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col3 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col3 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col3 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col3 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale,
+ CAST(col3 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+
+SELECT col4 as time,
+ CAST(col4 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col4 AS timetz DEFAULT NULL ON CONVERSION ERROR) IS NOT NULL as to_timetz,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+
+SELECT col5 as timetz,
+ CAST(col5 AS time DEFAULT NULL ON CONVERSION ERROR) as to_time,
+ CAST(col5 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_time_scale,
+ CAST(col5 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col5 AS timetz(6) DEFAULT NULL ON CONVERSION ERROR) as to_timetz_scale,
+ CAST(col5 AS interval DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col5 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+
+CREATE TABLE test_safecast3(col0 jsonb);
+INSERT INTO test_safecast3(col0) VALUES ('"test"');
+SELECT col0 as jsonb,
+ CAST(col0 AS integer DEFAULT NULL ON CONVERSION ERROR) as to_integer,
+ CAST(col0 AS numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col0 AS bigint DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col0 AS float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col0 AS float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col0 AS boolean DEFAULT NULL ON CONVERSION ERROR) as to_bool,
+ CAST(col0 AS smallint DEFAULT NULL ON CONVERSION ERROR) as to_smallint
+FROM test_safecast3;
+
+-- test deparse
+CREATE DOMAIN d_int_arr as int[];
+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 test_safecast1,
+ CAST(ARRAY[['1'], ['three'],['a']] AS int[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1,
+ CAST(ARRAY[['1', '2'], ['three', 'a']] AS date[] DEFAULT NULL ON CONVERSION ERROR) as cast2,
+ CAST(ARRAY['three'] AS INT[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast3;
+\sv safecastview
+
+CREATE VIEW safecastview1 AS
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS d_int_arr DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1,
+ CAST(ARRAY[['1', '2'], ['three', 'a']] AS d_int_arr DEFAULT '{21,22}' ON CONVERSION ERROR) as cast2;
+\sv safecastview1
+SELECT * FROM safecastview1;
+
+--error, default expression is mutable
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as int DEFAULT random(min=>1, max=>1) ON CONVERSION ERROR)));
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as xid DEFAULT NULL ON CONVERSION ERROR)));
+CREATE INDEX test_safecast3_idx ON test_safecast3((CAST(col0 as int DEFAULT NULL ON CONVERSION ERROR))); --ok
+SELECT pg_get_indexdef('test_safecast3_idx'::regclass);
+
+DROP TABLE test_safecast;
+DROP TABLE test_safecast1;
+DROP TABLE test_safecast2;
+DROP TABLE test_safecast3;
+DROP TABLE tcast;
+DROP FUNCTION ret_int8;
+DROP FUNCTION ret_setint;
+DROP TYPE comp_domain_with_typmod;
+DROP TYPE comp2;
+DROP DOMAIN d_varchar;
+DROP DOMAIN d_int42;
+DROP DOMAIN d_char3_not_null;
+RESET extra_float_digits;
diff --git a/src/test/regress/sql/create_cast.sql b/src/test/regress/sql/create_cast.sql
index 32187853cc7..0a15a795d87 100644
--- a/src/test/regress/sql/create_cast.sql
+++ b/src/test/regress/sql/create_cast.sql
@@ -62,6 +62,7 @@ $$ SELECT ('bar'::text || $1::text); $$;
CREATE CAST (int4 AS casttesttype) WITH FUNCTION bar_int4_text(int4) AS IMPLICIT;
SELECT 1234::int4::casttesttype; -- Should work now
+SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- error
-- check dependencies generated for that
SELECT pg_describe_object(classid, objid, objsubid) as obj,
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index cf3f6a7dafd..cf4bab63c89 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2672,6 +2672,9 @@ STRLEN
SV
SYNCHRONIZATION_BARRIER
SYSTEM_INFO
+SafeTypeCast
+SafeTypeCastExpr
+SafeTypeCastState
SampleScan
SampleScanGetSampleSize_function
SampleScanState
--
2.34.1
v13-0019-invent-some-error-safe-functions.patchtext/x-patch; charset=US-ASCII; name=v13-0019-invent-some-error-safe-functions.patchDownload
From 91548362b43cac6ca621ca5471517b99644c132c Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Mon, 24 Nov 2025 17:03:45 +0800
Subject: [PATCH v13 19/20] invent some error safe functions
stringTypeDatumSafe: error safe version of stringTypeDatum
evaluate_expr_extended: If you wish to evaluate a constant expression in a
manner that is safe from potential errors, set the error_safe parameter to true
ExecInitExprSafe: soft error variant of ExecInitExpr
OidInputFunctionCallSafe: soft error variant of OidInputFunctionCall
CoerceUnknownConstSafe: attempts to coerce an UNKNOWN Const to the target type.
Returns NULL if the coercion is not possible.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/executor/execExpr.c | 41 +++++++++++++
src/backend/optimizer/util/clauses.c | 21 ++++++-
src/backend/parser/parse_coerce.c | 92 ++++++++++++++++++++++++++++
src/backend/parser/parse_type.c | 14 +++++
src/backend/utils/fmgr/fmgr.c | 13 ++++
src/include/executor/executor.h | 1 +
src/include/fmgr.h | 3 +
src/include/optimizer/optimizer.h | 3 +
src/include/parser/parse_coerce.h | 4 ++
src/include/parser/parse_type.h | 2 +
10 files changed, 193 insertions(+), 1 deletion(-)
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index f1569879b52..b302be36f73 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -170,6 +170,47 @@ ExecInitExpr(Expr *node, PlanState *parent)
return state;
}
+/*
+ * ExecInitExprSafe: soft error variant of ExecInitExpr.
+ *
+ * use it only for expression nodes support soft errors, not all expression
+ * nodes support it.
+*/
+ExprState *
+ExecInitExprSafe(Expr *node, PlanState *parent)
+{
+ ExprState *state;
+ ExprEvalStep scratch = {0};
+
+ /* Special case: NULL expression produces a NULL ExprState pointer */
+ if (node == NULL)
+ return NULL;
+
+ /* Initialize ExprState with empty step list */
+ state = makeNode(ExprState);
+ state->expr = node;
+ state->parent = parent;
+ state->ext_params = NULL;
+ state->escontext = makeNode(ErrorSaveContext);
+ state->escontext->type = T_ErrorSaveContext;
+ state->escontext->error_occurred = false;
+ state->escontext->details_wanted = false;
+
+ /* Insert setup steps as needed */
+ ExecCreateExprSetupSteps(state, (Node *) node);
+
+ /* Compile the expression proper */
+ ExecInitExprRec(node, state, &state->resvalue, &state->resnull);
+
+ /* Finally, append a DONE step */
+ scratch.opcode = EEOP_DONE_RETURN;
+ ExprEvalPushStep(state, &scratch);
+
+ ExecReadyExpr(state);
+
+ return state;
+}
+
/*
* ExecInitExprWithParams: prepare a standalone expression tree for execution
*
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index bda4c4eb292..e5dbd40cda2 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -5156,6 +5156,16 @@ sql_inline_error_callback(void *arg)
Expr *
evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
Oid result_collation)
+{
+ return evaluate_expr_extended(expr,
+ result_type,
+ result_typmod,
+ result_collation,
+ false);
+}
+Expr *
+evaluate_expr_extended(Expr *expr, Oid result_type, int32 result_typmod,
+ Oid result_collation, bool error_safe)
{
EState *estate;
ExprState *exprstate;
@@ -5180,7 +5190,10 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
* Prepare expr for execution. (Note: we can't use ExecPrepareExpr
* because it'd result in recursively invoking eval_const_expressions.)
*/
- exprstate = ExecInitExpr(expr, NULL);
+ if (error_safe)
+ exprstate = ExecInitExprSafe(expr, NULL);
+ else
+ exprstate = ExecInitExpr(expr, NULL);
/*
* And evaluate it.
@@ -5200,6 +5213,12 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
/* Get back to outer memory context */
MemoryContextSwitchTo(oldcontext);
+ if (error_safe && SOFT_ERROR_OCCURRED(exprstate->escontext))
+ {
+ FreeExecutorState(estate);
+ return NULL;
+ }
+
/*
* Must copy result out of sub-context used by expression eval.
*
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 78b1e366ad7..cdcdd44a799 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -657,6 +657,98 @@ can_coerce_type(int nargs, const Oid *input_typeids, const Oid *target_typeids,
return true;
}
+/*
+ * Create an expression tree to represent coercion a UNKNOWN Const node.
+ *
+ * 'node': the input expression
+ * 'baseTypeId': base type of domain
+ * 'baseTypeMod': base type typmod of domain
+ * 'targetType': target type to coerce to
+ * 'targetTypeMod': target type typmod
+ * 'ccontext': context indicator to control coercions
+ * 'cformat': coercion display format
+ * 'location': coercion request location
+ * 'hideInputCoercion': if true, hide the input coercion under this one.
+ */
+Node *
+CoerceUnknownConstSafe(ParseState *pstate, Node *node, Oid targetType, int32 targetTypeMod,
+ CoercionContext ccontext, CoercionForm cformat, int location,
+ bool hideInputCoercion)
+{
+ Oid baseTypeId;
+ int32 baseTypeMod;
+ int32 inputTypeMod;
+ Type baseType;
+ char *string;
+ Datum datum;
+ Const *newcon;
+ Node *result = NULL;
+ Const *con = (Const *) node;
+
+ Assert(IsA(node, Const));
+ Assert(exprType(node) == UNKNOWNOID);
+
+ 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;
+
+ if (!stringTypeDatumSafe(baseType,
+ string,
+ inputTypeMod,
+ &datum))
+ {
+ ReleaseSysCache(baseType);
+ return NULL;
+ }
+
+ newcon = makeNode(Const);
+ newcon->consttype = baseTypeId;
+ newcon->consttypmod = inputTypeMod;
+ newcon->constcollid = typeTypeCollation(baseType);
+ newcon->constlen = typeLen(baseType);
+ newcon->constbyval = typeByVal(baseType);
+ newcon->constisnull = con->constisnull;
+ newcon->constvalue = datum;
+
+ /*
+ * 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)
+ newcon->constvalue =
+ PointerGetDatum(PG_DETOAST_DATUM(newcon->constvalue));
+
+ result = (Node *) newcon;
+
+ /* If target is a domain, apply constraints. */
+ if (baseTypeId != targetType)
+ result = coerce_to_domain(result,
+ baseTypeId, baseTypeMod,
+ targetType,
+ ccontext, cformat, location,
+ false);
+
+ ReleaseSysCache(baseType);
+
+ return result;
+}
+
/*
* Create an expression tree to represent coercion to a domain type.
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index 7713bdc6af0..d260aeec5dc 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,19 @@ stringTypeDatum(Type tp, char *string, int32 atttypmod)
return OidInputFunctionCall(typinput, string, typioparam, atttypmod);
}
+/* error safe version of stringTypeDatum */
+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,
+ (Node *) &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/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index 0fe63c6bb83..aaa4a42b1ea 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, Node *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/executor.h b/src/include/executor/executor.h
index fa2b657fb2f..f99fc26eb1f 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -324,6 +324,7 @@ ExecProcNode(PlanState *node)
* prototypes from functions in execExpr.c
*/
extern ExprState *ExecInitExpr(Expr *node, PlanState *parent);
+extern ExprState *ExecInitExprSafe(Expr *node, PlanState *parent);
extern ExprState *ExecInitExprWithParams(Expr *node, ParamListInfo ext_params);
extern ExprState *ExecInitQual(List *qual, PlanState *parent);
extern ExprState *ExecInitCheck(List *qual, PlanState *parent);
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index 74fe3ea0575..991e14034d3 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, Node *escontext,
+ Datum *result);
extern char *OutputFunctionCall(FmgrInfo *flinfo, Datum val);
extern char *OidOutputFunctionCall(Oid functionId, Datum val);
extern Datum ReceiveFunctionCall(FmgrInfo *flinfo, StringInfo buf,
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index 44ec5296a18..f7a8c353a6b 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -144,6 +144,9 @@ extern Node *estimate_expression_value(PlannerInfo *root, Node *node);
extern Expr *evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
Oid result_collation);
+extern Expr *evaluate_expr_extended(Expr *expr, Oid result_type,
+ int32 result_typmod, Oid result_collation,
+ bool error_safe);
extern bool var_is_nonnullable(PlannerInfo *root, Var *var, bool use_rel_info);
diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h
index 8d775c72c59..ad16cdd7022 100644
--- a/src/include/parser/parse_coerce.h
+++ b/src/include/parser/parse_coerce.h
@@ -48,6 +48,10 @@ extern bool can_coerce_type(int nargs, const Oid *input_typeids, const Oid *targ
extern Node *coerce_type(ParseState *pstate, Node *node,
Oid inputTypeId, Oid targetTypeId, int32 targetTypeMod,
CoercionContext ccontext, CoercionForm cformat, int location);
+extern Node *CoerceUnknownConstSafe(ParseState *pstate, Node *node,
+ Oid targetType, int32 targetTypeMod,
+ CoercionContext ccontext, CoercionForm cformat,
+ int location, bool hideInputCoercion);
extern Node *coerce_to_domain(Node *arg, Oid baseTypeId, int32 baseTypeMod,
Oid typeId,
CoercionContext ccontext, CoercionForm cformat, int location,
diff --git a/src/include/parser/parse_type.h b/src/include/parser/parse_type.h
index 0d919d8bfa2..12381aed64c 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);
--
2.34.1
v13-0016-error-safe-for-casting-timestamp-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v13-0016-error-safe-for-casting-timestamp-to-other-types-per-pg_cast.patchDownload
From 267e3a950faa281017affa6d79f99877ca6329ce Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Tue, 2 Dec 2025 12:14:36 +0800
Subject: [PATCH v13 16/20] error safe for casting timestamp to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc
and pc.castfunc > 0 and (castsource::regtype ='timestamp'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
-----------------------------+-----------------------------+----------+-------------+------------+-----------------------+-------------
timestamp without time zone | date | 2029 | a | f | timestamp_date | date
timestamp without time zone | time without time zone | 1316 | a | f | timestamp_time | time
timestamp without time zone | timestamp with time zone | 2028 | i | f | timestamp_timestamptz | timestamptz
timestamp without time zone | timestamp without time zone | 1961 | i | f | timestamp_scale | timestamp
(4 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 7 +++++--
src/backend/utils/adt/timestamp.c | 10 ++++++++--
2 files changed, 13 insertions(+), 4 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index bbc864a80cd..91cc8cc85f0 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -1330,7 +1330,10 @@ timestamp_date(PG_FUNCTION_ARGS)
Timestamp timestamp = PG_GETARG_TIMESTAMP(0);
DateADT result;
- result = timestamp2date_safe(timestamp, NULL);
+ result = timestamp2date_safe(timestamp, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
+
PG_RETURN_DATEADT(result);
}
@@ -2008,7 +2011,7 @@ timestamp_time(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 6adcf950426..442bc061ba2 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -352,7 +352,8 @@ timestamp_scale(PG_FUNCTION_ARGS)
result = timestamp;
- AdjustTimestampForTypmod(&result, typmod, NULL);
+ if (!AdjustTimestampForTypmod(&result, typmod, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_TIMESTAMP(result);
}
@@ -6432,8 +6433,13 @@ Datum
timestamp_timestamptz(PG_FUNCTION_ARGS)
{
Timestamp timestamp = PG_GETARG_TIMESTAMP(0);
+ TimestampTz result;
- PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(timestamp));
+ result = timestamp2timestamptz_safe(timestamp, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
+
+ PG_RETURN_TIMESTAMPTZ(result);
}
/*
--
2.34.1
v13-0015-error-safe-for-casting-timestamptz-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v13-0015-error-safe-for-casting-timestamptz-to-other-types-per-pg_cast.patchDownload
From 7404ae790cb80b8050ef3228e01dffca95c66048 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Tue, 2 Dec 2025 12:04:40 +0800
Subject: [PATCH v13 15/20] error safe for casting timestamptz to other types
per pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
and pc.castfunc > 0 and (castsource::regtype ='timestamptz'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
--------------------------+-----------------------------+----------+-------------+------------+-----------------------+-------------
timestamp with time zone | date | 1178 | a | f | timestamptz_date | date
timestamp with time zone | time without time zone | 2019 | a | f | timestamptz_time | time
timestamp with time zone | timestamp without time zone | 2027 | a | f | timestamptz_timestamp | timestamp
timestamp with time zone | time with time zone | 1388 | a | f | timestamptz_timetz | timetz
timestamp with time zone | timestamp with time zone | 1967 | i | f | timestamptz_scale | timestamptz
(5 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 9 ++++++---
src/backend/utils/adt/timestamp.c | 10 ++++++++--
2 files changed, 14 insertions(+), 5 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 8fa336da250..bbc864a80cd 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -1406,7 +1406,10 @@ timestamptz_date(PG_FUNCTION_ARGS)
TimestampTz timestamp = PG_GETARG_TIMESTAMP(0);
DateADT result;
- result = timestamptz2date_safe(timestamp, NULL);
+ result = timestamptz2date_safe(timestamp, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->args))
+ PG_RETURN_NULL();
+
PG_RETURN_DATEADT(result);
}
@@ -2036,7 +2039,7 @@ timestamptz_time(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
@@ -2955,7 +2958,7 @@ timestamptz_timetz(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 1e69e6adb77..6adcf950426 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -875,7 +875,8 @@ timestamptz_scale(PG_FUNCTION_ARGS)
result = timestamp;
- AdjustTimestampForTypmod(&result, typmod, NULL);
+ if (!AdjustTimestampForTypmod(&result, typmod, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_TIMESTAMPTZ(result);
}
@@ -6494,8 +6495,13 @@ Datum
timestamptz_timestamp(PG_FUNCTION_ARGS)
{
TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(0);
+ Timestamp result;
- PG_RETURN_TIMESTAMP(timestamptz2timestamp(timestamp));
+ result = timestamptz2timestamp_safe(timestamp, fcinfo->context);
+ if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
+
+ PG_RETURN_TIMESTAMP(result);
}
/*
--
2.34.1
v13-0017-error-safe-for-casting-jsonb-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v13-0017-error-safe-for-casting-jsonb-to-other-types-per-pg_cast.patchDownload
From 8e292e11d9e770011151b6bcb2cc49baa818688c Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Mon, 24 Nov 2025 14:26:53 +0800
Subject: [PATCH v13 17/20] error safe for casting jsonb to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'jsonb'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+---------------+---------
jsonb | boolean | 3556 | e | f | jsonb_bool | bool
jsonb | numeric | 3449 | e | f | jsonb_numeric | numeric
jsonb | smallint | 3450 | e | f | jsonb_int2 | int2
jsonb | integer | 3451 | e | f | jsonb_int4 | int4
jsonb | bigint | 3452 | e | f | jsonb_int8 | int8
jsonb | real | 3453 | e | f | jsonb_float4 | float4
jsonb | double precision | 2580 | e | f | jsonb_float8 | float8
(7 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/jsonb.c | 74 +++++++++++++++++++++++++++--------
1 file changed, 58 insertions(+), 16 deletions(-)
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index 9399cdb491a..ab24d6dc813 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -2006,7 +2006,7 @@ JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res)
* Emit correct, translatable cast error message
*/
static void
-cannotCastJsonbValue(enum jbvType type, const char *sqltype)
+cannotCastJsonbValue(enum jbvType type, const char *sqltype, Node *escontext)
{
static const struct
{
@@ -2027,7 +2027,7 @@ cannotCastJsonbValue(enum jbvType type, const char *sqltype)
for (i = 0; i < lengthof(messages); i++)
if (messages[i].type == type)
- ereport(ERROR,
+ ereturn(escontext,,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(messages[i].msg, sqltype)));
@@ -2042,7 +2042,10 @@ jsonb_bool(PG_FUNCTION_ARGS)
JsonbValue v;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "boolean");
+ {
+ cannotCastJsonbValue(v.type, "boolean", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2051,7 +2054,10 @@ jsonb_bool(PG_FUNCTION_ARGS)
}
if (v.type != jbvBool)
- cannotCastJsonbValue(v.type, "boolean");
+ {
+ cannotCastJsonbValue(v.type, "boolean", fcinfo->context);
+ PG_RETURN_NULL();
+ }
PG_FREE_IF_COPY(in, 0);
@@ -2066,7 +2072,10 @@ jsonb_numeric(PG_FUNCTION_ARGS)
Numeric retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "numeric");
+ {
+ cannotCastJsonbValue(v.type, "numeric", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2075,7 +2084,10 @@ jsonb_numeric(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "numeric");
+ {
+ cannotCastJsonbValue(v.type, "numeric", fcinfo->context);
+ PG_RETURN_NULL();
+ }
/*
* v.val.numeric points into jsonb body, so we need to make a copy to
@@ -2096,7 +2108,10 @@ jsonb_int2(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "smallint");
+ {
+ cannotCastJsonbValue(v.type, "smallint", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2105,7 +2120,10 @@ jsonb_int2(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "smallint");
+ {
+ cannotCastJsonbValue(v.type, "smallint", fcinfo->context);
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_int2,
NumericGetDatum(v.val.numeric));
@@ -2123,7 +2141,10 @@ jsonb_int4(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "integer");
+ {
+ cannotCastJsonbValue(v.type, "integer", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2132,7 +2153,10 @@ jsonb_int4(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "integer");
+ {
+ cannotCastJsonbValue(v.type, "integer", fcinfo->context);
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_int4,
NumericGetDatum(v.val.numeric));
@@ -2150,7 +2174,10 @@ jsonb_int8(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "bigint");
+ {
+ cannotCastJsonbValue(v.type, "bigint", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2159,7 +2186,10 @@ jsonb_int8(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "bigint");
+ {
+ cannotCastJsonbValue(v.type, "bigint", fcinfo->context);
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_int8,
NumericGetDatum(v.val.numeric));
@@ -2177,7 +2207,10 @@ jsonb_float4(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "real");
+ {
+ cannotCastJsonbValue(v.type, "real", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2186,7 +2219,10 @@ jsonb_float4(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "real");
+ {
+ cannotCastJsonbValue(v.type, "real", fcinfo->context);
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_float4,
NumericGetDatum(v.val.numeric));
@@ -2204,7 +2240,10 @@ jsonb_float8(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "double precision");
+ {
+ cannotCastJsonbValue(v.type, "double precision", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2213,7 +2252,10 @@ jsonb_float8(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "double precision");
+ {
+ cannotCastJsonbValue(v.type, "double precision", fcinfo->context);
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_float8,
NumericGetDatum(v.val.numeric));
--
2.34.1
v13-0013-error-safe-for-casting-date-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v13-0013-error-safe-for-casting-date-to-other-types-per-pg_cast.patchDownload
From f15b5bc1c06dddbdf2c09f7baf4ec9922bbc81ce Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Tue, 2 Dec 2025 13:15:32 +0800
Subject: [PATCH v13 13/20] error safe for casting date to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'date'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+-----------------------------+----------+-------------+------------+------------------+-------------
date | timestamp without time zone | 2024 | i | f | date_timestamp | timestamp
date | timestamp with time zone | 1174 | i | f | date_timestamptz | timestamptz
(2 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 17 ++++++-----------
1 file changed, 6 insertions(+), 11 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index c4b8125dd66..cf241ea9794 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -730,15 +730,6 @@ date2timestamptz_safe(DateADT dateVal, Node *escontext)
return result;
}
-/*
- * Promote date to timestamptz, throwing error for overflow.
- */
-static TimestampTz
-date2timestamptz(DateADT dateVal)
-{
- return date2timestamptz_safe(dateVal, NULL);
-}
-
/*
* date2timestamp_no_overflow
*
@@ -1323,7 +1314,9 @@ date_timestamp(PG_FUNCTION_ARGS)
DateADT dateVal = PG_GETARG_DATEADT(0);
Timestamp result;
- result = date2timestamp(dateVal);
+ result = date2timestamp_safe(dateVal, fcinfo->context);
+ if(SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_TIMESTAMP(result);
}
@@ -1396,7 +1389,9 @@ date_timestamptz(PG_FUNCTION_ARGS)
DateADT dateVal = PG_GETARG_DATEADT(0);
TimestampTz result;
- result = date2timestamptz(dateVal);
+ result = date2timestamptz_safe(dateVal, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_TIMESTAMP(result);
}
--
2.34.1
v13-0014-error-safe-for-casting-interval-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v13-0014-error-safe-for-casting-interval-to-other-types-per-pg_cast.patchDownload
From 4815fa673ae5da4609546c2ca793f3e00ff6deaf Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 18:43:29 +0800
Subject: [PATCH v13 14/20] error safe for casting interval to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'interval'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------------+----------+-------------+------------+----------------+----------
interval | time without time zone | 1419 | a | f | interval_time | time
interval | interval | 1200 | i | f | interval_scale | interval
(2 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 2 +-
src/backend/utils/adt/timestamp.c | 3 ++-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index cf241ea9794..8fa336da250 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2106,7 +2106,7 @@ interval_time(PG_FUNCTION_ARGS)
TimeADT result;
if (INTERVAL_NOT_FINITE(span))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("cannot convert infinite interval to time")));
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index af48527d436..1e69e6adb77 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1334,7 +1334,8 @@ interval_scale(PG_FUNCTION_ARGS)
result = palloc(sizeof(Interval));
*result = *interval;
- AdjustIntervalForTypmod(result, typmod, NULL);
+ if (!AdjustIntervalForTypmod(result, typmod, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_INTERVAL_P(result);
}
--
2.34.1
v13-0010-error-safe-for-casting-numeric-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v13-0010-error-safe-for-casting-numeric-to-other-types-per-pg_cast.patchDownload
From 14ecad4c8c6e1b87dfac93bc951869b4e5b65e2e Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:25:37 +0800
Subject: [PATCH v13 10/20] error safe for casting numeric to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'numeric'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+----------------+---------
numeric | bigint | 1779 | a | f | numeric_int8 | int8
numeric | smallint | 1783 | a | f | numeric_int2 | int2
numeric | integer | 1744 | a | f | numeric_int4 | int4
numeric | real | 1745 | i | f | numeric_float4 | float4
numeric | double precision | 1746 | i | f | numeric_float8 | float8
numeric | money | 3824 | a | f | numeric_cash | money
numeric | numeric | 1703 | i | f | numeric | numeric
(7 rows)
discussion: https://postgr.es/m/CACJufxHCMzrHOW=wRe8L30rMhB3sjwAv1LE928Fa7sxMu1Tx-g@mail.gmail.com
---
src/backend/utils/adt/numeric.c | 58 ++++++++++++++++++++++++---------
1 file changed, 43 insertions(+), 15 deletions(-)
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 2501007d981..40d2709b64a 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -1244,7 +1244,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));
}
@@ -1295,8 +1296,9 @@ numeric (PG_FUNCTION_ARGS)
init_var(&var);
set_var_from_num(num, &var);
- (void) apply_typmod(&var, typmod, NULL);
- new = make_result(&var);
+ if (!apply_typmod(&var, typmod, fcinfo->context))
+ PG_RETURN_NULL();
+ new = make_result_safe(&var, fcinfo->context);
free_var(&var);
@@ -3019,7 +3021,10 @@ numeric_mul(PG_FUNCTION_ARGS)
Numeric num2 = PG_GETARG_NUMERIC(1);
Numeric res;
- res = numeric_mul_safe(num1, num2, NULL);
+ res = numeric_mul_safe(num1, num2, fcinfo->context);
+
+ if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
PG_RETURN_NUMERIC(res);
}
@@ -4393,9 +4398,15 @@ numeric_int4_safe(Numeric num, Node *escontext)
Datum
numeric_int4(PG_FUNCTION_ARGS)
{
+ int32 result;
Numeric num = PG_GETARG_NUMERIC(0);
- PG_RETURN_INT32(numeric_int4_safe(num, NULL));
+ result = numeric_int4_safe(num, fcinfo->context);
+
+ if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
+
+ PG_RETURN_INT32(result);
}
/*
@@ -4463,9 +4474,15 @@ numeric_int8_safe(Numeric num, Node *escontext)
Datum
numeric_int8(PG_FUNCTION_ARGS)
{
+ int64 result;
Numeric num = PG_GETARG_NUMERIC(0);
- PG_RETURN_INT64(numeric_int8_safe(num, NULL));
+ result = numeric_int8_safe(num, fcinfo->context);
+
+ if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
+
+ PG_RETURN_INT64(result);
}
@@ -4489,11 +4506,11 @@ numeric_int2(PG_FUNCTION_ARGS)
if (NUMERIC_IS_SPECIAL(num))
{
if (NUMERIC_IS_NAN(num))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert NaN to %s", "smallint")));
else
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert infinity to %s", "smallint")));
}
@@ -4502,12 +4519,12 @@ numeric_int2(PG_FUNCTION_ARGS)
init_var_from_num(num, &x);
if (!numericvar_to_int64(&x, &val))
- ereport(ERROR,
+ ereturn(fcinfo->context, (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(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
@@ -4572,10 +4589,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);
}
@@ -4667,7 +4688,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
v13-0011-error-safe-for-casting-float4-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v13-0011-error-safe-for-casting-float4-to-other-types-per-pg_cast.patchDownload
From a79115e9feb53040700473083abccd2fcb04f889 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:28:20 +0800
Subject: [PATCH v13 11/20] error safe for casting float4 to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'float4'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+----------------+---------
real | bigint | 653 | a | f | ftoi8 | int8
real | smallint | 238 | a | f | ftoi2 | int2
real | integer | 319 | a | f | ftoi4 | int4
real | double precision | 311 | i | f | ftod | float8
real | numeric | 1742 | a | f | float4_numeric | numeric
(5 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/float.c | 4 ++--
src/backend/utils/adt/int8.c | 2 +-
src/backend/utils/adt/numeric.c | 3 ++-
3 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index 7b97d2be6ca..ab5c38d723d 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -1298,7 +1298,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 +1323,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 d5631f35465..68299e91512 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -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 40d2709b64a..b89c35eee65 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -4658,7 +4658,8 @@ float4_numeric(PG_FUNCTION_ARGS)
init_var(&result);
/* Assume we need not worry about leading/trailing spaces */
- (void) set_var_from_str(buf, buf, &result, &endptr, NULL);
+ if (!set_var_from_str(buf, buf, &result, &endptr, fcinfo->context))
+ PG_RETURN_NULL();
res = make_result(&result);
--
2.34.1
v13-0012-error-safe-for-casting-float8-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v13-0012-error-safe-for-casting-float8-to-other-types-per-pg_cast.patchDownload
From 11e0c35f5dea406829914a58334f521b003a59dd Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:31:53 +0800
Subject: [PATCH v13 12/20] error safe for casting float8 to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'float8'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------------+------------+----------+-------------+------------+----------------+---------
double precision | bigint | 483 | a | f | dtoi8 | int8
double precision | smallint | 237 | a | f | dtoi2 | int2
double precision | integer | 317 | a | f | dtoi4 | int4
double precision | real | 312 | a | f | dtof | float4
double precision | numeric | 1743 | a | f | float8_numeric | numeric
(5 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/float.c | 13 +++++++++----
src/backend/utils/adt/int8.c | 2 +-
src/backend/utils/adt/numeric.c | 3 ++-
3 files changed, 12 insertions(+), 6 deletions(-)
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index ab5c38d723d..d746f961fd3 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -1199,9 +1199,14 @@ 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 +1229,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 +1254,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")));
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 68299e91512..7ff7f267b7e 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")));
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index b89c35eee65..507c607a799 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -4560,7 +4560,8 @@ float8_numeric(PG_FUNCTION_ARGS)
init_var(&result);
/* Assume we need not worry about leading/trailing spaces */
- (void) set_var_from_str(buf, buf, &result, &endptr, NULL);
+ if (!set_var_from_str(buf, buf, &result, &endptr, fcinfo->context))
+ PG_RETURN_NULL();
res = make_result(&result);
--
2.34.1
v13-0009-error-safe-for-casting-bigint-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v13-0009-error-safe-for-casting-bigint-to-other-types-per-pg_cast.patchDownload
From 6efb0ea1f56648b943210756d39e35c164df26f3 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:22:00 +0800
Subject: [PATCH v13 09/20] error safe for casting bigint to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'bigint'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+--------------+---------
bigint | smallint | 714 | a | f | int82 | int2
bigint | integer | 480 | a | f | int84 | int4
bigint | real | 652 | i | f | i8tof | float4
bigint | double precision | 482 | i | f | i8tod | float8
bigint | numeric | 1781 | i | f | int8_numeric | numeric
bigint | money | 3812 | a | f | int8_cash | money
bigint | oid | 1287 | i | f | i8tooid | oid
bigint | regproc | 1287 | i | f | i8tooid | oid
bigint | regprocedure | 1287 | i | f | i8tooid | oid
bigint | regoper | 1287 | i | f | i8tooid | oid
bigint | regoperator | 1287 | i | f | i8tooid | oid
bigint | regclass | 1287 | i | f | i8tooid | oid
bigint | regcollation | 1287 | i | f | i8tooid | oid
bigint | regtype | 1287 | i | f | i8tooid | oid
bigint | regconfig | 1287 | i | f | i8tooid | oid
bigint | regdictionary | 1287 | i | f | i8tooid | oid
bigint | regrole | 1287 | i | f | i8tooid | oid
bigint | regnamespace | 1287 | i | f | i8tooid | oid
bigint | regdatabase | 1287 | i | f | i8tooid | oid
bigint | bytea | 6369 | e | f | int8_bytea | bytea
bigint | bit | 2075 | e | f | bitfromint8 | bit
(21 rows)
already error safe: i8tof, i8tod, int8_numeric, int8_bytea, bitfromint8
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/int8.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 9cd420b4b9d..d5631f35465 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -1251,7 +1251,7 @@ int84(PG_FUNCTION_ARGS)
int64 arg = PG_GETARG_INT64(0);
if (unlikely(arg < PG_INT32_MIN) || unlikely(arg > PG_INT32_MAX))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
@@ -1272,7 +1272,7 @@ int82(PG_FUNCTION_ARGS)
int64 arg = PG_GETARG_INT64(0);
if (unlikely(arg < PG_INT16_MIN) || unlikely(arg > PG_INT16_MAX))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
@@ -1355,7 +1355,7 @@ i8tooid(PG_FUNCTION_ARGS)
int64 arg = PG_GETARG_INT64(0);
if (unlikely(arg < 0) || unlikely(arg > PG_UINT32_MAX))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("OID out of range")));
--
2.34.1
v13-0006-error-safe-for-casting-inet-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v13-0006-error-safe-for-casting-inet-to-other-types-per-pg_cast.patchDownload
From aa7af977a912a6a0c9a17e564f9da56327ecf08e Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 10:28:54 +0800
Subject: [PATCH v13 06/20] error safe for casting inet to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'inet'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+-------------------+----------+-------------+------------+--------------+---------
inet | cidr | 1715 | a | f | inet_to_cidr | cidr
inet | text | 730 | a | f | network_show | text
inet | character varying | 730 | a | f | network_show | text
inet | character | 730 | a | f | network_show | text
(4 rows)
inet_to_cidr is already error safe.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/network.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/backend/utils/adt/network.c b/src/backend/utils/adt/network.c
index 3cb0ab6829a..648c8d95f51 100644
--- a/src/backend/utils/adt/network.c
+++ b/src/backend/utils/adt/network.c
@@ -1137,7 +1137,7 @@ network_show(PG_FUNCTION_ARGS)
if (pg_inet_net_ntop(ip_family(ip), ip_addr(ip), ip_maxbits(ip),
tmp, sizeof(tmp)) == NULL)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
errmsg("could not format inet value: %m")));
--
2.34.1
v13-0007-error-safe-for-casting-macaddr8-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v13-0007-error-safe-for-casting-macaddr8-to-other-types-per-pg_cast.patchDownload
From f21da63d159abe81a2a3949662f349296af33cc8 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:17:11 +0800
Subject: [PATCH v13 07/20] error safe for casting macaddr8 to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
and pc.castfunc > 0 and castsource::regtype ='macaddr8'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------+----------+-------------+------------+-------------------+---------
macaddr8 | macaddr | 4124 | i | f | macaddr8tomacaddr | macaddr
(1 row)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/mac8.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/backend/utils/adt/mac8.c b/src/backend/utils/adt/mac8.c
index 08e41ba4eea..1c903f152de 100644
--- a/src/backend/utils/adt/mac8.c
+++ b/src/backend/utils/adt/mac8.c
@@ -550,7 +550,7 @@ macaddr8tomacaddr(PG_FUNCTION_ARGS)
result = (macaddr *) palloc0(sizeof(macaddr));
if ((addr->d != 0xFF) || (addr->e != 0xFE))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("macaddr8 data out of range to convert to macaddr"),
errhint("Only addresses that have FF and FE as values in the "
--
2.34.1
v13-0008-error-safe-for-casting-integer-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v13-0008-error-safe-for-casting-integer-to-other-types-per-pg_cast.patchDownload
From eac1fd0e0faebf8ebd0b90f01d538c309e404e17 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:20:10 +0800
Subject: [PATCH v13 08/20] error safe for casting integer to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'integer'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+--------------+---------
integer | bigint | 481 | i | f | int48 | int8
integer | smallint | 314 | a | f | i4toi2 | int2
integer | real | 318 | i | f | i4tof | float4
integer | double precision | 316 | i | f | i4tod | float8
integer | numeric | 1740 | i | f | int4_numeric | numeric
integer | money | 3811 | a | f | int4_cash | money
integer | boolean | 2557 | e | f | int4_bool | bool
integer | bytea | 6368 | e | f | int4_bytea | bytea
integer | "char" | 78 | e | f | i4tochar | char
integer | bit | 1683 | e | f | bitfromint4 | bit
(10 rows)
only int4_cash, i4toi2, i4tochar need take care of error handling. but support
for cash data type is not easy, so only i4toi2, i4tochar function refactoring.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/char.c | 2 +-
src/backend/utils/adt/int.c | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/backend/utils/adt/char.c b/src/backend/utils/adt/char.c
index 22dbfc950b1..e90844a29f0 100644
--- a/src/backend/utils/adt/char.c
+++ b/src/backend/utils/adt/char.c
@@ -192,7 +192,7 @@ i4tochar(PG_FUNCTION_ARGS)
int32 arg1 = PG_GETARG_INT32(0);
if (arg1 < SCHAR_MIN || arg1 > SCHAR_MAX)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("\"char\" out of range")));
diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c
index b5781989a64..b45599d402d 100644
--- a/src/backend/utils/adt/int.c
+++ b/src/backend/utils/adt/int.c
@@ -350,7 +350,7 @@ i4toi2(PG_FUNCTION_ARGS)
int32 arg1 = PG_GETARG_INT32(0);
if (unlikely(arg1 < SHRT_MIN) || unlikely(arg1 > SHRT_MAX))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
--
2.34.1
v13-0005-error-safe-for-casting-character-varying-to-other-types-per-pg_c.patchtext/x-patch; charset=US-ASCII; name=v13-0005-error-safe-for-casting-character-varying-to-other-types-per-pg_c.patchDownload
From 4e50479b892bad667251f2d0e810c4a8d048b98b Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:13:45 +0800
Subject: [PATCH v13 05/20] error safe for casting character varying to other
types per pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc and pc.castfunc > 0 and
(castsource::regtype = 'character varying'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
-------------------+-------------------+----------+-------------+------------+---------------+----------
character varying | regclass | 1079 | i | f | text_regclass | regclass
character varying | "char" | 944 | a | f | text_char | char
character varying | name | 1400 | i | f | text_name | name
character varying | xml | 2896 | e | f | texttoxml | xml
character varying | character varying | 669 | i | f | varchar | varchar
(5 rows)
texttoxml, text_regclass was refactored as error safe in prior patch.
text_char, text_name is already error safe.
so here we only need handle function "varchar".
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/varchar.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c
index 5cb5c8c46f9..08f1bf5a24d 100644
--- a/src/backend/utils/adt/varchar.c
+++ b/src/backend/utils/adt/varchar.c
@@ -634,7 +634,7 @@ varchar(PG_FUNCTION_ARGS)
{
for (i = maxmblen; i < len; i++)
if (s_data[i] != ' ')
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
errmsg("value too long for type character varying(%d)",
maxlen)));
--
2.34.1
v13-0003-error-safe-for-casting-character-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v13-0003-error-safe-for-casting-character-to-other-types-per-pg_cast.patchDownload
From 3069a32ec8a4360286ca782ae6033334e7a30e34 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Mon, 24 Nov 2025 12:52:16 +0800
Subject: [PATCH v13 03/20] error safe for casting character to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype ='character'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+-------------------+----------+-------------+------------+-------------+---------
character | text | 401 | i | f | rtrim1 | text
character | character varying | 401 | i | f | rtrim1 | text
character | "char" | 944 | a | f | text_char | char
character | name | 409 | i | f | bpchar_name | name
character | xml | 2896 | e | f | texttoxml | xml
character | character | 668 | i | f | bpchar | bpchar
(6 rows)
only texttoxml, bpchar(PG_FUNCTION_ARGS) need take care of error handling.
other functions already error safe.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/executor/execExprInterp.c | 2 +-
src/backend/utils/adt/varchar.c | 2 +-
src/backend/utils/adt/xml.c | 18 ++++++++++++------
src/include/utils/xml.h | 2 +-
4 files changed, 15 insertions(+), 9 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 0e1a74976f7..67f4e00eac4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -4542,7 +4542,7 @@ ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op)
*op->resvalue = PointerGetDatum(xmlparse(data,
xexpr->xmloption,
- preserve_whitespace));
+ preserve_whitespace, NULL));
*op->resnull = false;
}
break;
diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c
index 3f40c9da1a0..5cb5c8c46f9 100644
--- a/src/backend/utils/adt/varchar.c
+++ b/src/backend/utils/adt/varchar.c
@@ -307,7 +307,7 @@ bpchar(PG_FUNCTION_ARGS)
{
for (i = maxmblen; i < len; i++)
if (s[i] != ' ')
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
errmsg("value too long for type character(%d)",
maxlen)));
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 41e775570ec..9e8016456ce 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -659,7 +659,7 @@ texttoxml(PG_FUNCTION_ARGS)
{
text *data = PG_GETARG_TEXT_PP(0);
- PG_RETURN_XML_P(xmlparse(data, xmloption, true));
+ PG_RETURN_XML_P(xmlparse(data, xmloption, true, fcinfo->context));
}
@@ -1028,19 +1028,25 @@ xmlelement(XmlExpr *xexpr,
xmltype *
-xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace)
+xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, Node *escontext)
{
#ifdef USE_LIBXML
xmlDocPtr doc;
doc = xml_parse(data, xmloption_arg, preserve_whitespace,
- GetDatabaseEncoding(), NULL, NULL, NULL);
- xmlFreeDoc(doc);
+ GetDatabaseEncoding(), NULL, NULL, escontext);
+ if (doc)
+ xmlFreeDoc(doc);
+
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return NULL;
return (xmltype *) data;
#else
- NO_XML_SUPPORT();
- return NULL;
+ ereturn(escontext, NULL
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unsupported XML feature"),
+ errdetail("This functionality requires the server to be built with libxml support."));
#endif
}
diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h
index 732dac47bc4..b15168c430e 100644
--- a/src/include/utils/xml.h
+++ b/src/include/utils/xml.h
@@ -73,7 +73,7 @@ extern xmltype *xmlconcat(List *args);
extern xmltype *xmlelement(XmlExpr *xexpr,
const Datum *named_argvalue, const bool *named_argnull,
const Datum *argvalue, const bool *argnull);
-extern xmltype *xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace);
+extern xmltype *xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, Node *escontext);
extern xmltype *xmlpi(const char *target, text *arg, bool arg_is_null, bool *result_is_null);
extern xmltype *xmlroot(xmltype *data, text *version, int standalone);
extern bool xml_is_document(xmltype *arg);
--
2.34.1
v13-0004-error-safe-for-casting-character-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v13-0004-error-safe-for-casting-character-to-other-types-per-pg_cast.patchDownload
From 55106063589c356276a6c5b9475b516ce4b09cc0 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Tue, 25 Nov 2025 20:07:28 +0800
Subject: [PATCH v13 04/20] error safe for casting character to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype ='character'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+-------------------+----------+-------------+------------+-------------+---------
character | text | 401 | i | f | rtrim1 | text
character | character varying | 401 | i | f | rtrim1 | text
character | "char" | 944 | a | f | text_char | char
character | name | 409 | i | f | bpchar_name | name
character | xml | 2896 | e | f | texttoxml | xml
character | character | 668 | i | f | bpchar | bpchar
(6 rows)
only texttoxml, bpchar(PG_FUNCTION_ARGS) need take care of error handling.
other functions already error safe.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/catalog/namespace.c | 58 ++++++++++++++++++++++++++-------
src/backend/utils/adt/regproc.c | 13 ++++++--
src/backend/utils/adt/varlena.c | 10 ++++--
src/backend/utils/adt/xml.c | 2 +-
src/include/catalog/namespace.h | 6 ++++
src/include/utils/varlena.h | 1 +
6 files changed, 73 insertions(+), 17 deletions(-)
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index d23474da4fb..bef2de5dd39 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -440,6 +440,16 @@ Oid
RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
uint32 flags,
RangeVarGetRelidCallback callback, void *callback_arg)
+{
+ return RangeVarGetRelidExtendedSafe(relation, lockmode, flags,
+ callback, callback_arg,
+ NULL);
+}
+
+Oid
+RangeVarGetRelidExtendedSafe(const RangeVar *relation, LOCKMODE lockmode, uint32 flags,
+ RangeVarGetRelidCallback callback, void *callback_arg,
+ Node *escontext)
{
uint64 inval_count;
Oid relId;
@@ -456,7 +466,7 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
if (relation->catalogname)
{
if (strcmp(relation->catalogname, get_database_name(MyDatabaseId)) != 0)
- ereport(ERROR,
+ ereturn(escontext, InvalidOid,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cross-database references are not implemented: \"%s.%s.%s\"",
relation->catalogname, relation->schemaname,
@@ -513,7 +523,7 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
* return InvalidOid.
*/
if (namespaceId != myTempNamespace)
- ereport(ERROR,
+ ereturn(escontext, InvalidOid,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("temporary tables cannot specify a schema name")));
}
@@ -593,13 +603,23 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
{
int elevel = (flags & RVR_SKIP_LOCKED) ? DEBUG1 : ERROR;
- if (relation->schemaname)
- ereport(elevel,
+ if (relation->schemaname && elevel == DEBUG1)
+ ereport(DEBUG1,
(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
errmsg("could not obtain lock on relation \"%s.%s\"",
relation->schemaname, relation->relname)));
- else
- ereport(elevel,
+ else if (relation->schemaname && elevel == ERROR)
+ ereturn(escontext, InvalidOid,
+ errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s.%s\"",
+ relation->schemaname, relation->relname));
+ else if (elevel == DEBUG1)
+ ereport(DEBUG1,
+ errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s\"",
+ relation->relname));
+ else if (elevel == ERROR)
+ ereturn(escontext, InvalidOid,
(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
errmsg("could not obtain lock on relation \"%s\"",
relation->relname)));
@@ -626,13 +646,23 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
{
int elevel = missing_ok ? DEBUG1 : ERROR;
- if (relation->schemaname)
- ereport(elevel,
+ if (relation->schemaname && elevel == DEBUG1)
+ ereport(DEBUG1,
(errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("relation \"%s.%s\" does not exist",
relation->schemaname, relation->relname)));
- else
- ereport(elevel,
+ else if (relation->schemaname && elevel == ERROR)
+ ereturn(escontext, InvalidOid,
+ errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s.%s\" does not exist",
+ relation->schemaname, relation->relname));
+ else if (elevel == DEBUG1)
+ ereport(DEBUG1,
+ errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" does not exist",
+ relation->relname));
+ else if (elevel == ERROR)
+ ereturn(escontext, InvalidOid,
(errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("relation \"%s\" does not exist",
relation->relname)));
@@ -3622,6 +3652,12 @@ get_namespace_oid(const char *nspname, bool missing_ok)
*/
RangeVar *
makeRangeVarFromNameList(const List *names)
+{
+ return makeRangeVarFromNameListSafe(names, NULL);
+}
+
+RangeVar *
+makeRangeVarFromNameListSafe(const List *names, Node *escontext)
{
RangeVar *rel = makeRangeVar(NULL, NULL, -1);
@@ -3640,7 +3676,7 @@ makeRangeVarFromNameList(const List *names)
rel->relname = strVal(lthird(names));
break;
default:
- ereport(ERROR,
+ ereturn(escontext, NULL,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("improper relation name (too many dotted names): %s",
NameListToString(names))));
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index e5c2246f2c9..59cc508f805 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -1901,12 +1901,19 @@ text_regclass(PG_FUNCTION_ARGS)
text *relname = PG_GETARG_TEXT_PP(0);
Oid result;
RangeVar *rv;
+ List *namelist;
- rv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
+ namelist = textToQualifiedNameListSafe(relname, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
+
+ rv = makeRangeVarFromNameListSafe(namelist, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
/* We might not even have permissions on this relation; don't lock it. */
- result = RangeVarGetRelid(rv, NoLock, false);
-
+ result = RangeVarGetRelidExtendedSafe(rv, NoLock, 0, NULL, NULL,
+ fcinfo->context);
PG_RETURN_OID(result);
}
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index 3894457ab40..f8becbcde51 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -2684,6 +2684,12 @@ name_text(PG_FUNCTION_ARGS)
*/
List *
textToQualifiedNameList(text *textval)
+{
+ return textToQualifiedNameListSafe(textval, NULL);
+}
+
+List *
+textToQualifiedNameListSafe(text *textval, Node *escontext)
{
char *rawname;
List *result = NIL;
@@ -2695,12 +2701,12 @@ textToQualifiedNameList(text *textval)
rawname = text_to_cstring(textval);
if (!SplitIdentifierString(rawname, '.', &namelist))
- ereport(ERROR,
+ ereturn(escontext, NIL,
(errcode(ERRCODE_INVALID_NAME),
errmsg("invalid name syntax")));
if (namelist == NIL)
- ereport(ERROR,
+ ereturn(escontext, NIL,
(errcode(ERRCODE_INVALID_NAME),
errmsg("invalid name syntax")));
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 9e8016456ce..8de1f1fc741 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -1043,7 +1043,7 @@ xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, Node
return (xmltype *) data;
#else
- ereturn(escontext, NULL
+ ereturn(escontext, NULL,
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("unsupported XML feature"),
errdetail("This functionality requires the server to be built with libxml support."));
diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h
index f1423f28c32..ab61af55ddc 100644
--- a/src/include/catalog/namespace.h
+++ b/src/include/catalog/namespace.h
@@ -103,6 +103,11 @@ extern Oid RangeVarGetRelidExtended(const RangeVar *relation,
LOCKMODE lockmode, uint32 flags,
RangeVarGetRelidCallback callback,
void *callback_arg);
+extern Oid RangeVarGetRelidExtendedSafe(const RangeVar *relation,
+ LOCKMODE lockmode, uint32 flags,
+ RangeVarGetRelidCallback callback,
+ void *callback_arg,
+ Node *escontext);
extern Oid RangeVarGetCreationNamespace(const RangeVar *newRelation);
extern Oid RangeVarGetAndCheckCreationNamespace(RangeVar *relation,
LOCKMODE lockmode,
@@ -168,6 +173,7 @@ extern Oid LookupCreationNamespace(const char *nspname);
extern void CheckSetNamespace(Oid oldNspOid, Oid nspOid);
extern Oid QualifiedNameGetCreationNamespace(const List *names, char **objname_p);
extern RangeVar *makeRangeVarFromNameList(const List *names);
+extern RangeVar *makeRangeVarFromNameListSafe(const List *names, Node *escontext);
extern char *NameListToString(const List *names);
extern char *NameListToQuotedString(const List *names);
diff --git a/src/include/utils/varlena.h b/src/include/utils/varlena.h
index db9fdf72941..0cf01ae5281 100644
--- a/src/include/utils/varlena.h
+++ b/src/include/utils/varlena.h
@@ -27,6 +27,7 @@ extern int varstr_levenshtein_less_equal(const char *source, int slen,
int ins_c, int del_c, int sub_c,
int max_d, bool trusted);
extern List *textToQualifiedNameList(text *textval);
+extern List *textToQualifiedNameListSafe(text *textval, Node *escontext);
extern bool SplitIdentifierString(char *rawstring, char separator,
List **namelist);
extern bool SplitDirectoriesString(char *rawstring, char separator,
--
2.34.1
v13-0002-error-safe-for-casting-bit-varbit-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v13-0002-error-safe-for-casting-bit-varbit-to-other-types-per-pg_cast.patchDownload
From cc1824b43dc0340f474fc80e27cfa81a7edada26 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 10:33:08 +0800
Subject: [PATCH v13 02/20] error safe for casting bit/varbit to other types
per pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
where pc.castfunc > 0 and (castsource::regtype ='bit'::regtype or
castsource::regtype ='varbit'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
-------------+-------------+----------+-------------+------------+-----------+---------
bit | bigint | 2076 | e | f | bittoint8 | int8
bit | integer | 1684 | e | f | bittoint4 | int4
bit | bit | 1685 | i | f | bit | bit
bit varying | bit varying | 1687 | i | f | varbit | varbit
(4 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/varbit.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/backend/utils/adt/varbit.c b/src/backend/utils/adt/varbit.c
index 205a67dafc5..6e9b808e20a 100644
--- a/src/backend/utils/adt/varbit.c
+++ b/src/backend/utils/adt/varbit.c
@@ -401,7 +401,7 @@ bit(PG_FUNCTION_ARGS)
PG_RETURN_VARBIT_P(arg);
if (!isExplicit)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_STRING_DATA_LENGTH_MISMATCH),
errmsg("bit string length %d does not match type bit(%d)",
VARBITLEN(arg), len)));
@@ -752,7 +752,7 @@ varbit(PG_FUNCTION_ARGS)
PG_RETURN_VARBIT_P(arg);
if (!isExplicit)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
errmsg("bit string too long for type bit varying(%d)",
len)));
@@ -1591,7 +1591,7 @@ bittoint4(PG_FUNCTION_ARGS)
/* Check that the bit string is not too long */
if (VARBITLEN(arg) > sizeof(result) * BITS_PER_BYTE)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
@@ -1671,7 +1671,7 @@ bittoint8(PG_FUNCTION_ARGS)
/* Check that the bit string is not too long */
if (VARBITLEN(arg) > sizeof(result) * BITS_PER_BYTE)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("bigint out of range")));
--
2.34.1
v13-0001-error-safe-for-casting-bytea-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v13-0001-error-safe-for-casting-bytea-to-other-types-per-pg_cast.patchDownload
From f4f0e093e60f50b5ee0da5a941bd2e1015be3236 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:08:00 +0800
Subject: [PATCH v13 01/20] error safe for casting bytea to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc and pc.castfunc > 0 and castsource::regtype ='bytea'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------+----------+-------------+------------+------------+---------
bytea | smallint | 6370 | e | f | bytea_int2 | int2
bytea | integer | 6371 | e | f | bytea_int4 | int4
bytea | bigint | 6372 | e | f | bytea_int8 | int8
(3 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/bytea.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/backend/utils/adt/bytea.c b/src/backend/utils/adt/bytea.c
index 6e7b914c563..f43ad6b4aff 100644
--- a/src/backend/utils/adt/bytea.c
+++ b/src/backend/utils/adt/bytea.c
@@ -1027,7 +1027,7 @@ bytea_int2(PG_FUNCTION_ARGS)
/* Check that the byte array is not too long */
if (len > sizeof(result))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range"));
@@ -1052,7 +1052,7 @@ bytea_int4(PG_FUNCTION_ARGS)
/* Check that the byte array is not too long */
if (len > sizeof(result))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range"));
@@ -1077,7 +1077,7 @@ bytea_int8(PG_FUNCTION_ARGS)
/* Check that the byte array is not too long */
if (len > sizeof(result))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("bigint out of range"));
--
2.34.1
On Tue, Dec 2, 2025 at 1:23 PM jian he <jian.universality@gmail.com> wrote:
On Mon, Dec 1, 2025 at 8:09 PM Amul Sul <sulamul@gmail.com> wrote:
Since you declared float8_div_safe() as static inline, I believe it
wouldn't have any performance degradation since most compilers
optimize it. Also, I suggest you pass the ErrorSafeContext to
float_overflow_error(), float_underflow_error(), and
float_zero_divide_error() so that you can avoid duplicating error
messages.hi.
First I want to use ereturn, then I found out
float_overflow_error, float_underflow_error, float_zero_divide_error
used both in float4, float8.
ereturn would not be appropriate for both types.
so I choose errsave.
for these 3 functions, now it looks like:
Make sense, thanks for updating the patch.
Regarding v13-0019 and v13-0020 patches, I have a bunch of comments
for the code, but I'd like to understand the implemented design first.
I have some basic questions regarding the commit message and code
comment, as follows:
"
We cannot simply prohibit user-defined functions in pg_cast for safe cast
evaluation because CREATE CAST can also utilize built-in functions. So, to
completely disallow custom casts created via CREATE CAST used in safe cast
evaluation, a new field in pg_cast would unfortunately be necessary.
"
I might not have understood the implementation completely -- can't we
use fmgr_last_builtin_oid to detect built-in functions?
--
+ /*
+ * We have to to use CoerceUnknownConstSafe rather than
+ * coerce_to_target_type. because coerce_to_target_type is not error
+ * safe.
+ */
How difficult would it be to modify coerce_to_target_type() to make it
error safe?
Could we utilize the existing type casting infrastructure for this, perhaps by
simply considering the evolution and use of the additional default
expression? If we could, the resulting implementation would be very
clean, IMHO.
Kindly excuse and point me if that is already discussed.
--
Here are few comments for v13-0018:
static inline void point_add_point(Point *result, Point *pt1, Point *pt2);
+static inline bool point_add_point_safe(Point *result, Point *pt1, Point *pt2,
+ Node *escontext);
+static inline float8 point_dt_safe(Point *pt1, Point *pt2, Node *escontext);
static inline float8 point_sl(Point *pt1, Point *pt2);
I think we should avoid introducing the "_safe" version of static
routines in the .c file. Instead, we can add the Node *escontext
argument to the existing routines, similar to how single_decode() was
previously modified to accept it.
--
-static void poly_to_circle(CIRCLE *result, POLYGON *poly);
+static bool poly_to_circle_safe(CIRCLE *result, POLYGON *poly, Node
*escontext);
Following the previous suggestion, please keep the existing function
as it is and just add one more argument to it.
--
}
+
static inline float4
float4_mi(const float4 val1, const float4 val2)
{
A spurious change.
--
Regards,
Amul
On Thu, Dec 4, 2025 at 8:47 PM Amul Sul <sulamul@gmail.com> wrote:
Regarding v13-0019 and v13-0020 patches, I have a bunch of comments
for the code, but I'd like to understand the implemented design first.
I have some basic questions regarding the commit message and code
comment, as follows:"
We cannot simply prohibit user-defined functions in pg_cast for safe cast
evaluation because CREATE CAST can also utilize built-in functions. So, to
completely disallow custom casts created via CREATE CAST used in safe cast
evaluation, a new field in pg_cast would unfortunately be necessary.
"I might not have understood the implementation completely -- can't we
use fmgr_last_builtin_oid to detect built-in functions?
--
hi, Amul.
I have removed the pg_cast.casterrorsafe field.
But in the future, to make the below (Examples) CASTs associated with
built-in function
error safe, I think we need pg_cast.casterrorsafe. Otherwise,
how can we know if the pg_cast.castfunc (built-in function) is error
safe or not.
CREATE CAST (interval AS uuid ) WITH FUNCTION uuidv7(interval);
CREATE CAST (jsonb AS text) WITH FUNCTION jsonb_array_element_text(jsonb, int);
CREATE CAST (json AS text) WITH FUNCTION json_array_element_text(json, int);
I’ve added a dedicated function: CoercionErrorSafeCheck, which
checks whether the cast can be evaluated safely. If not, we throw an error.
SELECT CAST('A' AS DATE DEFAULT NULL ON CONVERSION ERROR)
Here, 'A' is an Unknown Const. As mentioned earlier, Unknown Const Node must be
coerced error safe, otherwise, it will raise an error instead of returning NULL.
The coercion of Unknown Const happens inside coerce_to_target_type, which means
part of that function must run in an error safe. coerce_to_target_type is used
widely, adding another parameter to it seems no good, so I introduced
coerce_to_target_type_extended, coerce_type_extend.
CoerceUnknownConstSafe is being removed.
stringTypeDatumSafe, OidInputFunctionCallSafe functions are needed for coercing
Unknown Const to the target type. Refactoring existing function seems not
ideal, so i invented these two functions.
Attachments:
v14-0016-error-safe-for-casting-timestamp-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v14-0016-error-safe-for-casting-timestamp-to-other-types-per-pg_cast.patchDownload
From 732b6d48fe00592a81842c271712aff933179891 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Tue, 2 Dec 2025 12:14:36 +0800
Subject: [PATCH v14 16/19] error safe for casting timestamp to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc
and pc.castfunc > 0 and (castsource::regtype ='timestamp'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
-----------------------------+-----------------------------+----------+-------------+------------+-----------------------+-------------
timestamp without time zone | date | 2029 | a | f | timestamp_date | date
timestamp without time zone | time without time zone | 1316 | a | f | timestamp_time | time
timestamp without time zone | timestamp with time zone | 2028 | i | f | timestamp_timestamptz | timestamptz
timestamp without time zone | timestamp without time zone | 1961 | i | f | timestamp_scale | timestamp
(4 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 7 +++++--
src/backend/utils/adt/timestamp.c | 10 ++++++++--
2 files changed, 13 insertions(+), 4 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index bbc864a80cd..91cc8cc85f0 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -1330,7 +1330,10 @@ timestamp_date(PG_FUNCTION_ARGS)
Timestamp timestamp = PG_GETARG_TIMESTAMP(0);
DateADT result;
- result = timestamp2date_safe(timestamp, NULL);
+ result = timestamp2date_safe(timestamp, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
+
PG_RETURN_DATEADT(result);
}
@@ -2008,7 +2011,7 @@ timestamp_time(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index c144caf2458..e84cf4b25f8 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -352,7 +352,8 @@ timestamp_scale(PG_FUNCTION_ARGS)
result = timestamp;
- AdjustTimestampForTypmod(&result, typmod, NULL);
+ if (!AdjustTimestampForTypmod(&result, typmod, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_TIMESTAMP(result);
}
@@ -6432,8 +6433,13 @@ Datum
timestamp_timestamptz(PG_FUNCTION_ARGS)
{
Timestamp timestamp = PG_GETARG_TIMESTAMP(0);
+ TimestampTz result;
- PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(timestamp));
+ result = timestamp2timestamptz_safe(timestamp, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
+
+ PG_RETURN_TIMESTAMPTZ(result);
}
/*
--
2.34.1
v14-0019-CAST-expr-AS-newtype-DEFAULT-ON-ERROR.patchtext/x-patch; charset=UTF-8; name=v14-0019-CAST-expr-AS-newtype-DEFAULT-ON-ERROR.patchDownload
From 7bb658eeabe81b0aba4a0c29de1c386bbbec8d6a Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Mon, 8 Dec 2025 22:53:15 +0800
Subject: [PATCH v14 19/19] CAST(expr AS newtype DEFAULT ON ERROR)
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.
draft doc also added.
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}
[0]: https://git.postgresql.org/cgit/postgresql.git/commit/?id=aaaf9449ec6be62cb0d30ed3588dc384f56274b
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
contrib/citext/expected/citext_1.out | 5 +
contrib/citext/sql/citext.sql | 2 +
.../pg_stat_statements/expected/select.out | 23 +-
contrib/pg_stat_statements/sql/select.sql | 5 +
doc/src/sgml/syntax.sgml | 30 +
src/backend/executor/execExpr.c | 102 ++-
src/backend/executor/execExprInterp.c | 29 +
src/backend/jit/llvm/llvmjit_expr.c | 26 +
src/backend/nodes/nodeFuncs.c | 61 ++
src/backend/optimizer/util/clauses.c | 51 +-
src/backend/parser/gram.y | 22 +
src/backend/parser/parse_agg.c | 9 +
src/backend/parser/parse_coerce.c | 78 +-
src/backend/parser/parse_expr.c | 412 ++++++++-
src/backend/parser/parse_func.c | 3 +
src/backend/parser/parse_target.c | 14 +
src/backend/parser/parse_type.c | 14 +
src/backend/parser/parse_utilcmd.c | 2 +-
src/backend/utils/adt/arrayfuncs.c | 8 +
src/backend/utils/adt/ruleutils.c | 21 +
src/backend/utils/fmgr/fmgr.c | 13 +
src/include/executor/execExpr.h | 7 +
src/include/executor/executor.h | 1 +
src/include/fmgr.h | 3 +
src/include/nodes/execnodes.h | 21 +
src/include/nodes/parsenodes.h | 11 +
src/include/nodes/primnodes.h | 36 +
src/include/optimizer/optimizer.h | 2 +-
src/include/parser/parse_coerce.h | 15 +
src/include/parser/parse_node.h | 2 +
src/include/parser/parse_type.h | 2 +
src/test/regress/expected/cast.out | 810 ++++++++++++++++++
src/test/regress/expected/create_cast.out | 5 +
src/test/regress/expected/equivclass.out | 7 +
src/test/regress/parallel_schedule | 2 +-
src/test/regress/sql/cast.sql | 350 ++++++++
src/test/regress/sql/create_cast.sql | 1 +
src/test/regress/sql/equivclass.sql | 3 +
src/tools/pgindent/typedefs.list | 3 +
39 files changed, 2166 insertions(+), 45 deletions(-)
create mode 100644 src/test/regress/expected/cast.out
create mode 100644 src/test/regress/sql/cast.sql
diff --git a/contrib/citext/expected/citext_1.out b/contrib/citext/expected/citext_1.out
index c5e5f180f2b..647eea19142 100644
--- a/contrib/citext/expected/citext_1.out
+++ b/contrib/citext/expected/citext_1.out
@@ -10,6 +10,11 @@ WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid);
--------+---------
(0 rows)
+SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR: cannot cast type character to citext when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSI...
+ ^
+HINT: Safe type cast for user-defined types are not yet supported
-- Test the operators and indexing functions
-- Test = and <>.
SELECT 'a'::citext = 'a'::citext AS t;
diff --git a/contrib/citext/sql/citext.sql b/contrib/citext/sql/citext.sql
index aa1cf9abd5c..99794497d47 100644
--- a/contrib/citext/sql/citext.sql
+++ b/contrib/citext/sql/citext.sql
@@ -9,6 +9,8 @@ SELECT amname, opcname
FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod
WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid);
+SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR); --error
+
-- Test the operators and indexing functions
-- Test = and <>.
diff --git a/contrib/pg_stat_statements/expected/select.out b/contrib/pg_stat_statements/expected/select.out
index 75c896f3885..6e67997f0a2 100644
--- a/contrib/pg_stat_statements/expected/select.out
+++ b/contrib/pg_stat_statements/expected/select.out
@@ -73,6 +73,25 @@ SELECT 1 AS "int" OFFSET 2 FETCH FIRST 3 ROW ONLY;
-----
(0 rows)
+--error safe type cast
+SELECT CAST('a' AS int DEFAULT 2 ON CONVERSION ERROR);
+ int4
+------
+ 2
+(1 row)
+
+SELECT CAST('11' AS int DEFAULT 2 ON CONVERSION ERROR);
+ int4
+------
+ 11
+(1 row)
+
+SELECT CAST('12' AS numeric DEFAULT 2 ON CONVERSION ERROR);
+ numeric
+---------
+ 12
+(1 row)
+
-- DISTINCT and ORDER BY patterns
-- Try some query permutations which once produced identical query IDs
SELECT DISTINCT 1 AS "int";
@@ -222,6 +241,8 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
2 | 2 | SELECT $1 AS "int" ORDER BY 1
1 | 2 | SELECT $1 AS i UNION SELECT $2 ORDER BY i
1 | 1 | SELECT $1 || $2
+ 2 | 2 | SELECT CAST($1 AS int DEFAULT $2 ON CONVERSION ERROR)
+ 1 | 1 | SELECT CAST($1 AS numeric DEFAULT $2 ON CONVERSION ERROR)
2 | 2 | SELECT DISTINCT $1 AS "int"
0 | 0 | SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"
1 | 1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
@@ -230,7 +251,7 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
| | ) +
| | SELECT f FROM t ORDER BY f
1 | 1 | select $1::jsonb ? $2
-(17 rows)
+(19 rows)
SELECT pg_stat_statements_reset() IS NOT NULL AS t;
t
diff --git a/contrib/pg_stat_statements/sql/select.sql b/contrib/pg_stat_statements/sql/select.sql
index 11662cde08c..7ee8160fd84 100644
--- a/contrib/pg_stat_statements/sql/select.sql
+++ b/contrib/pg_stat_statements/sql/select.sql
@@ -25,6 +25,11 @@ SELECT 1 AS "int" LIMIT 3 OFFSET 3;
SELECT 1 AS "int" OFFSET 1 FETCH FIRST 2 ROW ONLY;
SELECT 1 AS "int" OFFSET 2 FETCH FIRST 3 ROW ONLY;
+--error safe type cast
+SELECT CAST('a' AS int DEFAULT 2 ON CONVERSION ERROR);
+SELECT CAST('11' AS int DEFAULT 2 ON CONVERSION ERROR);
+SELECT CAST('12' AS numeric DEFAULT 2 ON CONVERSION ERROR);
+
-- DISTINCT and ORDER BY patterns
-- Try some query permutations which once produced identical query IDs
SELECT DISTINCT 1 AS "int";
diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml
index 34c83880a66..526df9922e7 100644
--- a/doc/src/sgml/syntax.sgml
+++ b/doc/src/sgml/syntax.sgml
@@ -2106,6 +2106,10 @@ CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable>
The <literal>CAST</literal> syntax conforms to SQL; the syntax with
<literal>::</literal> is historical <productname>PostgreSQL</productname>
usage.
+ The equivalent ON CONVERSION ERROR behavior is:
+<synopsis>
+CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable> ERROR ON CONVERSION ERROR )
+</synopsis>
</para>
<para>
@@ -2160,6 +2164,32 @@ CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable>
<xref linkend="sql-createcast"/>.
</para>
</note>
+
+ <sect3 id="sql-syntax-type-casts-safe">
+ <title>Safe Type Cast</title>
+ <para>
+Sometimes a type cast may fail; to avoid such fail case, an <literal>ON ERROR</literal> clause can be
+used to avoid potential errors. The syntax is:
+<synopsis>
+CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable> DEFAULT <replaceable>expression</replaceable> ON CONVERSION ERROR )
+</synopsis>
+ If type cast fails, it falls back to evaluating the supplied
+ default <replaceable>expression</replaceable> in <literal>ON ERROR</literal> clause.
+ Currently, this only support built-in system type casts, see <xref linkend="catalog-pg-cast"/>.
+ Type casts created using <link linkend="sql-createcast">CREATE CAST</link> are not supported.
+ </para>
+
+ <para>
+ Some examples:
+<screen>
+SELECT CAST(TEXT 'error' AS integer DEFAULT 3 ON CONVERSION ERROR);
+<lineannotation>Result: </lineannotation><computeroutput>3</computeroutput>
+SELECT CAST(TEXT 'error' AS numeric DEFAULT 1.1 ON CONVERSION ERROR);
+<lineannotation>Result: </lineannotation><computeroutput>1.1</computeroutput>
+</screen>
+ </para>
+ </sect3>
+
</sect2>
<sect2 id="sql-syntax-collate-exprs">
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index b05ff476a63..2629aefe74f 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,
@@ -170,6 +173,47 @@ ExecInitExpr(Expr *node, PlanState *parent)
return state;
}
+/*
+ * ExecInitExprSafe: soft error variant of ExecInitExpr.
+ *
+ * use it only for expression nodes support soft errors, not all expression
+ * nodes support it.
+*/
+ExprState *
+ExecInitExprSafe(Expr *node, PlanState *parent)
+{
+ ExprState *state;
+ ExprEvalStep scratch = {0};
+
+ /* Special case: NULL expression produces a NULL ExprState pointer */
+ if (node == NULL)
+ return NULL;
+
+ /* Initialize ExprState with empty step list */
+ state = makeNode(ExprState);
+ state->expr = node;
+ state->parent = parent;
+ state->ext_params = NULL;
+ state->escontext = makeNode(ErrorSaveContext);
+ state->escontext->type = T_ErrorSaveContext;
+ state->escontext->error_occurred = false;
+ state->escontext->details_wanted = false;
+
+ /* Insert setup steps as needed */
+ ExecCreateExprSetupSteps(state, (Node *) node);
+
+ /* Compile the expression proper */
+ ExecInitExprRec(node, state, &state->resvalue, &state->resnull);
+
+ /* Finally, append a DONE step */
+ scratch.opcode = EEOP_DONE_RETURN;
+ ExprEvalPushStep(state, &scratch);
+
+ ExecReadyExpr(state);
+
+ return state;
+}
+
/*
* ExecInitExprWithParams: prepare a standalone expression tree for execution
*
@@ -1701,6 +1745,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
elemstate->innermost_caseval = (Datum *) palloc(sizeof(Datum));
elemstate->innermost_casenull = (bool *) palloc(sizeof(bool));
+ elemstate->escontext = state->escontext;
ExecInitExprRec(acoerce->elemexpr, elemstate,
&elemstate->resvalue, &elemstate->resnull);
@@ -2177,6 +2222,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;
@@ -2743,7 +2796,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;
@@ -4741,6 +4794,53 @@ ExecBuildParamSetEqual(TupleDesc desc,
return state;
}
+/*
+ * Push steps to evaluate a SafeTypeCastExpr and its various subsidiary
+ * expressions.
+ */
+static void
+ExecInitSafeTypeCastExpr(SafeTypeCastExpr *stcexpr , ExprState *state,
+ Datum *resv, bool *resnull,
+ ExprEvalStep *scratch)
+{
+ /*
+ * If we can not coerce to the target type, fallback to the DEFAULT
+ * expression specified in the ON CONVERSION ERROR clause, and we are done.
+ */
+ if (stcexpr->cast_expr == NULL)
+ {
+ ExecInitExprRec((Expr *) stcexpr->default_expr,
+ state, resv, resnull);
+ return;
+ }
+ else
+ {
+ SafeTypeCastState *stcstate;
+ ErrorSaveContext *saved_escontext;
+
+ stcstate = palloc0(sizeof(SafeTypeCastState));
+ stcstate->stcexpr = stcexpr;
+ stcstate->escontext.type = T_ErrorSaveContext;
+ state->escontext = &stcstate->escontext;
+
+ /* evaluate argument expression into step's result area */
+ ExecInitExprRec((Expr *) stcexpr->cast_expr,
+ state, resv, resnull);
+ scratch->opcode = EEOP_SAFETYPE_CAST;
+ scratch->d.stcexpr.stcstate = stcstate;
+ ExprEvalPushStep(state, scratch);
+
+ /* Steps to evaluate the DEFAULT expression */
+ saved_escontext = state->escontext;
+ state->escontext = NULL;
+ ExecInitExprRec((Expr *) stcstate->stcexpr->default_expr,
+ state, resv, resnull);
+ state->escontext = saved_escontext;
+
+ 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 1d88cdd2cb4..ca6e0fd56cd 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,28 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
EEO_NEXT();
}
+ EEO_CASE(EEOP_SAFETYPE_CAST)
+ {
+ SafeTypeCastState *stcstate = op->d.stcexpr.stcstate;
+
+ if (SOFT_ERROR_OCCURRED(&stcstate->escontext))
+ {
+ *op->resvalue = (Datum) 0;
+ *op->resnull = true;
+
+ /*
+ * Reset for next use such as for catching errors when coercing
+ * a expression.
+ */
+ stcstate->escontext.error_occurred = false;
+ stcstate->escontext.details_wanted = false;
+
+ EEO_NEXT();
+ }
+ else
+ EEO_JUMP(stcstate->jump_end);
+ }
+
EEO_CASE(EEOP_JSONEXPR_PATH)
{
/* too complex for an inline implementation */
@@ -3644,6 +3667,12 @@ 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/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index ac88881e995..bc7cd3283a5 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -2256,6 +2256,32 @@ llvm_compile_expr(ExprState *state)
LLVMBuildBr(b, opblocks[opno + 1]);
break;
+ case EEOP_SAFETYPE_CAST:
+ {
+ SafeTypeCastState *stcstate = op->d.stcexpr.stcstate;
+
+ if (SOFT_ERROR_OCCURRED(&stcstate->escontext))
+ {
+ /*
+ * Reset for next use such as for catching errors when
+ * coercing a expression.
+ */
+ stcstate->escontext.error_occurred = false;
+ stcstate->escontext.details_wanted = false;
+
+ /* set resnull to true */
+ LLVMBuildStore(b, l_sbool_const(1), v_resnullp);
+ /* reset resvalue */
+ LLVMBuildStore(b, l_datum_const(0), v_resvaluep);
+
+ LLVMBuildBr(b, opblocks[opno + 1]);
+ }
+ else
+ 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 d228318dc72..b3ae69068d0 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;
@@ -1550,6 +1561,9 @@ exprLocation(const Node *expr)
/* just use leftmost argument's location */
loc = exprLocation((Node *) ((const RowCompareExpr *) expr)->largs);
break;
+ case T_SafeTypeCastExpr:
+ loc = ((const SafeTypeCastExpr *) expr)->location;
+ break;
case T_CoalesceExpr:
/* COALESCE keyword should always be the first thing */
loc = ((const CoalesceExpr *) expr)->location;
@@ -2321,6 +2335,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:
@@ -3330,6 +3356,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;
@@ -4464,6 +4503,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->raw_default))
+ 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/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index bda4c4eb292..e6d8b6c467f 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -2447,7 +2447,8 @@ estimate_expression_value(PlannerInfo *root, Node *node)
((Node *) evaluate_expr((Expr *) (node), \
exprType((Node *) (node)), \
exprTypmod((Node *) (node)), \
- exprCollation((Node *) (node))))
+ exprCollation((Node *) (node)), \
+ false))
/*
* Recursive guts of eval_const_expressions/estimate_expression_value
@@ -2958,6 +2959,32 @@ 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);
+
+ /*
+ * We must not reduce any recognizably constant subexpressions
+ * in cast_expr here, since we don’t want it to fail
+ * prematurely.
+ */
+ 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:
@@ -3380,7 +3407,8 @@ eval_const_expressions_mutator(Node *node,
return (Node *) evaluate_expr((Expr *) svf,
svf->type,
svf->typmod,
- InvalidOid);
+ InvalidOid,
+ false);
else
return copyObject((Node *) svf);
}
@@ -4698,7 +4726,7 @@ evaluate_function(Oid funcid, Oid result_type, int32 result_typmod,
newexpr->location = -1;
return evaluate_expr((Expr *) newexpr, result_type, result_typmod,
- result_collid);
+ result_collid, false);
}
/*
@@ -5152,10 +5180,14 @@ sql_inline_error_callback(void *arg)
*
* We use the executor's routine ExecEvalExpr() to avoid duplication of
* code and ensure we get the same result as the executor would get.
+ *
+ * When error_safe set to true, we will evaluate the constant expression in a
+ * error safe way. If the evaluation fails, return NULL instead of throwing
+ * error.
*/
Expr *
evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
- Oid result_collation)
+ Oid result_collation, bool error_safe)
{
EState *estate;
ExprState *exprstate;
@@ -5180,7 +5212,10 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
* Prepare expr for execution. (Note: we can't use ExecPrepareExpr
* because it'd result in recursively invoking eval_const_expressions.)
*/
- exprstate = ExecInitExpr(expr, NULL);
+ if (error_safe)
+ exprstate = ExecInitExprSafe(expr, NULL);
+ else
+ exprstate = ExecInitExpr(expr, NULL);
/*
* And evaluate it.
@@ -5200,6 +5235,12 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
/* Get back to outer memory context */
MemoryContextSwitchTo(oldcontext);
+ if (error_safe && SOFT_ERROR_OCCURRED(exprstate->escontext))
+ {
+ FreeExecutorState(estate);
+ return NULL;
+ }
+
/*
* Must copy result out of sub-context used by expression eval.
*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c3a0a354a9c..8a27e045bc0 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -16013,6 +16013,28 @@ func_expr_common_subexpr:
}
| CAST '(' a_expr AS Typename ')'
{ $$ = makeTypeCast($3, $5, @1); }
+ | CAST '(' a_expr AS Typename ERROR_P ON CONVERSION_P ERROR_P ')'
+ { $$ = makeTypeCast($3, $5, @1); }
+ | CAST '(' a_expr AS Typename NULL_P ON CONVERSION_P ERROR_P ')'
+ {
+ TypeCast *cast = (TypeCast *) makeTypeCast($3, $5, @1);
+
+ SafeTypeCast *safecast = makeNode(SafeTypeCast);
+ safecast->cast = (Node *) cast;
+ safecast->raw_default = makeNullAConst(-1);;
+
+ $$ = (Node *) safecast;
+ }
+ | CAST '(' a_expr AS Typename DEFAULT a_expr ON CONVERSION_P ERROR_P ')'
+ {
+ TypeCast *cast = (TypeCast *) makeTypeCast($3, $5, @1);
+
+ SafeTypeCast *safecast = makeNode(SafeTypeCast);
+ safecast->cast = (Node *) cast;
+ safecast->raw_default = $7;
+
+ $$ = (Node *) safecast;
+ }
| EXTRACT '(' extract_list ')'
{
$$ = (Node *) makeFuncCall(SystemFuncName("extract"),
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index b8340557b34..de77f6e9302 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -490,6 +490,12 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
err = _("grouping operations are not allowed in check constraints");
break;
+ case EXPR_KIND_CAST_DEFAULT:
+ if (isAgg)
+ err = _("aggregate functions are not allowed in CAST DEFAULT expressions");
+ else
+ err = _("grouping operations are not allowed in CAST DEFAULT expressions");
+ break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
@@ -983,6 +989,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_DOMAIN_CHECK:
err = _("window functions are not allowed in check constraints");
break;
+ case EXPR_KIND_CAST_DEFAULT:
+ err = _("window functions are not allowed in CAST DEFAULT expressions");
+ break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
err = _("window functions are not allowed in DEFAULT expressions");
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 78b1e366ad7..4ae87e2030b 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -81,6 +81,31 @@ coerce_to_target_type(ParseState *pstate, Node *expr, Oid exprtype,
CoercionContext ccontext,
CoercionForm cformat,
int location)
+{
+ return coerce_to_target_type_extended(pstate,
+ expr,
+ exprtype,
+ targettype,
+ targettypmod,
+ ccontext,
+ cformat,
+ location,
+ NULL);
+}
+
+/*
+ * escontext: If not NULL, expr (Unknown Const node type) will be coerced to the
+ * target type in an error-safe way. If it fails, NULL is returned.
+ *
+ * For other parameters, see above coerce_to_target_type.
+ */
+Node *
+coerce_to_target_type_extended(ParseState *pstate, Node *expr, Oid exprtype,
+ Oid targettype, int32 targettypmod,
+ CoercionContext ccontext,
+ CoercionForm cformat,
+ int location,
+ Node *escontext)
{
Node *result;
Node *origexpr;
@@ -102,9 +127,12 @@ 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_extend(pstate, expr, exprtype,
+ targettype, targettypmod,
+ ccontext, cformat, location,
+ escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return NULL;
/*
* If the target is a fixed-length type, it may need a length coercion as
@@ -158,6 +186,18 @@ Node *
coerce_type(ParseState *pstate, Node *node,
Oid inputTypeId, Oid targetTypeId, int32 targetTypeMod,
CoercionContext ccontext, CoercionForm cformat, int location)
+{
+ return coerce_type_extend(pstate, node,
+ inputTypeId, targetTypeId, targetTypeMod,
+ ccontext, cformat, location, NULL);
+}
+
+Node *
+coerce_type_extend(ParseState *pstate, Node *node,
+ Oid inputTypeId, Oid targetTypeId, int32 targetTypeMod,
+ CoercionContext ccontext, CoercionForm cformat,
+ int location,
+ Node *escontext)
{
Node *result;
CoercionPathType pathtype;
@@ -256,6 +296,8 @@ coerce_type(ParseState *pstate, Node *node,
int32 inputTypeMod;
Type baseType;
ParseCallbackState pcbstate;
+ char *string;
+ bool coercion_failed = false;
/*
* If the target type is a domain, we want to call its base type's
@@ -308,21 +350,27 @@ 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 (con->constisnull)
+ string = NULL;
else
- newcon->constvalue = stringTypeDatum(baseType,
- NULL,
- inputTypeMod);
+ string = DatumGetCString(con->constvalue);
+
+ if (!stringTypeDatumSafe(baseType,
+ string,
+ inputTypeMod,
+ escontext,
+ &newcon->constvalue))
+ {
+ coercion_failed = true;
+ newcon->constisnull = true;
+ }
/*
* 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 (!coercion_failed && !con->constisnull && newcon->constlen == -1)
newcon->constvalue =
PointerGetDatum(PG_DETOAST_DATUM(newcon->constvalue));
@@ -339,7 +387,7 @@ 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 (!coercion_failed && !con->constisnull && !newcon->constbyval)
{
Datum val2;
@@ -358,8 +406,10 @@ coerce_type(ParseState *pstate, Node *node,
result = (Node *) newcon;
- /* If target is a domain, apply constraints. */
- if (baseTypeId != targetTypeId)
+ if (coercion_failed)
+ result = NULL;
+ else if (baseTypeId != targetTypeId)
+ /* If target is a domain, apply constraints. */
result = coerce_to_domain(result,
baseTypeId, baseTypeMod,
targetTypeId,
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 4524e49c326..7d3ed0eb890 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -17,6 +17,7 @@
#include "access/htup_details.h"
#include "catalog/pg_aggregate.h"
+#include "catalog/pg_cast.h"
#include "catalog/pg_type.h"
#include "miscadmin.h"
#include "nodes/makefuncs.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,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 *can_coerce);
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 +79,10 @@ 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 void CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr,
+ Node *sourceexpr, Oid inputType,
+ Oid targetType);
static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
static Node *transformJsonObjectConstructor(ParseState *pstate,
JsonObjectConstructor *ctor);
@@ -164,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;
@@ -564,6 +575,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
case EXPR_KIND_VALUES_SINGLE:
case EXPR_KIND_CHECK_CONSTRAINT:
case EXPR_KIND_DOMAIN_CHECK:
+ case EXPR_KIND_CAST_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
case EXPR_KIND_INDEX_EXPRESSION:
case EXPR_KIND_INDEX_PREDICATE:
@@ -1824,6 +1836,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_DOMAIN_CHECK:
err = _("cannot use subquery in check constraint");
break;
+ case EXPR_KIND_CAST_DEFAULT:
+ err = _("cannot use subquery in CAST DEFAULT expression");
+ break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
err = _("cannot use subquery in DEFAULT expression");
@@ -2011,17 +2026,24 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
* 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().
+ *
+ * Most of the time, can_coerce will be NULL. It is not NULL only when
+ * performing parse analysis for CAST(DEFAULT ... ON CONVERSION ERROR)
+ * expression. It's default to true. If coercing array elements fails, it will
+ * be set to 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;
List *newcoercedelems = NIL;
ListCell *element;
Oid coerce_type;
+ Oid coerce_type_coll;
bool coerce_hard;
+ ErrorSaveContext escontext = {T_ErrorSaveContext};
/*
* Transform the element expressions
@@ -2045,9 +2067,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
@@ -2088,6 +2111,9 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
}
else
{
+ /* Target type must valid for CAST DEFAULT */
+ Assert(can_coerce == NULL);
+
/* Can't handle an empty array without a target type */
if (newelems == NIL)
ereport(ERROR,
@@ -2125,6 +2151,8 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
coerce_hard = false;
}
+ coerce_type_coll = get_typcollation(coerce_type);
+
/*
* Coerce elements to target type
*
@@ -2134,28 +2162,82 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
* If the array's type was merely derived from the common type of its
* elements, then the elements are implicitly coerced to the common type.
* This is consistent with other uses of select_common_type().
+ *
+ * If can_coerce is not NULL, we need to safely test whether each element
+ * can be coerced to the target type, similar to what is done in
+ * transformTypeSafeCast.
*/
foreach(element, newelems)
{
Node *e = (Node *) lfirst(element);
- Node *newe;
+ Node *newe = NULL;
if (coerce_hard)
{
- newe = coerce_to_target_type(pstate, e,
- exprType(e),
- coerce_type,
- typmod,
- COERCION_EXPLICIT,
- COERCE_EXPLICIT_CAST,
- -1);
- if (newe == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_CANNOT_COERCE),
- errmsg("cannot cast type %s to %s",
- format_type_be(exprType(e)),
- format_type_be(coerce_type)),
- parser_errposition(pstate, exprLocation(e))));
+ /*
+ * Can not coerce, just append the transformed element expression to
+ * the list.
+ */
+ if (can_coerce && (!*can_coerce))
+ newe = e;
+ else
+ {
+ Node *ecopy = NULL;
+
+ if (can_coerce)
+ ecopy = copyObject(e);
+
+ newe = coerce_to_target_type_extended(pstate, e,
+ exprType(e),
+ coerce_type,
+ typmod,
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ -1,
+ (Node *) &escontext);
+ if (newe == NULL)
+ {
+ if (!can_coerce)
+ ereport(ERROR,
+ (errcode(ERRCODE_CANNOT_COERCE),
+ errmsg("cannot cast type %s to %s",
+ format_type_be(exprType(e)),
+ format_type_be(coerce_type)),
+ parser_errposition(pstate, exprLocation(e))));
+ else
+ {
+ /*
+ * Can not coerce, just append the transformed element
+ * expression to the list.
+ */
+ newe = ecopy;
+ *can_coerce = false;
+ }
+ }
+ else if (can_coerce && IsA(ecopy, Const) && IsA(newe, FuncExpr))
+ {
+ Node *result;
+
+ /*
+ * pre-evaluate simple constant cast expressions in a way
+ * that tolerate errors.
+ */
+ FuncExpr *newexpr = (FuncExpr *) copyObject(newe);
+
+ assign_expr_collations(pstate, (Node *) newexpr);
+
+ result = (Node *) evaluate_expr((Expr *) newexpr,
+ coerce_type,
+ typmod,
+ coerce_type_coll,
+ true);
+ if (result == NULL)
+ {
+ newe = ecopy;
+ *can_coerce = false;
+ }
+ }
+ }
}
else
newe = coerce_to_common_type(pstate, e,
@@ -2743,7 +2825,8 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
(A_ArrayExpr *) arg,
targetBaseType,
elementType,
- targetBaseTypmod);
+ targetBaseTypmod,
+ NULL);
}
else
expr = transformExprRecurse(pstate, arg);
@@ -2780,6 +2863,291 @@ 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;
+ TypeCast *tcast = (TypeCast *) tc->cast;
+ Node *source_expr;
+ Node *srcexpr;
+ Node *defexpr;
+ Node *cast_expr = NULL;
+ Oid inputType;
+ Oid targetType;
+ int32 targetTypmod;
+ Oid targetTypecoll;
+ Oid targetBaseType;
+ int32 targetBaseTypmod;
+ bool can_coerce = true;
+
+ /* Look up the type name first */
+ typenameTypeIdAndMod(pstate, tcast->typeName, &targetType, &targetTypmod);
+ targetBaseTypmod = targetTypmod;
+
+ targetTypecoll = get_typcollation(targetType);
+
+ targetBaseType = getBaseTypeAndTypmod(targetType, &targetBaseTypmod);
+
+ /* now looking at DEFAULT expression */
+ defexpr = transformExpr(pstate, tc->raw_default, EXPR_KIND_CAST_DEFAULT);
+
+ defexpr = coerce_to_target_type(pstate, defexpr, exprType(defexpr),
+ targetType, targetTypmod,
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ exprLocation(defexpr));
+ if (defexpr == NULL)
+ ereport(ERROR,
+ errcode(ERRCODE_CANNOT_COERCE),
+ errmsg("cannot coerce %s expression to type %s",
+ "CAST DEFAULT",
+ format_type_be(targetType)),
+ parser_coercion_errposition(pstate, exprLocation(tc->raw_default), defexpr));
+
+ assign_expr_collations(pstate, defexpr);
+
+ /*
+ * The collation of DEFAULT expression must match the collation of the
+ * target type.
+ */
+ if (OidIsValid(targetTypecoll))
+ {
+ Oid defColl = exprCollation(defexpr);
+
+ if (OidIsValid(defColl) && targetTypecoll != defColl)
+ ereport(ERROR,
+ errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("the collation of CAST DEFAULT expression conflicts with target type collation"),
+ errdetail("\"%s\" versus \"%s\"",
+ get_collation_name(targetTypecoll),
+ get_collation_name(defColl)),
+ parser_errposition(pstate, exprLocation(defexpr)));
+ }
+
+ /*
+ * If the type cast 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(tcast->arg, A_ArrayExpr))
+ {
+ 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.
+ */
+ elementType = get_element_type(targetBaseType);
+
+ if (OidIsValid(elementType))
+ {
+ source_expr = transformArrayExpr(pstate,
+ (A_ArrayExpr *) tcast->arg,
+ targetBaseType,
+ elementType,
+ targetBaseTypmod,
+ &can_coerce);
+ }
+ else
+ source_expr = transformExprRecurse(pstate, tcast->arg);
+ }
+ else
+ source_expr = transformExprRecurse(pstate, tcast->arg);
+
+ /*
+ * Since we can't be certain that coerce_to_target_type_extended won't
+ * modify the source expression, we create a copy of it first. This ensures
+ * the transformed source expression is correctly passed to SafeTypeCastExpr
+ */
+ srcexpr = copyObject(source_expr);
+
+ inputType = exprType(source_expr);
+ if (inputType == InvalidOid)
+ return (Node *) NULL; /* return NULL if NULL input */
+
+ if (can_coerce)
+ {
+ ErrorSaveContext escontext = {T_ErrorSaveContext};
+
+ cast_expr = coerce_to_target_type_extended(pstate, source_expr, inputType,
+ targetType, targetTypmod,
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ tcast->location,
+ (Node *) &escontext);
+ if (cast_expr == NULL)
+ can_coerce = false;
+
+ CoercionErrorSafeCheck(pstate, cast_expr, source_expr, inputType,
+ targetType);
+ }
+
+ if (IsA(srcexpr, Const) && cast_expr && IsA(cast_expr, FuncExpr))
+ {
+ Node *result;
+
+ /*
+ * pre-evaluate simple constant cast expressions in a error safe way
+ *
+ * Rationale:
+ * 1. When deparsing safe cast expressions (or in other cases),
+ * eval_const_expressions might be invoked, but it cannot
+ * handle errors gracefully.
+ * 2. If the cast expression involves only simple constants, we
+ * can safely evaluate it ahead of time. If the evaluation
+ * fails, it indicates that such a cast is not possible, and
+ * we can then fall back to the CAST DEFAULT expression.
+ * 3. Even if FuncExpr (for castfunc) node has three arguments,
+ * the second and third arguments will also be constants per
+ * coerce_to_target_type. Evaluating a FuncExpr whose
+ * arguments are all Const is safe.
+ */
+ FuncExpr *newexpr = (FuncExpr *) copyObject(cast_expr);
+
+ assign_expr_collations(pstate, (Node *) newexpr);
+
+ result = (Node *) evaluate_expr((Expr *) newexpr,
+ targetType,
+ targetTypmod,
+ targetTypecoll,
+ true);
+ if (result == NULL)
+ {
+ /* can not coerce, set can_coerce to false */
+ can_coerce = false;
+ cast_expr = NULL;
+ }
+ }
+
+ Assert(can_coerce || cast_expr == NULL);
+
+ result = makeNode(SafeTypeCastExpr);
+ result->source_expr = srcexpr;
+ result->cast_expr = cast_expr;
+ result->default_expr = defexpr;
+ result->resulttype = targetType;
+ result->resulttypmod = targetTypmod;
+ result->resultcollid = targetTypecoll;
+ result->location = tcast->location;
+
+ return (Node *) result;
+}
+
+/*
+ * Check coercion is error safe or not. If not then report error
+ */
+static void
+CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr, Node *sourceexpr, Oid inputType,
+ Oid targetType)
+{
+ HeapTuple tuple;
+ bool errorsafe = true;
+ bool userdefined = false;
+ Oid inputBaseType;
+ Oid targetBaseType;
+ Oid inputElementType;
+ Oid inputElementBaseType;
+ Oid targetElementType;
+ Oid targetElementBaseType;
+
+ /*
+ * Binary coercion cast is equivalent to no cast at all. CoerceViaIO can
+ * also be evaluated in an error-safe manner. So skip these two cases.
+ */
+ if (!castexpr || IsA(castexpr, RelabelType) ||
+ IsA(castexpr, CoerceViaIO))
+ return;
+
+ /*
+ * Type cast involving with some types is not error safe, do the check now.
+ * We need consider domain over array and array over domain scarenio. We
+ * already checked user-defined type cast above.
+ */
+ inputElementType = get_element_type(inputType);
+
+ if (OidIsValid(inputElementType))
+ inputElementBaseType = getBaseType(inputElementType);
+ else
+ {
+ inputBaseType = getBaseType(inputType);
+
+ inputElementBaseType = get_element_type(inputBaseType);
+ if (!OidIsValid(inputElementBaseType))
+ inputElementBaseType = inputBaseType;
+ }
+
+ targetElementType = get_element_type(targetType);
+
+ if (OidIsValid(targetElementType))
+ targetElementBaseType = getBaseType(targetElementType);
+ else
+ {
+ targetBaseType = getBaseType(targetType);
+
+ targetElementBaseType = get_element_type(targetBaseType);
+ if (!OidIsValid(targetElementBaseType))
+ targetElementBaseType = targetBaseType;
+ }
+
+ if (inputElementBaseType == MONEYOID ||
+ targetElementBaseType == MONEYOID ||
+ (inputElementBaseType == CIRCLEOID &&
+ targetElementBaseType == POLYGONOID))
+ {
+ /*
+ * Casts involving MONEY type are not error safe. The CIRCLE to POLYGON
+ * cast is also unsafe because it relies on a SQL-language function;
+ * only C-language functions are currently supported for error safe
+ * casts.
+ */
+ errorsafe = false;
+ }
+
+ if (errorsafe)
+ {
+ /* Look in pg_cast */
+ tuple = SearchSysCache2(CASTSOURCETARGET,
+ ObjectIdGetDatum(inputElementBaseType),
+ ObjectIdGetDatum(targetElementBaseType));
+
+ if (HeapTupleIsValid(tuple))
+ {
+ Form_pg_cast castForm = (Form_pg_cast) GETSTRUCT(tuple);
+
+ if (castForm->oid > FirstUnpinnedObjectId)
+ {
+ errorsafe = false;
+ userdefined = true;
+ }
+
+ ReleaseSysCache(tuple);
+ }
+ }
+
+ if (!errorsafe)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot cast type %s to %s when %s expression is specified in %s",
+ format_type_be(inputType),
+ format_type_be(targetType),
+ "DEFAULT",
+ "CAST ... ON CONVERSION ERROR"),
+ userdefined
+ ? errhint("Safe type cast for user-defined types are not yet supported")
+ : errhint("Explicit cast is defined but definition is not error safe"),
+ parser_errposition(pstate, exprLocation(sourceexpr)));
+}
+
+
/*
* Handle an explicit COLLATE clause.
*
@@ -3193,6 +3561,8 @@ ParseExprKindName(ParseExprKind exprKind)
case EXPR_KIND_CHECK_CONSTRAINT:
case EXPR_KIND_DOMAIN_CHECK:
return "CHECK";
+ case EXPR_KIND_CAST_DEFAULT:
+ return "CAST DEFAULT";
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
return "DEFAULT";
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 778d69c6f3c..a90705b9847 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2743,6 +2743,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
case EXPR_KIND_DOMAIN_CHECK:
err = _("set-returning functions are not allowed in check constraints");
break;
+ case EXPR_KIND_CAST_DEFAULT:
+ err = _("set-returning functions are not allowed in CAST DEFAULT expressions");
+ break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
err = _("set-returning functions are not allowed in DEFAULT expressions");
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 905c975d83b..dc03cf4ce74 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1822,6 +1822,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 7713bdc6af0..d04a729f4db 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,19 @@ stringTypeDatum(Type tp, char *string, int32 atttypmod)
return OidInputFunctionCall(typinput, string, typioparam, atttypmod);
}
+/* error safe version of stringTypeDatum */
+bool
+stringTypeDatumSafe(Type tp, char *string, int32 atttypmod,
+ Node *escontext, Datum *result)
+{
+ Form_pg_type typform = (Form_pg_type) GETSTRUCT(tp);
+ Oid typinput = typform->typinput;
+ Oid typioparam = getTypeIOParam(tp);
+
+ return OidInputFunctionCallSafe(typinput, string, typioparam, atttypmod,
+ escontext, result);
+}
+
/*
* Given a typeid, return the type's typrelid (associated relation), if any.
* Returns InvalidOid if type is not a composite type.
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..6df0b975a88 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -4630,7 +4630,7 @@ transformPartitionBoundValue(ParseState *pstate, Node *val,
assign_expr_collations(pstate, value);
value = (Node *) expression_planner((Expr *) value);
value = (Node *) evaluate_expr((Expr *) value, colType, colTypmod,
- partCollation);
+ partCollation, false);
if (!IsA(value, Const))
elog(ERROR, "could not evaluate partition bound expression");
}
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index cc76bdde723..2a3b4649123 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -3289,6 +3289,14 @@ array_map(Datum arrayd,
/* Apply the given expression to source element */
values[i] = ExecEvalExpr(exprstate, econtext, &nulls[i]);
+ /* Exit early if the evaluation fails */
+ 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 6cf90be40bb..50ae41837ba 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -10562,6 +10562,27 @@ get_rule_expr(Node *node, deparse_context *context,
}
break;
+ case T_SafeTypeCastExpr:
+ {
+ SafeTypeCastExpr *stcexpr = castNode(SafeTypeCastExpr, node);
+
+ /*
+ * Here, we cannot deparsing cast_expr directly, since
+ * transformTypeSafeCast may have folded it into a simple
+ * constant or NULL. Instead, we use source_expr and
+ * default_expr to reconstruct the CAST DEFAULT clause.
+ */
+ 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 0fe63c6bb83..aaa4a42b1ea 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, Node *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 75366203706..937cbf3bc9b 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_SAFETYPE_CAST */
+ struct
+ {
+ struct SafeTypeCastState *stcstate;
+ } stcexpr;
+
/* for EEOP_JSONEXPR_PATH */
struct
{
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index fa2b657fb2f..f99fc26eb1f 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -324,6 +324,7 @@ ExecProcNode(PlanState *node)
* prototypes from functions in execExpr.c
*/
extern ExprState *ExecInitExpr(Expr *node, PlanState *parent);
+extern ExprState *ExecInitExprSafe(Expr *node, PlanState *parent);
extern ExprState *ExecInitExprWithParams(Expr *node, ParamListInfo ext_params);
extern ExprState *ExecInitQual(List *qual, PlanState *parent);
extern ExprState *ExecInitCheck(List *qual, PlanState *parent);
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index c0dbe85ed1c..a9357b98d70 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, Node *escontext,
+ Datum *result);
extern char *OutputFunctionCall(FmgrInfo *flinfo, Datum val);
extern char *OidOutputFunctionCall(Oid functionId, Datum val);
extern Datum ReceiveFunctionCall(FmgrInfo *flinfo, StringInfo buf,
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 64ff6996431..9018e190cc7 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1059,6 +1059,27 @@ typedef struct DomainConstraintState
ExprState *check_exprstate; /* check_expr's eval state, or NULL */
} DomainConstraintState;
+typedef struct SafeTypeCastState
+{
+ SafeTypeCastExpr *stcexpr;
+
+ /*
+ * Address to jump to when skipping all the steps to evaulate the default
+ * expression.
+ */
+ int jump_end;
+
+ /*
+ * For error-safe evaluation of coercions. 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 d14294a4ece..82b0fb83b4b 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -400,6 +400,17 @@ typedef struct TypeCast
ParseLoc location; /* token location, or -1 if unknown */
} TypeCast;
+/*
+ * SafeTypeCast - a CAST(source_expr AS target_type) DEFAULT ON CONVERSION ERROR
+ * construct
+ */
+typedef struct SafeTypeCast
+{
+ NodeTag type;
+ Node *cast; /* TypeCast expression */
+ Node *raw_default; /* untransformed DEFAULT expression */
+} SafeTypeCast;
+
/*
* CollateClause - a COLLATE expression
*/
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 1b4436f2ff6..1401109fb3c 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -769,6 +769,42 @@ 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
+{
+ Expr xpr;
+
+ /* transformed source expression */
+ Node *source_expr;
+
+ /*
+ * The transformed cast expression; NULL if we can not cocerce source
+ * expression to the target type
+ */
+ Node *cast_expr pg_node_attr(query_jumble_ignore);
+
+ /* Fall back to the default expression if cast evaluation fails */
+ Node *default_expr;
+
+ /* cast result data type */
+ Oid resulttype;
+
+ /* cast result data type typmod (usually -1) */
+ int32 resulttypmod;
+
+ /* cast result data type collation */
+ Oid resultcollid;
+
+ /* Original SafeTypeCastExpr's location */
+ ParseLoc location;
+
+} SafeTypeCastExpr;
+
/*
* FuncExpr - expression node for a function call
*
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index 44ec5296a18..587e711596a 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -143,7 +143,7 @@ extern void convert_saop_to_hashed_saop(Node *node);
extern Node *estimate_expression_value(PlannerInfo *root, Node *node);
extern Expr *evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
- Oid result_collation);
+ Oid result_collation, bool error_safe);
extern bool var_is_nonnullable(PlannerInfo *root, Var *var, bool use_rel_info);
diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h
index 8d775c72c59..1eca1ce727d 100644
--- a/src/include/parser/parse_coerce.h
+++ b/src/include/parser/parse_coerce.h
@@ -43,11 +43,26 @@ extern Node *coerce_to_target_type(ParseState *pstate,
CoercionContext ccontext,
CoercionForm cformat,
int location);
+extern Node *coerce_to_target_type_extended(ParseState *pstate,
+ Node *expr,
+ Oid exprtype,
+ Oid targettype,
+ int32 targettypmod,
+ CoercionContext ccontext,
+ CoercionForm cformat,
+ int location,
+ Node *escontext);
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_extend(ParseState *pstate, Node *node,
+ Oid inputTypeId,
+ Oid targetTypeId, int32 targetTypeMod,
+ CoercionContext ccontext, CoercionForm cformat,
+ int location,
+ Node *escontext);
extern Node *coerce_to_domain(Node *arg, Oid baseTypeId, int32 baseTypeMod,
Oid typeId,
CoercionContext ccontext, CoercionForm cformat, int location,
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index f7d07c84542..9f5b32e0360 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -67,6 +67,8 @@ typedef enum ParseExprKind
EXPR_KIND_VALUES_SINGLE, /* single-row VALUES (in INSERT only) */
EXPR_KIND_CHECK_CONSTRAINT, /* CHECK constraint for a table */
EXPR_KIND_DOMAIN_CHECK, /* CHECK constraint for a domain */
+ EXPR_KIND_CAST_DEFAULT, /* default expression in
+ CAST DEFAULT ON CONVERSION ERROR */
EXPR_KIND_COLUMN_DEFAULT, /* default value for a table column */
EXPR_KIND_FUNCTION_DEFAULT, /* default parameter value for function */
EXPR_KIND_INDEX_EXPRESSION, /* index expression */
diff --git a/src/include/parser/parse_type.h b/src/include/parser/parse_type.h
index 0d919d8bfa2..40aca2b31c9 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,
+ Node *escontext, Datum *result);
extern Oid typeidTypeRelid(Oid type_id);
extern Oid typeOrDomainTypeRelid(Oid type_id);
diff --git a/src/test/regress/expected/cast.out b/src/test/regress/expected/cast.out
new file mode 100644
index 00000000000..b0dc7b8faea
--- /dev/null
+++ b/src/test/regress/expected/cast.out
@@ -0,0 +1,810 @@
+SET extra_float_digits = 0;
+-- CAST DEFAULT ON CONVERSION ERROR
+SELECT CAST(B'01' AS date DEFAULT NULL ON CONVERSION ERROR);
+ date
+------
+
+(1 row)
+
+SELECT CAST(BIT'01' AS date DEFAULT NULL ON CONVERSION ERROR);
+ date
+------
+
+(1 row)
+
+SELECT CAST(TRUE AS date DEFAULT NULL ON CONVERSION ERROR);
+ date
+------
+
+(1 row)
+
+SELECT CAST(1.1 AS date DEFAULT NULL ON CONVERSION ERROR);
+ date
+------
+
+(1 row)
+
+SELECT CAST(1 AS date DEFAULT NULL ON CONVERSION ERROR);
+ date
+------
+
+(1 row)
+
+SELECT CAST(1111 AS "char" DEFAULT 'A' ON CONVERSION ERROR);
+ char
+------
+ A
+(1 row)
+
+--test source expression is a unknown const
+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('a' as int DEFAULT 18 ON CONVERSION ERROR);
+ int4
+------
+ 18
+(1 row)
+
+SELECT CAST('a' as int DEFAULT sum(1) ON CONVERSION ERROR); --error
+ERROR: aggregate functions are not allowed in CAST DEFAULT expressions
+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: window functions are not allowed in CAST DEFAULT expressions
+LINE 1: SELECT CAST('a' as int DEFAULT sum(1) over() ON CONVERSION E...
+ ^
+SELECT CAST('a' as int DEFAULT (SELECT NULL) ON CONVERSION ERROR); --error
+ERROR: cannot use subquery in CAST DEFAULT expression
+LINE 1: SELECT CAST('a' as int DEFAULT (SELECT NULL) 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('a'::int as int DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR: invalid input syntax for type integer: "a"
+LINE 1: SELECT CAST('a'::int as int DEFAULT NULL ON CONVERSION ERROR...
+ ^
+--the default expression’s collation should match the collation of the cast
+--target type
+VALUES (CAST('error' AS text DEFAULT '1' COLLATE "C" ON CONVERSION ERROR));
+ERROR: the collation of CAST DEFAULT expression conflicts with target type collation
+LINE 1: VALUES (CAST('error' AS text DEFAULT '1' COLLATE "C" ON CONV...
+ ^
+DETAIL: "default" versus "C"
+VALUES (CAST('error' AS int2vector DEFAULT '1 3' ON CONVERSION ERROR));
+ column1
+---------
+ 1 3
+(1 row)
+
+VALUES (CAST('error' AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR));
+ column1
+---------
+ {"1 3"}
+(1 row)
+
+-- test source expression contain subquery
+SELECT CAST((SELECT b FROM generate_series(1, 1), (VALUES('H')) s(b))
+ AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR);
+ b
+---------
+ {"1 3"}
+(1 row)
+
+SELECT CAST((SELECT ARRAY((SELECT b FROM generate_series(1, 2) g, (VALUES('H')) s(b))))
+ AS INT[]
+ DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+CREATE FUNCTION ret_int8() RETURNS BIGINT AS
+$$
+BEGIN RETURN 2147483648; END;
+$$
+LANGUAGE plpgsql IMMUTABLE;
+SELECT CAST('a' as int DEFAULT ret_int8() ON CONVERSION ERROR); --error
+ERROR: integer out of range
+SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERROR); --error
+ERROR: cannot coerce CAST DEFAULT expression to type date
+LINE 1: SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERR...
+ ^
+-- DEFAULT expression can not be set-returning
+CREATE 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);
+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: set-returning functions are not allowed in CAST DEFAULT expressions
+LINE 1: SELECT CAST('a' as int DEFAULT ret_setint() ON CONVERSION ER...
+ ^
+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 user-defined type, domain, array over domain, domain over array
+CREATE DOMAIN d_int42 as int check (value = 42) NOT NULL;
+CREATE DOMAIN d_char3_not_null as char(3) NOT NULL;
+CREATE DOMAIN d_varchar as varchar(3) NOT NULL;
+CREATE DOMAIN d_int_arr as int[] check (value = '{41, 43}') NOT NULL;
+CREATE TYPE comp_domain_with_typmod AS (a d_char3_not_null, b int);
+CREATE TYPE comp2 AS (a d_varchar);
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION ERROR); --error
+ERROR: value too long for type character varying(3)
+LINE 1: SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION...
+ ^
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(123)' ON CONVERSION ERROR);
+ comp2
+-------
+ (123)
+(1 row)
+
+SELECT CAST(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);
+ 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);
+ 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)'...
+ ^
+SELECT CAST(ARRAY[42,41] AS d_int42[] DEFAULT '{42, 42}' ON CONVERSION ERROR);
+ array
+---------
+ {42,42}
+(1 row)
+
+SELECT CAST(ARRAY[42,41] AS d_int_arr DEFAULT '{41, 43}' ON CONVERSION ERROR);
+ array
+---------
+ {41,43}
+(1 row)
+
+-- test array coerce
+SELECT CAST(array['a'::text] AS int[] DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR: invalid input syntax for type integer: "a"
+SELECT CAST(array['a'] AS int[] DEFAULT ARRAY[1] ON CONVERSION ERROR);
+ array
+-------
+ {1}
+(1 row)
+
+SELECT CAST(array[11.12] AS date[] DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+SELECT CAST(array[11111111111111111] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+SELECT CAST(array[['abc'],[456]] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+SELECT CAST('{123,abc,456}' AS int[] DEFAULT '{-789}' ON CONVERSION ERROR);
+ int4
+--------
+ {-789}
+(1 row)
+
+SELECT CAST('{234,def,567}'::text[] AS integer[] DEFAULT '{-1011}' ON CONVERSION ERROR);
+ int4
+---------
+ {-1011}
+(1 row)
+
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS int[] DEFAULT '{1,2}' ON CONVERSION ERROR);
+ array
+-------
+ {1,2}
+(1 row)
+
+SELECT CAST(ARRAY[['1', '2'], ['three', 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --ok
+ array
+---------
+ {21,22}
+(1 row)
+
+SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --error
+ERROR: invalid input syntax for type integer: "three"
+LINE 1: SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] ...
+ ^
+SELECT CAST(ARRAY[1, 'three'::int] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --error
+ERROR: invalid input syntax for type integer: "three"
+LINE 1: SELECT CAST(ARRAY[1, 'three'::int] AS int[] DEFAULT '{21,22}...
+ ^
+-----safe cast with geometry data type
+SELECT CAST('(1,2)'::point AS box DEFAULT NULL ON CONVERSION ERROR);
+ box
+-------------
+ (1,2),(1,2)
+(1 row)
+
+SELECT CAST('[(NaN,1),(NaN,infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+----------------
+ (NaN,Infinity)
+(1 row)
+
+SELECT CAST('[(1e+300,Infinity),(1e+300,Infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+-------------------
+ (1e+300,Infinity)
+(1 row)
+
+SELECT CAST('[(1,2),(3,4)]'::path as polygon DEFAULT NULL ON CONVERSION ERROR);
+ polygon
+---------
+
+(1 row)
+
+SELECT CAST('(NaN,1.0,NaN,infinity)'::box AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+----------------
+ (NaN,Infinity)
+(1 row)
+
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS lseg DEFAULT NULL ON CONVERSION ERROR);
+ lseg
+---------------
+ [(2,2),(0,0)]
+(1 row)
+
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS polygon DEFAULT NULL ON CONVERSION ERROR);
+ polygon
+---------------------------
+ ((0,0),(0,2),(2,2),(2,0))
+(1 row)
+
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS path DEFAULT NULL ON CONVERSION ERROR);
+ path
+------
+
+(1 row)
+
+SELECT CAST('(2.0,infinity,NaN,infinity)'::box AS circle DEFAULT NULL ON CONVERSION ERROR);
+ circle
+----------------------
+ <(NaN,Infinity),NaN>
+(1 row)
+
+SELECT CAST('(NaN,0.0),(2.0,4.0),(0.0,infinity)'::polygon AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+----------------
+ (NaN,Infinity)
+(1 row)
+
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS path DEFAULT NULL ON CONVERSION ERROR);
+ path
+---------------------
+ ((2,0),(2,4),(0,0))
+(1 row)
+
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS box DEFAULT NULL ON CONVERSION ERROR);
+ box
+-------------
+ (2,4),(0,0)
+(1 row)
+
+SELECT CAST('(NaN,infinity),(2.0,4.0),(0.0,infinity)'::polygon AS circle DEFAULT NULL ON CONVERSION ERROR);
+ circle
+----------------------
+ <(NaN,Infinity),NaN>
+(1 row)
+
+SELECT CAST('<(5,1),3>'::circle AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+-------
+ (5,1)
+(1 row)
+
+SELECT CAST('<(3,5),0>'::circle as box DEFAULT NULL ON CONVERSION ERROR);
+ box
+-------------
+ (3,5),(3,5)
+(1 row)
+
+-- not supported because the cast from circle to polygon is implemented as a SQL
+-- function, which cannot be error-safe.
+SELECT CAST('<(3,5),0>'::circle as polygon DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type circle to polygon when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST('<(3,5),0>'::circle as polygon DEFAULT NULL ON C...
+ ^
+HINT: Explicit cast is defined but definition is not error safe
+-----safe cast from/to money type is not supported, all the following would result error
+SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type bigint to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION E...
+ ^
+HINT: Explicit cast is defined but definition is not error safe
+SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type integer to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION E...
+ ^
+HINT: Explicit cast is defined but definition is not error safe
+SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type numeric to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSIO...
+ ^
+HINT: Explicit cast is defined but definition is not error safe
+SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type money to numeric when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSIO...
+ ^
+HINT: Explicit cast is defined but definition is not error safe
+SELECT CAST(NULL::money[] AS numeric[] DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type money[] to numeric[] when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::money[] AS numeric[] DEFAULT NULL ON CONVE...
+ ^
+HINT: Explicit cast is defined but definition is not error safe
+-----safe cast from bytea type to other data types
+SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT 19 ON CONVERSION ERROR);
+ int8
+------
+ 19
+(1 row)
+
+SELECT CAST('\x123456789A'::bytea AS int4 DEFAULT 20 ON CONVERSION ERROR);
+ int4
+------
+ 20
+(1 row)
+
+SELECT CAST('\x123456'::bytea AS int2 DEFAULT 21 ON CONVERSION ERROR);
+ int2
+------
+ 21
+(1 row)
+
+-----safe cast from bit type to other data types
+SELECT CAST('111111111100001'::bit(100) AS INT DEFAULT 22 ON CONVERSION ERROR);
+ int4
+------
+ 22
+(1 row)
+
+SELECT CAST ('111111111100001'::bit(100) AS INT8 DEFAULT 23 ON CONVERSION ERROR);
+ int8
+------
+ 23
+(1 row)
+
+-----safe cast from text type to other data types
+select CAST('a.b.c.d'::text as regclass default NULL on conversion error);
+ regclass
+----------
+
+(1 row)
+
+CREATE TABLE test_safecast(col0 text);
+INSERT INTO test_safecast(col0) VALUES ('<value>one</value');
+SELECT col0 as text,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) as to_regclass,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) IS NULL as expect_true,
+ CAST(col0 AS "char" DEFAULT NULL ON CONVERSION ERROR) as to_char,
+ CAST(col0 AS name DEFAULT NULL ON CONVERSION ERROR) as to_name,
+ CAST(col0 AS xml DEFAULT NULL ON CONVERSION ERROR) as to_xml
+FROM test_safecast;
+ text | to_regclass | expect_true | to_char | to_name | to_xml
+-------------------+-------------+-------------+---------+-------------------+--------
+ <value>one</value | | t | < | <value>one</value |
+(1 row)
+
+SELECT CAST('192.168.1.x' as inet DEFAULT NULL ON CONVERSION ERROR);
+ inet
+------
+
+(1 row)
+
+SELECT CAST('192.168.1.2/30' as cidr DEFAULT NULL ON CONVERSION ERROR);
+ cidr
+------
+
+(1 row)
+
+SELECT CAST('22:00:5c:08:55:08:01:02'::macaddr8 as macaddr DEFAULT NULL ON CONVERSION ERROR);
+ macaddr
+---------
+
+(1 row)
+
+-----safe cast betweeen range data types
+SELECT CAST('[1,2]'::int4range AS int4multirange DEFAULT NULL ON CONVERSION ERROR);
+ int4multirange
+----------------
+ {[1,3)}
+(1 row)
+
+SELECT CAST('[1,2]'::int8range AS int8multirange DEFAULT NULL ON CONVERSION ERROR);
+ int8multirange
+----------------
+ {[1,3)}
+(1 row)
+
+SELECT CAST('[1,2]'::numrange AS nummultirange DEFAULT NULL ON CONVERSION ERROR);
+ nummultirange
+---------------
+ {[1,2]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::daterange AS datemultirange DEFAULT NULL ON CONVERSION ERROR);
+ datemultirange
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::tsrange AS tsmultirange DEFAULT NULL ON CONVERSION ERROR);
+ tsmultirange
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::tstzrange AS tstzmultirange DEFAULT NULL ON CONVERSION ERROR);
+ tstzmultirange
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+-----safe cast betweeen numeric data types
+CREATE TABLE test_safecast1(
+ col1 float4, col2 float8, col3 numeric, col4 numeric[],
+ col5 int2 default 32767,
+ col6 int4 default 32768,
+ col7 int8 default 4294967296);
+INSERT INTO test_safecast1 VALUES('11.1234', '11.1234', '9223372036854775808', '{11.1234}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('inf', 'inf', 'inf', '{11.1234, 12, inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('-inf', '-inf', '-inf', '{11.1234, 12, -inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('NaN', 'NaN', 'NaN', '{11.1234, 12, -inf, NaN}'::numeric[]);
+SELECT col5 as int2,
+ CAST(col5 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col5 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col5 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col5 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col5 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col5 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col5 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col5 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ int2 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+(4 rows)
+
+SELECT col6 as int4,
+ CAST(col6 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col6 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col6 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col6 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col6 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col6 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col6 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col6 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ int4 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+(4 rows)
+
+SELECT col7 as int8,
+ CAST(col7 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col7 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col7 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col7 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col7 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col7 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col7 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col7 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ int8 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+------------+---------+---------+--------+------------+-------------+------------+------------+------------------
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+(4 rows)
+
+SELECT col3 as numeric,
+ CAST(col3 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col3 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col3 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col3 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col3 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col3 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col3 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col3 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ numeric | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+---------------------+---------+---------+--------+---------+-------------+----------------------+---------------------+------------------
+ 9223372036854775808 | | | | | 9.22337e+18 | 9.22337203685478e+18 | 9223372036854775808 |
+ Infinity | | | | | Infinity | Infinity | Infinity |
+ -Infinity | | | | | -Infinity | -Infinity | -Infinity |
+ NaN | | | | | NaN | NaN | NaN | NaN
+(4 rows)
+
+SELECT col4 as num_arr,
+ CAST(col4 AS int2[] DEFAULT NULL ON CONVERSION ERROR) as int2arr,
+ CAST(col4 AS int4[] DEFAULT NULL ON CONVERSION ERROR) as int4arr,
+ CAST(col4 as int8[] DEFAULT NULL ON CONVERSION ERROR) as int8arr,
+ CAST(col4 as float4[] DEFAULT NULL ON CONVERSION ERROR) as f4arr,
+ CAST(col4 as float8[] DEFAULT NULL ON CONVERSION ERROR) as f8arr,
+ CAST(col4 as numeric(10,1)[] DEFAULT NULL ON CONVERSION ERROR) as numarr
+FROM test_safecast1;
+ num_arr | int2arr | int4arr | int8arr | f4arr | f8arr | numarr
+----------------------------+---------+---------+---------+----------------------------+----------------------------+--------
+ {11.1234} | {11} | {11} | {11} | {11.1234} | {11.1234} | {11.1}
+ {11.1234,12,Infinity,NaN} | | | | {11.1234,12,Infinity,NaN} | {11.1234,12,Infinity,NaN} |
+ {11.1234,12,-Infinity,NaN} | | | | {11.1234,12,-Infinity,NaN} | {11.1234,12,-Infinity,NaN} |
+ {11.1234,12,-Infinity,NaN} | | | | {11.1234,12,-Infinity,NaN} | {11.1234,12,-Infinity,NaN} |
+(4 rows)
+
+SELECT col1 as float4,
+ CAST(col1 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col1 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col1 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col1 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col1 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col1 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col1 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col1 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ float4 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-----------+---------+---------+--------+---------+-----------+------------------+------------+------------------
+ 11.1234 | 11 | 11 | | 11 | 11.1234 | 11.1233997344971 | 11.1234 | 11.1
+ Infinity | | | | | Infinity | Infinity | Infinity |
+ -Infinity | | | | | -Infinity | -Infinity | -Infinity |
+ NaN | | | | | NaN | NaN | NaN | NaN
+(4 rows)
+
+SELECT col2 as float8,
+ CAST(col2 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col2 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col2 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col2 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col2 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col2 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col2 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col2 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ float8 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-----------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 11.1234 | 11 | 11 | | 11 | 11.1234 | 11.1234 | 11.1234 | 11.1
+ Infinity | | | | | Infinity | Infinity | Infinity |
+ -Infinity | | | | | -Infinity | -Infinity | -Infinity |
+ NaN | | | | | NaN | NaN | NaN | NaN
+(4 rows)
+
+-- date/timestamp/interval related safe type cast
+CREATE TABLE test_safecast2(
+ col0 date, col1 timestamp, col2 timestamptz,
+ col3 interval, col4 time, col5 timetz);
+INSERT INTO test_safecast2 VALUES
+('-infinity', '-infinity', '-infinity', '-infinity',
+ '2003-03-07 15:36:39 America/New_York', '2003-07-07 15:36:39 America/New_York'),
+('-infinity', 'infinity', 'infinity', 'infinity',
+ '11:59:59.99 PM', '11:59:59.99 PM PDT');
+SELECT col0 as date,
+ CAST(col0 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col0 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col0 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col0 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col0 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+ date | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale
+-----------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity | -infinity | | | -infinity
+ -infinity | -infinity | -infinity | | | -infinity
+(2 rows)
+
+SELECT col1 as timestamp,
+ CAST(col1 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col1 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col1 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col1 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col1 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+ timestamp | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale
+-----------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity | -infinity | | | -infinity
+ infinity | infinity | infinity | | | infinity
+(2 rows)
+
+SELECT col2 as timestamptz,
+ CAST(col2 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col2 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col2 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col2 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col2 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+ timestamptz | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale
+-------------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity | -infinity | | | -infinity
+ infinity | infinity | infinity | | | infinity
+(2 rows)
+
+SELECT col3 as interval,
+ CAST(col3 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col3 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col3 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col3 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col3 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale,
+ CAST(col3 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+ interval | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale | to_interval_scale
+-----------+----------------+---------+----------+-----------+--------------------+-------------------
+ -infinity | | | | | | -infinity
+ infinity | | | | | | infinity
+(2 rows)
+
+SELECT col4 as time,
+ CAST(col4 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col4 AS timetz DEFAULT NULL ON CONVERSION ERROR) IS NOT NULL as to_timetz,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+ time | to_times | to_timetz | to_interval | to_interval_scale
+-------------+-------------+-----------+-------------------------------+-------------------------------
+ 15:36:39 | 15:36:39 | t | @ 15 hours 36 mins 39 secs | @ 15 hours 36 mins 39 secs
+ 23:59:59.99 | 23:59:59.99 | t | @ 23 hours 59 mins 59.99 secs | @ 23 hours 59 mins 59.99 secs
+(2 rows)
+
+SELECT col5 as timetz,
+ CAST(col5 AS time DEFAULT NULL ON CONVERSION ERROR) as to_time,
+ CAST(col5 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_time_scale,
+ CAST(col5 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col5 AS timetz(6) DEFAULT NULL ON CONVERSION ERROR) as to_timetz_scale,
+ CAST(col5 AS interval DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col5 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+ timetz | to_time | to_time_scale | to_timetz | to_timetz_scale | to_interval | to_interval_scale
+----------------+-------------+---------------+----------------+-----------------+-------------+-------------------
+ 15:36:39-04 | 15:36:39 | 15:36:39 | 15:36:39-04 | 15:36:39-04 | |
+ 23:59:59.99-07 | 23:59:59.99 | 23:59:59.99 | 23:59:59.99-07 | 23:59:59.99-07 | |
+(2 rows)
+
+CREATE TABLE test_safecast3(col0 jsonb);
+INSERT INTO test_safecast3(col0) VALUES ('"test"');
+SELECT col0 as jsonb,
+ CAST(col0 AS integer DEFAULT NULL ON CONVERSION ERROR) as to_integer,
+ CAST(col0 AS numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col0 AS bigint DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col0 AS float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col0 AS float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col0 AS boolean DEFAULT NULL ON CONVERSION ERROR) as to_bool,
+ CAST(col0 AS smallint DEFAULT NULL ON CONVERSION ERROR) as to_smallint
+FROM test_safecast3;
+ jsonb | to_integer | to_numeric | to_int8 | to_float4 | to_float8 | to_bool | to_smallint
+--------+------------+------------+---------+-----------+-----------+---------+-------------
+ "test" | | | | | | |
+(1 row)
+
+-- test deparse
+SET datestyle TO ISO, YMD;
+CREATE VIEW safecastview AS
+SELECT CAST('1234' as char(3) DEFAULT -1111 ON CONVERSION ERROR),
+ CAST(1 as date DEFAULT (('2025-Dec-06'::date + random(min=>1, max=>1::int))) ON CONVERSION ERROR) as cast0,
+ CAST(ARRAY[['1'], ['three'],['a']] AS int[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1,
+ CAST(ARRAY[['1', '2'], ['three', 'a']] AS date[] DEFAULT NULL ON CONVERSION ERROR) as cast2,
+ CAST(ARRAY['three'] AS INT[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast3;
+\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 '2025-12-06'::date + random(min => 1, max => 1) ON CONVERSION ERROR) AS cast0,
+ CAST(ARRAY[ARRAY[1], ARRAY['three'], ARRAY['a']] AS integer[] DEFAULT '{1,2}'::integer[] ON CONVERSION ERROR) AS cast1,
+ CAST(ARRAY[ARRAY['1', '2'], ARRAY['three', 'a']] AS date[] DEFAULT NULL::date[] ON CONVERSION ERROR) AS cast2,
+ CAST(ARRAY['three'] AS integer[] DEFAULT '{1,2}'::integer[] ON CONVERSION ERROR) AS cast3
+SELECT * FROM safecastview;
+ bpchar | cast0 | cast1 | cast2 | cast3
+--------+------------+-------+-------+-------
+ 123 | 2025-12-07 | {1,2} | | {1,2}
+(1 row)
+
+CREATE VIEW safecastview1 AS
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS d_int_arr
+ DEFAULT '{41,43}' ON CONVERSION ERROR) as cast1,
+ CAST(ARRAY[['1', '2', 1.1], ['three', true, B'01']] AS d_int_arr
+ DEFAULT '{41,43}' ON CONVERSION ERROR) as cast2;
+\sv safecastview1
+CREATE OR REPLACE VIEW public.safecastview1 AS
+ SELECT CAST(ARRAY[ARRAY[1], ARRAY['three'], ARRAY['a']] AS d_int_arr DEFAULT '{41,43}'::integer[]::d_int_arr ON CONVERSION ERROR) AS cast1,
+ CAST(ARRAY[ARRAY[1, 2, 1.1::integer], ARRAY['three', true, '01'::"bit"]] AS d_int_arr DEFAULT '{41,43}'::integer[]::d_int_arr ON CONVERSION ERROR) AS cast2
+SELECT * FROM safecastview1;
+ cast1 | cast2
+---------+---------
+ {41,43} | {41,43}
+(1 row)
+
+RESET datestyle;
+--test CAST DEFAULT expression mutability
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as int DEFAULT random(min=>1, max=>1) ON CONVERSION ERROR)));
+ERROR: functions in index expression must be marked IMMUTABLE
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as xid DEFAULT NULL ON CONVERSION ERROR)));
+ERROR: data type xid has no default operator class for access method "btree"
+HINT: You must specify an operator class for the index or define a default operator class for the data type.
+CREATE INDEX test_safecast3_idx ON test_safecast3((CAST(col0 as int DEFAULT NULL ON CONVERSION ERROR))); --ok
+SELECT pg_get_indexdef('test_safecast3_idx'::regclass);
+ pg_get_indexdef
+------------------------------------------------------------------------------------------------------------------------------------------
+ CREATE INDEX test_safecast3_idx ON public.test_safecast3 USING btree ((CAST(col0 AS integer DEFAULT NULL::integer ON CONVERSION ERROR)))
+(1 row)
+
+DROP TABLE test_safecast;
+DROP TABLE test_safecast1;
+DROP TABLE test_safecast2;
+DROP TABLE test_safecast3;
+DROP TABLE tcast;
+DROP FUNCTION ret_int8;
+DROP FUNCTION ret_setint;
+DROP TYPE comp_domain_with_typmod;
+DROP TYPE comp2;
+DROP DOMAIN d_varchar;
+DROP DOMAIN d_int42;
+DROP DOMAIN d_char3_not_null;
+RESET extra_float_digits;
diff --git a/src/test/regress/expected/create_cast.out b/src/test/regress/expected/create_cast.out
index 0e69644bca2..0054ed0ef67 100644
--- a/src/test/regress/expected/create_cast.out
+++ b/src/test/regress/expected/create_cast.out
@@ -88,6 +88,11 @@ SELECT 1234::int4::casttesttype; -- Should work now
bar1234
(1 row)
+SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- error
+ERROR: cannot cast type integer to casttesttype when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVE...
+ ^
+HINT: Safe type cast for user-defined types are not yet supported
-- check dependencies generated for that
SELECT pg_describe_object(classid, objid, objsubid) as obj,
pg_describe_object(refclassid, refobjid, refobjsubid) as objref,
diff --git a/src/test/regress/expected/equivclass.out b/src/test/regress/expected/equivclass.out
index ad8ab294ff6..5aee2c7a9fb 100644
--- a/src/test/regress/expected/equivclass.out
+++ b/src/test/regress/expected/equivclass.out
@@ -95,6 +95,13 @@ create function int8alias1cmp(int8, int8alias1) returns int
strict immutable language internal as 'btint8cmp';
alter operator family integer_ops using btree add
function 1 int8alias1cmp (int8, int8alias1);
+-- int8alias2 binary-coercible to int8. so this is ok
+select cast('1'::int8 as int8alias2 default null on conversion error);
+ int8alias2
+------------
+ 1
+(1 row)
+
create table ec0 (ff int8 primary key, f1 int8, f2 int8);
create table ec1 (ff int8 primary key, f1 int8alias1, f2 int8alias2);
create table ec2 (xf int8 primary key, x1 int8alias1, x2 int8alias2);
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index cc6d799bcea..0b031a37c36 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -81,7 +81,7 @@ test: create_table_like alter_generic alter_operator misc async dbsize merge mis
# collate.linux.utf8 and collate.icu.utf8 tests cannot be run in parallel with each other
# psql depends on create_am
# amutils depends on geometry, create_index_spgist, hash_index, brin
-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 00000000000..9b6f3cc0649
--- /dev/null
+++ b/src/test/regress/sql/cast.sql
@@ -0,0 +1,350 @@
+SET extra_float_digits = 0;
+
+-- CAST DEFAULT ON CONVERSION ERROR
+SELECT CAST(B'01' AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(BIT'01' AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(TRUE AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(1.1 AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(1 AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(1111 AS "char" DEFAULT 'A' ON CONVERSION ERROR);
+
+--test source expression is a unknown const
+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('a' as int DEFAULT 18 ON CONVERSION 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 (SELECT NULL) ON CONVERSION ERROR); --error
+SELECT CAST('a' as int DEFAULT 'b' ON CONVERSION ERROR); --error
+SELECT CAST('a'::int as int DEFAULT NULL ON CONVERSION ERROR); --error
+
+--the default expression’s collation should match the collation of the cast
+--target type
+VALUES (CAST('error' AS text DEFAULT '1' COLLATE "C" ON CONVERSION ERROR));
+
+VALUES (CAST('error' AS int2vector DEFAULT '1 3' ON CONVERSION ERROR));
+VALUES (CAST('error' AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR));
+
+-- test source expression contain subquery
+SELECT CAST((SELECT b FROM generate_series(1, 1), (VALUES('H')) s(b))
+ AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR);
+SELECT CAST((SELECT ARRAY((SELECT b FROM generate_series(1, 2) g, (VALUES('H')) s(b))))
+ AS INT[]
+ DEFAULT NULL ON CONVERSION ERROR);
+
+CREATE FUNCTION ret_int8() RETURNS BIGINT AS
+$$
+BEGIN RETURN 2147483648; END;
+$$
+LANGUAGE plpgsql IMMUTABLE;
+
+SELECT CAST('a' as int DEFAULT ret_int8() ON CONVERSION ERROR); --error
+SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERROR); --error
+
+-- DEFAULT expression can not be set-returning
+CREATE 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);
+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(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 user-defined type, domain, array over domain, domain over array
+CREATE DOMAIN d_int42 as int check (value = 42) NOT NULL;
+CREATE DOMAIN d_char3_not_null as char(3) NOT NULL;
+CREATE DOMAIN d_varchar as varchar(3) NOT NULL;
+CREATE DOMAIN d_int_arr as int[] check (value = '{41, 43}') NOT NULL;
+CREATE TYPE comp_domain_with_typmod AS (a d_char3_not_null, b int);
+CREATE TYPE comp2 AS (a d_varchar);
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION ERROR); --error
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(123)' ON CONVERSION ERROR);
+SELECT CAST(11 AS d_int42 DEFAULT 41 ON CONVERSION ERROR); --error
+SELECT CAST(11 AS d_int42 DEFAULT 42 ON CONVERSION ERROR);
+SELECT CAST(NULL AS d_int42 DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(NULL AS d_int42 DEFAULT 42 ON CONVERSION ERROR);
+SELECT CAST('(,42)' AS comp_domain_with_typmod DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1,2)' ON CONVERSION ERROR);
+SELECT CAST('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1234,2)' ON CONVERSION ERROR); --error
+
+SELECT CAST(ARRAY[42,41] AS d_int42[] DEFAULT '{42, 42}' ON CONVERSION ERROR);
+SELECT CAST(ARRAY[42,41] AS d_int_arr DEFAULT '{41, 43}' ON CONVERSION ERROR);
+
+-- test array coerce
+SELECT CAST(array['a'::text] AS int[] DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(array['a'] AS int[] DEFAULT ARRAY[1] ON CONVERSION ERROR);
+SELECT CAST(array[11.12] AS date[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(array[11111111111111111] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(array[['abc'],[456]] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('{123,abc,456}' AS int[] 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 int[] DEFAULT '{1,2}' ON CONVERSION ERROR);
+SELECT CAST(ARRAY[['1', '2'], ['three', 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --ok
+SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --error
+SELECT CAST(ARRAY[1, 'three'::int] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --error
+
+-----safe cast with geometry data type
+SELECT CAST('(1,2)'::point AS box DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[(NaN,1),(NaN,infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[(1e+300,Infinity),(1e+300,Infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[(1,2),(3,4)]'::path as polygon DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NaN,1.0,NaN,infinity)'::box AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS lseg DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS polygon DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS path DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,infinity,NaN,infinity)'::box AS circle DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NaN,0.0),(2.0,4.0),(0.0,infinity)'::polygon AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS path DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS box DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NaN,infinity),(2.0,4.0),(0.0,infinity)'::polygon AS circle DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('<(5,1),3>'::circle AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('<(3,5),0>'::circle as box DEFAULT NULL ON CONVERSION ERROR);
+
+-- not supported because the cast from circle to polygon is implemented as a SQL
+-- function, which cannot be error-safe.
+SELECT CAST('<(3,5),0>'::circle as polygon DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast from/to money type is not supported, all the following would result error
+SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::money[] AS numeric[] DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast from bytea type to other data types
+SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT 19 ON CONVERSION ERROR);
+SELECT CAST('\x123456789A'::bytea AS int4 DEFAULT 20 ON CONVERSION ERROR);
+SELECT CAST('\x123456'::bytea AS int2 DEFAULT 21 ON CONVERSION ERROR);
+
+-----safe cast from bit type to other data types
+SELECT CAST('111111111100001'::bit(100) AS INT DEFAULT 22 ON CONVERSION ERROR);
+SELECT CAST ('111111111100001'::bit(100) AS INT8 DEFAULT 23 ON CONVERSION ERROR);
+
+-----safe cast from text type to other data types
+select CAST('a.b.c.d'::text as regclass default NULL on conversion error);
+
+CREATE TABLE test_safecast(col0 text);
+INSERT INTO test_safecast(col0) VALUES ('<value>one</value');
+
+SELECT col0 as text,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) as to_regclass,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) IS NULL as expect_true,
+ CAST(col0 AS "char" DEFAULT NULL ON CONVERSION ERROR) as to_char,
+ CAST(col0 AS name DEFAULT NULL ON CONVERSION ERROR) as to_name,
+ CAST(col0 AS xml DEFAULT NULL ON CONVERSION ERROR) as to_xml
+FROM test_safecast;
+
+SELECT CAST('192.168.1.x' as inet DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('192.168.1.2/30' as cidr DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('22:00:5c:08:55:08:01:02'::macaddr8 as macaddr DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast betweeen range data types
+SELECT CAST('[1,2]'::int4range AS int4multirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[1,2]'::int8range AS int8multirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[1,2]'::numrange AS nummultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::daterange AS datemultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::tsrange AS tsmultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::tstzrange AS tstzmultirange DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast betweeen numeric data types
+CREATE TABLE test_safecast1(
+ col1 float4, col2 float8, col3 numeric, col4 numeric[],
+ col5 int2 default 32767,
+ col6 int4 default 32768,
+ col7 int8 default 4294967296);
+INSERT INTO test_safecast1 VALUES('11.1234', '11.1234', '9223372036854775808', '{11.1234}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('inf', 'inf', 'inf', '{11.1234, 12, inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('-inf', '-inf', '-inf', '{11.1234, 12, -inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('NaN', 'NaN', 'NaN', '{11.1234, 12, -inf, NaN}'::numeric[]);
+
+SELECT col5 as int2,
+ CAST(col5 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col5 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col5 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col5 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col5 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col5 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col5 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col5 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col6 as int4,
+ CAST(col6 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col6 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col6 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col6 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col6 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col6 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col6 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col6 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col7 as int8,
+ CAST(col7 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col7 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col7 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col7 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col7 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col7 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col7 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col7 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col3 as numeric,
+ CAST(col3 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col3 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col3 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col3 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col3 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col3 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col3 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col3 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col4 as num_arr,
+ CAST(col4 AS int2[] DEFAULT NULL ON CONVERSION ERROR) as int2arr,
+ CAST(col4 AS int4[] DEFAULT NULL ON CONVERSION ERROR) as int4arr,
+ CAST(col4 as int8[] DEFAULT NULL ON CONVERSION ERROR) as int8arr,
+ CAST(col4 as float4[] DEFAULT NULL ON CONVERSION ERROR) as f4arr,
+ CAST(col4 as float8[] DEFAULT NULL ON CONVERSION ERROR) as f8arr,
+ CAST(col4 as numeric(10,1)[] DEFAULT NULL ON CONVERSION ERROR) as numarr
+FROM test_safecast1;
+
+SELECT col1 as float4,
+ CAST(col1 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col1 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col1 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col1 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col1 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col1 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col1 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col1 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col2 as float8,
+ CAST(col2 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col2 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col2 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col2 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col2 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col2 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col2 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col2 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+-- date/timestamp/interval related safe type cast
+CREATE TABLE test_safecast2(
+ col0 date, col1 timestamp, col2 timestamptz,
+ col3 interval, col4 time, col5 timetz);
+INSERT INTO test_safecast2 VALUES
+('-infinity', '-infinity', '-infinity', '-infinity',
+ '2003-03-07 15:36:39 America/New_York', '2003-07-07 15:36:39 America/New_York'),
+('-infinity', 'infinity', 'infinity', 'infinity',
+ '11:59:59.99 PM', '11:59:59.99 PM PDT');
+
+SELECT col0 as date,
+ CAST(col0 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col0 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col0 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col0 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col0 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+
+SELECT col1 as timestamp,
+ CAST(col1 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col1 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col1 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col1 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col1 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+
+SELECT col2 as timestamptz,
+ CAST(col2 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col2 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col2 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col2 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col2 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+
+SELECT col3 as interval,
+ CAST(col3 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col3 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col3 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col3 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col3 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale,
+ CAST(col3 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+
+SELECT col4 as time,
+ CAST(col4 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col4 AS timetz DEFAULT NULL ON CONVERSION ERROR) IS NOT NULL as to_timetz,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+
+SELECT col5 as timetz,
+ CAST(col5 AS time DEFAULT NULL ON CONVERSION ERROR) as to_time,
+ CAST(col5 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_time_scale,
+ CAST(col5 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col5 AS timetz(6) DEFAULT NULL ON CONVERSION ERROR) as to_timetz_scale,
+ CAST(col5 AS interval DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col5 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+
+CREATE TABLE test_safecast3(col0 jsonb);
+INSERT INTO test_safecast3(col0) VALUES ('"test"');
+SELECT col0 as jsonb,
+ CAST(col0 AS integer DEFAULT NULL ON CONVERSION ERROR) as to_integer,
+ CAST(col0 AS numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col0 AS bigint DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col0 AS float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col0 AS float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col0 AS boolean DEFAULT NULL ON CONVERSION ERROR) as to_bool,
+ CAST(col0 AS smallint DEFAULT NULL ON CONVERSION ERROR) as to_smallint
+FROM test_safecast3;
+
+-- test deparse
+SET datestyle TO ISO, YMD;
+CREATE VIEW safecastview AS
+SELECT CAST('1234' as char(3) DEFAULT -1111 ON CONVERSION ERROR),
+ CAST(1 as date DEFAULT (('2025-Dec-06'::date + random(min=>1, max=>1::int))) ON CONVERSION ERROR) as cast0,
+ CAST(ARRAY[['1'], ['three'],['a']] AS int[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1,
+ CAST(ARRAY[['1', '2'], ['three', 'a']] AS date[] DEFAULT NULL ON CONVERSION ERROR) as cast2,
+ CAST(ARRAY['three'] AS INT[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast3;
+\sv safecastview
+SELECT * FROM safecastview;
+
+CREATE VIEW safecastview1 AS
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS d_int_arr
+ DEFAULT '{41,43}' ON CONVERSION ERROR) as cast1,
+ CAST(ARRAY[['1', '2', 1.1], ['three', true, B'01']] AS d_int_arr
+ DEFAULT '{41,43}' ON CONVERSION ERROR) as cast2;
+\sv safecastview1
+SELECT * FROM safecastview1;
+
+RESET datestyle;
+
+--test CAST DEFAULT expression mutability
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as int DEFAULT random(min=>1, max=>1) ON CONVERSION ERROR)));
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as xid DEFAULT NULL ON CONVERSION ERROR)));
+CREATE INDEX test_safecast3_idx ON test_safecast3((CAST(col0 as int DEFAULT NULL ON CONVERSION ERROR))); --ok
+SELECT pg_get_indexdef('test_safecast3_idx'::regclass);
+
+DROP TABLE test_safecast;
+DROP TABLE test_safecast1;
+DROP TABLE test_safecast2;
+DROP TABLE test_safecast3;
+DROP TABLE tcast;
+DROP FUNCTION ret_int8;
+DROP FUNCTION ret_setint;
+DROP TYPE comp_domain_with_typmod;
+DROP TYPE comp2;
+DROP DOMAIN d_varchar;
+DROP DOMAIN d_int42;
+DROP DOMAIN d_char3_not_null;
+RESET extra_float_digits;
diff --git a/src/test/regress/sql/create_cast.sql b/src/test/regress/sql/create_cast.sql
index 32187853cc7..0a15a795d87 100644
--- a/src/test/regress/sql/create_cast.sql
+++ b/src/test/regress/sql/create_cast.sql
@@ -62,6 +62,7 @@ $$ SELECT ('bar'::text || $1::text); $$;
CREATE CAST (int4 AS casttesttype) WITH FUNCTION bar_int4_text(int4) AS IMPLICIT;
SELECT 1234::int4::casttesttype; -- Should work now
+SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- error
-- check dependencies generated for that
SELECT pg_describe_object(classid, objid, objsubid) as obj,
diff --git a/src/test/regress/sql/equivclass.sql b/src/test/regress/sql/equivclass.sql
index 7fc2159349b..5ad1d26311d 100644
--- a/src/test/regress/sql/equivclass.sql
+++ b/src/test/regress/sql/equivclass.sql
@@ -98,6 +98,9 @@ create function int8alias1cmp(int8, int8alias1) returns int
alter operator family integer_ops using btree add
function 1 int8alias1cmp (int8, int8alias1);
+-- int8alias2 binary-coercible to int8. so this is ok
+select cast('1'::int8 as int8alias2 default null on conversion error);
+
create table ec0 (ff int8 primary key, f1 int8, f2 int8);
create table ec1 (ff int8 primary key, f1 int8alias1, f2 int8alias2);
create table ec2 (xf int8 primary key, x1 int8alias1, x2 int8alias2);
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 6e2ed0c8825..f196d3671c5 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2673,6 +2673,9 @@ STRLEN
SV
SYNCHRONIZATION_BARRIER
SYSTEM_INFO
+SafeTypeCast
+SafeTypeCastExpr
+SafeTypeCastState
SampleScan
SampleScanGetSampleSize_function
SampleScanState
--
2.34.1
v14-0017-error-safe-for-casting-jsonb-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v14-0017-error-safe-for-casting-jsonb-to-other-types-per-pg_cast.patchDownload
From 20f7e337f2677267566521e135d4be7bf9c63a7d Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Mon, 24 Nov 2025 14:26:53 +0800
Subject: [PATCH v14 17/19] error safe for casting jsonb to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'jsonb'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+---------------+---------
jsonb | boolean | 3556 | e | f | jsonb_bool | bool
jsonb | numeric | 3449 | e | f | jsonb_numeric | numeric
jsonb | smallint | 3450 | e | f | jsonb_int2 | int2
jsonb | integer | 3451 | e | f | jsonb_int4 | int4
jsonb | bigint | 3452 | e | f | jsonb_int8 | int8
jsonb | real | 3453 | e | f | jsonb_float4 | float4
jsonb | double precision | 2580 | e | f | jsonb_float8 | float8
(7 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/jsonb.c | 74 +++++++++++++++++++++++++++--------
1 file changed, 58 insertions(+), 16 deletions(-)
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index c4fe6e00dcd..1600204fa4a 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -1815,7 +1815,7 @@ JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res)
* Emit correct, translatable cast error message
*/
static void
-cannotCastJsonbValue(enum jbvType type, const char *sqltype)
+cannotCastJsonbValue(enum jbvType type, const char *sqltype, Node *escontext)
{
static const struct
{
@@ -1836,7 +1836,7 @@ cannotCastJsonbValue(enum jbvType type, const char *sqltype)
for (i = 0; i < lengthof(messages); i++)
if (messages[i].type == type)
- ereport(ERROR,
+ ereturn(escontext,,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(messages[i].msg, sqltype)));
@@ -1851,7 +1851,10 @@ jsonb_bool(PG_FUNCTION_ARGS)
JsonbValue v;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "boolean");
+ {
+ cannotCastJsonbValue(v.type, "boolean", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -1860,7 +1863,10 @@ jsonb_bool(PG_FUNCTION_ARGS)
}
if (v.type != jbvBool)
- cannotCastJsonbValue(v.type, "boolean");
+ {
+ cannotCastJsonbValue(v.type, "boolean", fcinfo->context);
+ PG_RETURN_NULL();
+ }
PG_FREE_IF_COPY(in, 0);
@@ -1875,7 +1881,10 @@ jsonb_numeric(PG_FUNCTION_ARGS)
Numeric retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "numeric");
+ {
+ cannotCastJsonbValue(v.type, "numeric", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -1884,7 +1893,10 @@ jsonb_numeric(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "numeric");
+ {
+ cannotCastJsonbValue(v.type, "numeric", fcinfo->context);
+ PG_RETURN_NULL();
+ }
/*
* v.val.numeric points into jsonb body, so we need to make a copy to
@@ -1905,7 +1917,10 @@ jsonb_int2(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "smallint");
+ {
+ cannotCastJsonbValue(v.type, "smallint", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -1914,7 +1929,10 @@ jsonb_int2(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "smallint");
+ {
+ cannotCastJsonbValue(v.type, "smallint", fcinfo->context);
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_int2,
NumericGetDatum(v.val.numeric));
@@ -1932,7 +1950,10 @@ jsonb_int4(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "integer");
+ {
+ cannotCastJsonbValue(v.type, "integer", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -1941,7 +1962,10 @@ jsonb_int4(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "integer");
+ {
+ cannotCastJsonbValue(v.type, "integer", fcinfo->context);
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_int4,
NumericGetDatum(v.val.numeric));
@@ -1959,7 +1983,10 @@ jsonb_int8(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "bigint");
+ {
+ cannotCastJsonbValue(v.type, "bigint", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -1968,7 +1995,10 @@ jsonb_int8(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "bigint");
+ {
+ cannotCastJsonbValue(v.type, "bigint", fcinfo->context);
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_int8,
NumericGetDatum(v.val.numeric));
@@ -1986,7 +2016,10 @@ jsonb_float4(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "real");
+ {
+ cannotCastJsonbValue(v.type, "real", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -1995,7 +2028,10 @@ jsonb_float4(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "real");
+ {
+ cannotCastJsonbValue(v.type, "real", fcinfo->context);
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_float4,
NumericGetDatum(v.val.numeric));
@@ -2013,7 +2049,10 @@ jsonb_float8(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "double precision");
+ {
+ cannotCastJsonbValue(v.type, "double precision", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2022,7 +2061,10 @@ jsonb_float8(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "double precision");
+ {
+ cannotCastJsonbValue(v.type, "double precision", fcinfo->context);
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_float8,
NumericGetDatum(v.val.numeric));
--
2.34.1
v14-0018-error-safe-for-casting-geometry-data-type.patchtext/x-patch; charset=US-ASCII; name=v14-0018-error-safe-for-casting-geometry-data-type.patchDownload
From 4e74eb428baff34133cc6e5462e8d43edfc256c7 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Mon, 8 Dec 2025 10:02:08 +0800
Subject: [PATCH v14 18/19] error safe for casting geometry data type
select castsource::regtype, casttarget::regtype, pp.prosrc
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
join pg_type pt on pt.oid = castsource
join pg_type pt1 on pt1.oid = casttarget
and pc.castfunc > 0 and pt.typarray <> 0
and pt.typnamespace = 'pg_catalog'::regnamespace
and pt1.typnamespace = 'pg_catalog'::regnamespace
and (pt.typcategory = 'G' or pt1.typcategory = 'G')
order by castsource::regtype, casttarget::regtype;
castsource | casttarget | prosrc
------------+------------+---------------
point | box | point_box
lseg | point | lseg_center
path | polygon | path_poly
box | point | box_center
box | lseg | box_diagonal
box | polygon | box_poly
box | circle | box_circle
polygon | point | poly_center
polygon | path | poly_path
polygon | box | poly_box
polygon | circle | poly_circle
circle | point | circle_center
circle | box | circle_box
circle | polygon |
(14 rows)
already error safe: point_box, box_diagonal, box_poly, poly_path, poly_box, circle_center
almost error safe: path_poly
patch make these functions error safe: lseg_center, box_center, box_circle, poly_center, poly_circle
circle_box
can not error safe: cast circle to polygon, because it's a SQL function
discussion: https://postgr.es/m/CACJufxHCMzrHOW=wRe8L30rMhB3sjwAv1LE928Fa7sxMu1Tx-g@mail.gmail.com
---
contrib/btree_gist/btree_float4.c | 2 +-
contrib/btree_gist/btree_float8.c | 4 +-
src/backend/utils/adt/float.c | 104 ++++++------
src/backend/utils/adt/geo_ops.c | 268 ++++++++++++++++++++++--------
src/include/utils/float.h | 110 ++++++++----
5 files changed, 330 insertions(+), 158 deletions(-)
diff --git a/contrib/btree_gist/btree_float4.c b/contrib/btree_gist/btree_float4.c
index d9c859835da..a7325a7bb29 100644
--- a/contrib/btree_gist/btree_float4.c
+++ b/contrib/btree_gist/btree_float4.c
@@ -101,7 +101,7 @@ float4_dist(PG_FUNCTION_ARGS)
r = a - b;
if (unlikely(isinf(r)) && !isinf(a) && !isinf(b))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT4(fabsf(r));
}
diff --git a/contrib/btree_gist/btree_float8.c b/contrib/btree_gist/btree_float8.c
index 567beede178..7c99b84de35 100644
--- a/contrib/btree_gist/btree_float8.c
+++ b/contrib/btree_gist/btree_float8.c
@@ -79,7 +79,7 @@ gbt_float8_dist(const void *a, const void *b, FmgrInfo *flinfo)
r = arg1 - arg2;
if (unlikely(isinf(r)) && !isinf(arg1) && !isinf(arg2))
- float_overflow_error();
+ float_overflow_error(NULL);
return fabs(r);
}
@@ -109,7 +109,7 @@ float8_dist(PG_FUNCTION_ARGS)
r = a - b;
if (unlikely(isinf(r)) && !isinf(a) && !isinf(b))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(fabs(r));
}
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index 55c7030ba81..2710e42f5bb 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -83,25 +83,25 @@ static void init_degree_constants(void);
* This does mean that you don't get a useful error location indicator.
*/
pg_noinline void
-float_overflow_error(void)
+float_overflow_error(struct Node *escontext)
{
- ereport(ERROR,
+ errsave(escontext,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("value out of range: overflow")));
}
pg_noinline void
-float_underflow_error(void)
+float_underflow_error(struct Node *escontext)
{
- ereport(ERROR,
+ errsave(escontext,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("value out of range: underflow")));
}
pg_noinline void
-float_zero_divide_error(void)
+float_zero_divide_error(struct Node *escontext)
{
- ereport(ERROR,
+ errsave(escontext,
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
}
@@ -1460,9 +1460,9 @@ dsqrt(PG_FUNCTION_ARGS)
result = sqrt(arg1);
if (unlikely(isinf(result)) && !isinf(arg1))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0) && arg1 != 0.0)
- float_underflow_error();
+ float_underflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1479,9 +1479,9 @@ dcbrt(PG_FUNCTION_ARGS)
result = cbrt(arg1);
if (unlikely(isinf(result)) && !isinf(arg1))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0) && arg1 != 0.0)
- float_underflow_error();
+ float_underflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1617,24 +1617,24 @@ dpow(PG_FUNCTION_ARGS)
if (absx == 1.0)
result = 1.0;
else if (arg2 >= 0.0 ? (absx > 1.0) : (absx < 1.0))
- float_overflow_error();
+ float_overflow_error(NULL);
else
- float_underflow_error();
+ float_underflow_error(NULL);
}
}
else if (errno == ERANGE)
{
if (result != 0.0)
- float_overflow_error();
+ float_overflow_error(NULL);
else
- float_underflow_error();
+ float_underflow_error(NULL);
}
else
{
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0) && arg1 != 0.0)
- float_underflow_error();
+ float_underflow_error(NULL);
}
}
@@ -1674,14 +1674,14 @@ dexp(PG_FUNCTION_ARGS)
if (unlikely(errno == ERANGE))
{
if (result != 0.0)
- float_overflow_error();
+ float_overflow_error(NULL);
else
- float_underflow_error();
+ float_underflow_error(NULL);
}
else if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
else if (unlikely(result == 0.0))
- float_underflow_error();
+ float_underflow_error(NULL);
}
PG_RETURN_FLOAT8(result);
@@ -1712,9 +1712,9 @@ dlog1(PG_FUNCTION_ARGS)
result = log(arg1);
if (unlikely(isinf(result)) && !isinf(arg1))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0) && arg1 != 1.0)
- float_underflow_error();
+ float_underflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1745,9 +1745,9 @@ dlog10(PG_FUNCTION_ARGS)
result = log10(arg1);
if (unlikely(isinf(result)) && !isinf(arg1))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0) && arg1 != 1.0)
- float_underflow_error();
+ float_underflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1778,7 +1778,7 @@ dacos(PG_FUNCTION_ARGS)
result = acos(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1809,7 +1809,7 @@ dasin(PG_FUNCTION_ARGS)
result = asin(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1835,7 +1835,7 @@ datan(PG_FUNCTION_ARGS)
*/
result = atan(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1861,7 +1861,7 @@ datan2(PG_FUNCTION_ARGS)
*/
result = atan2(arg1, arg2);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1902,7 +1902,7 @@ dcos(PG_FUNCTION_ARGS)
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("input is out of range")));
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1957,7 +1957,7 @@ dsin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("input is out of range")));
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2137,7 +2137,7 @@ dacosd(PG_FUNCTION_ARGS)
result = 90.0 + asind_q1(-arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2174,7 +2174,7 @@ dasind(PG_FUNCTION_ARGS)
result = -asind_q1(-arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2206,7 +2206,7 @@ datand(PG_FUNCTION_ARGS)
result = (atan_arg1 / atan_1_0) * 45.0;
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2242,7 +2242,7 @@ datan2d(PG_FUNCTION_ARGS)
result = (atan2_arg1_arg2 / atan_1_0) * 45.0;
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2365,7 +2365,7 @@ dcosd(PG_FUNCTION_ARGS)
result = sign * cosd_q1(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2487,7 +2487,7 @@ dsind(PG_FUNCTION_ARGS)
result = sign * sind_q1(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2645,7 +2645,7 @@ dcosh(PG_FUNCTION_ARGS)
result = get_float8_infinity();
if (unlikely(result == 0.0))
- float_underflow_error();
+ float_underflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2665,7 +2665,7 @@ dtanh(PG_FUNCTION_ARGS)
result = tanh(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2765,7 +2765,7 @@ derf(PG_FUNCTION_ARGS)
result = erf(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2785,7 +2785,7 @@ derfc(PG_FUNCTION_ARGS)
result = erfc(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2814,7 +2814,7 @@ dgamma(PG_FUNCTION_ARGS)
/* Per POSIX, an input of -Inf causes a domain error */
if (arg1 < 0)
{
- float_overflow_error();
+ float_overflow_error(NULL);
result = get_float8_nan(); /* keep compiler quiet */
}
else
@@ -2836,12 +2836,12 @@ dgamma(PG_FUNCTION_ARGS)
if (errno != 0 || isinf(result) || isnan(result))
{
if (result != 0.0)
- float_overflow_error();
+ float_overflow_error(NULL);
else
- float_underflow_error();
+ float_underflow_error(NULL);
}
else if (result == 0.0)
- float_underflow_error();
+ float_underflow_error(NULL);
}
PG_RETURN_FLOAT8(result);
@@ -2873,7 +2873,7 @@ dlgamma(PG_FUNCTION_ARGS)
* to report overflow, but it should never underflow.
*/
if (errno == ERANGE || (isinf(result) && !isinf(arg1)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -3013,7 +3013,7 @@ float8_combine(PG_FUNCTION_ARGS)
tmp = Sx1 / N1 - Sx2 / N2;
Sxx = Sxx1 + Sxx2 + N1 * N2 * tmp * tmp / N;
if (unlikely(isinf(Sxx)) && !isinf(Sxx1) && !isinf(Sxx2))
- float_overflow_error();
+ float_overflow_error(NULL);
}
/*
@@ -3080,7 +3080,7 @@ float8_accum(PG_FUNCTION_ARGS)
if (isinf(Sx) || isinf(Sxx))
{
if (!isinf(transvalues[1]) && !isinf(newval))
- float_overflow_error();
+ float_overflow_error(NULL);
Sxx = get_float8_nan();
}
@@ -3163,7 +3163,7 @@ float4_accum(PG_FUNCTION_ARGS)
if (isinf(Sx) || isinf(Sxx))
{
if (!isinf(transvalues[1]) && !isinf(newval))
- float_overflow_error();
+ float_overflow_error(NULL);
Sxx = get_float8_nan();
}
@@ -3430,7 +3430,7 @@ float8_regr_accum(PG_FUNCTION_ARGS)
(isinf(Sxy) &&
!isinf(transvalues[1]) && !isinf(newvalX) &&
!isinf(transvalues[3]) && !isinf(newvalY)))
- float_overflow_error();
+ float_overflow_error(NULL);
if (isinf(Sxx))
Sxx = get_float8_nan();
@@ -3603,15 +3603,15 @@ float8_regr_combine(PG_FUNCTION_ARGS)
tmp1 = Sx1 / N1 - Sx2 / N2;
Sxx = Sxx1 + Sxx2 + N1 * N2 * tmp1 * tmp1 / N;
if (unlikely(isinf(Sxx)) && !isinf(Sxx1) && !isinf(Sxx2))
- float_overflow_error();
+ float_overflow_error(NULL);
Sy = float8_pl(Sy1, Sy2);
tmp2 = Sy1 / N1 - Sy2 / N2;
Syy = Syy1 + Syy2 + N1 * N2 * tmp2 * tmp2 / N;
if (unlikely(isinf(Syy)) && !isinf(Syy1) && !isinf(Syy2))
- float_overflow_error();
+ float_overflow_error(NULL);
Sxy = Sxy1 + Sxy2 + N1 * N2 * tmp1 * tmp2 / N;
if (unlikely(isinf(Sxy)) && !isinf(Sxy1) && !isinf(Sxy2))
- float_overflow_error();
+ float_overflow_error(NULL);
if (float8_eq(Cx1, Cx2))
Cx = Cx1;
else
diff --git a/src/backend/utils/adt/geo_ops.c b/src/backend/utils/adt/geo_ops.c
index 9101a720744..19f75e59a6d 100644
--- a/src/backend/utils/adt/geo_ops.c
+++ b/src/backend/utils/adt/geo_ops.c
@@ -77,12 +77,13 @@ enum path_delim
/* Routines for points */
static inline void point_construct(Point *result, float8 x, float8 y);
-static inline void point_add_point(Point *result, Point *pt1, Point *pt2);
+static inline void point_add_point(Point *result, Point *pt1, Point *pt2,
+ Node *escontext);
static inline void point_sub_point(Point *result, Point *pt1, Point *pt2);
static inline void point_mul_point(Point *result, Point *pt1, Point *pt2);
static inline void point_div_point(Point *result, Point *pt1, Point *pt2);
static inline bool point_eq_point(Point *pt1, Point *pt2);
-static inline float8 point_dt(Point *pt1, Point *pt2);
+static inline float8 point_dt(Point *pt1, Point *pt2, Node *escontext);
static inline float8 point_sl(Point *pt1, Point *pt2);
static int point_inside(Point *p, int npts, Point *plist);
@@ -108,7 +109,7 @@ static float8 lseg_closept_lseg(Point *result, LSEG *on_lseg, LSEG *to_lseg);
/* Routines for boxes */
static inline void box_construct(BOX *result, Point *pt1, Point *pt2);
-static void box_cn(Point *center, BOX *box);
+static void box_cn(Point *center, BOX *box, Node* escontext);
static bool box_ov(BOX *box1, BOX *box2);
static float8 box_ar(BOX *box);
static float8 box_ht(BOX *box);
@@ -125,7 +126,7 @@ static float8 circle_ar(CIRCLE *circle);
/* Routines for polygons */
static void make_bound_box(POLYGON *poly);
-static void poly_to_circle(CIRCLE *result, POLYGON *poly);
+static bool poly_to_circle(CIRCLE *result, POLYGON *poly, Node *escontext);
static bool lseg_inside_poly(Point *a, Point *b, POLYGON *poly, int start);
static bool poly_contain_poly(POLYGON *contains_poly, POLYGON *contained_poly);
static bool plist_same(int npts, Point *p1, Point *p2);
@@ -836,10 +837,10 @@ box_distance(PG_FUNCTION_ARGS)
Point a,
b;
- box_cn(&a, box1);
- box_cn(&b, box2);
+ box_cn(&a, box1, NULL);
+ box_cn(&b, box2, NULL);
- PG_RETURN_FLOAT8(point_dt(&a, &b));
+ PG_RETURN_FLOAT8(point_dt(&a, &b, NULL));
}
@@ -851,7 +852,9 @@ box_center(PG_FUNCTION_ARGS)
BOX *box = PG_GETARG_BOX_P(0);
Point *result = (Point *) palloc(sizeof(Point));
- box_cn(result, box);
+ box_cn(result, box, fcinfo->context);
+ if ((SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
PG_RETURN_POINT_P(result);
}
@@ -869,13 +872,26 @@ box_ar(BOX *box)
/* box_cn - stores the centerpoint of the box into *center.
*/
static void
-box_cn(Point *center, BOX *box)
+box_cn(Point *center, BOX *box, Node *escontext)
{
- center->x = float8_div(float8_pl(box->high.x, box->low.x), 2.0);
- center->y = float8_div(float8_pl(box->high.y, box->low.y), 2.0);
+ float8 x;
+ float8 y;
+
+ x = float8_pl_safe(box->high.x, box->low.x, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return;
+
+ center->x = float8_div_safe(x, 2.0, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return;
+
+ y = float8_pl_safe(box->high.y, box->low.y, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return;
+
+ center->y = float8_div_safe(y, 2.0, escontext);
}
-
/* box_wd - returns the width (length) of the box
* (horizontal magnitude).
*/
@@ -1808,7 +1824,7 @@ path_length(PG_FUNCTION_ARGS)
iprev = path->npts - 1; /* include the closure segment */
}
- result = float8_pl(result, point_dt(&path->p[iprev], &path->p[i]));
+ result = float8_pl(result, point_dt(&path->p[iprev], &path->p[i], NULL));
}
PG_RETURN_FLOAT8(result);
@@ -1995,13 +2011,24 @@ point_distance(PG_FUNCTION_ARGS)
Point *pt1 = PG_GETARG_POINT_P(0);
Point *pt2 = PG_GETARG_POINT_P(1);
- PG_RETURN_FLOAT8(point_dt(pt1, pt2));
+ PG_RETURN_FLOAT8(point_dt(pt1, pt2, NULL));
}
static inline float8
-point_dt(Point *pt1, Point *pt2)
+point_dt(Point *pt1, Point *pt2, Node *escontext)
{
- return hypot(float8_mi(pt1->x, pt2->x), float8_mi(pt1->y, pt2->y));
+ float8 x;
+ float8 y;
+
+ x = float8_mi_safe(pt1->x, pt2->x, escontext);
+ if (unlikely(SOFT_ERROR_OCCURRED(escontext)))
+ return 0.0;
+
+ y = float8_mi_safe(pt1->y, pt2->y, escontext);
+ if (unlikely(SOFT_ERROR_OCCURRED(escontext)))
+ return 0.0;
+
+ return hypot(x, y);
}
Datum
@@ -2173,7 +2200,7 @@ lseg_length(PG_FUNCTION_ARGS)
{
LSEG *lseg = PG_GETARG_LSEG_P(0);
- PG_RETURN_FLOAT8(point_dt(&lseg->p[0], &lseg->p[1]));
+ PG_RETURN_FLOAT8(point_dt(&lseg->p[0], &lseg->p[1], NULL));
}
/*----------------------------------------------------------
@@ -2258,8 +2285,8 @@ lseg_lt(PG_FUNCTION_ARGS)
LSEG *l1 = PG_GETARG_LSEG_P(0);
LSEG *l2 = PG_GETARG_LSEG_P(1);
- PG_RETURN_BOOL(FPlt(point_dt(&l1->p[0], &l1->p[1]),
- point_dt(&l2->p[0], &l2->p[1])));
+ PG_RETURN_BOOL(FPlt(point_dt(&l1->p[0], &l1->p[1], NULL),
+ point_dt(&l2->p[0], &l2->p[1], NULL)));
}
Datum
@@ -2268,8 +2295,8 @@ lseg_le(PG_FUNCTION_ARGS)
LSEG *l1 = PG_GETARG_LSEG_P(0);
LSEG *l2 = PG_GETARG_LSEG_P(1);
- PG_RETURN_BOOL(FPle(point_dt(&l1->p[0], &l1->p[1]),
- point_dt(&l2->p[0], &l2->p[1])));
+ PG_RETURN_BOOL(FPle(point_dt(&l1->p[0], &l1->p[1], NULL),
+ point_dt(&l2->p[0], &l2->p[1], NULL)));
}
Datum
@@ -2278,8 +2305,8 @@ lseg_gt(PG_FUNCTION_ARGS)
LSEG *l1 = PG_GETARG_LSEG_P(0);
LSEG *l2 = PG_GETARG_LSEG_P(1);
- PG_RETURN_BOOL(FPgt(point_dt(&l1->p[0], &l1->p[1]),
- point_dt(&l2->p[0], &l2->p[1])));
+ PG_RETURN_BOOL(FPgt(point_dt(&l1->p[0], &l1->p[1], NULL),
+ point_dt(&l2->p[0], &l2->p[1], NULL)));
}
Datum
@@ -2288,8 +2315,8 @@ lseg_ge(PG_FUNCTION_ARGS)
LSEG *l1 = PG_GETARG_LSEG_P(0);
LSEG *l2 = PG_GETARG_LSEG_P(1);
- PG_RETURN_BOOL(FPge(point_dt(&l1->p[0], &l1->p[1]),
- point_dt(&l2->p[0], &l2->p[1])));
+ PG_RETURN_BOOL(FPge(point_dt(&l1->p[0], &l1->p[1], NULL),
+ point_dt(&l2->p[0], &l2->p[1], NULL)));
}
@@ -2317,13 +2344,31 @@ lseg_center(PG_FUNCTION_ARGS)
{
LSEG *lseg = PG_GETARG_LSEG_P(0);
Point *result;
+ float8 x;
+ float8 y;
result = (Point *) palloc(sizeof(Point));
- result->x = float8_div(float8_pl(lseg->p[0].x, lseg->p[1].x), 2.0);
- result->y = float8_div(float8_pl(lseg->p[0].y, lseg->p[1].y), 2.0);
+ x = float8_pl_safe(lseg->p[0].x, lseg->p[1].x, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ result->x = float8_div_safe(x, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ y = float8_pl_safe(lseg->p[0].y, lseg->p[1].y, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ result->y = float8_div_safe(y, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
PG_RETURN_POINT_P(result);
+
+fail:
+ PG_RETURN_NULL();
}
@@ -2743,7 +2788,7 @@ line_closept_point(Point *result, LINE *line, Point *point)
if (result != NULL)
*result = closept;
- return point_dt(&closept, point);
+ return point_dt(&closept, point, NULL);
}
Datum
@@ -2784,7 +2829,7 @@ lseg_closept_point(Point *result, LSEG *lseg, Point *pt)
if (result != NULL)
*result = closept;
- return point_dt(&closept, pt);
+ return point_dt(&closept, pt, NULL);
}
Datum
@@ -3108,9 +3153,9 @@ on_pl(PG_FUNCTION_ARGS)
static bool
lseg_contain_point(LSEG *lseg, Point *pt)
{
- return FPeq(point_dt(pt, &lseg->p[0]) +
- point_dt(pt, &lseg->p[1]),
- point_dt(&lseg->p[0], &lseg->p[1]));
+ return FPeq(point_dt(pt, &lseg->p[0], NULL) +
+ point_dt(pt, &lseg->p[1], NULL),
+ point_dt(&lseg->p[0], &lseg->p[1], NULL));
}
Datum
@@ -3176,11 +3221,11 @@ on_ppath(PG_FUNCTION_ARGS)
if (!path->closed)
{
n = path->npts - 1;
- a = point_dt(pt, &path->p[0]);
+ a = point_dt(pt, &path->p[0], NULL);
for (i = 0; i < n; i++)
{
- b = point_dt(pt, &path->p[i + 1]);
- if (FPeq(float8_pl(a, b), point_dt(&path->p[i], &path->p[i + 1])))
+ b = point_dt(pt, &path->p[i + 1], NULL);
+ if (FPeq(float8_pl(a, b), point_dt(&path->p[i], &path->p[i + 1], NULL)))
PG_RETURN_BOOL(true);
a = b;
}
@@ -3277,7 +3322,7 @@ box_interpt_lseg(Point *result, BOX *box, LSEG *lseg)
if (result != NULL)
{
- box_cn(&point, box);
+ box_cn(&point, box, NULL);
lseg_closept_point(result, lseg, &point);
}
@@ -4108,11 +4153,20 @@ construct_point(PG_FUNCTION_ARGS)
static inline void
-point_add_point(Point *result, Point *pt1, Point *pt2)
+point_add_point(Point *result, Point *pt1, Point *pt2, Node *escontext)
{
- point_construct(result,
- float8_pl(pt1->x, pt2->x),
- float8_pl(pt1->y, pt2->y));
+ float8 x;
+ float8 y;
+
+ x = float8_pl_safe(pt1->x, pt2->x, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return;
+
+ y = float8_pl_safe(pt1->y, pt2->y, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return;
+
+ point_construct(result, x, y);
}
Datum
@@ -4124,7 +4178,7 @@ point_add(PG_FUNCTION_ARGS)
result = (Point *) palloc(sizeof(Point));
- point_add_point(result, p1, p2);
+ point_add_point(result, p1, p2, NULL);
PG_RETURN_POINT_P(result);
}
@@ -4236,8 +4290,8 @@ box_add(PG_FUNCTION_ARGS)
result = (BOX *) palloc(sizeof(BOX));
- point_add_point(&result->high, &box->high, p);
- point_add_point(&result->low, &box->low, p);
+ point_add_point(&result->high, &box->high, p, NULL);
+ point_add_point(&result->low, &box->low, p, NULL);
PG_RETURN_BOX_P(result);
}
@@ -4400,7 +4454,7 @@ path_add_pt(PG_FUNCTION_ARGS)
int i;
for (i = 0; i < path->npts; i++)
- point_add_point(&path->p[i], &path->p[i], point);
+ point_add_point(&path->p[i], &path->p[i], point, NULL);
PG_RETURN_PATH_P(path);
}
@@ -4458,7 +4512,7 @@ path_poly(PG_FUNCTION_ARGS)
/* This is not very consistent --- other similar cases return NULL ... */
if (!path->closed)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("open path cannot be converted to polygon")));
@@ -4508,7 +4562,9 @@ poly_center(PG_FUNCTION_ARGS)
result = (Point *) palloc(sizeof(Point));
- poly_to_circle(&circle, poly);
+ if (!poly_to_circle(&circle, poly, fcinfo->context))
+ PG_RETURN_NULL();
+
*result = circle.center;
PG_RETURN_POINT_P(result);
@@ -4766,7 +4822,7 @@ circle_overlap(PG_FUNCTION_ARGS)
CIRCLE *circle1 = PG_GETARG_CIRCLE_P(0);
CIRCLE *circle2 = PG_GETARG_CIRCLE_P(1);
- PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center),
+ PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center, NULL),
float8_pl(circle1->radius, circle2->radius)));
}
@@ -4828,7 +4884,7 @@ circle_contained(PG_FUNCTION_ARGS)
CIRCLE *circle1 = PG_GETARG_CIRCLE_P(0);
CIRCLE *circle2 = PG_GETARG_CIRCLE_P(1);
- PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center),
+ PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center, NULL),
float8_mi(circle2->radius, circle1->radius)));
}
@@ -4840,7 +4896,7 @@ circle_contain(PG_FUNCTION_ARGS)
CIRCLE *circle1 = PG_GETARG_CIRCLE_P(0);
CIRCLE *circle2 = PG_GETARG_CIRCLE_P(1);
- PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center),
+ PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center, NULL),
float8_mi(circle1->radius, circle2->radius)));
}
@@ -4970,7 +5026,7 @@ circle_add_pt(PG_FUNCTION_ARGS)
result = (CIRCLE *) palloc(sizeof(CIRCLE));
- point_add_point(&result->center, &circle->center, point);
+ point_add_point(&result->center, &circle->center, point, NULL);
result->radius = circle->radius;
PG_RETURN_CIRCLE_P(result);
@@ -5069,7 +5125,7 @@ circle_distance(PG_FUNCTION_ARGS)
CIRCLE *circle2 = PG_GETARG_CIRCLE_P(1);
float8 result;
- result = float8_mi(point_dt(&circle1->center, &circle2->center),
+ result = float8_mi(point_dt(&circle1->center, &circle2->center, NULL),
float8_pl(circle1->radius, circle2->radius));
if (result < 0.0)
result = 0.0;
@@ -5085,7 +5141,7 @@ circle_contain_pt(PG_FUNCTION_ARGS)
Point *point = PG_GETARG_POINT_P(1);
float8 d;
- d = point_dt(&circle->center, point);
+ d = point_dt(&circle->center, point, NULL);
PG_RETURN_BOOL(d <= circle->radius);
}
@@ -5097,7 +5153,7 @@ pt_contained_circle(PG_FUNCTION_ARGS)
CIRCLE *circle = PG_GETARG_CIRCLE_P(1);
float8 d;
- d = point_dt(&circle->center, point);
+ d = point_dt(&circle->center, point, NULL);
PG_RETURN_BOOL(d <= circle->radius);
}
@@ -5112,7 +5168,7 @@ dist_pc(PG_FUNCTION_ARGS)
CIRCLE *circle = PG_GETARG_CIRCLE_P(1);
float8 result;
- result = float8_mi(point_dt(point, &circle->center),
+ result = float8_mi(point_dt(point, &circle->center, NULL),
circle->radius);
if (result < 0.0)
result = 0.0;
@@ -5130,7 +5186,7 @@ dist_cpoint(PG_FUNCTION_ARGS)
Point *point = PG_GETARG_POINT_P(1);
float8 result;
- result = float8_mi(point_dt(point, &circle->center), circle->radius);
+ result = float8_mi(point_dt(point, &circle->center, NULL), circle->radius);
if (result < 0.0)
result = 0.0;
@@ -5191,14 +5247,30 @@ circle_box(PG_FUNCTION_ARGS)
box = (BOX *) palloc(sizeof(BOX));
- delta = float8_div(circle->radius, sqrt(2.0));
+ delta = float8_div_safe(circle->radius, sqrt(2.0), fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
- box->high.x = float8_pl(circle->center.x, delta);
- box->low.x = float8_mi(circle->center.x, delta);
- box->high.y = float8_pl(circle->center.y, delta);
- box->low.y = float8_mi(circle->center.y, delta);
+ box->high.x = float8_pl_safe(circle->center.x, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ box->low.x = float8_mi_safe(circle->center.x, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ box->high.y = float8_pl_safe(circle->center.y, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ box->low.y = float8_mi_safe(circle->center.y, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
PG_RETURN_BOX_P(box);
+
+fail:
+ PG_RETURN_NULL();
}
/* box_circle()
@@ -5209,15 +5281,35 @@ box_circle(PG_FUNCTION_ARGS)
{
BOX *box = PG_GETARG_BOX_P(0);
CIRCLE *circle;
+ float8 x;
+ float8 y;
circle = (CIRCLE *) palloc(sizeof(CIRCLE));
- circle->center.x = float8_div(float8_pl(box->high.x, box->low.x), 2.0);
- circle->center.y = float8_div(float8_pl(box->high.y, box->low.y), 2.0);
+ x = float8_pl_safe(box->high.x, box->low.x, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
- circle->radius = point_dt(&circle->center, &box->high);
+ circle->center.x = float8_div_safe(x, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ y = float8_pl_safe(box->high.y, box->low.y, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ circle->center.y = float8_div_safe(y, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ circle->radius = point_dt(&circle->center, &box->high, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
PG_RETURN_CIRCLE_P(circle);
+
+fail:
+ PG_RETURN_NULL();
}
@@ -5281,10 +5373,11 @@ circle_poly(PG_FUNCTION_ARGS)
* XXX This algorithm should use weighted means of line segments
* rather than straight average values of points - tgl 97/01/21.
*/
-static void
-poly_to_circle(CIRCLE *result, POLYGON *poly)
+static bool
+poly_to_circle(CIRCLE *result, POLYGON *poly, Node *escontext)
{
int i;
+ float8 x;
Assert(poly->npts > 0);
@@ -5293,14 +5386,44 @@ poly_to_circle(CIRCLE *result, POLYGON *poly)
result->radius = 0;
for (i = 0; i < poly->npts; i++)
- point_add_point(&result->center, &result->center, &poly->p[i]);
- result->center.x = float8_div(result->center.x, poly->npts);
- result->center.y = float8_div(result->center.y, poly->npts);
+ {
+ point_add_point(&result->center,
+ &result->center,
+ &poly->p[i],
+ escontext);
+
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+ }
+
+ result->center.x = float8_div_safe(result->center.x,
+ poly->npts,
+ escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ result->center.y = float8_div_safe(result->center.y,
+ poly->npts,
+ escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
for (i = 0; i < poly->npts; i++)
- result->radius = float8_pl(result->radius,
- point_dt(&poly->p[i], &result->center));
- result->radius = float8_div(result->radius, poly->npts);
+ {
+ x = point_dt(&poly->p[i], &result->center, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ result->radius = float8_pl_safe(result->radius, x, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+ }
+
+ result->radius = float8_div_safe(result->radius, poly->npts, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ return true;
}
Datum
@@ -5311,7 +5434,8 @@ poly_circle(PG_FUNCTION_ARGS)
result = (CIRCLE *) palloc(sizeof(CIRCLE));
- poly_to_circle(result, poly);
+ if (!poly_to_circle(result, poly, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_CIRCLE_P(result);
}
diff --git a/src/include/utils/float.h b/src/include/utils/float.h
index fc2a9cf6475..1b37e17defc 100644
--- a/src/include/utils/float.h
+++ b/src/include/utils/float.h
@@ -30,9 +30,9 @@ extern PGDLLIMPORT int extra_float_digits;
/*
* Utility functions in float.c
*/
-pg_noreturn extern void float_overflow_error(void);
-pg_noreturn extern void float_underflow_error(void);
-pg_noreturn extern void float_zero_divide_error(void);
+extern void float_overflow_error(struct Node *escontext);
+extern void float_underflow_error(struct Node *escontext);
+extern void float_zero_divide_error(struct Node *escontext);
extern int is_infinite(float8 val);
extern float8 float8in_internal(char *num, char **endptr_p,
const char *type_name, const char *orig_string,
@@ -104,7 +104,22 @@ float4_pl(const float4 val1, const float4 val2)
result = val1 + val2;
if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error();
+ float_overflow_error(NULL);
+
+ return result;
+}
+
+static inline float8
+float8_pl_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+ float8 result;
+
+ result = val1 + val2;
+ if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
+ {
+ float_overflow_error(escontext);
+ return 0.0;
+ }
return result;
}
@@ -112,13 +127,7 @@ float4_pl(const float4 val1, const float4 val2)
static inline float8
float8_pl(const float8 val1, const float8 val2)
{
- float8 result;
-
- result = val1 + val2;
- if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error();
-
- return result;
+ return float8_pl_safe(val1, val2, NULL);
}
static inline float4
@@ -128,7 +137,22 @@ float4_mi(const float4 val1, const float4 val2)
result = val1 - val2;
if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error();
+ float_overflow_error(NULL);
+
+ return result;
+}
+
+static inline float8
+float8_mi_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+ float8 result;
+
+ result = val1 - val2;
+ if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
+ {
+ float_overflow_error(escontext);
+ return 0.0;
+ }
return result;
}
@@ -136,13 +160,7 @@ float4_mi(const float4 val1, const float4 val2)
static inline float8
float8_mi(const float8 val1, const float8 val2)
{
- float8 result;
-
- result = val1 - val2;
- if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error();
-
- return result;
+ return float8_mi_safe(val1, val2, NULL);
}
static inline float4
@@ -152,59 +170,89 @@ float4_mul(const float4 val1, const float4 val2)
result = val1 * val2;
if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0f) && val1 != 0.0f && val2 != 0.0f)
- float_underflow_error();
+ float_underflow_error(NULL);
return result;
}
static inline float8
-float8_mul(const float8 val1, const float8 val2)
+float8_mul_safe(const float8 val1, const float8 val2, struct Node *escontext)
{
float8 result;
result = val1 * val2;
if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error();
+ {
+ float_overflow_error(escontext);
+ return 0.0;
+ }
+
if (unlikely(result == 0.0) && val1 != 0.0 && val2 != 0.0)
- float_underflow_error();
+ {
+ float_underflow_error(escontext);
+ return 0.0;
+ }
return result;
}
+static inline float8
+float8_mul(const float8 val1, const float8 val2)
+{
+ return float8_mul_safe(val1, val2, NULL);
+}
+
static inline float4
float4_div(const float4 val1, const float4 val2)
{
float4 result;
if (unlikely(val2 == 0.0f) && !isnan(val1))
- float_zero_divide_error();
+ float_zero_divide_error(NULL);
result = val1 / val2;
if (unlikely(isinf(result)) && !isinf(val1))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0f) && val1 != 0.0f && !isinf(val2))
- float_underflow_error();
+ float_underflow_error(NULL);
return result;
}
static inline float8
-float8_div(const float8 val1, const float8 val2)
+float8_div_safe(const float8 val1, const float8 val2, struct Node *escontext)
{
float8 result;
if (unlikely(val2 == 0.0) && !isnan(val1))
- float_zero_divide_error();
+ {
+ float_zero_divide_error(escontext);
+ return 0.0;
+ }
+
result = val1 / val2;
if (unlikely(isinf(result)) && !isinf(val1))
- float_overflow_error();
+ {
+ float_overflow_error(escontext);
+ return 0.0;
+ }
+
if (unlikely(result == 0.0) && val1 != 0.0 && !isinf(val2))
- float_underflow_error();
+ {
+ float_underflow_error(escontext);
+ return 0.0;
+ }
return result;
}
+static inline float8
+float8_div(const float8 val1, const float8 val2)
+{
+ return float8_div_safe(val1, val2, NULL);
+}
+
/*
* Routines for NaN-aware comparisons
*
--
2.34.1
v14-0015-error-safe-for-casting-timestamptz-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v14-0015-error-safe-for-casting-timestamptz-to-other-types-per-pg_cast.patchDownload
From 094ab28fb8ecda149d642d5b6039c587c4252783 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Tue, 2 Dec 2025 12:04:40 +0800
Subject: [PATCH v14 15/19] error safe for casting timestamptz to other types
per pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
and pc.castfunc > 0 and (castsource::regtype ='timestamptz'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
--------------------------+-----------------------------+----------+-------------+------------+-----------------------+-------------
timestamp with time zone | date | 1178 | a | f | timestamptz_date | date
timestamp with time zone | time without time zone | 2019 | a | f | timestamptz_time | time
timestamp with time zone | timestamp without time zone | 2027 | a | f | timestamptz_timestamp | timestamp
timestamp with time zone | time with time zone | 1388 | a | f | timestamptz_timetz | timetz
timestamp with time zone | timestamp with time zone | 1967 | i | f | timestamptz_scale | timestamptz
(5 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 9 ++++++---
src/backend/utils/adt/timestamp.c | 10 ++++++++--
2 files changed, 14 insertions(+), 5 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 8fa336da250..bbc864a80cd 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -1406,7 +1406,10 @@ timestamptz_date(PG_FUNCTION_ARGS)
TimestampTz timestamp = PG_GETARG_TIMESTAMP(0);
DateADT result;
- result = timestamptz2date_safe(timestamp, NULL);
+ result = timestamptz2date_safe(timestamp, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->args))
+ PG_RETURN_NULL();
+
PG_RETURN_DATEADT(result);
}
@@ -2036,7 +2039,7 @@ timestamptz_time(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
@@ -2955,7 +2958,7 @@ timestamptz_timetz(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 1e8b859ff29..c144caf2458 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -875,7 +875,8 @@ timestamptz_scale(PG_FUNCTION_ARGS)
result = timestamp;
- AdjustTimestampForTypmod(&result, typmod, NULL);
+ if (!AdjustTimestampForTypmod(&result, typmod, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_TIMESTAMPTZ(result);
}
@@ -6494,8 +6495,13 @@ Datum
timestamptz_timestamp(PG_FUNCTION_ARGS)
{
TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(0);
+ Timestamp result;
- PG_RETURN_TIMESTAMP(timestamptz2timestamp(timestamp));
+ result = timestamptz2timestamp_safe(timestamp, fcinfo->context);
+ if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
+
+ PG_RETURN_TIMESTAMP(result);
}
/*
--
2.34.1
v14-0014-error-safe-for-casting-interval-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v14-0014-error-safe-for-casting-interval-to-other-types-per-pg_cast.patchDownload
From 531bbac679e8fab04b85c4e2e02561df434ffb78 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 18:43:29 +0800
Subject: [PATCH v14 14/19] error safe for casting interval to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'interval'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------------+----------+-------------+------------+----------------+----------
interval | time without time zone | 1419 | a | f | interval_time | time
interval | interval | 1200 | i | f | interval_scale | interval
(2 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 2 +-
src/backend/utils/adt/timestamp.c | 3 ++-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index cf241ea9794..8fa336da250 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2106,7 +2106,7 @@ interval_time(PG_FUNCTION_ARGS)
TimeADT result;
if (INTERVAL_NOT_FINITE(span))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("cannot convert infinite interval to time")));
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 2dc90a2b8a9..1e8b859ff29 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1334,7 +1334,8 @@ interval_scale(PG_FUNCTION_ARGS)
result = palloc(sizeof(Interval));
*result = *interval;
- AdjustIntervalForTypmod(result, typmod, NULL);
+ if (!AdjustIntervalForTypmod(result, typmod, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_INTERVAL_P(result);
}
--
2.34.1
v14-0013-error-safe-for-casting-date-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v14-0013-error-safe-for-casting-date-to-other-types-per-pg_cast.patchDownload
From 90210f038093872ee0cffd3ca1fec15a582b90d2 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Tue, 2 Dec 2025 13:15:32 +0800
Subject: [PATCH v14 13/19] error safe for casting date to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'date'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+-----------------------------+----------+-------------+------------+------------------+-------------
date | timestamp without time zone | 2024 | i | f | date_timestamp | timestamp
date | timestamp with time zone | 1174 | i | f | date_timestamptz | timestamptz
(2 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 17 ++++++-----------
1 file changed, 6 insertions(+), 11 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index c4b8125dd66..cf241ea9794 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -730,15 +730,6 @@ date2timestamptz_safe(DateADT dateVal, Node *escontext)
return result;
}
-/*
- * Promote date to timestamptz, throwing error for overflow.
- */
-static TimestampTz
-date2timestamptz(DateADT dateVal)
-{
- return date2timestamptz_safe(dateVal, NULL);
-}
-
/*
* date2timestamp_no_overflow
*
@@ -1323,7 +1314,9 @@ date_timestamp(PG_FUNCTION_ARGS)
DateADT dateVal = PG_GETARG_DATEADT(0);
Timestamp result;
- result = date2timestamp(dateVal);
+ result = date2timestamp_safe(dateVal, fcinfo->context);
+ if(SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_TIMESTAMP(result);
}
@@ -1396,7 +1389,9 @@ date_timestamptz(PG_FUNCTION_ARGS)
DateADT dateVal = PG_GETARG_DATEADT(0);
TimestampTz result;
- result = date2timestamptz(dateVal);
+ result = date2timestamptz_safe(dateVal, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_TIMESTAMP(result);
}
--
2.34.1
v14-0012-error-safe-for-casting-float8-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v14-0012-error-safe-for-casting-float8-to-other-types-per-pg_cast.patchDownload
From 83e48fc73bf383a079c70c6bc5c20d67a294fae7 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:31:53 +0800
Subject: [PATCH v14 12/19] error safe for casting float8 to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'float8'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------------+------------+----------+-------------+------------+----------------+---------
double precision | bigint | 483 | a | f | dtoi8 | int8
double precision | smallint | 237 | a | f | dtoi2 | int2
double precision | integer | 317 | a | f | dtoi4 | int4
double precision | real | 312 | a | f | dtof | float4
double precision | numeric | 1743 | a | f | float8_numeric | numeric
(5 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/float.c | 13 +++++++++----
src/backend/utils/adt/int8.c | 2 +-
src/backend/utils/adt/numeric.c | 3 ++-
3 files changed, 12 insertions(+), 6 deletions(-)
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index d5f15bfa7de..55c7030ba81 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -1199,9 +1199,14 @@ 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 +1229,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 +1254,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")));
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 68299e91512..7ff7f267b7e 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")));
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index c9d6669a7de..11c76ee40b5 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -4560,7 +4560,8 @@ float8_numeric(PG_FUNCTION_ARGS)
init_var(&result);
/* Assume we need not worry about leading/trailing spaces */
- (void) set_var_from_str(buf, buf, &result, &endptr, NULL);
+ if (!set_var_from_str(buf, buf, &result, &endptr, fcinfo->context))
+ PG_RETURN_NULL();
res = make_result(&result);
--
2.34.1
v14-0010-error-safe-for-casting-numeric-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v14-0010-error-safe-for-casting-numeric-to-other-types-per-pg_cast.patchDownload
From 74e8597ebb8c26ae15e6eecd8bafcaab0f6d584c Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:25:37 +0800
Subject: [PATCH v14 10/19] error safe for casting numeric to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'numeric'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+----------------+---------
numeric | bigint | 1779 | a | f | numeric_int8 | int8
numeric | smallint | 1783 | a | f | numeric_int2 | int2
numeric | integer | 1744 | a | f | numeric_int4 | int4
numeric | real | 1745 | i | f | numeric_float4 | float4
numeric | double precision | 1746 | i | f | numeric_float8 | float8
numeric | money | 3824 | a | f | numeric_cash | money
numeric | numeric | 1703 | i | f | numeric | numeric
(7 rows)
discussion: https://postgr.es/m/CACJufxHCMzrHOW=wRe8L30rMhB3sjwAv1LE928Fa7sxMu1Tx-g@mail.gmail.com
---
src/backend/utils/adt/numeric.c | 58 ++++++++++++++++++++++++---------
1 file changed, 43 insertions(+), 15 deletions(-)
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 1d626aecbe7..3e78cdf4ea0 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -1244,7 +1244,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));
}
@@ -1295,8 +1296,9 @@ numeric (PG_FUNCTION_ARGS)
init_var(&var);
set_var_from_num(num, &var);
- (void) apply_typmod(&var, typmod, NULL);
- new = make_result(&var);
+ if (!apply_typmod(&var, typmod, fcinfo->context))
+ PG_RETURN_NULL();
+ new = make_result_safe(&var, fcinfo->context);
free_var(&var);
@@ -3019,7 +3021,10 @@ numeric_mul(PG_FUNCTION_ARGS)
Numeric num2 = PG_GETARG_NUMERIC(1);
Numeric res;
- res = numeric_mul_safe(num1, num2, NULL);
+ res = numeric_mul_safe(num1, num2, fcinfo->context);
+
+ if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
PG_RETURN_NUMERIC(res);
}
@@ -4393,9 +4398,15 @@ numeric_int4_safe(Numeric num, Node *escontext)
Datum
numeric_int4(PG_FUNCTION_ARGS)
{
+ int32 result;
Numeric num = PG_GETARG_NUMERIC(0);
- PG_RETURN_INT32(numeric_int4_safe(num, NULL));
+ result = numeric_int4_safe(num, fcinfo->context);
+
+ if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
+
+ PG_RETURN_INT32(result);
}
/*
@@ -4463,9 +4474,15 @@ numeric_int8_safe(Numeric num, Node *escontext)
Datum
numeric_int8(PG_FUNCTION_ARGS)
{
+ int64 result;
Numeric num = PG_GETARG_NUMERIC(0);
- PG_RETURN_INT64(numeric_int8_safe(num, NULL));
+ result = numeric_int8_safe(num, fcinfo->context);
+
+ if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
+
+ PG_RETURN_INT64(result);
}
@@ -4489,11 +4506,11 @@ numeric_int2(PG_FUNCTION_ARGS)
if (NUMERIC_IS_SPECIAL(num))
{
if (NUMERIC_IS_NAN(num))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert NaN to %s", "smallint")));
else
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert infinity to %s", "smallint")));
}
@@ -4502,12 +4519,12 @@ numeric_int2(PG_FUNCTION_ARGS)
init_var_from_num(num, &x);
if (!numericvar_to_int64(&x, &val))
- ereport(ERROR,
+ ereturn(fcinfo->context, (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(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
@@ -4572,10 +4589,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);
}
@@ -4667,7 +4688,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
v14-0011-error-safe-for-casting-float4-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v14-0011-error-safe-for-casting-float4-to-other-types-per-pg_cast.patchDownload
From 6c4cd958d9b95280d95d99c1d539c0bec9ce138f Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:28:20 +0800
Subject: [PATCH v14 11/19] error safe for casting float4 to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'float4'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+----------------+---------
real | bigint | 653 | a | f | ftoi8 | int8
real | smallint | 238 | a | f | ftoi2 | int2
real | integer | 319 | a | f | ftoi4 | int4
real | double precision | 311 | i | f | ftod | float8
real | numeric | 1742 | a | f | float4_numeric | numeric
(5 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/float.c | 4 ++--
src/backend/utils/adt/int8.c | 2 +-
src/backend/utils/adt/numeric.c | 3 ++-
3 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index 849639fda9f..d5f15bfa7de 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -1298,7 +1298,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 +1323,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 d5631f35465..68299e91512 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -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 3e78cdf4ea0..c9d6669a7de 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -4658,7 +4658,8 @@ float4_numeric(PG_FUNCTION_ARGS)
init_var(&result);
/* Assume we need not worry about leading/trailing spaces */
- (void) set_var_from_str(buf, buf, &result, &endptr, NULL);
+ if (!set_var_from_str(buf, buf, &result, &endptr, fcinfo->context))
+ PG_RETURN_NULL();
res = make_result(&result);
--
2.34.1
v14-0009-error-safe-for-casting-bigint-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v14-0009-error-safe-for-casting-bigint-to-other-types-per-pg_cast.patchDownload
From dd9e831d32adbcfde1b8a4ddec7da1d5974d7341 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:22:00 +0800
Subject: [PATCH v14 09/19] error safe for casting bigint to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'bigint'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+--------------+---------
bigint | smallint | 714 | a | f | int82 | int2
bigint | integer | 480 | a | f | int84 | int4
bigint | real | 652 | i | f | i8tof | float4
bigint | double precision | 482 | i | f | i8tod | float8
bigint | numeric | 1781 | i | f | int8_numeric | numeric
bigint | money | 3812 | a | f | int8_cash | money
bigint | oid | 1287 | i | f | i8tooid | oid
bigint | regproc | 1287 | i | f | i8tooid | oid
bigint | regprocedure | 1287 | i | f | i8tooid | oid
bigint | regoper | 1287 | i | f | i8tooid | oid
bigint | regoperator | 1287 | i | f | i8tooid | oid
bigint | regclass | 1287 | i | f | i8tooid | oid
bigint | regcollation | 1287 | i | f | i8tooid | oid
bigint | regtype | 1287 | i | f | i8tooid | oid
bigint | regconfig | 1287 | i | f | i8tooid | oid
bigint | regdictionary | 1287 | i | f | i8tooid | oid
bigint | regrole | 1287 | i | f | i8tooid | oid
bigint | regnamespace | 1287 | i | f | i8tooid | oid
bigint | regdatabase | 1287 | i | f | i8tooid | oid
bigint | bytea | 6369 | e | f | int8_bytea | bytea
bigint | bit | 2075 | e | f | bitfromint8 | bit
(21 rows)
already error safe: i8tof, i8tod, int8_numeric, int8_bytea, bitfromint8
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/int8.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 9cd420b4b9d..d5631f35465 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -1251,7 +1251,7 @@ int84(PG_FUNCTION_ARGS)
int64 arg = PG_GETARG_INT64(0);
if (unlikely(arg < PG_INT32_MIN) || unlikely(arg > PG_INT32_MAX))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
@@ -1272,7 +1272,7 @@ int82(PG_FUNCTION_ARGS)
int64 arg = PG_GETARG_INT64(0);
if (unlikely(arg < PG_INT16_MIN) || unlikely(arg > PG_INT16_MAX))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
@@ -1355,7 +1355,7 @@ i8tooid(PG_FUNCTION_ARGS)
int64 arg = PG_GETARG_INT64(0);
if (unlikely(arg < 0) || unlikely(arg > PG_UINT32_MAX))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("OID out of range")));
--
2.34.1
v14-0008-error-safe-for-casting-integer-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v14-0008-error-safe-for-casting-integer-to-other-types-per-pg_cast.patchDownload
From f821f985d05320bb0e07328e842e909a728a13b5 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:20:10 +0800
Subject: [PATCH v14 08/19] error safe for casting integer to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'integer'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+--------------+---------
integer | bigint | 481 | i | f | int48 | int8
integer | smallint | 314 | a | f | i4toi2 | int2
integer | real | 318 | i | f | i4tof | float4
integer | double precision | 316 | i | f | i4tod | float8
integer | numeric | 1740 | i | f | int4_numeric | numeric
integer | money | 3811 | a | f | int4_cash | money
integer | boolean | 2557 | e | f | int4_bool | bool
integer | bytea | 6368 | e | f | int4_bytea | bytea
integer | "char" | 78 | e | f | i4tochar | char
integer | bit | 1683 | e | f | bitfromint4 | bit
(10 rows)
only int4_cash, i4toi2, i4tochar need take care of error handling. but support
for cash data type is not easy, so only i4toi2, i4tochar function refactoring.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/char.c | 2 +-
src/backend/utils/adt/int.c | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/backend/utils/adt/char.c b/src/backend/utils/adt/char.c
index 22dbfc950b1..e90844a29f0 100644
--- a/src/backend/utils/adt/char.c
+++ b/src/backend/utils/adt/char.c
@@ -192,7 +192,7 @@ i4tochar(PG_FUNCTION_ARGS)
int32 arg1 = PG_GETARG_INT32(0);
if (arg1 < SCHAR_MIN || arg1 > SCHAR_MAX)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("\"char\" out of range")));
diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c
index b5781989a64..b45599d402d 100644
--- a/src/backend/utils/adt/int.c
+++ b/src/backend/utils/adt/int.c
@@ -350,7 +350,7 @@ i4toi2(PG_FUNCTION_ARGS)
int32 arg1 = PG_GETARG_INT32(0);
if (unlikely(arg1 < SHRT_MIN) || unlikely(arg1 > SHRT_MAX))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
--
2.34.1
v14-0007-error-safe-for-casting-macaddr8-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v14-0007-error-safe-for-casting-macaddr8-to-other-types-per-pg_cast.patchDownload
From 32a42d09a153a0ded3c76bf5dfeffc4bfbee9f40 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:17:11 +0800
Subject: [PATCH v14 07/19] error safe for casting macaddr8 to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
and pc.castfunc > 0 and castsource::regtype ='macaddr8'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------+----------+-------------+------------+-------------------+---------
macaddr8 | macaddr | 4124 | i | f | macaddr8tomacaddr | macaddr
(1 row)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/mac8.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/backend/utils/adt/mac8.c b/src/backend/utils/adt/mac8.c
index 08e41ba4eea..1c903f152de 100644
--- a/src/backend/utils/adt/mac8.c
+++ b/src/backend/utils/adt/mac8.c
@@ -550,7 +550,7 @@ macaddr8tomacaddr(PG_FUNCTION_ARGS)
result = (macaddr *) palloc0(sizeof(macaddr));
if ((addr->d != 0xFF) || (addr->e != 0xFE))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("macaddr8 data out of range to convert to macaddr"),
errhint("Only addresses that have FF and FE as values in the "
--
2.34.1
v14-0005-error-safe-for-casting-character-varying-to-other-types-per-pg_c.patchtext/x-patch; charset=US-ASCII; name=v14-0005-error-safe-for-casting-character-varying-to-other-types-per-pg_c.patchDownload
From b941b0cf4c0394d8da0c17a8d2cc19bfe43b5164 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:13:45 +0800
Subject: [PATCH v14 05/19] error safe for casting character varying to other
types per pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc and pc.castfunc > 0 and
(castsource::regtype = 'character varying'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
-------------------+-------------------+----------+-------------+------------+---------------+----------
character varying | regclass | 1079 | i | f | text_regclass | regclass
character varying | "char" | 944 | a | f | text_char | char
character varying | name | 1400 | i | f | text_name | name
character varying | xml | 2896 | e | f | texttoxml | xml
character varying | character varying | 669 | i | f | varchar | varchar
(5 rows)
texttoxml, text_regclass was refactored as error safe in prior patch.
text_char, text_name is already error safe.
so here we only need handle function "varchar".
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/varchar.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c
index 5cb5c8c46f9..08f1bf5a24d 100644
--- a/src/backend/utils/adt/varchar.c
+++ b/src/backend/utils/adt/varchar.c
@@ -634,7 +634,7 @@ varchar(PG_FUNCTION_ARGS)
{
for (i = maxmblen; i < len; i++)
if (s_data[i] != ' ')
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
errmsg("value too long for type character varying(%d)",
maxlen)));
--
2.34.1
v14-0006-error-safe-for-casting-inet-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v14-0006-error-safe-for-casting-inet-to-other-types-per-pg_cast.patchDownload
From 17bd7516d9b51d8a88227b8d3de48c15b52c8a93 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 10:28:54 +0800
Subject: [PATCH v14 06/19] error safe for casting inet to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'inet'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+-------------------+----------+-------------+------------+--------------+---------
inet | cidr | 1715 | a | f | inet_to_cidr | cidr
inet | text | 730 | a | f | network_show | text
inet | character varying | 730 | a | f | network_show | text
inet | character | 730 | a | f | network_show | text
(4 rows)
inet_to_cidr is already error safe.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/network.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/backend/utils/adt/network.c b/src/backend/utils/adt/network.c
index 3cb0ab6829a..648c8d95f51 100644
--- a/src/backend/utils/adt/network.c
+++ b/src/backend/utils/adt/network.c
@@ -1137,7 +1137,7 @@ network_show(PG_FUNCTION_ARGS)
if (pg_inet_net_ntop(ip_family(ip), ip_addr(ip), ip_maxbits(ip),
tmp, sizeof(tmp)) == NULL)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
errmsg("could not format inet value: %m")));
--
2.34.1
v14-0004-error-safe-for-casting-character-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v14-0004-error-safe-for-casting-character-to-other-types-per-pg_cast.patchDownload
From 438d1e598bba5427813381976d5d12cdf2fdc108 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Tue, 25 Nov 2025 20:07:28 +0800
Subject: [PATCH v14 04/19] error safe for casting character to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype ='character'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+-------------------+----------+-------------+------------+-------------+---------
character | text | 401 | i | f | rtrim1 | text
character | character varying | 401 | i | f | rtrim1 | text
character | "char" | 944 | a | f | text_char | char
character | name | 409 | i | f | bpchar_name | name
character | xml | 2896 | e | f | texttoxml | xml
character | character | 668 | i | f | bpchar | bpchar
(6 rows)
only texttoxml, bpchar(PG_FUNCTION_ARGS) need take care of error handling.
other functions already error safe.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/catalog/namespace.c | 58 ++++++++++++++++++++++++++-------
src/backend/utils/adt/regproc.c | 13 ++++++--
src/backend/utils/adt/varlena.c | 10 ++++--
src/backend/utils/adt/xml.c | 2 +-
src/include/catalog/namespace.h | 6 ++++
src/include/utils/varlena.h | 1 +
6 files changed, 73 insertions(+), 17 deletions(-)
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index d23474da4fb..bef2de5dd39 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -440,6 +440,16 @@ Oid
RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
uint32 flags,
RangeVarGetRelidCallback callback, void *callback_arg)
+{
+ return RangeVarGetRelidExtendedSafe(relation, lockmode, flags,
+ callback, callback_arg,
+ NULL);
+}
+
+Oid
+RangeVarGetRelidExtendedSafe(const RangeVar *relation, LOCKMODE lockmode, uint32 flags,
+ RangeVarGetRelidCallback callback, void *callback_arg,
+ Node *escontext)
{
uint64 inval_count;
Oid relId;
@@ -456,7 +466,7 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
if (relation->catalogname)
{
if (strcmp(relation->catalogname, get_database_name(MyDatabaseId)) != 0)
- ereport(ERROR,
+ ereturn(escontext, InvalidOid,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cross-database references are not implemented: \"%s.%s.%s\"",
relation->catalogname, relation->schemaname,
@@ -513,7 +523,7 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
* return InvalidOid.
*/
if (namespaceId != myTempNamespace)
- ereport(ERROR,
+ ereturn(escontext, InvalidOid,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("temporary tables cannot specify a schema name")));
}
@@ -593,13 +603,23 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
{
int elevel = (flags & RVR_SKIP_LOCKED) ? DEBUG1 : ERROR;
- if (relation->schemaname)
- ereport(elevel,
+ if (relation->schemaname && elevel == DEBUG1)
+ ereport(DEBUG1,
(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
errmsg("could not obtain lock on relation \"%s.%s\"",
relation->schemaname, relation->relname)));
- else
- ereport(elevel,
+ else if (relation->schemaname && elevel == ERROR)
+ ereturn(escontext, InvalidOid,
+ errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s.%s\"",
+ relation->schemaname, relation->relname));
+ else if (elevel == DEBUG1)
+ ereport(DEBUG1,
+ errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s\"",
+ relation->relname));
+ else if (elevel == ERROR)
+ ereturn(escontext, InvalidOid,
(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
errmsg("could not obtain lock on relation \"%s\"",
relation->relname)));
@@ -626,13 +646,23 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
{
int elevel = missing_ok ? DEBUG1 : ERROR;
- if (relation->schemaname)
- ereport(elevel,
+ if (relation->schemaname && elevel == DEBUG1)
+ ereport(DEBUG1,
(errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("relation \"%s.%s\" does not exist",
relation->schemaname, relation->relname)));
- else
- ereport(elevel,
+ else if (relation->schemaname && elevel == ERROR)
+ ereturn(escontext, InvalidOid,
+ errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s.%s\" does not exist",
+ relation->schemaname, relation->relname));
+ else if (elevel == DEBUG1)
+ ereport(DEBUG1,
+ errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" does not exist",
+ relation->relname));
+ else if (elevel == ERROR)
+ ereturn(escontext, InvalidOid,
(errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("relation \"%s\" does not exist",
relation->relname)));
@@ -3622,6 +3652,12 @@ get_namespace_oid(const char *nspname, bool missing_ok)
*/
RangeVar *
makeRangeVarFromNameList(const List *names)
+{
+ return makeRangeVarFromNameListSafe(names, NULL);
+}
+
+RangeVar *
+makeRangeVarFromNameListSafe(const List *names, Node *escontext)
{
RangeVar *rel = makeRangeVar(NULL, NULL, -1);
@@ -3640,7 +3676,7 @@ makeRangeVarFromNameList(const List *names)
rel->relname = strVal(lthird(names));
break;
default:
- ereport(ERROR,
+ ereturn(escontext, NULL,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("improper relation name (too many dotted names): %s",
NameListToString(names))));
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index e5c2246f2c9..59cc508f805 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -1901,12 +1901,19 @@ text_regclass(PG_FUNCTION_ARGS)
text *relname = PG_GETARG_TEXT_PP(0);
Oid result;
RangeVar *rv;
+ List *namelist;
- rv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
+ namelist = textToQualifiedNameListSafe(relname, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
+
+ rv = makeRangeVarFromNameListSafe(namelist, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
/* We might not even have permissions on this relation; don't lock it. */
- result = RangeVarGetRelid(rv, NoLock, false);
-
+ result = RangeVarGetRelidExtendedSafe(rv, NoLock, 0, NULL, NULL,
+ fcinfo->context);
PG_RETURN_OID(result);
}
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index f202b8df4e2..c4b8f61f5d6 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -2695,6 +2695,12 @@ name_text(PG_FUNCTION_ARGS)
*/
List *
textToQualifiedNameList(text *textval)
+{
+ return textToQualifiedNameListSafe(textval, NULL);
+}
+
+List *
+textToQualifiedNameListSafe(text *textval, Node *escontext)
{
char *rawname;
List *result = NIL;
@@ -2706,12 +2712,12 @@ textToQualifiedNameList(text *textval)
rawname = text_to_cstring(textval);
if (!SplitIdentifierString(rawname, '.', &namelist))
- ereport(ERROR,
+ ereturn(escontext, NIL,
(errcode(ERRCODE_INVALID_NAME),
errmsg("invalid name syntax")));
if (namelist == NIL)
- ereport(ERROR,
+ ereturn(escontext, NIL,
(errcode(ERRCODE_INVALID_NAME),
errmsg("invalid name syntax")));
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 9e8016456ce..8de1f1fc741 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -1043,7 +1043,7 @@ xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, Node
return (xmltype *) data;
#else
- ereturn(escontext, NULL
+ ereturn(escontext, NULL,
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("unsupported XML feature"),
errdetail("This functionality requires the server to be built with libxml support."));
diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h
index f1423f28c32..ab61af55ddc 100644
--- a/src/include/catalog/namespace.h
+++ b/src/include/catalog/namespace.h
@@ -103,6 +103,11 @@ extern Oid RangeVarGetRelidExtended(const RangeVar *relation,
LOCKMODE lockmode, uint32 flags,
RangeVarGetRelidCallback callback,
void *callback_arg);
+extern Oid RangeVarGetRelidExtendedSafe(const RangeVar *relation,
+ LOCKMODE lockmode, uint32 flags,
+ RangeVarGetRelidCallback callback,
+ void *callback_arg,
+ Node *escontext);
extern Oid RangeVarGetCreationNamespace(const RangeVar *newRelation);
extern Oid RangeVarGetAndCheckCreationNamespace(RangeVar *relation,
LOCKMODE lockmode,
@@ -168,6 +173,7 @@ extern Oid LookupCreationNamespace(const char *nspname);
extern void CheckSetNamespace(Oid oldNspOid, Oid nspOid);
extern Oid QualifiedNameGetCreationNamespace(const List *names, char **objname_p);
extern RangeVar *makeRangeVarFromNameList(const List *names);
+extern RangeVar *makeRangeVarFromNameListSafe(const List *names, Node *escontext);
extern char *NameListToString(const List *names);
extern char *NameListToQuotedString(const List *names);
diff --git a/src/include/utils/varlena.h b/src/include/utils/varlena.h
index db9fdf72941..0cf01ae5281 100644
--- a/src/include/utils/varlena.h
+++ b/src/include/utils/varlena.h
@@ -27,6 +27,7 @@ extern int varstr_levenshtein_less_equal(const char *source, int slen,
int ins_c, int del_c, int sub_c,
int max_d, bool trusted);
extern List *textToQualifiedNameList(text *textval);
+extern List *textToQualifiedNameListSafe(text *textval, Node *escontext);
extern bool SplitIdentifierString(char *rawstring, char separator,
List **namelist);
extern bool SplitDirectoriesString(char *rawstring, char separator,
--
2.34.1
v14-0003-error-safe-for-casting-character-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v14-0003-error-safe-for-casting-character-to-other-types-per-pg_cast.patchDownload
From 6835b9c6833b353f764f1d68e67e0692eb9cb3c1 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Mon, 24 Nov 2025 12:52:16 +0800
Subject: [PATCH v14 03/19] error safe for casting character to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype ='character'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+-------------------+----------+-------------+------------+-------------+---------
character | text | 401 | i | f | rtrim1 | text
character | character varying | 401 | i | f | rtrim1 | text
character | "char" | 944 | a | f | text_char | char
character | name | 409 | i | f | bpchar_name | name
character | xml | 2896 | e | f | texttoxml | xml
character | character | 668 | i | f | bpchar | bpchar
(6 rows)
only texttoxml, bpchar(PG_FUNCTION_ARGS) need take care of error handling.
other functions already error safe.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/executor/execExprInterp.c | 2 +-
src/backend/utils/adt/varchar.c | 2 +-
src/backend/utils/adt/xml.c | 18 ++++++++++++------
src/include/utils/xml.h | 2 +-
4 files changed, 15 insertions(+), 9 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 5e7bd933afc..1d88cdd2cb4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -4542,7 +4542,7 @@ ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op)
*op->resvalue = PointerGetDatum(xmlparse(data,
xexpr->xmloption,
- preserve_whitespace));
+ preserve_whitespace, NULL));
*op->resnull = false;
}
break;
diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c
index 3f40c9da1a0..5cb5c8c46f9 100644
--- a/src/backend/utils/adt/varchar.c
+++ b/src/backend/utils/adt/varchar.c
@@ -307,7 +307,7 @@ bpchar(PG_FUNCTION_ARGS)
{
for (i = maxmblen; i < len; i++)
if (s[i] != ' ')
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
errmsg("value too long for type character(%d)",
maxlen)));
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 41e775570ec..9e8016456ce 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -659,7 +659,7 @@ texttoxml(PG_FUNCTION_ARGS)
{
text *data = PG_GETARG_TEXT_PP(0);
- PG_RETURN_XML_P(xmlparse(data, xmloption, true));
+ PG_RETURN_XML_P(xmlparse(data, xmloption, true, fcinfo->context));
}
@@ -1028,19 +1028,25 @@ xmlelement(XmlExpr *xexpr,
xmltype *
-xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace)
+xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, Node *escontext)
{
#ifdef USE_LIBXML
xmlDocPtr doc;
doc = xml_parse(data, xmloption_arg, preserve_whitespace,
- GetDatabaseEncoding(), NULL, NULL, NULL);
- xmlFreeDoc(doc);
+ GetDatabaseEncoding(), NULL, NULL, escontext);
+ if (doc)
+ xmlFreeDoc(doc);
+
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return NULL;
return (xmltype *) data;
#else
- NO_XML_SUPPORT();
- return NULL;
+ ereturn(escontext, NULL
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unsupported XML feature"),
+ errdetail("This functionality requires the server to be built with libxml support."));
#endif
}
diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h
index 732dac47bc4..b15168c430e 100644
--- a/src/include/utils/xml.h
+++ b/src/include/utils/xml.h
@@ -73,7 +73,7 @@ extern xmltype *xmlconcat(List *args);
extern xmltype *xmlelement(XmlExpr *xexpr,
const Datum *named_argvalue, const bool *named_argnull,
const Datum *argvalue, const bool *argnull);
-extern xmltype *xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace);
+extern xmltype *xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, Node *escontext);
extern xmltype *xmlpi(const char *target, text *arg, bool arg_is_null, bool *result_is_null);
extern xmltype *xmlroot(xmltype *data, text *version, int standalone);
extern bool xml_is_document(xmltype *arg);
--
2.34.1
v14-0001-error-safe-for-casting-bytea-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v14-0001-error-safe-for-casting-bytea-to-other-types-per-pg_cast.patchDownload
From ca1c431c9e88f19c8badbf97d506a13f0a5c12b0 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:08:00 +0800
Subject: [PATCH v14 01/19] error safe for casting bytea to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc and pc.castfunc > 0 and castsource::regtype ='bytea'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------+----------+-------------+------------+------------+---------
bytea | smallint | 6370 | e | f | bytea_int2 | int2
bytea | integer | 6371 | e | f | bytea_int4 | int4
bytea | bigint | 6372 | e | f | bytea_int8 | int8
(3 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/bytea.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/backend/utils/adt/bytea.c b/src/backend/utils/adt/bytea.c
index 6e7b914c563..f43ad6b4aff 100644
--- a/src/backend/utils/adt/bytea.c
+++ b/src/backend/utils/adt/bytea.c
@@ -1027,7 +1027,7 @@ bytea_int2(PG_FUNCTION_ARGS)
/* Check that the byte array is not too long */
if (len > sizeof(result))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range"));
@@ -1052,7 +1052,7 @@ bytea_int4(PG_FUNCTION_ARGS)
/* Check that the byte array is not too long */
if (len > sizeof(result))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range"));
@@ -1077,7 +1077,7 @@ bytea_int8(PG_FUNCTION_ARGS)
/* Check that the byte array is not too long */
if (len > sizeof(result))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("bigint out of range"));
--
2.34.1
v14-0002-error-safe-for-casting-bit-varbit-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v14-0002-error-safe-for-casting-bit-varbit-to-other-types-per-pg_cast.patchDownload
From 177f97dc0078adebc0a10d50cd88b3fa1557c64e Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 10:33:08 +0800
Subject: [PATCH v14 02/19] error safe for casting bit/varbit to other types
per pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
where pc.castfunc > 0 and (castsource::regtype ='bit'::regtype or
castsource::regtype ='varbit'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
-------------+-------------+----------+-------------+------------+-----------+---------
bit | bigint | 2076 | e | f | bittoint8 | int8
bit | integer | 1684 | e | f | bittoint4 | int4
bit | bit | 1685 | i | f | bit | bit
bit varying | bit varying | 1687 | i | f | varbit | varbit
(4 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/varbit.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/backend/utils/adt/varbit.c b/src/backend/utils/adt/varbit.c
index 205a67dafc5..6e9b808e20a 100644
--- a/src/backend/utils/adt/varbit.c
+++ b/src/backend/utils/adt/varbit.c
@@ -401,7 +401,7 @@ bit(PG_FUNCTION_ARGS)
PG_RETURN_VARBIT_P(arg);
if (!isExplicit)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_STRING_DATA_LENGTH_MISMATCH),
errmsg("bit string length %d does not match type bit(%d)",
VARBITLEN(arg), len)));
@@ -752,7 +752,7 @@ varbit(PG_FUNCTION_ARGS)
PG_RETURN_VARBIT_P(arg);
if (!isExplicit)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
errmsg("bit string too long for type bit varying(%d)",
len)));
@@ -1591,7 +1591,7 @@ bittoint4(PG_FUNCTION_ARGS)
/* Check that the bit string is not too long */
if (VARBITLEN(arg) > sizeof(result) * BITS_PER_BYTE)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
@@ -1671,7 +1671,7 @@ bittoint8(PG_FUNCTION_ARGS)
/* Check that the bit string is not too long */
if (VARBITLEN(arg) > sizeof(result) * BITS_PER_BYTE)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("bigint out of range")));
--
2.34.1
On Mon, Dec 1, 2025 at 1:41 PM Corey Huinker <corey.huinker@gmail.com> wrote:
I'm fine with it. I can see having 'f' and 's' both mean cast functions, but 's' means safe, but the extra boolean works too and we'll be fine with either method.
I can work on this part if you don't have time.
Do you mean change pg_cast.casterrorsafe from boolean to char?
No, I meant implementing the syntax for being able to declare a custom CAST function as safe (or not). Basically adding the [SAFE] to
CREATE CAST (source_type AS target_type)
WITH [SAFE] FUNCTION function_name [ (argument_type [, ...]) ]I'm not tied to this syntax choice, but this one seemed the most obvious and least invasive.
hi.
In v14, I have removed pg_cast.casterrorsafe, but for user-defined CREATE CAST,
castfunc can be built-in function or user-defined function, we do need a way to
distinguish if the cast function is error safe or not.
I’ll incorporate pg_cast.casterrorsafe along with the implementation of the
user-defined CREATE CAST syntax into one patch.
But this brings up an interesting point: if a cast is declared as WITHOUT FUNCTION aka COERCION_METHOD_BINARY, then the cast can never fail, and we should probably check for that because a cast that cannot fail can ignore the DEFAULT clause altogether and fall back to being an ordinary CAST().
integer and oid are binary coercible, but the following should fail.
SELECT CAST(11 as oid DEFAULT 'a' ON CONVERSION ERROR);
if you mean that skip
+ ExecInitExprRec((Expr *) stcstate->stcexpr->default_expr,
+ state, resv, resnull);
For binary-coercible types, this approach seems fine. We’ve done something
similar in ExecInitExprRec for T_ArrayCoerceExpr.
```
if (elemstate->steps_len == 1 &&
elemstate->steps[0].opcode == EEOP_CASE_TESTVAL)
{
/* Trivial, so we need no per-element work at runtime */
elemstate = NULL;
}
```
in V14, I didn't do this part, I'll keep this in mind.
On Tue, Dec 9, 2025 at 11:39 AM jian he <jian.universality@gmail.com> wrote:
On Mon, Dec 1, 2025 at 1:41 PM Corey Huinker <corey.huinker@gmail.com> wrote:
No, I meant implementing the syntax for being able to declare a custom CAST function as safe (or not). Basically adding the [SAFE] to
CREATE CAST (source_type AS target_type)
WITH [SAFE] FUNCTION function_name [ (argument_type [, ...]) ]I'm not tied to this syntax choice, but this one seemed the most obvious and least invasive.
hi.
please see the attached v15.
the primary implementation of CAST DEFAULT is contained in V15-0021.
changes compared to v14.
1. separate patch (v15-0017) for float error.
-pg_noreturn extern void float_overflow_error(void);
-pg_noreturn extern void float_underflow_error(void);
-pg_noreturn extern void float_zero_divide_error(void);
+extern void float_overflow_error(struct Node *escontext);
+extern void float_underflow_error(struct Node *escontext);
+extern void float_zero_divide_error(struct Node *escontext);
2. separate patch (v15-0018) for newly added float8 functions:
float8_pl_safe
float8_mi_safe
float8_mul_safe
float8_div_safe
refactoring existing functions is too invasive, I choose not to.
3. refactor point_dt (v15-0019). This is necessary for making geometry data type
error-safe, separate from the main patch (v15-0020). I hope to make it easier to
review.
-static inline float8 point_dt(Point *pt1, Point *pt2);
+static inline float8 point_dt(Point *pt1, Point *pt2, Node *escontext);
4. skip compile DEFAULT expression (ExecInitExprRec) for binary coercion cast,
as mentioned before. See ExecInitSafeTypeCastExpr.
5. Support user-defined type cast error-safe, see v15-0022.
user-defined error-safe cast syntax:
CREATE CAST (source_type AS target_type)
WITH [SAFE] FUNCTION function_name [ (argument_type [, ...]) ]
[ AS ASSIGNMENT | AS IMPLICIT ]
this only adds a new keyword SAFE.
This works for C and internal language functions only now.
To make it really usable, I have made citext, hstore module castfunc error safe.
A new column: pg_cast.casterrorsafe was added, this is needed for
CREATE CAST WITH SAFE FUNCTION.
+select CAST(ARRAY['a','g','b','h',null,'i'] AS hstore
+ DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
6. slightly polished the doc.
Attachments:
v15-0022-error-safe-for-user-defined-CREATE-CAST.patchtext/x-patch; charset=US-ASCII; name=v15-0022-error-safe-for-user-defined-CREATE-CAST.patchDownload
From 99d299237edcb65151a94b7ab17497dad6d7b778 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Wed, 10 Dec 2025 16:15:37 +0800
Subject: [PATCH v15 22/22] error safe for user defined CREATE CAST
pg_cast.casterrorsafe column to indicate castfunc is error safe or not.
change src/include/catalog/pg_cast.dat to indicate that most of the system cast
function support soft error evaluation.
The SAFE keyword is introduced for allow user-defined CREATE CAST can also be
evaluated in soft-error. now the synopsis of CREATE CAST is:
CREATE CAST (source_type AS target_type)
WITH [SAFE] FUNCTION function_name [ (argument_type [, ...]) ]
[ AS ASSIGNMENT | AS IMPLICIT ]
The following cast in citext, hstore module refactored to error safe:
CAST (bpchar AS citext)
CAST (boolean AS citext)
CAST (inet AS citext)
CAST (text[] AS hstore)
CAST (hstore AS json)
CAST (hstore AS jsonb)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
contrib/citext/citext--1.4.sql | 6 +-
contrib/citext/expected/citext.out | 24 +-
contrib/citext/expected/citext_1.out | 24 +-
contrib/citext/sql/citext.sql | 5 +-
contrib/hstore/expected/hstore.out | 37 +++
contrib/hstore/hstore--1.2--1.3.sql | 2 +-
contrib/hstore/hstore--1.4.sql | 6 +-
contrib/hstore/hstore_io.c | 8 +-
contrib/hstore/sql/hstore.sql | 11 +
doc/src/sgml/catalogs.sgml | 15 +
doc/src/sgml/ref/create_cast.sgml | 14 +-
doc/src/sgml/syntax.sgml | 3 +-
src/backend/catalog/pg_cast.c | 4 +-
src/backend/commands/functioncmds.c | 9 +-
src/backend/commands/typecmds.c | 1 +
src/backend/parser/gram.y | 18 +-
src/backend/parser/parse_expr.c | 10 +-
src/include/catalog/pg_cast.dat | 330 +++++++++++-----------
src/include/catalog/pg_cast.h | 5 +
src/include/nodes/parsenodes.h | 1 +
src/include/parser/kwlist.h | 1 +
src/test/regress/expected/create_cast.out | 8 +-
src/test/regress/expected/opr_sanity.out | 24 +-
src/test/regress/sql/create_cast.sql | 5 +
24 files changed, 353 insertions(+), 218 deletions(-)
diff --git a/contrib/citext/citext--1.4.sql b/contrib/citext/citext--1.4.sql
index 7b061989352..5c87820388f 100644
--- a/contrib/citext/citext--1.4.sql
+++ b/contrib/citext/citext--1.4.sql
@@ -85,9 +85,9 @@ CREATE CAST (citext AS varchar) WITHOUT FUNCTION AS IMPLICIT;
CREATE CAST (citext AS bpchar) WITHOUT FUNCTION AS ASSIGNMENT;
CREATE CAST (text AS citext) WITHOUT FUNCTION AS ASSIGNMENT;
CREATE CAST (varchar AS citext) WITHOUT FUNCTION AS ASSIGNMENT;
-CREATE CAST (bpchar AS citext) WITH FUNCTION citext(bpchar) AS ASSIGNMENT;
-CREATE CAST (boolean AS citext) WITH FUNCTION citext(boolean) AS ASSIGNMENT;
-CREATE CAST (inet AS citext) WITH FUNCTION citext(inet) AS ASSIGNMENT;
+CREATE CAST (bpchar AS citext) WITH SAFE FUNCTION citext(bpchar) AS ASSIGNMENT;
+CREATE CAST (boolean AS citext) WITH SAFE FUNCTION citext(boolean) AS ASSIGNMENT;
+CREATE CAST (inet AS citext) WITH SAFE FUNCTION citext(inet) AS ASSIGNMENT;
--
-- Operator Functions.
diff --git a/contrib/citext/expected/citext.out b/contrib/citext/expected/citext.out
index 33da19d8df4..be328715492 100644
--- a/contrib/citext/expected/citext.out
+++ b/contrib/citext/expected/citext.out
@@ -10,11 +10,12 @@ WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid);
--------+---------
(0 rows)
-SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR); --error
-ERROR: cannot cast type character to citext when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
-LINE 1: SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSI...
- ^
-HINT: Safe type cast for user-defined types are not yet supported
+SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR);
+ citext
+--------
+ abc
+(1 row)
+
-- Test the operators and indexing functions
-- Test = and <>.
SELECT 'a'::citext = 'a'::citext AS t;
@@ -523,6 +524,12 @@ SELECT true::citext = 'true' AS t;
t
(1 row)
+SELECT CAST(true AS citext DEFAULT NULL ON CONVERSION ERROR);
+ citext
+--------
+ true
+(1 row)
+
SELECT 'true'::citext::boolean = true AS t;
t
---
@@ -787,6 +794,13 @@ SELECT '192.168.100.128'::citext::inet = '192.168.100.128'::inet AS t;
t
(1 row)
+SELECT CAST(inet '192.168.100.128' AS citext
+ DEFAULT NULL ON CONVERSION ERROR) = '192.168.100.128/32' AS t;
+ t
+---
+ t
+(1 row)
+
SELECT '08:00:2b:01:02:03'::macaddr::citext = '08:00:2b:01:02:03' AS t;
t
---
diff --git a/contrib/citext/expected/citext_1.out b/contrib/citext/expected/citext_1.out
index 647eea19142..e9f8454c662 100644
--- a/contrib/citext/expected/citext_1.out
+++ b/contrib/citext/expected/citext_1.out
@@ -10,11 +10,12 @@ WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid);
--------+---------
(0 rows)
-SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR); --error
-ERROR: cannot cast type character to citext when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
-LINE 1: SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSI...
- ^
-HINT: Safe type cast for user-defined types are not yet supported
+SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR);
+ citext
+--------
+ abc
+(1 row)
+
-- Test the operators and indexing functions
-- Test = and <>.
SELECT 'a'::citext = 'a'::citext AS t;
@@ -523,6 +524,12 @@ SELECT true::citext = 'true' AS t;
t
(1 row)
+SELECT CAST(true AS citext DEFAULT NULL ON CONVERSION ERROR);
+ citext
+--------
+ true
+(1 row)
+
SELECT 'true'::citext::boolean = true AS t;
t
---
@@ -787,6 +794,13 @@ SELECT '192.168.100.128'::citext::inet = '192.168.100.128'::inet AS t;
t
(1 row)
+SELECT CAST(inet '192.168.100.128' AS citext
+ DEFAULT NULL ON CONVERSION ERROR) = '192.168.100.128/32' AS t;
+ t
+---
+ t
+(1 row)
+
SELECT '08:00:2b:01:02:03'::macaddr::citext = '08:00:2b:01:02:03' AS t;
t
---
diff --git a/contrib/citext/sql/citext.sql b/contrib/citext/sql/citext.sql
index 99794497d47..62f83d749f9 100644
--- a/contrib/citext/sql/citext.sql
+++ b/contrib/citext/sql/citext.sql
@@ -9,7 +9,7 @@ SELECT amname, opcname
FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod
WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid);
-SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR);
-- Test the operators and indexing functions
@@ -165,6 +165,7 @@ SELECT name FROM srt WHERE name SIMILAR TO '%A.*';
-- Explicit casts.
SELECT true::citext = 'true' AS t;
+SELECT CAST(true AS citext DEFAULT NULL ON CONVERSION ERROR);
SELECT 'true'::citext::boolean = true AS t;
SELECT 4::citext = '4' AS t;
@@ -224,6 +225,8 @@ SELECT '192.168.100.128/25'::citext::cidr = '192.168.100.128/25'::cidr AS t;
SELECT '192.168.100.128'::inet::citext = '192.168.100.128/32' AS t;
SELECT '192.168.100.128'::citext::inet = '192.168.100.128'::inet AS t;
+SELECT CAST(inet '192.168.100.128' AS citext
+ DEFAULT NULL ON CONVERSION ERROR) = '192.168.100.128/32' AS t;
SELECT '08:00:2b:01:02:03'::macaddr::citext = '08:00:2b:01:02:03' AS t;
SELECT '08:00:2b:01:02:03'::citext::macaddr = '08:00:2b:01:02:03'::macaddr AS t;
diff --git a/contrib/hstore/expected/hstore.out b/contrib/hstore/expected/hstore.out
index 1836c9acf39..2622137cbf9 100644
--- a/contrib/hstore/expected/hstore.out
+++ b/contrib/hstore/expected/hstore.out
@@ -841,12 +841,28 @@ select '{}'::text[]::hstore;
select ARRAY['a','g','b','h','asd']::hstore;
ERROR: array must have even number of elements
+select CAST(ARRAY['a','g','b','h','asd'] AS hstore
+ DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
select ARRAY['a','g','b','h','asd','i']::hstore;
array
--------------------------------
"a"=>"g", "b"=>"h", "asd"=>"i"
(1 row)
+select ARRAY['a','g','b','h',null,'i']::hstore;
+ERROR: null value not allowed for hstore key
+select CAST(ARRAY['a','g','b','h',null,'i'] AS hstore
+ DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
select ARRAY[['a','g'],['b','h'],['asd','i']]::hstore;
array
--------------------------------
@@ -855,6 +871,13 @@ select ARRAY[['a','g'],['b','h'],['asd','i']]::hstore;
select ARRAY[['a','g','b'],['h','asd','i']]::hstore;
ERROR: array must have two columns
+select CAST(ARRAY[['a','g','b'],['h','asd','i']] AS hstore
+ DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
select ARRAY[[['a','g'],['b','h'],['asd','i']]]::hstore;
ERROR: wrong number of array subscripts
select hstore('{}'::text[]);
@@ -1553,6 +1576,13 @@ select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=
{"b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4", "a key": "1"}
(1 row)
+select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as json
+ default null on conversion error);
+ json
+-------------------------------------------------------------------------------------------------
+ {"b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4", "a key": "1"}
+(1 row)
+
select hstore_to_json_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4, h=> "2016-01-01"');
hstore_to_json_loose
-------------------------------------------------------------------------------------------------------------
@@ -1571,6 +1601,13 @@ select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=
{"b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4", "a key": "1"}
(1 row)
+select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as jsonb
+ default null on conversion error);
+ jsonb
+-------------------------------------------------------------------------------------------------
+ {"b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4", "a key": "1"}
+(1 row)
+
select hstore_to_jsonb_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4, h=> "2016-01-01"');
hstore_to_jsonb_loose
----------------------------------------------------------------------------------------------------------
diff --git a/contrib/hstore/hstore--1.2--1.3.sql b/contrib/hstore/hstore--1.2--1.3.sql
index 0a7056015b7..cbb1a139e69 100644
--- a/contrib/hstore/hstore--1.2--1.3.sql
+++ b/contrib/hstore/hstore--1.2--1.3.sql
@@ -9,7 +9,7 @@ AS 'MODULE_PATHNAME', 'hstore_to_jsonb'
LANGUAGE C IMMUTABLE STRICT;
CREATE CAST (hstore AS jsonb)
- WITH FUNCTION hstore_to_jsonb(hstore);
+ WITH SAFE FUNCTION hstore_to_jsonb(hstore);
CREATE FUNCTION hstore_to_jsonb_loose(hstore)
RETURNS jsonb
diff --git a/contrib/hstore/hstore--1.4.sql b/contrib/hstore/hstore--1.4.sql
index 4294d14ceb5..451c2ed8187 100644
--- a/contrib/hstore/hstore--1.4.sql
+++ b/contrib/hstore/hstore--1.4.sql
@@ -232,7 +232,7 @@ AS 'MODULE_PATHNAME', 'hstore_from_array'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE CAST (text[] AS hstore)
- WITH FUNCTION hstore(text[]);
+ WITH SAFE FUNCTION hstore(text[]);
CREATE FUNCTION hstore_to_json(hstore)
RETURNS json
@@ -240,7 +240,7 @@ AS 'MODULE_PATHNAME', 'hstore_to_json'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE CAST (hstore AS json)
- WITH FUNCTION hstore_to_json(hstore);
+ WITH SAFE FUNCTION hstore_to_json(hstore);
CREATE FUNCTION hstore_to_json_loose(hstore)
RETURNS json
@@ -253,7 +253,7 @@ AS 'MODULE_PATHNAME', 'hstore_to_jsonb'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE CAST (hstore AS jsonb)
- WITH FUNCTION hstore_to_jsonb(hstore);
+ WITH SAFE FUNCTION hstore_to_jsonb(hstore);
CREATE FUNCTION hstore_to_jsonb_loose(hstore)
RETURNS jsonb
diff --git a/contrib/hstore/hstore_io.c b/contrib/hstore/hstore_io.c
index 34e3918811c..f5166679783 100644
--- a/contrib/hstore/hstore_io.c
+++ b/contrib/hstore/hstore_io.c
@@ -738,20 +738,20 @@ hstore_from_array(PG_FUNCTION_ARGS)
case 1:
if ((ARR_DIMS(in_array)[0]) % 2)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
errmsg("array must have even number of elements")));
break;
case 2:
if ((ARR_DIMS(in_array)[1]) != 2)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
errmsg("array must have two columns")));
break;
default:
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
errmsg("wrong number of array subscripts")));
}
@@ -772,7 +772,7 @@ hstore_from_array(PG_FUNCTION_ARGS)
for (i = 0; i < count; ++i)
{
if (in_nulls[i * 2])
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("null value not allowed for hstore key")));
diff --git a/contrib/hstore/sql/hstore.sql b/contrib/hstore/sql/hstore.sql
index efef91292a3..8fa46630d6d 100644
--- a/contrib/hstore/sql/hstore.sql
+++ b/contrib/hstore/sql/hstore.sql
@@ -192,9 +192,16 @@ select pg_column_size(slice(hstore 'aa=>1, b=>2, c=>3', ARRAY['c','b','aa']))
-- array input
select '{}'::text[]::hstore;
select ARRAY['a','g','b','h','asd']::hstore;
+select CAST(ARRAY['a','g','b','h','asd'] AS hstore
+ DEFAULT NULL ON CONVERSION ERROR);
select ARRAY['a','g','b','h','asd','i']::hstore;
+select ARRAY['a','g','b','h',null,'i']::hstore;
+select CAST(ARRAY['a','g','b','h',null,'i'] AS hstore
+ DEFAULT NULL ON CONVERSION ERROR);
select ARRAY[['a','g'],['b','h'],['asd','i']]::hstore;
select ARRAY[['a','g','b'],['h','asd','i']]::hstore;
+select CAST(ARRAY[['a','g','b'],['h','asd','i']] AS hstore
+ DEFAULT NULL ON CONVERSION ERROR);
select ARRAY[[['a','g'],['b','h'],['asd','i']]]::hstore;
select hstore('{}'::text[]);
select hstore(ARRAY['a','g','b','h','asd']);
@@ -363,10 +370,14 @@ select count(*) from testhstore where h = 'pos=>98, line=>371, node=>CBA, indexe
-- json and jsonb
select hstore_to_json('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4');
select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as json);
+select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as json
+ default null on conversion error);
select hstore_to_json_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4, h=> "2016-01-01"');
select hstore_to_jsonb('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4');
select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as jsonb);
+select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as jsonb
+ default null on conversion error);
select hstore_to_jsonb_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4, h=> "2016-01-01"');
create table test_json_agg (f1 text, f2 hstore);
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2fc63442980..8fca3534f32 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1849,6 +1849,21 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<literal>b</literal> means that the types are binary-coercible, thus no conversion is required.
</para></entry>
</row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>casterrorsafe</structfield> <type>bool</type>
+ </para>
+ <para>
+ This flag indicates whether the <structfield>castfunc</structfield> function
+ is error-safe. It is meaningful only when <structfield>castfunc</structfield>
+ is not zero. User-defined casts can set it
+ to <literal>true</literal> via <link linkend="sql-createcast">CREATE CAST</link>.
+ For error-safe type cast, see <xref linkend="sql-syntax-type-casts-safe"/>.
+ </para>
+ </entry>
+ </row>
+
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/ref/create_cast.sgml b/doc/src/sgml/ref/create_cast.sgml
index bad75bc1dce..888d7142e42 100644
--- a/doc/src/sgml/ref/create_cast.sgml
+++ b/doc/src/sgml/ref/create_cast.sgml
@@ -22,7 +22,7 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE CAST (<replaceable>source_type</replaceable> AS <replaceable>target_type</replaceable>)
- WITH FUNCTION <replaceable>function_name</replaceable> [ (<replaceable>argument_type</replaceable> [, ...]) ]
+ WITH [SAFE] FUNCTION <replaceable>function_name</replaceable> [ (<replaceable>argument_type</replaceable> [, ...]) ]
[ AS ASSIGNMENT | AS IMPLICIT ]
CREATE CAST (<replaceable>source_type</replaceable> AS <replaceable>target_type</replaceable>)
@@ -194,6 +194,18 @@ SELECT CAST ( 2 AS numeric ) + 4.0;
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>SAFE</literal></term>
+ <listitem>
+ <para>
+ The function used to perform the cast support soft-error evaluation,
+ Currently, only functions written in C or the internal language are supported.
+ An alternate expression can be specified to be evaluated if the cast
+ error occurs. See <link linkend="sql-syntax-type-casts-safe">safe type cast</link>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal><replaceable>function_name</replaceable>[(<replaceable>argument_type</replaceable> [, ...])]</literal></term>
diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml
index 32af9ea061c..24d1dc6de0b 100644
--- a/doc/src/sgml/syntax.sgml
+++ b/doc/src/sgml/syntax.sgml
@@ -2176,8 +2176,7 @@ CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable>
</synopsis>
If the type cast fails, instead of error out, evaluation falls back to the
default <replaceable>expression</replaceable> specified in the <literal>ON ERROR</literal> clause.
- At present, this only support built-in type casts; see <xref linkend="catalog-pg-cast"/>.
- User-defined type casts created with <link linkend="sql-createcast">CREATE CAST</link> are not supported.
+ User-defined type casts created with <link linkend="sql-createcast">CREATE CAST</link> are supported too.
</para>
<para>
diff --git a/src/backend/catalog/pg_cast.c b/src/backend/catalog/pg_cast.c
index 1773c9c5491..4116b1708b0 100644
--- a/src/backend/catalog/pg_cast.c
+++ b/src/backend/catalog/pg_cast.c
@@ -48,7 +48,8 @@
ObjectAddress
CastCreate(Oid sourcetypeid, Oid targettypeid,
Oid funcid, Oid incastid, Oid outcastid,
- char castcontext, char castmethod, DependencyType behavior)
+ char castcontext, char castmethod, bool casterrorsafe,
+ DependencyType behavior)
{
Relation relation;
HeapTuple tuple;
@@ -84,6 +85,7 @@ CastCreate(Oid sourcetypeid, Oid targettypeid,
values[Anum_pg_cast_castfunc - 1] = ObjectIdGetDatum(funcid);
values[Anum_pg_cast_castcontext - 1] = CharGetDatum(castcontext);
values[Anum_pg_cast_castmethod - 1] = CharGetDatum(castmethod);
+ values[Anum_pg_cast_casterrorsafe - 1] = BoolGetDatum(casterrorsafe);
tuple = heap_form_tuple(RelationGetDescr(relation), values, nulls);
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index 59d00638ee6..2f7bdf26d37 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -1667,6 +1667,13 @@ CreateCast(CreateCastStmt *stmt)
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("cast function must not return a set")));
+ if (stmt->safe &&
+ procstruct->prolang != INTERNALlanguageId &&
+ procstruct->prolang != ClanguageId)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Safe type cast functions are only supported for C and internal languages"));
+
ReleaseSysCache(tuple);
}
else
@@ -1795,7 +1802,7 @@ CreateCast(CreateCastStmt *stmt)
}
myself = CastCreate(sourcetypeid, targettypeid, funcid, incastid, outcastid,
- castcontext, castmethod, DEPENDENCY_NORMAL);
+ castcontext, castmethod, stmt->safe, DEPENDENCY_NORMAL);
return myself;
}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 47d5047fe8b..6fce17ce02b 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -1754,6 +1754,7 @@ DefineRange(ParseState *pstate, CreateRangeStmt *stmt)
/* Create cast from the range type to its multirange type */
CastCreate(typoid, multirangeOid, castFuncOid, InvalidOid, InvalidOid,
COERCION_CODE_EXPLICIT, COERCION_METHOD_FUNCTION,
+ false,
DEPENDENCY_INTERNAL);
pfree(multirangeArrayName);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 8a27e045bc0..e7ff7e5c275 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -353,7 +353,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <defelt> drop_option
%type <boolean> opt_or_replace opt_no
opt_grant_grant_option
- opt_nowait opt_if_exists opt_with_data
+ opt_nowait opt_safe opt_if_exists opt_with_data
opt_transaction_chain
%type <list> grant_role_opt_list
%type <defelt> grant_role_opt
@@ -774,7 +774,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
RESET RESPECT_P RESTART RESTRICT RETURN RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
ROUTINE ROUTINES ROW ROWS RULE
- SAVEPOINT SCALAR SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT
+ SAFE SAVEPOINT SCALAR SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT
SEQUENCE SEQUENCES
SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SOURCE SQL_P STABLE STANDALONE_P
@@ -9291,14 +9291,15 @@ dostmt_opt_item:
*****************************************************************************/
CreateCastStmt: CREATE CAST '(' Typename AS Typename ')'
- WITH FUNCTION function_with_argtypes cast_context
+ WITH opt_safe FUNCTION function_with_argtypes cast_context
{
CreateCastStmt *n = makeNode(CreateCastStmt);
n->sourcetype = $4;
n->targettype = $6;
- n->func = $10;
- n->context = (CoercionContext) $11;
+ n->safe = $9;
+ n->func = $11;
+ n->context = (CoercionContext) $12;
n->inout = false;
$$ = (Node *) n;
}
@@ -9309,6 +9310,7 @@ CreateCastStmt: CREATE CAST '(' Typename AS Typename ')'
n->sourcetype = $4;
n->targettype = $6;
+ n->safe = false;
n->func = NULL;
n->context = (CoercionContext) $10;
n->inout = false;
@@ -9321,6 +9323,7 @@ CreateCastStmt: CREATE CAST '(' Typename AS Typename ')'
n->sourcetype = $4;
n->targettype = $6;
+ n->safe = false;
n->func = NULL;
n->context = (CoercionContext) $10;
n->inout = true;
@@ -9333,6 +9336,9 @@ cast_context: AS IMPLICIT_P { $$ = COERCION_IMPLICIT; }
| /*EMPTY*/ { $$ = COERCION_EXPLICIT; }
;
+opt_safe: SAFE { $$ = true; }
+ | /*EMPTY*/ { $$ = false; }
+ ;
DropCastStmt: DROP CAST opt_if_exists '(' Typename AS Typename ')' opt_drop_behavior
{
@@ -18108,6 +18114,7 @@ unreserved_keyword:
| ROUTINES
| ROWS
| RULE
+ | SAFE
| SAVEPOINT
| SCALAR
| SCHEMA
@@ -18744,6 +18751,7 @@ bare_label_keyword:
| ROW
| ROWS
| RULE
+ | SAFE
| SAVEPOINT
| SCALAR
| SCHEMA
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 7d3ed0eb890..a350c7edd11 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -3051,7 +3051,6 @@ CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr, Node *sourceexpr, Oid
{
HeapTuple tuple;
bool errorsafe = true;
- bool userdefined = false;
Oid inputBaseType;
Oid targetBaseType;
Oid inputElementType;
@@ -3123,11 +3122,8 @@ CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr, Node *sourceexpr, Oid
{
Form_pg_cast castForm = (Form_pg_cast) GETSTRUCT(tuple);
- if (castForm->oid > FirstUnpinnedObjectId)
- {
+ if (OidIsValid(castForm->castfunc) && !castForm->casterrorsafe)
errorsafe = false;
- userdefined = true;
- }
ReleaseSysCache(tuple);
}
@@ -3141,9 +3137,7 @@ CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr, Node *sourceexpr, Oid
format_type_be(targetType),
"DEFAULT",
"CAST ... ON CONVERSION ERROR"),
- userdefined
- ? errhint("Safe type cast for user-defined types are not yet supported")
- : errhint("Explicit cast is defined but definition is not error safe"),
+ errhint("Explicit cast is defined but definition is not error safe"),
parser_errposition(pstate, exprLocation(sourceexpr)));
}
diff --git a/src/include/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat
index fbfd669587f..ca52cfcd086 100644
--- a/src/include/catalog/pg_cast.dat
+++ b/src/include/catalog/pg_cast.dat
@@ -19,65 +19,65 @@
# int2->int4->int8->numeric->float4->float8, while casts in the
# reverse direction are assignment-only.
{ castsource => 'int8', casttarget => 'int2', castfunc => 'int2(int8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'int4', castfunc => 'int4(int8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'float4', castfunc => 'float4(int8)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'float8', castfunc => 'float8(int8)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'numeric', castfunc => 'numeric(int8)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'int8', castfunc => 'int8(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'int4', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'float4', castfunc => 'float4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'float8', castfunc => 'float8(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'numeric', castfunc => 'numeric(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'int8', castfunc => 'int8(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'int2', castfunc => 'int2(int4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'float4', castfunc => 'float4(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'float8', castfunc => 'float8(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'numeric', castfunc => 'numeric(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'int8', castfunc => 'int8(float4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'int2', castfunc => 'int2(float4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'int4', castfunc => 'int4(float4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'float8', castfunc => 'float8(float4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'numeric',
- castfunc => 'numeric(float4)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'numeric(float4)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'int8', castfunc => 'int8(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'int2', castfunc => 'int2(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'int4', castfunc => 'int4(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'float4', castfunc => 'float4(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'numeric',
- castfunc => 'numeric(float8)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'numeric(float8)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'int8', castfunc => 'int8(numeric)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'int2', castfunc => 'int2(numeric)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'int4', castfunc => 'int4(numeric)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'float4',
- castfunc => 'float4(numeric)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'float4(numeric)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'float8',
- castfunc => 'float8(numeric)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'float8(numeric)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'money', casttarget => 'numeric', castfunc => 'numeric(money)',
castcontext => 'a', castmethod => 'f' },
{ castsource => 'numeric', casttarget => 'money', castfunc => 'money(numeric)',
@@ -89,13 +89,13 @@
# Allow explicit coercions between int4 and bool
{ castsource => 'int4', casttarget => 'bool', castfunc => 'bool(int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'int4', castfunc => 'int4(bool)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Allow explicit coercions between xid8 and xid
{ castsource => 'xid8', casttarget => 'xid', castfunc => 'xid(xid8)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# OID category: allow implicit conversion from any integral type (including
# int8, to support OID literals > 2G) to OID, as well as assignment coercion
@@ -106,13 +106,13 @@
# casts from text and varchar to regclass, which exist mainly to support
# legacy forms of nextval() and related functions.
{ castsource => 'int8', casttarget => 'oid', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'oid', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'oid', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regproc', castfunc => '0',
@@ -120,13 +120,13 @@
{ castsource => 'regproc', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regproc', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regproc', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regproc', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regproc', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regproc', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'regproc', casttarget => 'regprocedure', castfunc => '0',
@@ -138,13 +138,13 @@
{ castsource => 'regprocedure', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regprocedure', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regprocedure', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regprocedure', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regprocedure', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regprocedure', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regoper', castfunc => '0',
@@ -152,13 +152,13 @@
{ castsource => 'regoper', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regoper', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regoper', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regoper', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regoper', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regoper', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'regoper', casttarget => 'regoperator', castfunc => '0',
@@ -170,13 +170,13 @@
{ castsource => 'regoperator', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regoperator', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regoperator', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regoperator', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regoperator', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regoperator', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regclass', castfunc => '0',
@@ -184,13 +184,13 @@
{ castsource => 'regclass', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regclass', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regclass', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regclass', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regclass', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regclass', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regcollation', castfunc => '0',
@@ -198,13 +198,13 @@
{ castsource => 'regcollation', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regcollation', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regcollation', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regcollation', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regcollation', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regcollation', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regtype', castfunc => '0',
@@ -212,13 +212,13 @@
{ castsource => 'regtype', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regtype', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regtype', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regtype', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regtype', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regtype', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regconfig', castfunc => '0',
@@ -226,13 +226,13 @@
{ castsource => 'regconfig', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regconfig', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regconfig', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regconfig', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regconfig', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regconfig', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regdictionary', castfunc => '0',
@@ -240,31 +240,31 @@
{ castsource => 'regdictionary', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regdictionary', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regdictionary', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regdictionary', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regdictionary', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regdictionary', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'text', casttarget => 'regclass', castfunc => 'regclass',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'regclass', castfunc => 'regclass',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'oid', casttarget => 'regrole', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regrole', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regrole', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regrole', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regrole', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regrole', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regrole', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regnamespace', castfunc => '0',
@@ -272,13 +272,13 @@
{ castsource => 'regnamespace', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regnamespace', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regnamespace', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regnamespace', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regnamespace', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regnamespace', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regdatabase', castfunc => '0',
@@ -286,13 +286,13 @@
{ castsource => 'regdatabase', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regdatabase', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regdatabase', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regdatabase', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regdatabase', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regdatabase', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
@@ -302,57 +302,57 @@
{ castsource => 'text', casttarget => 'varchar', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'bpchar', casttarget => 'text', castfunc => 'text(bpchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bpchar', casttarget => 'varchar', castfunc => 'text(bpchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'text', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'varchar', casttarget => 'bpchar', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'char', casttarget => 'text', castfunc => 'text(char)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'char', casttarget => 'bpchar', castfunc => 'bpchar(char)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'char', casttarget => 'varchar', castfunc => 'text(char)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'name', casttarget => 'text', castfunc => 'text(name)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'name', casttarget => 'bpchar', castfunc => 'bpchar(name)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'name', casttarget => 'varchar', castfunc => 'varchar(name)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'text', casttarget => 'char', castfunc => 'char(text)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bpchar', casttarget => 'char', castfunc => 'char(text)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'char', castfunc => 'char(text)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'text', casttarget => 'name', castfunc => 'name(text)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bpchar', casttarget => 'name', castfunc => 'name(bpchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'name', castfunc => 'name(varchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
# Allow explicit coercions between bytea and integer types
{ castsource => 'int2', casttarget => 'bytea', castfunc => 'bytea(int2)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'bytea', castfunc => 'bytea(int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'bytea', castfunc => 'bytea(int8)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bytea', casttarget => 'int2', castfunc => 'int2(bytea)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bytea', casttarget => 'int4', castfunc => 'int4(bytea)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bytea', casttarget => 'int8', castfunc => 'int8(bytea)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Allow explicit coercions between int4 and "char"
{ castsource => 'char', casttarget => 'int4', castfunc => 'int4(char)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'char', castfunc => 'char(int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# pg_node_tree can be coerced to, but not from, text
{ castsource => 'pg_node_tree', casttarget => 'text', castfunc => '0',
@@ -378,73 +378,73 @@
# Datetime category
{ castsource => 'date', casttarget => 'timestamp',
- castfunc => 'timestamp(date)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timestamp(date)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'date', casttarget => 'timestamptz',
- castfunc => 'timestamptz(date)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timestamptz(date)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'time', casttarget => 'interval', castfunc => 'interval(time)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'time', casttarget => 'timetz', castfunc => 'timetz(time)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'date',
- castfunc => 'date(timestamp)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'date(timestamp)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'time',
- castfunc => 'time(timestamp)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'time(timestamp)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'timestamptz',
- castfunc => 'timestamptz(timestamp)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timestamptz(timestamp)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'date',
- castfunc => 'date(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'date(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'time',
- castfunc => 'time(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'time(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'timestamp',
- castfunc => 'timestamp(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'timestamp(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'timetz',
- castfunc => 'timetz(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'timetz(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'interval', casttarget => 'time', castfunc => 'time(interval)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timetz', casttarget => 'time', castfunc => 'time(timetz)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
# Geometric category
{ castsource => 'point', casttarget => 'box', castfunc => 'box(point)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'lseg', casttarget => 'point', castfunc => 'point(lseg)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'path', casttarget => 'polygon', castfunc => 'polygon(path)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'box', casttarget => 'point', castfunc => 'point(box)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'box', casttarget => 'lseg', castfunc => 'lseg(box)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'box', casttarget => 'polygon', castfunc => 'polygon(box)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'box', casttarget => 'circle', castfunc => 'circle(box)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'polygon', casttarget => 'point', castfunc => 'point(polygon)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'polygon', casttarget => 'path', castfunc => 'path',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'polygon', casttarget => 'box', castfunc => 'box(polygon)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'polygon', casttarget => 'circle',
- castfunc => 'circle(polygon)', castcontext => 'e', castmethod => 'f' },
+ castfunc => 'circle(polygon)', castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'circle', casttarget => 'point', castfunc => 'point(circle)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'circle', casttarget => 'box', castfunc => 'box(circle)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'circle', casttarget => 'polygon',
- castfunc => 'polygon(circle)', castcontext => 'e', castmethod => 'f' },
+ castfunc => 'polygon(circle)', castcontext => 'e', castmethod => 'f'},
# MAC address category
{ castsource => 'macaddr', casttarget => 'macaddr8', castfunc => 'macaddr8',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'macaddr8', casttarget => 'macaddr', castfunc => 'macaddr',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
# INET category
{ castsource => 'cidr', casttarget => 'inet', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'inet', casttarget => 'cidr', castfunc => 'cidr',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
# BitString category
{ castsource => 'bit', casttarget => 'varbit', castfunc => '0',
@@ -454,13 +454,13 @@
# Cross-category casts between bit and int4, int8
{ castsource => 'int8', casttarget => 'bit', castfunc => 'bit(int8,int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'bit', castfunc => 'bit(int4,int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bit', casttarget => 'int8', castfunc => 'int8(bit)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bit', casttarget => 'int4', castfunc => 'int4(bit)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Cross-category casts to and from TEXT
# We need entries here only for a few specialized cases where the behavior
@@ -471,68 +471,68 @@
# behavior will ensue when the automatic cast is applied instead of the
# pg_cast entry!
{ castsource => 'cidr', casttarget => 'text', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'inet', casttarget => 'text', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'text', castfunc => 'text(bool)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'xml', casttarget => 'text', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'text', casttarget => 'xml', castfunc => 'xml',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Cross-category casts to and from VARCHAR
# We support all the same casts as for TEXT.
{ castsource => 'cidr', casttarget => 'varchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'inet', casttarget => 'varchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'varchar', castfunc => 'text(bool)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'xml', casttarget => 'varchar', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'varchar', casttarget => 'xml', castfunc => 'xml',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Cross-category casts to and from BPCHAR
# We support all the same casts as for TEXT.
{ castsource => 'cidr', casttarget => 'bpchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'inet', casttarget => 'bpchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'bpchar', castfunc => 'text(bool)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'xml', casttarget => 'bpchar', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'bpchar', casttarget => 'xml', castfunc => 'xml',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Length-coercion functions
{ castsource => 'bpchar', casttarget => 'bpchar',
castfunc => 'bpchar(bpchar,int4,bool)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'varchar',
castfunc => 'varchar(varchar,int4,bool)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'time', casttarget => 'time', castfunc => 'time(time,int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'timestamp',
castfunc => 'timestamp(timestamp,int4)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'timestamptz',
castfunc => 'timestamptz(timestamptz,int4)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'interval', casttarget => 'interval',
castfunc => 'interval(interval,int4)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timetz', casttarget => 'timetz',
- castfunc => 'timetz(timetz,int4)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timetz(timetz,int4)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bit', casttarget => 'bit', castfunc => 'bit(bit,int4,bool)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varbit', casttarget => 'varbit', castfunc => 'varbit',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'numeric',
- castfunc => 'numeric(numeric,int4)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'numeric(numeric,int4)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
# json to/from jsonb
{ castsource => 'json', casttarget => 'jsonb', castfunc => '0',
@@ -542,36 +542,36 @@
# jsonb to numeric and bool types
{ castsource => 'jsonb', casttarget => 'bool', castfunc => 'bool(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'numeric', castfunc => 'numeric(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'int2', castfunc => 'int2(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'int4', castfunc => 'int4(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'int8', castfunc => 'int8(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'float4', castfunc => 'float4(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'float8', castfunc => 'float8(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# range to multirange
{ castsource => 'int4range', casttarget => 'int4multirange',
castfunc => 'int4multirange(int4range)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8range', casttarget => 'int8multirange',
castfunc => 'int8multirange(int8range)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numrange', casttarget => 'nummultirange',
castfunc => 'nummultirange(numrange)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'daterange', casttarget => 'datemultirange',
castfunc => 'datemultirange(daterange)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'tsrange', casttarget => 'tsmultirange',
- castfunc => 'tsmultirange(tsrange)', castcontext => 'e', castmethod => 'f' },
+ castfunc => 'tsmultirange(tsrange)', castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'tstzrange', casttarget => 'tstzmultirange',
castfunc => 'tstzmultirange(tstzrange)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
]
diff --git a/src/include/catalog/pg_cast.h b/src/include/catalog/pg_cast.h
index 6a0ca337153..bf47544f675 100644
--- a/src/include/catalog/pg_cast.h
+++ b/src/include/catalog/pg_cast.h
@@ -47,6 +47,10 @@ CATALOG(pg_cast,2605,CastRelationId)
/* cast method */
char castmethod;
+
+ /* cast function error safe */
+ bool casterrorsafe BKI_DEFAULT(f);
+
} FormData_pg_cast;
/* ----------------
@@ -101,6 +105,7 @@ extern ObjectAddress CastCreate(Oid sourcetypeid,
Oid outcastid,
char castcontext,
char castmethod,
+ bool casterrorsafe,
DependencyType behavior);
#endif /* PG_CAST_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 82b0fb83b4b..e99499196cb 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4155,6 +4155,7 @@ typedef struct CreateCastStmt
ObjectWithArgs *func;
CoercionContext context;
bool inout;
+ bool safe;
} CreateCastStmt;
/* ----------------------
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 5d4fe27ef96..f2ff6091fd2 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -396,6 +396,7 @@ PG_KEYWORD("routines", ROUTINES, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("row", ROW, COL_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("rows", ROWS, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("rule", RULE, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("safe", SAFE, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("savepoint", SAVEPOINT, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("scalar", SCALAR, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("schema", SCHEMA, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/test/regress/expected/create_cast.out b/src/test/regress/expected/create_cast.out
index 0054ed0ef67..1e32c041b9f 100644
--- a/src/test/regress/expected/create_cast.out
+++ b/src/test/regress/expected/create_cast.out
@@ -81,6 +81,12 @@ NOTICE: drop cascades to cast from integer to casttesttype
-- Try it with a function that requires an implicit cast
CREATE FUNCTION bar_int4_text(int4) RETURNS text LANGUAGE SQL AS
$$ SELECT ('bar'::text || $1::text); $$;
+CREATE FUNCTION bar_int4_text_plpg(int4) RETURNS text LANGUAGE plpgsql AS
+$$ BEGIN RETURN ('bar'::text || $1::text); END $$;
+CREATE CAST (int4 AS casttesttype) WITH SAFE FUNCTION bar_int4_text(int4) AS IMPLICIT; -- error
+ERROR: Safe type cast functions are only supported for C and internal languages
+CREATE CAST (int4 AS casttesttype) WITH SAFE FUNCTION bar_int4_text_plpg(int4) AS IMPLICIT; -- error
+ERROR: Safe type cast functions are only supported for C and internal languages
CREATE CAST (int4 AS casttesttype) WITH FUNCTION bar_int4_text(int4) AS IMPLICIT;
SELECT 1234::int4::casttesttype; -- Should work now
casttesttype
@@ -92,7 +98,7 @@ SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- err
ERROR: cannot cast type integer to casttesttype when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
LINE 1: SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVE...
^
-HINT: Safe type cast for user-defined types are not yet supported
+HINT: Explicit cast is defined but definition is not error safe
-- check dependencies generated for that
SELECT pg_describe_object(classid, objid, objsubid) as obj,
pg_describe_object(refclassid, refobjid, refobjsubid) as objref,
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index a357e1d0c0e..81ea244859f 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -943,8 +943,8 @@ SELECT *
FROM pg_cast c
WHERE castsource = 0 OR casttarget = 0 OR castcontext NOT IN ('e', 'a', 'i')
OR castmethod NOT IN ('f', 'b' ,'i');
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Check that castfunc is nonzero only for cast methods that need a function,
@@ -953,8 +953,8 @@ SELECT *
FROM pg_cast c
WHERE (castmethod = 'f' AND castfunc = 0)
OR (castmethod IN ('b', 'i') AND castfunc <> 0);
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Look for casts to/from the same type that aren't length coercion functions.
@@ -963,15 +963,15 @@ WHERE (castmethod = 'f' AND castfunc = 0)
SELECT *
FROM pg_cast c
WHERE castsource = casttarget AND castfunc = 0;
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
SELECT c.*
FROM pg_cast c, pg_proc p
WHERE c.castfunc = p.oid AND p.pronargs < 2 AND castsource = casttarget;
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Look for cast functions that don't have the right signature. The
@@ -989,8 +989,8 @@ WHERE c.castfunc = p.oid AND
OR (c.castsource = 'character'::regtype AND
p.proargtypes[0] = 'text'::regtype))
OR NOT binary_coercible(p.prorettype, c.casttarget));
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
SELECT c.*
@@ -998,8 +998,8 @@ FROM pg_cast c, pg_proc p
WHERE c.castfunc = p.oid AND
((p.pronargs > 1 AND p.proargtypes[1] != 'int4'::regtype) OR
(p.pronargs > 2 AND p.proargtypes[2] != 'bool'::regtype));
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Look for binary compatible casts that do not have the reverse
diff --git a/src/test/regress/sql/create_cast.sql b/src/test/regress/sql/create_cast.sql
index 0a15a795d87..30a0ff077c9 100644
--- a/src/test/regress/sql/create_cast.sql
+++ b/src/test/regress/sql/create_cast.sql
@@ -60,6 +60,11 @@ DROP FUNCTION int4_casttesttype(int4) CASCADE;
CREATE FUNCTION bar_int4_text(int4) RETURNS text LANGUAGE SQL AS
$$ SELECT ('bar'::text || $1::text); $$;
+CREATE FUNCTION bar_int4_text_plpg(int4) RETURNS text LANGUAGE plpgsql AS
+$$ BEGIN RETURN ('bar'::text || $1::text); END $$;
+
+CREATE CAST (int4 AS casttesttype) WITH SAFE FUNCTION bar_int4_text(int4) AS IMPLICIT; -- error
+CREATE CAST (int4 AS casttesttype) WITH SAFE FUNCTION bar_int4_text_plpg(int4) AS IMPLICIT; -- error
CREATE CAST (int4 AS casttesttype) WITH FUNCTION bar_int4_text(int4) AS IMPLICIT;
SELECT 1234::int4::casttesttype; -- Should work now
SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- error
--
2.34.1
v15-0019-refactor-point_dt.patchtext/x-patch; charset=US-ASCII; name=v15-0019-refactor-point_dt.patchDownload
From 35715cfacd3ee194601443ba00dcac65630454d5 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Wed, 10 Dec 2025 12:00:54 +0800
Subject: [PATCH v15 19/22] refactor point_dt
point_dt used in many places, it will be used in later on patch, thus
refactoring make it error safe.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/geo_ops.c | 77 +++++++++++++++++++--------------
1 file changed, 44 insertions(+), 33 deletions(-)
diff --git a/src/backend/utils/adt/geo_ops.c b/src/backend/utils/adt/geo_ops.c
index 9101a720744..ad6ba1d6621 100644
--- a/src/backend/utils/adt/geo_ops.c
+++ b/src/backend/utils/adt/geo_ops.c
@@ -82,7 +82,7 @@ static inline void point_sub_point(Point *result, Point *pt1, Point *pt2);
static inline void point_mul_point(Point *result, Point *pt1, Point *pt2);
static inline void point_div_point(Point *result, Point *pt1, Point *pt2);
static inline bool point_eq_point(Point *pt1, Point *pt2);
-static inline float8 point_dt(Point *pt1, Point *pt2);
+static inline float8 point_dt(Point *pt1, Point *pt2, Node *escontext);
static inline float8 point_sl(Point *pt1, Point *pt2);
static int point_inside(Point *p, int npts, Point *plist);
@@ -839,7 +839,7 @@ box_distance(PG_FUNCTION_ARGS)
box_cn(&a, box1);
box_cn(&b, box2);
- PG_RETURN_FLOAT8(point_dt(&a, &b));
+ PG_RETURN_FLOAT8(point_dt(&a, &b, NULL));
}
@@ -1808,7 +1808,7 @@ path_length(PG_FUNCTION_ARGS)
iprev = path->npts - 1; /* include the closure segment */
}
- result = float8_pl(result, point_dt(&path->p[iprev], &path->p[i]));
+ result = float8_pl(result, point_dt(&path->p[iprev], &path->p[i], NULL));
}
PG_RETURN_FLOAT8(result);
@@ -1995,13 +1995,24 @@ point_distance(PG_FUNCTION_ARGS)
Point *pt1 = PG_GETARG_POINT_P(0);
Point *pt2 = PG_GETARG_POINT_P(1);
- PG_RETURN_FLOAT8(point_dt(pt1, pt2));
+ PG_RETURN_FLOAT8(point_dt(pt1, pt2, NULL));
}
static inline float8
-point_dt(Point *pt1, Point *pt2)
+point_dt(Point *pt1, Point *pt2, Node *escontext)
{
- return hypot(float8_mi(pt1->x, pt2->x), float8_mi(pt1->y, pt2->y));
+ float8 x;
+ float8 y;
+
+ x = float8_mi_safe(pt1->x, pt2->x, escontext);
+ if (unlikely(SOFT_ERROR_OCCURRED(escontext)))
+ return 0.0;
+
+ y = float8_mi_safe(pt1->y, pt2->y, escontext);
+ if (unlikely(SOFT_ERROR_OCCURRED(escontext)))
+ return 0.0;
+
+ return hypot(x, y);
}
Datum
@@ -2173,7 +2184,7 @@ lseg_length(PG_FUNCTION_ARGS)
{
LSEG *lseg = PG_GETARG_LSEG_P(0);
- PG_RETURN_FLOAT8(point_dt(&lseg->p[0], &lseg->p[1]));
+ PG_RETURN_FLOAT8(point_dt(&lseg->p[0], &lseg->p[1], NULL));
}
/*----------------------------------------------------------
@@ -2258,8 +2269,8 @@ lseg_lt(PG_FUNCTION_ARGS)
LSEG *l1 = PG_GETARG_LSEG_P(0);
LSEG *l2 = PG_GETARG_LSEG_P(1);
- PG_RETURN_BOOL(FPlt(point_dt(&l1->p[0], &l1->p[1]),
- point_dt(&l2->p[0], &l2->p[1])));
+ PG_RETURN_BOOL(FPlt(point_dt(&l1->p[0], &l1->p[1], NULL),
+ point_dt(&l2->p[0], &l2->p[1], NULL)));
}
Datum
@@ -2268,8 +2279,8 @@ lseg_le(PG_FUNCTION_ARGS)
LSEG *l1 = PG_GETARG_LSEG_P(0);
LSEG *l2 = PG_GETARG_LSEG_P(1);
- PG_RETURN_BOOL(FPle(point_dt(&l1->p[0], &l1->p[1]),
- point_dt(&l2->p[0], &l2->p[1])));
+ PG_RETURN_BOOL(FPle(point_dt(&l1->p[0], &l1->p[1], NULL),
+ point_dt(&l2->p[0], &l2->p[1], NULL)));
}
Datum
@@ -2278,8 +2289,8 @@ lseg_gt(PG_FUNCTION_ARGS)
LSEG *l1 = PG_GETARG_LSEG_P(0);
LSEG *l2 = PG_GETARG_LSEG_P(1);
- PG_RETURN_BOOL(FPgt(point_dt(&l1->p[0], &l1->p[1]),
- point_dt(&l2->p[0], &l2->p[1])));
+ PG_RETURN_BOOL(FPgt(point_dt(&l1->p[0], &l1->p[1], NULL),
+ point_dt(&l2->p[0], &l2->p[1], NULL)));
}
Datum
@@ -2288,8 +2299,8 @@ lseg_ge(PG_FUNCTION_ARGS)
LSEG *l1 = PG_GETARG_LSEG_P(0);
LSEG *l2 = PG_GETARG_LSEG_P(1);
- PG_RETURN_BOOL(FPge(point_dt(&l1->p[0], &l1->p[1]),
- point_dt(&l2->p[0], &l2->p[1])));
+ PG_RETURN_BOOL(FPge(point_dt(&l1->p[0], &l1->p[1], NULL),
+ point_dt(&l2->p[0], &l2->p[1], NULL)));
}
@@ -2743,7 +2754,7 @@ line_closept_point(Point *result, LINE *line, Point *point)
if (result != NULL)
*result = closept;
- return point_dt(&closept, point);
+ return point_dt(&closept, point, NULL);
}
Datum
@@ -2784,7 +2795,7 @@ lseg_closept_point(Point *result, LSEG *lseg, Point *pt)
if (result != NULL)
*result = closept;
- return point_dt(&closept, pt);
+ return point_dt(&closept, pt, NULL);
}
Datum
@@ -3108,9 +3119,9 @@ on_pl(PG_FUNCTION_ARGS)
static bool
lseg_contain_point(LSEG *lseg, Point *pt)
{
- return FPeq(point_dt(pt, &lseg->p[0]) +
- point_dt(pt, &lseg->p[1]),
- point_dt(&lseg->p[0], &lseg->p[1]));
+ return FPeq(point_dt(pt, &lseg->p[0], NULL) +
+ point_dt(pt, &lseg->p[1], NULL),
+ point_dt(&lseg->p[0], &lseg->p[1], NULL));
}
Datum
@@ -3176,11 +3187,11 @@ on_ppath(PG_FUNCTION_ARGS)
if (!path->closed)
{
n = path->npts - 1;
- a = point_dt(pt, &path->p[0]);
+ a = point_dt(pt, &path->p[0], NULL);
for (i = 0; i < n; i++)
{
- b = point_dt(pt, &path->p[i + 1]);
- if (FPeq(float8_pl(a, b), point_dt(&path->p[i], &path->p[i + 1])))
+ b = point_dt(pt, &path->p[i + 1], NULL);
+ if (FPeq(float8_pl(a, b), point_dt(&path->p[i], &path->p[i + 1], NULL)))
PG_RETURN_BOOL(true);
a = b;
}
@@ -4766,7 +4777,7 @@ circle_overlap(PG_FUNCTION_ARGS)
CIRCLE *circle1 = PG_GETARG_CIRCLE_P(0);
CIRCLE *circle2 = PG_GETARG_CIRCLE_P(1);
- PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center),
+ PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center, NULL),
float8_pl(circle1->radius, circle2->radius)));
}
@@ -4828,7 +4839,7 @@ circle_contained(PG_FUNCTION_ARGS)
CIRCLE *circle1 = PG_GETARG_CIRCLE_P(0);
CIRCLE *circle2 = PG_GETARG_CIRCLE_P(1);
- PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center),
+ PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center, NULL),
float8_mi(circle2->radius, circle1->radius)));
}
@@ -4840,7 +4851,7 @@ circle_contain(PG_FUNCTION_ARGS)
CIRCLE *circle1 = PG_GETARG_CIRCLE_P(0);
CIRCLE *circle2 = PG_GETARG_CIRCLE_P(1);
- PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center),
+ PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center, NULL),
float8_mi(circle1->radius, circle2->radius)));
}
@@ -5069,7 +5080,7 @@ circle_distance(PG_FUNCTION_ARGS)
CIRCLE *circle2 = PG_GETARG_CIRCLE_P(1);
float8 result;
- result = float8_mi(point_dt(&circle1->center, &circle2->center),
+ result = float8_mi(point_dt(&circle1->center, &circle2->center, NULL),
float8_pl(circle1->radius, circle2->radius));
if (result < 0.0)
result = 0.0;
@@ -5085,7 +5096,7 @@ circle_contain_pt(PG_FUNCTION_ARGS)
Point *point = PG_GETARG_POINT_P(1);
float8 d;
- d = point_dt(&circle->center, point);
+ d = point_dt(&circle->center, point, NULL);
PG_RETURN_BOOL(d <= circle->radius);
}
@@ -5097,7 +5108,7 @@ pt_contained_circle(PG_FUNCTION_ARGS)
CIRCLE *circle = PG_GETARG_CIRCLE_P(1);
float8 d;
- d = point_dt(&circle->center, point);
+ d = point_dt(&circle->center, point, NULL);
PG_RETURN_BOOL(d <= circle->radius);
}
@@ -5112,7 +5123,7 @@ dist_pc(PG_FUNCTION_ARGS)
CIRCLE *circle = PG_GETARG_CIRCLE_P(1);
float8 result;
- result = float8_mi(point_dt(point, &circle->center),
+ result = float8_mi(point_dt(point, &circle->center, NULL),
circle->radius);
if (result < 0.0)
result = 0.0;
@@ -5130,7 +5141,7 @@ dist_cpoint(PG_FUNCTION_ARGS)
Point *point = PG_GETARG_POINT_P(1);
float8 result;
- result = float8_mi(point_dt(point, &circle->center), circle->radius);
+ result = float8_mi(point_dt(point, &circle->center, NULL), circle->radius);
if (result < 0.0)
result = 0.0;
@@ -5215,7 +5226,7 @@ box_circle(PG_FUNCTION_ARGS)
circle->center.x = float8_div(float8_pl(box->high.x, box->low.x), 2.0);
circle->center.y = float8_div(float8_pl(box->high.y, box->low.y), 2.0);
- circle->radius = point_dt(&circle->center, &box->high);
+ circle->radius = point_dt(&circle->center, &box->high, NULL);
PG_RETURN_CIRCLE_P(circle);
}
@@ -5299,7 +5310,7 @@ poly_to_circle(CIRCLE *result, POLYGON *poly)
for (i = 0; i < poly->npts; i++)
result->radius = float8_pl(result->radius,
- point_dt(&poly->p[i], &result->center));
+ point_dt(&poly->p[i], &result->center, NULL));
result->radius = float8_div(result->radius, poly->npts);
}
--
2.34.1
v15-0020-error-safe-for-casting-geometry-data-type.patchtext/x-patch; charset=US-ASCII; name=v15-0020-error-safe-for-casting-geometry-data-type.patchDownload
From 89539ec5c8663872ad954e98d435f3d4e321b7d0 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Wed, 10 Dec 2025 13:35:42 +0800
Subject: [PATCH v15 20/22] error safe for casting geometry data type
select castsource::regtype, casttarget::regtype, pp.prosrc
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
join pg_type pt on pt.oid = castsource
join pg_type pt1 on pt1.oid = casttarget
and pc.castfunc > 0 and pt.typarray <> 0
and pt.typnamespace = 'pg_catalog'::regnamespace
and pt1.typnamespace = 'pg_catalog'::regnamespace
and (pt.typcategory = 'G' or pt1.typcategory = 'G')
order by castsource::regtype, casttarget::regtype;
castsource | casttarget | prosrc
------------+------------+---------------
point | box | point_box
lseg | point | lseg_center
path | polygon | path_poly
box | point | box_center
box | lseg | box_diagonal
box | polygon | box_poly
box | circle | box_circle
polygon | point | poly_center
polygon | path | poly_path
polygon | box | poly_box
polygon | circle | poly_circle
circle | point | circle_center
circle | box | circle_box
circle | polygon |
(14 rows)
already error safe: point_box, box_diagonal, box_poly, poly_path, poly_box,
circle_center
This patch make these functions error safe: path_poly, lseg_center, box_center,
box_circle, poly_center, poly_circle, circle_box
can not error safe: cast circle to polygon, because it's a SQL function
discussion: https://postgr.es/m/CACJufxHCMzrHOW=wRe8L30rMhB3sjwAv1LE928Fa7sxMu1Tx-g@mail.gmail.com
---
src/backend/utils/adt/geo_ops.c | 194 +++++++++++++++++++++++++-------
1 file changed, 153 insertions(+), 41 deletions(-)
diff --git a/src/backend/utils/adt/geo_ops.c b/src/backend/utils/adt/geo_ops.c
index ad6ba1d6621..abb401a913a 100644
--- a/src/backend/utils/adt/geo_ops.c
+++ b/src/backend/utils/adt/geo_ops.c
@@ -77,7 +77,8 @@ enum path_delim
/* Routines for points */
static inline void point_construct(Point *result, float8 x, float8 y);
-static inline void point_add_point(Point *result, Point *pt1, Point *pt2);
+static inline void point_add_point(Point *result, Point *pt1, Point *pt2,
+ Node *escontext);
static inline void point_sub_point(Point *result, Point *pt1, Point *pt2);
static inline void point_mul_point(Point *result, Point *pt1, Point *pt2);
static inline void point_div_point(Point *result, Point *pt1, Point *pt2);
@@ -108,7 +109,7 @@ static float8 lseg_closept_lseg(Point *result, LSEG *on_lseg, LSEG *to_lseg);
/* Routines for boxes */
static inline void box_construct(BOX *result, Point *pt1, Point *pt2);
-static void box_cn(Point *center, BOX *box);
+static void box_cn(Point *center, BOX *box, Node* escontext);
static bool box_ov(BOX *box1, BOX *box2);
static float8 box_ar(BOX *box);
static float8 box_ht(BOX *box);
@@ -125,7 +126,7 @@ static float8 circle_ar(CIRCLE *circle);
/* Routines for polygons */
static void make_bound_box(POLYGON *poly);
-static void poly_to_circle(CIRCLE *result, POLYGON *poly);
+static bool poly_to_circle(CIRCLE *result, POLYGON *poly, Node *escontext);
static bool lseg_inside_poly(Point *a, Point *b, POLYGON *poly, int start);
static bool poly_contain_poly(POLYGON *contains_poly, POLYGON *contained_poly);
static bool plist_same(int npts, Point *p1, Point *p2);
@@ -836,8 +837,8 @@ box_distance(PG_FUNCTION_ARGS)
Point a,
b;
- box_cn(&a, box1);
- box_cn(&b, box2);
+ box_cn(&a, box1, NULL);
+ box_cn(&b, box2, NULL);
PG_RETURN_FLOAT8(point_dt(&a, &b, NULL));
}
@@ -851,7 +852,9 @@ box_center(PG_FUNCTION_ARGS)
BOX *box = PG_GETARG_BOX_P(0);
Point *result = (Point *) palloc(sizeof(Point));
- box_cn(result, box);
+ box_cn(result, box, fcinfo->context);
+ if ((SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
PG_RETURN_POINT_P(result);
}
@@ -869,13 +872,26 @@ box_ar(BOX *box)
/* box_cn - stores the centerpoint of the box into *center.
*/
static void
-box_cn(Point *center, BOX *box)
+box_cn(Point *center, BOX *box, Node *escontext)
{
- center->x = float8_div(float8_pl(box->high.x, box->low.x), 2.0);
- center->y = float8_div(float8_pl(box->high.y, box->low.y), 2.0);
+ float8 x;
+ float8 y;
+
+ x = float8_pl_safe(box->high.x, box->low.x, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return;
+
+ center->x = float8_div_safe(x, 2.0, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return;
+
+ y = float8_pl_safe(box->high.y, box->low.y, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return;
+
+ center->y = float8_div_safe(y, 2.0, escontext);
}
-
/* box_wd - returns the width (length) of the box
* (horizontal magnitude).
*/
@@ -2328,13 +2344,31 @@ lseg_center(PG_FUNCTION_ARGS)
{
LSEG *lseg = PG_GETARG_LSEG_P(0);
Point *result;
+ float8 x;
+ float8 y;
result = (Point *) palloc(sizeof(Point));
- result->x = float8_div(float8_pl(lseg->p[0].x, lseg->p[1].x), 2.0);
- result->y = float8_div(float8_pl(lseg->p[0].y, lseg->p[1].y), 2.0);
+ x = float8_pl_safe(lseg->p[0].x, lseg->p[1].x, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ result->x = float8_div_safe(x, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ y = float8_pl_safe(lseg->p[0].y, lseg->p[1].y, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ result->y = float8_div_safe(y, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
PG_RETURN_POINT_P(result);
+
+fail:
+ PG_RETURN_NULL();
}
@@ -3288,7 +3322,7 @@ box_interpt_lseg(Point *result, BOX *box, LSEG *lseg)
if (result != NULL)
{
- box_cn(&point, box);
+ box_cn(&point, box, NULL);
lseg_closept_point(result, lseg, &point);
}
@@ -4119,11 +4153,20 @@ construct_point(PG_FUNCTION_ARGS)
static inline void
-point_add_point(Point *result, Point *pt1, Point *pt2)
+point_add_point(Point *result, Point *pt1, Point *pt2, Node *escontext)
{
- point_construct(result,
- float8_pl(pt1->x, pt2->x),
- float8_pl(pt1->y, pt2->y));
+ float8 x;
+ float8 y;
+
+ x = float8_pl_safe(pt1->x, pt2->x, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return;
+
+ y = float8_pl_safe(pt1->y, pt2->y, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return;
+
+ point_construct(result, x, y);
}
Datum
@@ -4135,7 +4178,7 @@ point_add(PG_FUNCTION_ARGS)
result = (Point *) palloc(sizeof(Point));
- point_add_point(result, p1, p2);
+ point_add_point(result, p1, p2, NULL);
PG_RETURN_POINT_P(result);
}
@@ -4247,8 +4290,8 @@ box_add(PG_FUNCTION_ARGS)
result = (BOX *) palloc(sizeof(BOX));
- point_add_point(&result->high, &box->high, p);
- point_add_point(&result->low, &box->low, p);
+ point_add_point(&result->high, &box->high, p, NULL);
+ point_add_point(&result->low, &box->low, p, NULL);
PG_RETURN_BOX_P(result);
}
@@ -4411,7 +4454,7 @@ path_add_pt(PG_FUNCTION_ARGS)
int i;
for (i = 0; i < path->npts; i++)
- point_add_point(&path->p[i], &path->p[i], point);
+ point_add_point(&path->p[i], &path->p[i], point, NULL);
PG_RETURN_PATH_P(path);
}
@@ -4469,7 +4512,7 @@ path_poly(PG_FUNCTION_ARGS)
/* This is not very consistent --- other similar cases return NULL ... */
if (!path->closed)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("open path cannot be converted to polygon")));
@@ -4519,7 +4562,9 @@ poly_center(PG_FUNCTION_ARGS)
result = (Point *) palloc(sizeof(Point));
- poly_to_circle(&circle, poly);
+ if (!poly_to_circle(&circle, poly, fcinfo->context))
+ PG_RETURN_NULL();
+
*result = circle.center;
PG_RETURN_POINT_P(result);
@@ -4981,7 +5026,7 @@ circle_add_pt(PG_FUNCTION_ARGS)
result = (CIRCLE *) palloc(sizeof(CIRCLE));
- point_add_point(&result->center, &circle->center, point);
+ point_add_point(&result->center, &circle->center, point, NULL);
result->radius = circle->radius;
PG_RETURN_CIRCLE_P(result);
@@ -5202,14 +5247,30 @@ circle_box(PG_FUNCTION_ARGS)
box = (BOX *) palloc(sizeof(BOX));
- delta = float8_div(circle->radius, sqrt(2.0));
+ delta = float8_div_safe(circle->radius, sqrt(2.0), fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
- box->high.x = float8_pl(circle->center.x, delta);
- box->low.x = float8_mi(circle->center.x, delta);
- box->high.y = float8_pl(circle->center.y, delta);
- box->low.y = float8_mi(circle->center.y, delta);
+ box->high.x = float8_pl_safe(circle->center.x, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ box->low.x = float8_mi_safe(circle->center.x, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ box->high.y = float8_pl_safe(circle->center.y, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ box->low.y = float8_mi_safe(circle->center.y, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
PG_RETURN_BOX_P(box);
+
+fail:
+ PG_RETURN_NULL();
}
/* box_circle()
@@ -5220,15 +5281,35 @@ box_circle(PG_FUNCTION_ARGS)
{
BOX *box = PG_GETARG_BOX_P(0);
CIRCLE *circle;
+ float8 x;
+ float8 y;
circle = (CIRCLE *) palloc(sizeof(CIRCLE));
- circle->center.x = float8_div(float8_pl(box->high.x, box->low.x), 2.0);
- circle->center.y = float8_div(float8_pl(box->high.y, box->low.y), 2.0);
+ x = float8_pl_safe(box->high.x, box->low.x, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
- circle->radius = point_dt(&circle->center, &box->high, NULL);
+ circle->center.x = float8_div_safe(x, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ y = float8_pl_safe(box->high.y, box->low.y, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ circle->center.y = float8_div_safe(y, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ circle->radius = point_dt(&circle->center, &box->high, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
PG_RETURN_CIRCLE_P(circle);
+
+fail:
+ PG_RETURN_NULL();
}
@@ -5292,10 +5373,11 @@ circle_poly(PG_FUNCTION_ARGS)
* XXX This algorithm should use weighted means of line segments
* rather than straight average values of points - tgl 97/01/21.
*/
-static void
-poly_to_circle(CIRCLE *result, POLYGON *poly)
+static bool
+poly_to_circle(CIRCLE *result, POLYGON *poly, Node *escontext)
{
int i;
+ float8 x;
Assert(poly->npts > 0);
@@ -5304,14 +5386,43 @@ poly_to_circle(CIRCLE *result, POLYGON *poly)
result->radius = 0;
for (i = 0; i < poly->npts; i++)
- point_add_point(&result->center, &result->center, &poly->p[i]);
- result->center.x = float8_div(result->center.x, poly->npts);
- result->center.y = float8_div(result->center.y, poly->npts);
+ {
+ point_add_point(&result->center,
+ &result->center,
+ &poly->p[i],
+ escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+ }
+
+ result->center.x = float8_div_safe(result->center.x,
+ poly->npts,
+ escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ result->center.y = float8_div_safe(result->center.y,
+ poly->npts,
+ escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
for (i = 0; i < poly->npts; i++)
- result->radius = float8_pl(result->radius,
- point_dt(&poly->p[i], &result->center, NULL));
- result->radius = float8_div(result->radius, poly->npts);
+ {
+ x = point_dt(&poly->p[i], &result->center, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ result->radius = float8_pl_safe(result->radius, x, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+ }
+
+ result->radius = float8_div_safe(result->radius, poly->npts, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ return true;
}
Datum
@@ -5322,7 +5433,8 @@ poly_circle(PG_FUNCTION_ARGS)
result = (CIRCLE *) palloc(sizeof(CIRCLE));
- poly_to_circle(result, poly);
+ if (!poly_to_circle(result, poly, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_CIRCLE_P(result);
}
--
2.34.1
v15-0018-introduce-float8-safe-function.patchtext/x-patch; charset=US-ASCII; name=v15-0018-introduce-float8-safe-function.patchDownload
From 72fa0fc1a8a154b68442d2a8731bb82e39eff4fa Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Wed, 10 Dec 2025 11:55:40 +0800
Subject: [PATCH v15 18/22] introduce float8 safe function
this patch introduce the following function:
float8_pl_safe
float8_mi_safe
float8_mul_safe
float8_div_safe
refactoring existing function is be too invasive. thus add these new functions.
It's close to existing non-safe version functions.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/include/utils/float.h | 79 ++++++++++++++++++++++++++++-----------
1 file changed, 58 insertions(+), 21 deletions(-)
diff --git a/src/include/utils/float.h b/src/include/utils/float.h
index 1d0cb026d4e..f46861ab5c3 100644
--- a/src/include/utils/float.h
+++ b/src/include/utils/float.h
@@ -109,16 +109,24 @@ float4_pl(const float4 val1, const float4 val2)
return result;
}
+static inline float8
+float8_pl_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+ float8 result;
+
+ result = val1 + val2;
+ if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
+ {
+ float_overflow_error(escontext);
+ return 0.0;
+ }
+ return result;
+}
+
static inline float8
float8_pl(const float8 val1, const float8 val2)
{
- float8 result;
-
- result = val1 + val2;
- if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error(NULL);
-
- return result;
+ return float8_pl_safe(val1, val2, NULL);;
}
static inline float4
@@ -133,16 +141,24 @@ float4_mi(const float4 val1, const float4 val2)
return result;
}
+static inline float8
+float8_mi_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+ float8 result;
+
+ result = val1 - val2;
+ if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
+ {
+ float_overflow_error(escontext);
+ return 0.0;
+ }
+ return result;
+}
+
static inline float8
float8_mi(const float8 val1, const float8 val2)
{
- float8 result;
-
- result = val1 - val2;
- if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error(NULL);
-
- return result;
+ return float8_mi_safe(val1, val2, NULL);
}
static inline float4
@@ -160,19 +176,33 @@ float4_mul(const float4 val1, const float4 val2)
}
static inline float8
-float8_mul(const float8 val1, const float8 val2)
+float8_mul_safe(const float8 val1, const float8 val2, struct Node *escontext)
{
float8 result;
result = val1 * val2;
if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error(NULL);
+ {
+ float_overflow_error(escontext);
+ return 0.0;
+ }
+
if (unlikely(result == 0.0) && val1 != 0.0 && val2 != 0.0)
- float_underflow_error(NULL);
+ {
+ float_underflow_error(escontext);
+ return 0.0;
+ }
return result;
}
+static inline float8
+float8_mul(const float8 val1, const float8 val2)
+{
+ return float8_mul_safe(val1, val2, NULL);
+}
+
+
static inline float4
float4_div(const float4 val1, const float4 val2)
{
@@ -190,21 +220,28 @@ float4_div(const float4 val1, const float4 val2)
}
static inline float8
-float8_div(const float8 val1, const float8 val2)
+float8_div_safe(const float8 val1, const float8 val2, struct Node *escontext)
{
float8 result;
if (unlikely(val2 == 0.0) && !isnan(val1))
- float_zero_divide_error(NULL);
+ float_zero_divide_error(escontext);
result = val1 / val2;
if (unlikely(isinf(result)) && !isinf(val1))
- float_overflow_error(NULL);
+ float_overflow_error(escontext);
if (unlikely(result == 0.0) && val1 != 0.0 && !isinf(val2))
- float_underflow_error(NULL);
+ float_underflow_error(escontext);
return result;
}
+static inline float8
+float8_div(const float8 val1, const float8 val2)
+{
+ return float8_div_safe(val1, val2, NULL);
+}
+
+
/*
* Routines for NaN-aware comparisons
*
--
2.34.1
v15-0021-CAST-expr-AS-newtype-DEFAULT-ON-ERROR.patchtext/x-patch; charset=UTF-8; name=v15-0021-CAST-expr-AS-newtype-DEFAULT-ON-ERROR.patchDownload
From 08a0595584f9540a54586cfce0c50ea410a592bc Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Wed, 10 Dec 2025 16:10:38 +0800
Subject: [PATCH v15 21/22] CAST(expr AS newtype DEFAULT ON ERROR)
* With this patchset, most functions in pg_cast.castfunc are now error-safe.
* CoerceViaIO and CoerceToDomain were already error-safe in the HEAD.
* this patch extends error-safe behavior to ArrayCoerceExpr.
* We also ensure that when a coercion fails, execution falls back to evaluating
the specified default node.
* The doc has been refined, though it may still need more review.
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}
[0]: https://git.postgresql.org/cgit/postgresql.git/commit/?id=aaaf9449ec6be62cb0d30ed3588dc384f56274b
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
contrib/citext/expected/citext.out | 5 +
contrib/citext/expected/citext_1.out | 5 +
contrib/citext/sql/citext.sql | 2 +
.../pg_stat_statements/expected/select.out | 23 +-
contrib/pg_stat_statements/sql/select.sql | 5 +
doc/src/sgml/syntax.sgml | 30 +
src/backend/executor/execExpr.c | 109 ++-
src/backend/executor/execExprInterp.c | 29 +
src/backend/jit/llvm/llvmjit_expr.c | 26 +
src/backend/nodes/nodeFuncs.c | 61 ++
src/backend/optimizer/util/clauses.c | 51 +-
src/backend/parser/gram.y | 22 +
src/backend/parser/parse_agg.c | 9 +
src/backend/parser/parse_coerce.c | 78 +-
src/backend/parser/parse_expr.c | 412 ++++++++-
src/backend/parser/parse_func.c | 3 +
src/backend/parser/parse_target.c | 14 +
src/backend/parser/parse_type.c | 14 +
src/backend/parser/parse_utilcmd.c | 2 +-
src/backend/utils/adt/arrayfuncs.c | 8 +
src/backend/utils/adt/ruleutils.c | 21 +
src/backend/utils/fmgr/fmgr.c | 14 +
src/include/executor/execExpr.h | 7 +
src/include/executor/executor.h | 1 +
src/include/fmgr.h | 3 +
src/include/nodes/execnodes.h | 21 +
src/include/nodes/parsenodes.h | 11 +
src/include/nodes/primnodes.h | 36 +
src/include/optimizer/optimizer.h | 2 +-
src/include/parser/parse_coerce.h | 15 +
src/include/parser/parse_node.h | 2 +
src/include/parser/parse_type.h | 2 +
src/test/regress/expected/cast.out | 810 ++++++++++++++++++
src/test/regress/expected/create_cast.out | 5 +
src/test/regress/expected/equivclass.out | 7 +
src/test/regress/parallel_schedule | 2 +-
src/test/regress/sql/cast.sql | 350 ++++++++
src/test/regress/sql/create_cast.sql | 1 +
src/test/regress/sql/equivclass.sql | 3 +
src/tools/pgindent/typedefs.list | 3 +
40 files changed, 2179 insertions(+), 45 deletions(-)
create mode 100644 src/test/regress/expected/cast.out
create mode 100644 src/test/regress/sql/cast.sql
diff --git a/contrib/citext/expected/citext.out b/contrib/citext/expected/citext.out
index 8c0bf54f0f3..33da19d8df4 100644
--- a/contrib/citext/expected/citext.out
+++ b/contrib/citext/expected/citext.out
@@ -10,6 +10,11 @@ WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid);
--------+---------
(0 rows)
+SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR: cannot cast type character to citext when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSI...
+ ^
+HINT: Safe type cast for user-defined types are not yet supported
-- Test the operators and indexing functions
-- Test = and <>.
SELECT 'a'::citext = 'a'::citext AS t;
diff --git a/contrib/citext/expected/citext_1.out b/contrib/citext/expected/citext_1.out
index c5e5f180f2b..647eea19142 100644
--- a/contrib/citext/expected/citext_1.out
+++ b/contrib/citext/expected/citext_1.out
@@ -10,6 +10,11 @@ WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid);
--------+---------
(0 rows)
+SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR: cannot cast type character to citext when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSI...
+ ^
+HINT: Safe type cast for user-defined types are not yet supported
-- Test the operators and indexing functions
-- Test = and <>.
SELECT 'a'::citext = 'a'::citext AS t;
diff --git a/contrib/citext/sql/citext.sql b/contrib/citext/sql/citext.sql
index aa1cf9abd5c..99794497d47 100644
--- a/contrib/citext/sql/citext.sql
+++ b/contrib/citext/sql/citext.sql
@@ -9,6 +9,8 @@ SELECT amname, opcname
FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod
WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid);
+SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR); --error
+
-- Test the operators and indexing functions
-- Test = and <>.
diff --git a/contrib/pg_stat_statements/expected/select.out b/contrib/pg_stat_statements/expected/select.out
index 75c896f3885..6e67997f0a2 100644
--- a/contrib/pg_stat_statements/expected/select.out
+++ b/contrib/pg_stat_statements/expected/select.out
@@ -73,6 +73,25 @@ SELECT 1 AS "int" OFFSET 2 FETCH FIRST 3 ROW ONLY;
-----
(0 rows)
+--error safe type cast
+SELECT CAST('a' AS int DEFAULT 2 ON CONVERSION ERROR);
+ int4
+------
+ 2
+(1 row)
+
+SELECT CAST('11' AS int DEFAULT 2 ON CONVERSION ERROR);
+ int4
+------
+ 11
+(1 row)
+
+SELECT CAST('12' AS numeric DEFAULT 2 ON CONVERSION ERROR);
+ numeric
+---------
+ 12
+(1 row)
+
-- DISTINCT and ORDER BY patterns
-- Try some query permutations which once produced identical query IDs
SELECT DISTINCT 1 AS "int";
@@ -222,6 +241,8 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
2 | 2 | SELECT $1 AS "int" ORDER BY 1
1 | 2 | SELECT $1 AS i UNION SELECT $2 ORDER BY i
1 | 1 | SELECT $1 || $2
+ 2 | 2 | SELECT CAST($1 AS int DEFAULT $2 ON CONVERSION ERROR)
+ 1 | 1 | SELECT CAST($1 AS numeric DEFAULT $2 ON CONVERSION ERROR)
2 | 2 | SELECT DISTINCT $1 AS "int"
0 | 0 | SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"
1 | 1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
@@ -230,7 +251,7 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
| | ) +
| | SELECT f FROM t ORDER BY f
1 | 1 | select $1::jsonb ? $2
-(17 rows)
+(19 rows)
SELECT pg_stat_statements_reset() IS NOT NULL AS t;
t
diff --git a/contrib/pg_stat_statements/sql/select.sql b/contrib/pg_stat_statements/sql/select.sql
index 11662cde08c..7ee8160fd84 100644
--- a/contrib/pg_stat_statements/sql/select.sql
+++ b/contrib/pg_stat_statements/sql/select.sql
@@ -25,6 +25,11 @@ SELECT 1 AS "int" LIMIT 3 OFFSET 3;
SELECT 1 AS "int" OFFSET 1 FETCH FIRST 2 ROW ONLY;
SELECT 1 AS "int" OFFSET 2 FETCH FIRST 3 ROW ONLY;
+--error safe type cast
+SELECT CAST('a' AS int DEFAULT 2 ON CONVERSION ERROR);
+SELECT CAST('11' AS int DEFAULT 2 ON CONVERSION ERROR);
+SELECT CAST('12' AS numeric DEFAULT 2 ON CONVERSION ERROR);
+
-- DISTINCT and ORDER BY patterns
-- Try some query permutations which once produced identical query IDs
SELECT DISTINCT 1 AS "int";
diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml
index 34c83880a66..32af9ea061c 100644
--- a/doc/src/sgml/syntax.sgml
+++ b/doc/src/sgml/syntax.sgml
@@ -2106,6 +2106,10 @@ CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable>
The <literal>CAST</literal> syntax conforms to SQL; the syntax with
<literal>::</literal> is historical <productname>PostgreSQL</productname>
usage.
+ The equivalent ON CONVERSION ERROR behavior is:
+<synopsis>
+CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable> ERROR ON CONVERSION ERROR )
+</synopsis>
</para>
<para>
@@ -2160,6 +2164,32 @@ CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable>
<xref linkend="sql-createcast"/>.
</para>
</note>
+
+ <sect3 id="sql-syntax-type-casts-safe">
+ <title>Safe Type Cast</title>
+ <para>
+ A type cast may occasionally fail. To guard against such failures, you can
+ provide an <literal>ON ERROR</literal> clause to handle potential errors.
+ The syntax for safe type cast is:
+<synopsis>
+CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable> DEFAULT <replaceable>expression</replaceable> ON CONVERSION ERROR )
+</synopsis>
+ If the type cast fails, instead of error out, evaluation falls back to the
+ default <replaceable>expression</replaceable> specified in the <literal>ON ERROR</literal> clause.
+ At present, this only support built-in type casts; see <xref linkend="catalog-pg-cast"/>.
+ User-defined type casts created with <link linkend="sql-createcast">CREATE CAST</link> are not supported.
+ </para>
+
+ <para>
+ Some examples:
+<screen>
+SELECT CAST(TEXT 'error' AS integer DEFAULT 3 ON CONVERSION ERROR);
+<lineannotation>Result: </lineannotation><computeroutput>3</computeroutput>
+SELECT CAST(TEXT 'error' AS numeric DEFAULT 1.1 ON CONVERSION ERROR);
+<lineannotation>Result: </lineannotation><computeroutput>1.1</computeroutput>
+</screen>
+ </para>
+ </sect3>
</sect2>
<sect2 id="sql-syntax-collate-exprs">
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index b05ff476a63..e6738c0ae75 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,
@@ -170,6 +173,47 @@ ExecInitExpr(Expr *node, PlanState *parent)
return state;
}
+/*
+ * ExecInitExprSafe: soft error variant of ExecInitExpr.
+ *
+ * use it only for expression nodes support soft errors, not all expression
+ * nodes support it.
+*/
+ExprState *
+ExecInitExprSafe(Expr *node, PlanState *parent)
+{
+ ExprState *state;
+ ExprEvalStep scratch = {0};
+
+ /* Special case: NULL expression produces a NULL ExprState pointer */
+ if (node == NULL)
+ return NULL;
+
+ /* Initialize ExprState with empty step list */
+ state = makeNode(ExprState);
+ state->expr = node;
+ state->parent = parent;
+ state->ext_params = NULL;
+ state->escontext = makeNode(ErrorSaveContext);
+ state->escontext->type = T_ErrorSaveContext;
+ state->escontext->error_occurred = false;
+ state->escontext->details_wanted = false;
+
+ /* Insert setup steps as needed */
+ ExecCreateExprSetupSteps(state, (Node *) node);
+
+ /* Compile the expression proper */
+ ExecInitExprRec(node, state, &state->resvalue, &state->resnull);
+
+ /* Finally, append a DONE step */
+ scratch.opcode = EEOP_DONE_RETURN;
+ ExprEvalPushStep(state, &scratch);
+
+ ExecReadyExpr(state);
+
+ return state;
+}
+
/*
* ExecInitExprWithParams: prepare a standalone expression tree for execution
*
@@ -1701,6 +1745,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
elemstate->innermost_caseval = (Datum *) palloc(sizeof(Datum));
elemstate->innermost_casenull = (bool *) palloc(sizeof(bool));
+ elemstate->escontext = state->escontext;
ExecInitExprRec(acoerce->elemexpr, elemstate,
&elemstate->resvalue, &elemstate->resnull);
@@ -2177,6 +2222,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;
@@ -2743,7 +2796,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;
@@ -4741,6 +4794,60 @@ ExecBuildParamSetEqual(TupleDesc desc,
return state;
}
+/*
+ * Push steps to evaluate a SafeTypeCastExpr and its various subsidiary
+ * expressions.
+ */
+static void
+ExecInitSafeTypeCastExpr(SafeTypeCastExpr *stcexpr , ExprState *state,
+ Datum *resv, bool *resnull,
+ ExprEvalStep *scratch)
+{
+ /*
+ * If we can not coerce to the target type, fallback to the DEFAULT
+ * expression specified in the ON CONVERSION ERROR clause, and we are done.
+ */
+ if (stcexpr->cast_expr == NULL)
+ {
+ ExecInitExprRec((Expr *) stcexpr->default_expr,
+ state, resv, resnull);
+ return;
+ }
+ else
+ {
+ SafeTypeCastState *stcstate;
+
+ stcstate = palloc0(sizeof(SafeTypeCastState));
+ stcstate->stcexpr = stcexpr;
+ stcstate->escontext.type = T_ErrorSaveContext;
+ state->escontext = &stcstate->escontext;
+
+ /* evaluate argument expression into step's result area */
+ ExecInitExprRec((Expr *) stcexpr->cast_expr,
+ state, resv, resnull);
+ scratch->opcode = EEOP_SAFETYPE_CAST;
+ scratch->d.stcexpr.stcstate = stcstate;
+ ExprEvalPushStep(state, scratch);
+
+ /*
+ * Steps to evaluate the DEFAULT expression. Skip it if this is a
+ * binary coercion cast.
+ */
+ if (!IsA(stcexpr->cast_expr, RelabelType))
+ {
+ ErrorSaveContext *saved_escontext;
+
+ saved_escontext = state->escontext;
+ state->escontext = NULL;
+ ExecInitExprRec((Expr *) stcstate->stcexpr->default_expr,
+ state, resv, resnull);
+ state->escontext = saved_escontext;
+ }
+
+ 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 1d88cdd2cb4..ca6e0fd56cd 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,28 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
EEO_NEXT();
}
+ EEO_CASE(EEOP_SAFETYPE_CAST)
+ {
+ SafeTypeCastState *stcstate = op->d.stcexpr.stcstate;
+
+ if (SOFT_ERROR_OCCURRED(&stcstate->escontext))
+ {
+ *op->resvalue = (Datum) 0;
+ *op->resnull = true;
+
+ /*
+ * Reset for next use such as for catching errors when coercing
+ * a expression.
+ */
+ stcstate->escontext.error_occurred = false;
+ stcstate->escontext.details_wanted = false;
+
+ EEO_NEXT();
+ }
+ else
+ EEO_JUMP(stcstate->jump_end);
+ }
+
EEO_CASE(EEOP_JSONEXPR_PATH)
{
/* too complex for an inline implementation */
@@ -3644,6 +3667,12 @@ 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/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index ac88881e995..bc7cd3283a5 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -2256,6 +2256,32 @@ llvm_compile_expr(ExprState *state)
LLVMBuildBr(b, opblocks[opno + 1]);
break;
+ case EEOP_SAFETYPE_CAST:
+ {
+ SafeTypeCastState *stcstate = op->d.stcexpr.stcstate;
+
+ if (SOFT_ERROR_OCCURRED(&stcstate->escontext))
+ {
+ /*
+ * Reset for next use such as for catching errors when
+ * coercing a expression.
+ */
+ stcstate->escontext.error_occurred = false;
+ stcstate->escontext.details_wanted = false;
+
+ /* set resnull to true */
+ LLVMBuildStore(b, l_sbool_const(1), v_resnullp);
+ /* reset resvalue */
+ LLVMBuildStore(b, l_datum_const(0), v_resvaluep);
+
+ LLVMBuildBr(b, opblocks[opno + 1]);
+ }
+ else
+ 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 d228318dc72..b3ae69068d0 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;
@@ -1550,6 +1561,9 @@ exprLocation(const Node *expr)
/* just use leftmost argument's location */
loc = exprLocation((Node *) ((const RowCompareExpr *) expr)->largs);
break;
+ case T_SafeTypeCastExpr:
+ loc = ((const SafeTypeCastExpr *) expr)->location;
+ break;
case T_CoalesceExpr:
/* COALESCE keyword should always be the first thing */
loc = ((const CoalesceExpr *) expr)->location;
@@ -2321,6 +2335,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:
@@ -3330,6 +3356,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;
@@ -4464,6 +4503,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->raw_default))
+ 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/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index bda4c4eb292..e6d8b6c467f 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -2447,7 +2447,8 @@ estimate_expression_value(PlannerInfo *root, Node *node)
((Node *) evaluate_expr((Expr *) (node), \
exprType((Node *) (node)), \
exprTypmod((Node *) (node)), \
- exprCollation((Node *) (node))))
+ exprCollation((Node *) (node)), \
+ false))
/*
* Recursive guts of eval_const_expressions/estimate_expression_value
@@ -2958,6 +2959,32 @@ 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);
+
+ /*
+ * We must not reduce any recognizably constant subexpressions
+ * in cast_expr here, since we don’t want it to fail
+ * prematurely.
+ */
+ 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:
@@ -3380,7 +3407,8 @@ eval_const_expressions_mutator(Node *node,
return (Node *) evaluate_expr((Expr *) svf,
svf->type,
svf->typmod,
- InvalidOid);
+ InvalidOid,
+ false);
else
return copyObject((Node *) svf);
}
@@ -4698,7 +4726,7 @@ evaluate_function(Oid funcid, Oid result_type, int32 result_typmod,
newexpr->location = -1;
return evaluate_expr((Expr *) newexpr, result_type, result_typmod,
- result_collid);
+ result_collid, false);
}
/*
@@ -5152,10 +5180,14 @@ sql_inline_error_callback(void *arg)
*
* We use the executor's routine ExecEvalExpr() to avoid duplication of
* code and ensure we get the same result as the executor would get.
+ *
+ * When error_safe set to true, we will evaluate the constant expression in a
+ * error safe way. If the evaluation fails, return NULL instead of throwing
+ * error.
*/
Expr *
evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
- Oid result_collation)
+ Oid result_collation, bool error_safe)
{
EState *estate;
ExprState *exprstate;
@@ -5180,7 +5212,10 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
* Prepare expr for execution. (Note: we can't use ExecPrepareExpr
* because it'd result in recursively invoking eval_const_expressions.)
*/
- exprstate = ExecInitExpr(expr, NULL);
+ if (error_safe)
+ exprstate = ExecInitExprSafe(expr, NULL);
+ else
+ exprstate = ExecInitExpr(expr, NULL);
/*
* And evaluate it.
@@ -5200,6 +5235,12 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
/* Get back to outer memory context */
MemoryContextSwitchTo(oldcontext);
+ if (error_safe && SOFT_ERROR_OCCURRED(exprstate->escontext))
+ {
+ FreeExecutorState(estate);
+ return NULL;
+ }
+
/*
* Must copy result out of sub-context used by expression eval.
*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c3a0a354a9c..8a27e045bc0 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -16013,6 +16013,28 @@ func_expr_common_subexpr:
}
| CAST '(' a_expr AS Typename ')'
{ $$ = makeTypeCast($3, $5, @1); }
+ | CAST '(' a_expr AS Typename ERROR_P ON CONVERSION_P ERROR_P ')'
+ { $$ = makeTypeCast($3, $5, @1); }
+ | CAST '(' a_expr AS Typename NULL_P ON CONVERSION_P ERROR_P ')'
+ {
+ TypeCast *cast = (TypeCast *) makeTypeCast($3, $5, @1);
+
+ SafeTypeCast *safecast = makeNode(SafeTypeCast);
+ safecast->cast = (Node *) cast;
+ safecast->raw_default = makeNullAConst(-1);;
+
+ $$ = (Node *) safecast;
+ }
+ | CAST '(' a_expr AS Typename DEFAULT a_expr ON CONVERSION_P ERROR_P ')'
+ {
+ TypeCast *cast = (TypeCast *) makeTypeCast($3, $5, @1);
+
+ SafeTypeCast *safecast = makeNode(SafeTypeCast);
+ safecast->cast = (Node *) cast;
+ safecast->raw_default = $7;
+
+ $$ = (Node *) safecast;
+ }
| EXTRACT '(' extract_list ')'
{
$$ = (Node *) makeFuncCall(SystemFuncName("extract"),
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index b8340557b34..de77f6e9302 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -490,6 +490,12 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
err = _("grouping operations are not allowed in check constraints");
break;
+ case EXPR_KIND_CAST_DEFAULT:
+ if (isAgg)
+ err = _("aggregate functions are not allowed in CAST DEFAULT expressions");
+ else
+ err = _("grouping operations are not allowed in CAST DEFAULT expressions");
+ break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
@@ -983,6 +989,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_DOMAIN_CHECK:
err = _("window functions are not allowed in check constraints");
break;
+ case EXPR_KIND_CAST_DEFAULT:
+ err = _("window functions are not allowed in CAST DEFAULT expressions");
+ break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
err = _("window functions are not allowed in DEFAULT expressions");
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 78b1e366ad7..4ae87e2030b 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -81,6 +81,31 @@ coerce_to_target_type(ParseState *pstate, Node *expr, Oid exprtype,
CoercionContext ccontext,
CoercionForm cformat,
int location)
+{
+ return coerce_to_target_type_extended(pstate,
+ expr,
+ exprtype,
+ targettype,
+ targettypmod,
+ ccontext,
+ cformat,
+ location,
+ NULL);
+}
+
+/*
+ * escontext: If not NULL, expr (Unknown Const node type) will be coerced to the
+ * target type in an error-safe way. If it fails, NULL is returned.
+ *
+ * For other parameters, see above coerce_to_target_type.
+ */
+Node *
+coerce_to_target_type_extended(ParseState *pstate, Node *expr, Oid exprtype,
+ Oid targettype, int32 targettypmod,
+ CoercionContext ccontext,
+ CoercionForm cformat,
+ int location,
+ Node *escontext)
{
Node *result;
Node *origexpr;
@@ -102,9 +127,12 @@ 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_extend(pstate, expr, exprtype,
+ targettype, targettypmod,
+ ccontext, cformat, location,
+ escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return NULL;
/*
* If the target is a fixed-length type, it may need a length coercion as
@@ -158,6 +186,18 @@ Node *
coerce_type(ParseState *pstate, Node *node,
Oid inputTypeId, Oid targetTypeId, int32 targetTypeMod,
CoercionContext ccontext, CoercionForm cformat, int location)
+{
+ return coerce_type_extend(pstate, node,
+ inputTypeId, targetTypeId, targetTypeMod,
+ ccontext, cformat, location, NULL);
+}
+
+Node *
+coerce_type_extend(ParseState *pstate, Node *node,
+ Oid inputTypeId, Oid targetTypeId, int32 targetTypeMod,
+ CoercionContext ccontext, CoercionForm cformat,
+ int location,
+ Node *escontext)
{
Node *result;
CoercionPathType pathtype;
@@ -256,6 +296,8 @@ coerce_type(ParseState *pstate, Node *node,
int32 inputTypeMod;
Type baseType;
ParseCallbackState pcbstate;
+ char *string;
+ bool coercion_failed = false;
/*
* If the target type is a domain, we want to call its base type's
@@ -308,21 +350,27 @@ 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 (con->constisnull)
+ string = NULL;
else
- newcon->constvalue = stringTypeDatum(baseType,
- NULL,
- inputTypeMod);
+ string = DatumGetCString(con->constvalue);
+
+ if (!stringTypeDatumSafe(baseType,
+ string,
+ inputTypeMod,
+ escontext,
+ &newcon->constvalue))
+ {
+ coercion_failed = true;
+ newcon->constisnull = true;
+ }
/*
* 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 (!coercion_failed && !con->constisnull && newcon->constlen == -1)
newcon->constvalue =
PointerGetDatum(PG_DETOAST_DATUM(newcon->constvalue));
@@ -339,7 +387,7 @@ 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 (!coercion_failed && !con->constisnull && !newcon->constbyval)
{
Datum val2;
@@ -358,8 +406,10 @@ coerce_type(ParseState *pstate, Node *node,
result = (Node *) newcon;
- /* If target is a domain, apply constraints. */
- if (baseTypeId != targetTypeId)
+ if (coercion_failed)
+ result = NULL;
+ else if (baseTypeId != targetTypeId)
+ /* If target is a domain, apply constraints. */
result = coerce_to_domain(result,
baseTypeId, baseTypeMod,
targetTypeId,
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 4524e49c326..7d3ed0eb890 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -17,6 +17,7 @@
#include "access/htup_details.h"
#include "catalog/pg_aggregate.h"
+#include "catalog/pg_cast.h"
#include "catalog/pg_type.h"
#include "miscadmin.h"
#include "nodes/makefuncs.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,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 *can_coerce);
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 +79,10 @@ 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 void CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr,
+ Node *sourceexpr, Oid inputType,
+ Oid targetType);
static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
static Node *transformJsonObjectConstructor(ParseState *pstate,
JsonObjectConstructor *ctor);
@@ -164,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;
@@ -564,6 +575,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
case EXPR_KIND_VALUES_SINGLE:
case EXPR_KIND_CHECK_CONSTRAINT:
case EXPR_KIND_DOMAIN_CHECK:
+ case EXPR_KIND_CAST_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
case EXPR_KIND_INDEX_EXPRESSION:
case EXPR_KIND_INDEX_PREDICATE:
@@ -1824,6 +1836,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_DOMAIN_CHECK:
err = _("cannot use subquery in check constraint");
break;
+ case EXPR_KIND_CAST_DEFAULT:
+ err = _("cannot use subquery in CAST DEFAULT expression");
+ break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
err = _("cannot use subquery in DEFAULT expression");
@@ -2011,17 +2026,24 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
* 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().
+ *
+ * Most of the time, can_coerce will be NULL. It is not NULL only when
+ * performing parse analysis for CAST(DEFAULT ... ON CONVERSION ERROR)
+ * expression. It's default to true. If coercing array elements fails, it will
+ * be set to 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;
List *newcoercedelems = NIL;
ListCell *element;
Oid coerce_type;
+ Oid coerce_type_coll;
bool coerce_hard;
+ ErrorSaveContext escontext = {T_ErrorSaveContext};
/*
* Transform the element expressions
@@ -2045,9 +2067,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
@@ -2088,6 +2111,9 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
}
else
{
+ /* Target type must valid for CAST DEFAULT */
+ Assert(can_coerce == NULL);
+
/* Can't handle an empty array without a target type */
if (newelems == NIL)
ereport(ERROR,
@@ -2125,6 +2151,8 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
coerce_hard = false;
}
+ coerce_type_coll = get_typcollation(coerce_type);
+
/*
* Coerce elements to target type
*
@@ -2134,28 +2162,82 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
* If the array's type was merely derived from the common type of its
* elements, then the elements are implicitly coerced to the common type.
* This is consistent with other uses of select_common_type().
+ *
+ * If can_coerce is not NULL, we need to safely test whether each element
+ * can be coerced to the target type, similar to what is done in
+ * transformTypeSafeCast.
*/
foreach(element, newelems)
{
Node *e = (Node *) lfirst(element);
- Node *newe;
+ Node *newe = NULL;
if (coerce_hard)
{
- newe = coerce_to_target_type(pstate, e,
- exprType(e),
- coerce_type,
- typmod,
- COERCION_EXPLICIT,
- COERCE_EXPLICIT_CAST,
- -1);
- if (newe == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_CANNOT_COERCE),
- errmsg("cannot cast type %s to %s",
- format_type_be(exprType(e)),
- format_type_be(coerce_type)),
- parser_errposition(pstate, exprLocation(e))));
+ /*
+ * Can not coerce, just append the transformed element expression to
+ * the list.
+ */
+ if (can_coerce && (!*can_coerce))
+ newe = e;
+ else
+ {
+ Node *ecopy = NULL;
+
+ if (can_coerce)
+ ecopy = copyObject(e);
+
+ newe = coerce_to_target_type_extended(pstate, e,
+ exprType(e),
+ coerce_type,
+ typmod,
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ -1,
+ (Node *) &escontext);
+ if (newe == NULL)
+ {
+ if (!can_coerce)
+ ereport(ERROR,
+ (errcode(ERRCODE_CANNOT_COERCE),
+ errmsg("cannot cast type %s to %s",
+ format_type_be(exprType(e)),
+ format_type_be(coerce_type)),
+ parser_errposition(pstate, exprLocation(e))));
+ else
+ {
+ /*
+ * Can not coerce, just append the transformed element
+ * expression to the list.
+ */
+ newe = ecopy;
+ *can_coerce = false;
+ }
+ }
+ else if (can_coerce && IsA(ecopy, Const) && IsA(newe, FuncExpr))
+ {
+ Node *result;
+
+ /*
+ * pre-evaluate simple constant cast expressions in a way
+ * that tolerate errors.
+ */
+ FuncExpr *newexpr = (FuncExpr *) copyObject(newe);
+
+ assign_expr_collations(pstate, (Node *) newexpr);
+
+ result = (Node *) evaluate_expr((Expr *) newexpr,
+ coerce_type,
+ typmod,
+ coerce_type_coll,
+ true);
+ if (result == NULL)
+ {
+ newe = ecopy;
+ *can_coerce = false;
+ }
+ }
+ }
}
else
newe = coerce_to_common_type(pstate, e,
@@ -2743,7 +2825,8 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
(A_ArrayExpr *) arg,
targetBaseType,
elementType,
- targetBaseTypmod);
+ targetBaseTypmod,
+ NULL);
}
else
expr = transformExprRecurse(pstate, arg);
@@ -2780,6 +2863,291 @@ 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;
+ TypeCast *tcast = (TypeCast *) tc->cast;
+ Node *source_expr;
+ Node *srcexpr;
+ Node *defexpr;
+ Node *cast_expr = NULL;
+ Oid inputType;
+ Oid targetType;
+ int32 targetTypmod;
+ Oid targetTypecoll;
+ Oid targetBaseType;
+ int32 targetBaseTypmod;
+ bool can_coerce = true;
+
+ /* Look up the type name first */
+ typenameTypeIdAndMod(pstate, tcast->typeName, &targetType, &targetTypmod);
+ targetBaseTypmod = targetTypmod;
+
+ targetTypecoll = get_typcollation(targetType);
+
+ targetBaseType = getBaseTypeAndTypmod(targetType, &targetBaseTypmod);
+
+ /* now looking at DEFAULT expression */
+ defexpr = transformExpr(pstate, tc->raw_default, EXPR_KIND_CAST_DEFAULT);
+
+ defexpr = coerce_to_target_type(pstate, defexpr, exprType(defexpr),
+ targetType, targetTypmod,
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ exprLocation(defexpr));
+ if (defexpr == NULL)
+ ereport(ERROR,
+ errcode(ERRCODE_CANNOT_COERCE),
+ errmsg("cannot coerce %s expression to type %s",
+ "CAST DEFAULT",
+ format_type_be(targetType)),
+ parser_coercion_errposition(pstate, exprLocation(tc->raw_default), defexpr));
+
+ assign_expr_collations(pstate, defexpr);
+
+ /*
+ * The collation of DEFAULT expression must match the collation of the
+ * target type.
+ */
+ if (OidIsValid(targetTypecoll))
+ {
+ Oid defColl = exprCollation(defexpr);
+
+ if (OidIsValid(defColl) && targetTypecoll != defColl)
+ ereport(ERROR,
+ errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("the collation of CAST DEFAULT expression conflicts with target type collation"),
+ errdetail("\"%s\" versus \"%s\"",
+ get_collation_name(targetTypecoll),
+ get_collation_name(defColl)),
+ parser_errposition(pstate, exprLocation(defexpr)));
+ }
+
+ /*
+ * If the type cast 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(tcast->arg, A_ArrayExpr))
+ {
+ 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.
+ */
+ elementType = get_element_type(targetBaseType);
+
+ if (OidIsValid(elementType))
+ {
+ source_expr = transformArrayExpr(pstate,
+ (A_ArrayExpr *) tcast->arg,
+ targetBaseType,
+ elementType,
+ targetBaseTypmod,
+ &can_coerce);
+ }
+ else
+ source_expr = transformExprRecurse(pstate, tcast->arg);
+ }
+ else
+ source_expr = transformExprRecurse(pstate, tcast->arg);
+
+ /*
+ * Since we can't be certain that coerce_to_target_type_extended won't
+ * modify the source expression, we create a copy of it first. This ensures
+ * the transformed source expression is correctly passed to SafeTypeCastExpr
+ */
+ srcexpr = copyObject(source_expr);
+
+ inputType = exprType(source_expr);
+ if (inputType == InvalidOid)
+ return (Node *) NULL; /* return NULL if NULL input */
+
+ if (can_coerce)
+ {
+ ErrorSaveContext escontext = {T_ErrorSaveContext};
+
+ cast_expr = coerce_to_target_type_extended(pstate, source_expr, inputType,
+ targetType, targetTypmod,
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ tcast->location,
+ (Node *) &escontext);
+ if (cast_expr == NULL)
+ can_coerce = false;
+
+ CoercionErrorSafeCheck(pstate, cast_expr, source_expr, inputType,
+ targetType);
+ }
+
+ if (IsA(srcexpr, Const) && cast_expr && IsA(cast_expr, FuncExpr))
+ {
+ Node *result;
+
+ /*
+ * pre-evaluate simple constant cast expressions in a error safe way
+ *
+ * Rationale:
+ * 1. When deparsing safe cast expressions (or in other cases),
+ * eval_const_expressions might be invoked, but it cannot
+ * handle errors gracefully.
+ * 2. If the cast expression involves only simple constants, we
+ * can safely evaluate it ahead of time. If the evaluation
+ * fails, it indicates that such a cast is not possible, and
+ * we can then fall back to the CAST DEFAULT expression.
+ * 3. Even if FuncExpr (for castfunc) node has three arguments,
+ * the second and third arguments will also be constants per
+ * coerce_to_target_type. Evaluating a FuncExpr whose
+ * arguments are all Const is safe.
+ */
+ FuncExpr *newexpr = (FuncExpr *) copyObject(cast_expr);
+
+ assign_expr_collations(pstate, (Node *) newexpr);
+
+ result = (Node *) evaluate_expr((Expr *) newexpr,
+ targetType,
+ targetTypmod,
+ targetTypecoll,
+ true);
+ if (result == NULL)
+ {
+ /* can not coerce, set can_coerce to false */
+ can_coerce = false;
+ cast_expr = NULL;
+ }
+ }
+
+ Assert(can_coerce || cast_expr == NULL);
+
+ result = makeNode(SafeTypeCastExpr);
+ result->source_expr = srcexpr;
+ result->cast_expr = cast_expr;
+ result->default_expr = defexpr;
+ result->resulttype = targetType;
+ result->resulttypmod = targetTypmod;
+ result->resultcollid = targetTypecoll;
+ result->location = tcast->location;
+
+ return (Node *) result;
+}
+
+/*
+ * Check coercion is error safe or not. If not then report error
+ */
+static void
+CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr, Node *sourceexpr, Oid inputType,
+ Oid targetType)
+{
+ HeapTuple tuple;
+ bool errorsafe = true;
+ bool userdefined = false;
+ Oid inputBaseType;
+ Oid targetBaseType;
+ Oid inputElementType;
+ Oid inputElementBaseType;
+ Oid targetElementType;
+ Oid targetElementBaseType;
+
+ /*
+ * Binary coercion cast is equivalent to no cast at all. CoerceViaIO can
+ * also be evaluated in an error-safe manner. So skip these two cases.
+ */
+ if (!castexpr || IsA(castexpr, RelabelType) ||
+ IsA(castexpr, CoerceViaIO))
+ return;
+
+ /*
+ * Type cast involving with some types is not error safe, do the check now.
+ * We need consider domain over array and array over domain scarenio. We
+ * already checked user-defined type cast above.
+ */
+ inputElementType = get_element_type(inputType);
+
+ if (OidIsValid(inputElementType))
+ inputElementBaseType = getBaseType(inputElementType);
+ else
+ {
+ inputBaseType = getBaseType(inputType);
+
+ inputElementBaseType = get_element_type(inputBaseType);
+ if (!OidIsValid(inputElementBaseType))
+ inputElementBaseType = inputBaseType;
+ }
+
+ targetElementType = get_element_type(targetType);
+
+ if (OidIsValid(targetElementType))
+ targetElementBaseType = getBaseType(targetElementType);
+ else
+ {
+ targetBaseType = getBaseType(targetType);
+
+ targetElementBaseType = get_element_type(targetBaseType);
+ if (!OidIsValid(targetElementBaseType))
+ targetElementBaseType = targetBaseType;
+ }
+
+ if (inputElementBaseType == MONEYOID ||
+ targetElementBaseType == MONEYOID ||
+ (inputElementBaseType == CIRCLEOID &&
+ targetElementBaseType == POLYGONOID))
+ {
+ /*
+ * Casts involving MONEY type are not error safe. The CIRCLE to POLYGON
+ * cast is also unsafe because it relies on a SQL-language function;
+ * only C-language functions are currently supported for error safe
+ * casts.
+ */
+ errorsafe = false;
+ }
+
+ if (errorsafe)
+ {
+ /* Look in pg_cast */
+ tuple = SearchSysCache2(CASTSOURCETARGET,
+ ObjectIdGetDatum(inputElementBaseType),
+ ObjectIdGetDatum(targetElementBaseType));
+
+ if (HeapTupleIsValid(tuple))
+ {
+ Form_pg_cast castForm = (Form_pg_cast) GETSTRUCT(tuple);
+
+ if (castForm->oid > FirstUnpinnedObjectId)
+ {
+ errorsafe = false;
+ userdefined = true;
+ }
+
+ ReleaseSysCache(tuple);
+ }
+ }
+
+ if (!errorsafe)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot cast type %s to %s when %s expression is specified in %s",
+ format_type_be(inputType),
+ format_type_be(targetType),
+ "DEFAULT",
+ "CAST ... ON CONVERSION ERROR"),
+ userdefined
+ ? errhint("Safe type cast for user-defined types are not yet supported")
+ : errhint("Explicit cast is defined but definition is not error safe"),
+ parser_errposition(pstate, exprLocation(sourceexpr)));
+}
+
+
/*
* Handle an explicit COLLATE clause.
*
@@ -3193,6 +3561,8 @@ ParseExprKindName(ParseExprKind exprKind)
case EXPR_KIND_CHECK_CONSTRAINT:
case EXPR_KIND_DOMAIN_CHECK:
return "CHECK";
+ case EXPR_KIND_CAST_DEFAULT:
+ return "CAST DEFAULT";
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
return "DEFAULT";
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 778d69c6f3c..a90705b9847 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2743,6 +2743,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
case EXPR_KIND_DOMAIN_CHECK:
err = _("set-returning functions are not allowed in check constraints");
break;
+ case EXPR_KIND_CAST_DEFAULT:
+ err = _("set-returning functions are not allowed in CAST DEFAULT expressions");
+ break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
err = _("set-returning functions are not allowed in DEFAULT expressions");
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 905c975d83b..dc03cf4ce74 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1822,6 +1822,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 7713bdc6af0..fef83ce73b4 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,19 @@ stringTypeDatum(Type tp, char *string, int32 atttypmod)
return OidInputFunctionCall(typinput, string, typioparam, atttypmod);
}
+/* error-safe version of stringTypeDatum */
+bool
+stringTypeDatumSafe(Type tp, char *string, int32 atttypmod,
+ Node *escontext, Datum *result)
+{
+ Form_pg_type typform = (Form_pg_type) GETSTRUCT(tp);
+ Oid typinput = typform->typinput;
+ Oid typioparam = getTypeIOParam(tp);
+
+ return OidInputFunctionCallSafe(typinput, string, typioparam, atttypmod,
+ escontext, result);
+}
+
/*
* Given a typeid, return the type's typrelid (associated relation), if any.
* Returns InvalidOid if type is not a composite type.
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..6df0b975a88 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -4630,7 +4630,7 @@ transformPartitionBoundValue(ParseState *pstate, Node *val,
assign_expr_collations(pstate, value);
value = (Node *) expression_planner((Expr *) value);
value = (Node *) evaluate_expr((Expr *) value, colType, colTypmod,
- partCollation);
+ partCollation, false);
if (!IsA(value, Const))
elog(ERROR, "could not evaluate partition bound expression");
}
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index cc76bdde723..2a3b4649123 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -3289,6 +3289,14 @@ array_map(Datum arrayd,
/* Apply the given expression to source element */
values[i] = ExecEvalExpr(exprstate, econtext, &nulls[i]);
+ /* Exit early if the evaluation fails */
+ 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 6cf90be40bb..50ae41837ba 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -10562,6 +10562,27 @@ get_rule_expr(Node *node, deparse_context *context,
}
break;
+ case T_SafeTypeCastExpr:
+ {
+ SafeTypeCastExpr *stcexpr = castNode(SafeTypeCastExpr, node);
+
+ /*
+ * Here, we cannot deparsing cast_expr directly, since
+ * transformTypeSafeCast may have folded it into a simple
+ * constant or NULL. Instead, we use source_expr and
+ * default_expr to reconstruct the CAST DEFAULT clause.
+ */
+ 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 0fe63c6bb83..c9e5d0de8ed 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -1759,6 +1759,20 @@ OidInputFunctionCall(Oid functionId, char *str, Oid typioparam, int32 typmod)
return InputFunctionCall(&flinfo, str, typioparam, typmod);
}
+/* error-safe version of OidInputFunctionCall */
+bool
+OidInputFunctionCallSafe(Oid functionId, char *str, Oid typioparam,
+ int32 typmod, Node *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 75366203706..937cbf3bc9b 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_SAFETYPE_CAST */
+ struct
+ {
+ struct SafeTypeCastState *stcstate;
+ } stcexpr;
+
/* for EEOP_JSONEXPR_PATH */
struct
{
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index fa2b657fb2f..f99fc26eb1f 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -324,6 +324,7 @@ ExecProcNode(PlanState *node)
* prototypes from functions in execExpr.c
*/
extern ExprState *ExecInitExpr(Expr *node, PlanState *parent);
+extern ExprState *ExecInitExprSafe(Expr *node, PlanState *parent);
extern ExprState *ExecInitExprWithParams(Expr *node, ParamListInfo ext_params);
extern ExprState *ExecInitQual(List *qual, PlanState *parent);
extern ExprState *ExecInitCheck(List *qual, PlanState *parent);
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index c0dbe85ed1c..a9357b98d70 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, Node *escontext,
+ Datum *result);
extern char *OutputFunctionCall(FmgrInfo *flinfo, Datum val);
extern char *OidOutputFunctionCall(Oid functionId, Datum val);
extern Datum ReceiveFunctionCall(FmgrInfo *flinfo, StringInfo buf,
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 64ff6996431..9018e190cc7 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1059,6 +1059,27 @@ typedef struct DomainConstraintState
ExprState *check_exprstate; /* check_expr's eval state, or NULL */
} DomainConstraintState;
+typedef struct SafeTypeCastState
+{
+ SafeTypeCastExpr *stcexpr;
+
+ /*
+ * Address to jump to when skipping all the steps to evaulate the default
+ * expression.
+ */
+ int jump_end;
+
+ /*
+ * For error-safe evaluation of coercions. 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 d14294a4ece..82b0fb83b4b 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -400,6 +400,17 @@ typedef struct TypeCast
ParseLoc location; /* token location, or -1 if unknown */
} TypeCast;
+/*
+ * SafeTypeCast - a CAST(source_expr AS target_type) DEFAULT ON CONVERSION ERROR
+ * construct
+ */
+typedef struct SafeTypeCast
+{
+ NodeTag type;
+ Node *cast; /* TypeCast expression */
+ Node *raw_default; /* untransformed DEFAULT expression */
+} SafeTypeCast;
+
/*
* CollateClause - a COLLATE expression
*/
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 1b4436f2ff6..1401109fb3c 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -769,6 +769,42 @@ 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
+{
+ Expr xpr;
+
+ /* transformed source expression */
+ Node *source_expr;
+
+ /*
+ * The transformed cast expression; NULL if we can not cocerce source
+ * expression to the target type
+ */
+ Node *cast_expr pg_node_attr(query_jumble_ignore);
+
+ /* Fall back to the default expression if cast evaluation fails */
+ Node *default_expr;
+
+ /* cast result data type */
+ Oid resulttype;
+
+ /* cast result data type typmod (usually -1) */
+ int32 resulttypmod;
+
+ /* cast result data type collation */
+ Oid resultcollid;
+
+ /* Original SafeTypeCastExpr's location */
+ ParseLoc location;
+
+} SafeTypeCastExpr;
+
/*
* FuncExpr - expression node for a function call
*
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index 44ec5296a18..587e711596a 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -143,7 +143,7 @@ extern void convert_saop_to_hashed_saop(Node *node);
extern Node *estimate_expression_value(PlannerInfo *root, Node *node);
extern Expr *evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
- Oid result_collation);
+ Oid result_collation, bool error_safe);
extern bool var_is_nonnullable(PlannerInfo *root, Var *var, bool use_rel_info);
diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h
index 8d775c72c59..1eca1ce727d 100644
--- a/src/include/parser/parse_coerce.h
+++ b/src/include/parser/parse_coerce.h
@@ -43,11 +43,26 @@ extern Node *coerce_to_target_type(ParseState *pstate,
CoercionContext ccontext,
CoercionForm cformat,
int location);
+extern Node *coerce_to_target_type_extended(ParseState *pstate,
+ Node *expr,
+ Oid exprtype,
+ Oid targettype,
+ int32 targettypmod,
+ CoercionContext ccontext,
+ CoercionForm cformat,
+ int location,
+ Node *escontext);
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_extend(ParseState *pstate, Node *node,
+ Oid inputTypeId,
+ Oid targetTypeId, int32 targetTypeMod,
+ CoercionContext ccontext, CoercionForm cformat,
+ int location,
+ Node *escontext);
extern Node *coerce_to_domain(Node *arg, Oid baseTypeId, int32 baseTypeMod,
Oid typeId,
CoercionContext ccontext, CoercionForm cformat, int location,
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index f7d07c84542..9f5b32e0360 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -67,6 +67,8 @@ typedef enum ParseExprKind
EXPR_KIND_VALUES_SINGLE, /* single-row VALUES (in INSERT only) */
EXPR_KIND_CHECK_CONSTRAINT, /* CHECK constraint for a table */
EXPR_KIND_DOMAIN_CHECK, /* CHECK constraint for a domain */
+ EXPR_KIND_CAST_DEFAULT, /* default expression in
+ CAST DEFAULT ON CONVERSION ERROR */
EXPR_KIND_COLUMN_DEFAULT, /* default value for a table column */
EXPR_KIND_FUNCTION_DEFAULT, /* default parameter value for function */
EXPR_KIND_INDEX_EXPRESSION, /* index expression */
diff --git a/src/include/parser/parse_type.h b/src/include/parser/parse_type.h
index 0d919d8bfa2..40aca2b31c9 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,
+ Node *escontext, Datum *result);
extern Oid typeidTypeRelid(Oid type_id);
extern Oid typeOrDomainTypeRelid(Oid type_id);
diff --git a/src/test/regress/expected/cast.out b/src/test/regress/expected/cast.out
new file mode 100644
index 00000000000..b0dc7b8faea
--- /dev/null
+++ b/src/test/regress/expected/cast.out
@@ -0,0 +1,810 @@
+SET extra_float_digits = 0;
+-- CAST DEFAULT ON CONVERSION ERROR
+SELECT CAST(B'01' AS date DEFAULT NULL ON CONVERSION ERROR);
+ date
+------
+
+(1 row)
+
+SELECT CAST(BIT'01' AS date DEFAULT NULL ON CONVERSION ERROR);
+ date
+------
+
+(1 row)
+
+SELECT CAST(TRUE AS date DEFAULT NULL ON CONVERSION ERROR);
+ date
+------
+
+(1 row)
+
+SELECT CAST(1.1 AS date DEFAULT NULL ON CONVERSION ERROR);
+ date
+------
+
+(1 row)
+
+SELECT CAST(1 AS date DEFAULT NULL ON CONVERSION ERROR);
+ date
+------
+
+(1 row)
+
+SELECT CAST(1111 AS "char" DEFAULT 'A' ON CONVERSION ERROR);
+ char
+------
+ A
+(1 row)
+
+--test source expression is a unknown const
+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('a' as int DEFAULT 18 ON CONVERSION ERROR);
+ int4
+------
+ 18
+(1 row)
+
+SELECT CAST('a' as int DEFAULT sum(1) ON CONVERSION ERROR); --error
+ERROR: aggregate functions are not allowed in CAST DEFAULT expressions
+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: window functions are not allowed in CAST DEFAULT expressions
+LINE 1: SELECT CAST('a' as int DEFAULT sum(1) over() ON CONVERSION E...
+ ^
+SELECT CAST('a' as int DEFAULT (SELECT NULL) ON CONVERSION ERROR); --error
+ERROR: cannot use subquery in CAST DEFAULT expression
+LINE 1: SELECT CAST('a' as int DEFAULT (SELECT NULL) 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('a'::int as int DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR: invalid input syntax for type integer: "a"
+LINE 1: SELECT CAST('a'::int as int DEFAULT NULL ON CONVERSION ERROR...
+ ^
+--the default expression’s collation should match the collation of the cast
+--target type
+VALUES (CAST('error' AS text DEFAULT '1' COLLATE "C" ON CONVERSION ERROR));
+ERROR: the collation of CAST DEFAULT expression conflicts with target type collation
+LINE 1: VALUES (CAST('error' AS text DEFAULT '1' COLLATE "C" ON CONV...
+ ^
+DETAIL: "default" versus "C"
+VALUES (CAST('error' AS int2vector DEFAULT '1 3' ON CONVERSION ERROR));
+ column1
+---------
+ 1 3
+(1 row)
+
+VALUES (CAST('error' AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR));
+ column1
+---------
+ {"1 3"}
+(1 row)
+
+-- test source expression contain subquery
+SELECT CAST((SELECT b FROM generate_series(1, 1), (VALUES('H')) s(b))
+ AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR);
+ b
+---------
+ {"1 3"}
+(1 row)
+
+SELECT CAST((SELECT ARRAY((SELECT b FROM generate_series(1, 2) g, (VALUES('H')) s(b))))
+ AS INT[]
+ DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+CREATE FUNCTION ret_int8() RETURNS BIGINT AS
+$$
+BEGIN RETURN 2147483648; END;
+$$
+LANGUAGE plpgsql IMMUTABLE;
+SELECT CAST('a' as int DEFAULT ret_int8() ON CONVERSION ERROR); --error
+ERROR: integer out of range
+SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERROR); --error
+ERROR: cannot coerce CAST DEFAULT expression to type date
+LINE 1: SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERR...
+ ^
+-- DEFAULT expression can not be set-returning
+CREATE 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);
+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: set-returning functions are not allowed in CAST DEFAULT expressions
+LINE 1: SELECT CAST('a' as int DEFAULT ret_setint() ON CONVERSION ER...
+ ^
+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 user-defined type, domain, array over domain, domain over array
+CREATE DOMAIN d_int42 as int check (value = 42) NOT NULL;
+CREATE DOMAIN d_char3_not_null as char(3) NOT NULL;
+CREATE DOMAIN d_varchar as varchar(3) NOT NULL;
+CREATE DOMAIN d_int_arr as int[] check (value = '{41, 43}') NOT NULL;
+CREATE TYPE comp_domain_with_typmod AS (a d_char3_not_null, b int);
+CREATE TYPE comp2 AS (a d_varchar);
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION ERROR); --error
+ERROR: value too long for type character varying(3)
+LINE 1: SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION...
+ ^
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(123)' ON CONVERSION ERROR);
+ comp2
+-------
+ (123)
+(1 row)
+
+SELECT CAST(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);
+ 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);
+ 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)'...
+ ^
+SELECT CAST(ARRAY[42,41] AS d_int42[] DEFAULT '{42, 42}' ON CONVERSION ERROR);
+ array
+---------
+ {42,42}
+(1 row)
+
+SELECT CAST(ARRAY[42,41] AS d_int_arr DEFAULT '{41, 43}' ON CONVERSION ERROR);
+ array
+---------
+ {41,43}
+(1 row)
+
+-- test array coerce
+SELECT CAST(array['a'::text] AS int[] DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR: invalid input syntax for type integer: "a"
+SELECT CAST(array['a'] AS int[] DEFAULT ARRAY[1] ON CONVERSION ERROR);
+ array
+-------
+ {1}
+(1 row)
+
+SELECT CAST(array[11.12] AS date[] DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+SELECT CAST(array[11111111111111111] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+SELECT CAST(array[['abc'],[456]] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+SELECT CAST('{123,abc,456}' AS int[] DEFAULT '{-789}' ON CONVERSION ERROR);
+ int4
+--------
+ {-789}
+(1 row)
+
+SELECT CAST('{234,def,567}'::text[] AS integer[] DEFAULT '{-1011}' ON CONVERSION ERROR);
+ int4
+---------
+ {-1011}
+(1 row)
+
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS int[] DEFAULT '{1,2}' ON CONVERSION ERROR);
+ array
+-------
+ {1,2}
+(1 row)
+
+SELECT CAST(ARRAY[['1', '2'], ['three', 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --ok
+ array
+---------
+ {21,22}
+(1 row)
+
+SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --error
+ERROR: invalid input syntax for type integer: "three"
+LINE 1: SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] ...
+ ^
+SELECT CAST(ARRAY[1, 'three'::int] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --error
+ERROR: invalid input syntax for type integer: "three"
+LINE 1: SELECT CAST(ARRAY[1, 'three'::int] AS int[] DEFAULT '{21,22}...
+ ^
+-----safe cast with geometry data type
+SELECT CAST('(1,2)'::point AS box DEFAULT NULL ON CONVERSION ERROR);
+ box
+-------------
+ (1,2),(1,2)
+(1 row)
+
+SELECT CAST('[(NaN,1),(NaN,infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+----------------
+ (NaN,Infinity)
+(1 row)
+
+SELECT CAST('[(1e+300,Infinity),(1e+300,Infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+-------------------
+ (1e+300,Infinity)
+(1 row)
+
+SELECT CAST('[(1,2),(3,4)]'::path as polygon DEFAULT NULL ON CONVERSION ERROR);
+ polygon
+---------
+
+(1 row)
+
+SELECT CAST('(NaN,1.0,NaN,infinity)'::box AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+----------------
+ (NaN,Infinity)
+(1 row)
+
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS lseg DEFAULT NULL ON CONVERSION ERROR);
+ lseg
+---------------
+ [(2,2),(0,0)]
+(1 row)
+
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS polygon DEFAULT NULL ON CONVERSION ERROR);
+ polygon
+---------------------------
+ ((0,0),(0,2),(2,2),(2,0))
+(1 row)
+
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS path DEFAULT NULL ON CONVERSION ERROR);
+ path
+------
+
+(1 row)
+
+SELECT CAST('(2.0,infinity,NaN,infinity)'::box AS circle DEFAULT NULL ON CONVERSION ERROR);
+ circle
+----------------------
+ <(NaN,Infinity),NaN>
+(1 row)
+
+SELECT CAST('(NaN,0.0),(2.0,4.0),(0.0,infinity)'::polygon AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+----------------
+ (NaN,Infinity)
+(1 row)
+
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS path DEFAULT NULL ON CONVERSION ERROR);
+ path
+---------------------
+ ((2,0),(2,4),(0,0))
+(1 row)
+
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS box DEFAULT NULL ON CONVERSION ERROR);
+ box
+-------------
+ (2,4),(0,0)
+(1 row)
+
+SELECT CAST('(NaN,infinity),(2.0,4.0),(0.0,infinity)'::polygon AS circle DEFAULT NULL ON CONVERSION ERROR);
+ circle
+----------------------
+ <(NaN,Infinity),NaN>
+(1 row)
+
+SELECT CAST('<(5,1),3>'::circle AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+-------
+ (5,1)
+(1 row)
+
+SELECT CAST('<(3,5),0>'::circle as box DEFAULT NULL ON CONVERSION ERROR);
+ box
+-------------
+ (3,5),(3,5)
+(1 row)
+
+-- not supported because the cast from circle to polygon is implemented as a SQL
+-- function, which cannot be error-safe.
+SELECT CAST('<(3,5),0>'::circle as polygon DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type circle to polygon when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST('<(3,5),0>'::circle as polygon DEFAULT NULL ON C...
+ ^
+HINT: Explicit cast is defined but definition is not error safe
+-----safe cast from/to money type is not supported, all the following would result error
+SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type bigint to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION E...
+ ^
+HINT: Explicit cast is defined but definition is not error safe
+SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type integer to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION E...
+ ^
+HINT: Explicit cast is defined but definition is not error safe
+SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type numeric to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSIO...
+ ^
+HINT: Explicit cast is defined but definition is not error safe
+SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type money to numeric when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSIO...
+ ^
+HINT: Explicit cast is defined but definition is not error safe
+SELECT CAST(NULL::money[] AS numeric[] DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type money[] to numeric[] when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::money[] AS numeric[] DEFAULT NULL ON CONVE...
+ ^
+HINT: Explicit cast is defined but definition is not error safe
+-----safe cast from bytea type to other data types
+SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT 19 ON CONVERSION ERROR);
+ int8
+------
+ 19
+(1 row)
+
+SELECT CAST('\x123456789A'::bytea AS int4 DEFAULT 20 ON CONVERSION ERROR);
+ int4
+------
+ 20
+(1 row)
+
+SELECT CAST('\x123456'::bytea AS int2 DEFAULT 21 ON CONVERSION ERROR);
+ int2
+------
+ 21
+(1 row)
+
+-----safe cast from bit type to other data types
+SELECT CAST('111111111100001'::bit(100) AS INT DEFAULT 22 ON CONVERSION ERROR);
+ int4
+------
+ 22
+(1 row)
+
+SELECT CAST ('111111111100001'::bit(100) AS INT8 DEFAULT 23 ON CONVERSION ERROR);
+ int8
+------
+ 23
+(1 row)
+
+-----safe cast from text type to other data types
+select CAST('a.b.c.d'::text as regclass default NULL on conversion error);
+ regclass
+----------
+
+(1 row)
+
+CREATE TABLE test_safecast(col0 text);
+INSERT INTO test_safecast(col0) VALUES ('<value>one</value');
+SELECT col0 as text,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) as to_regclass,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) IS NULL as expect_true,
+ CAST(col0 AS "char" DEFAULT NULL ON CONVERSION ERROR) as to_char,
+ CAST(col0 AS name DEFAULT NULL ON CONVERSION ERROR) as to_name,
+ CAST(col0 AS xml DEFAULT NULL ON CONVERSION ERROR) as to_xml
+FROM test_safecast;
+ text | to_regclass | expect_true | to_char | to_name | to_xml
+-------------------+-------------+-------------+---------+-------------------+--------
+ <value>one</value | | t | < | <value>one</value |
+(1 row)
+
+SELECT CAST('192.168.1.x' as inet DEFAULT NULL ON CONVERSION ERROR);
+ inet
+------
+
+(1 row)
+
+SELECT CAST('192.168.1.2/30' as cidr DEFAULT NULL ON CONVERSION ERROR);
+ cidr
+------
+
+(1 row)
+
+SELECT CAST('22:00:5c:08:55:08:01:02'::macaddr8 as macaddr DEFAULT NULL ON CONVERSION ERROR);
+ macaddr
+---------
+
+(1 row)
+
+-----safe cast betweeen range data types
+SELECT CAST('[1,2]'::int4range AS int4multirange DEFAULT NULL ON CONVERSION ERROR);
+ int4multirange
+----------------
+ {[1,3)}
+(1 row)
+
+SELECT CAST('[1,2]'::int8range AS int8multirange DEFAULT NULL ON CONVERSION ERROR);
+ int8multirange
+----------------
+ {[1,3)}
+(1 row)
+
+SELECT CAST('[1,2]'::numrange AS nummultirange DEFAULT NULL ON CONVERSION ERROR);
+ nummultirange
+---------------
+ {[1,2]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::daterange AS datemultirange DEFAULT NULL ON CONVERSION ERROR);
+ datemultirange
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::tsrange AS tsmultirange DEFAULT NULL ON CONVERSION ERROR);
+ tsmultirange
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::tstzrange AS tstzmultirange DEFAULT NULL ON CONVERSION ERROR);
+ tstzmultirange
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+-----safe cast betweeen numeric data types
+CREATE TABLE test_safecast1(
+ col1 float4, col2 float8, col3 numeric, col4 numeric[],
+ col5 int2 default 32767,
+ col6 int4 default 32768,
+ col7 int8 default 4294967296);
+INSERT INTO test_safecast1 VALUES('11.1234', '11.1234', '9223372036854775808', '{11.1234}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('inf', 'inf', 'inf', '{11.1234, 12, inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('-inf', '-inf', '-inf', '{11.1234, 12, -inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('NaN', 'NaN', 'NaN', '{11.1234, 12, -inf, NaN}'::numeric[]);
+SELECT col5 as int2,
+ CAST(col5 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col5 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col5 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col5 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col5 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col5 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col5 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col5 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ int2 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+(4 rows)
+
+SELECT col6 as int4,
+ CAST(col6 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col6 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col6 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col6 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col6 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col6 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col6 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col6 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ int4 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+(4 rows)
+
+SELECT col7 as int8,
+ CAST(col7 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col7 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col7 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col7 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col7 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col7 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col7 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col7 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ int8 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+------------+---------+---------+--------+------------+-------------+------------+------------+------------------
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+(4 rows)
+
+SELECT col3 as numeric,
+ CAST(col3 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col3 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col3 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col3 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col3 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col3 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col3 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col3 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ numeric | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+---------------------+---------+---------+--------+---------+-------------+----------------------+---------------------+------------------
+ 9223372036854775808 | | | | | 9.22337e+18 | 9.22337203685478e+18 | 9223372036854775808 |
+ Infinity | | | | | Infinity | Infinity | Infinity |
+ -Infinity | | | | | -Infinity | -Infinity | -Infinity |
+ NaN | | | | | NaN | NaN | NaN | NaN
+(4 rows)
+
+SELECT col4 as num_arr,
+ CAST(col4 AS int2[] DEFAULT NULL ON CONVERSION ERROR) as int2arr,
+ CAST(col4 AS int4[] DEFAULT NULL ON CONVERSION ERROR) as int4arr,
+ CAST(col4 as int8[] DEFAULT NULL ON CONVERSION ERROR) as int8arr,
+ CAST(col4 as float4[] DEFAULT NULL ON CONVERSION ERROR) as f4arr,
+ CAST(col4 as float8[] DEFAULT NULL ON CONVERSION ERROR) as f8arr,
+ CAST(col4 as numeric(10,1)[] DEFAULT NULL ON CONVERSION ERROR) as numarr
+FROM test_safecast1;
+ num_arr | int2arr | int4arr | int8arr | f4arr | f8arr | numarr
+----------------------------+---------+---------+---------+----------------------------+----------------------------+--------
+ {11.1234} | {11} | {11} | {11} | {11.1234} | {11.1234} | {11.1}
+ {11.1234,12,Infinity,NaN} | | | | {11.1234,12,Infinity,NaN} | {11.1234,12,Infinity,NaN} |
+ {11.1234,12,-Infinity,NaN} | | | | {11.1234,12,-Infinity,NaN} | {11.1234,12,-Infinity,NaN} |
+ {11.1234,12,-Infinity,NaN} | | | | {11.1234,12,-Infinity,NaN} | {11.1234,12,-Infinity,NaN} |
+(4 rows)
+
+SELECT col1 as float4,
+ CAST(col1 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col1 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col1 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col1 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col1 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col1 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col1 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col1 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ float4 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-----------+---------+---------+--------+---------+-----------+------------------+------------+------------------
+ 11.1234 | 11 | 11 | | 11 | 11.1234 | 11.1233997344971 | 11.1234 | 11.1
+ Infinity | | | | | Infinity | Infinity | Infinity |
+ -Infinity | | | | | -Infinity | -Infinity | -Infinity |
+ NaN | | | | | NaN | NaN | NaN | NaN
+(4 rows)
+
+SELECT col2 as float8,
+ CAST(col2 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col2 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col2 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col2 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col2 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col2 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col2 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col2 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ float8 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-----------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 11.1234 | 11 | 11 | | 11 | 11.1234 | 11.1234 | 11.1234 | 11.1
+ Infinity | | | | | Infinity | Infinity | Infinity |
+ -Infinity | | | | | -Infinity | -Infinity | -Infinity |
+ NaN | | | | | NaN | NaN | NaN | NaN
+(4 rows)
+
+-- date/timestamp/interval related safe type cast
+CREATE TABLE test_safecast2(
+ col0 date, col1 timestamp, col2 timestamptz,
+ col3 interval, col4 time, col5 timetz);
+INSERT INTO test_safecast2 VALUES
+('-infinity', '-infinity', '-infinity', '-infinity',
+ '2003-03-07 15:36:39 America/New_York', '2003-07-07 15:36:39 America/New_York'),
+('-infinity', 'infinity', 'infinity', 'infinity',
+ '11:59:59.99 PM', '11:59:59.99 PM PDT');
+SELECT col0 as date,
+ CAST(col0 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col0 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col0 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col0 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col0 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+ date | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale
+-----------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity | -infinity | | | -infinity
+ -infinity | -infinity | -infinity | | | -infinity
+(2 rows)
+
+SELECT col1 as timestamp,
+ CAST(col1 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col1 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col1 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col1 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col1 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+ timestamp | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale
+-----------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity | -infinity | | | -infinity
+ infinity | infinity | infinity | | | infinity
+(2 rows)
+
+SELECT col2 as timestamptz,
+ CAST(col2 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col2 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col2 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col2 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col2 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+ timestamptz | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale
+-------------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity | -infinity | | | -infinity
+ infinity | infinity | infinity | | | infinity
+(2 rows)
+
+SELECT col3 as interval,
+ CAST(col3 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col3 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col3 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col3 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col3 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale,
+ CAST(col3 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+ interval | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale | to_interval_scale
+-----------+----------------+---------+----------+-----------+--------------------+-------------------
+ -infinity | | | | | | -infinity
+ infinity | | | | | | infinity
+(2 rows)
+
+SELECT col4 as time,
+ CAST(col4 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col4 AS timetz DEFAULT NULL ON CONVERSION ERROR) IS NOT NULL as to_timetz,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+ time | to_times | to_timetz | to_interval | to_interval_scale
+-------------+-------------+-----------+-------------------------------+-------------------------------
+ 15:36:39 | 15:36:39 | t | @ 15 hours 36 mins 39 secs | @ 15 hours 36 mins 39 secs
+ 23:59:59.99 | 23:59:59.99 | t | @ 23 hours 59 mins 59.99 secs | @ 23 hours 59 mins 59.99 secs
+(2 rows)
+
+SELECT col5 as timetz,
+ CAST(col5 AS time DEFAULT NULL ON CONVERSION ERROR) as to_time,
+ CAST(col5 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_time_scale,
+ CAST(col5 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col5 AS timetz(6) DEFAULT NULL ON CONVERSION ERROR) as to_timetz_scale,
+ CAST(col5 AS interval DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col5 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+ timetz | to_time | to_time_scale | to_timetz | to_timetz_scale | to_interval | to_interval_scale
+----------------+-------------+---------------+----------------+-----------------+-------------+-------------------
+ 15:36:39-04 | 15:36:39 | 15:36:39 | 15:36:39-04 | 15:36:39-04 | |
+ 23:59:59.99-07 | 23:59:59.99 | 23:59:59.99 | 23:59:59.99-07 | 23:59:59.99-07 | |
+(2 rows)
+
+CREATE TABLE test_safecast3(col0 jsonb);
+INSERT INTO test_safecast3(col0) VALUES ('"test"');
+SELECT col0 as jsonb,
+ CAST(col0 AS integer DEFAULT NULL ON CONVERSION ERROR) as to_integer,
+ CAST(col0 AS numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col0 AS bigint DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col0 AS float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col0 AS float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col0 AS boolean DEFAULT NULL ON CONVERSION ERROR) as to_bool,
+ CAST(col0 AS smallint DEFAULT NULL ON CONVERSION ERROR) as to_smallint
+FROM test_safecast3;
+ jsonb | to_integer | to_numeric | to_int8 | to_float4 | to_float8 | to_bool | to_smallint
+--------+------------+------------+---------+-----------+-----------+---------+-------------
+ "test" | | | | | | |
+(1 row)
+
+-- test deparse
+SET datestyle TO ISO, YMD;
+CREATE VIEW safecastview AS
+SELECT CAST('1234' as char(3) DEFAULT -1111 ON CONVERSION ERROR),
+ CAST(1 as date DEFAULT (('2025-Dec-06'::date + random(min=>1, max=>1::int))) ON CONVERSION ERROR) as cast0,
+ CAST(ARRAY[['1'], ['three'],['a']] AS int[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1,
+ CAST(ARRAY[['1', '2'], ['three', 'a']] AS date[] DEFAULT NULL ON CONVERSION ERROR) as cast2,
+ CAST(ARRAY['three'] AS INT[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast3;
+\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 '2025-12-06'::date + random(min => 1, max => 1) ON CONVERSION ERROR) AS cast0,
+ CAST(ARRAY[ARRAY[1], ARRAY['three'], ARRAY['a']] AS integer[] DEFAULT '{1,2}'::integer[] ON CONVERSION ERROR) AS cast1,
+ CAST(ARRAY[ARRAY['1', '2'], ARRAY['three', 'a']] AS date[] DEFAULT NULL::date[] ON CONVERSION ERROR) AS cast2,
+ CAST(ARRAY['three'] AS integer[] DEFAULT '{1,2}'::integer[] ON CONVERSION ERROR) AS cast3
+SELECT * FROM safecastview;
+ bpchar | cast0 | cast1 | cast2 | cast3
+--------+------------+-------+-------+-------
+ 123 | 2025-12-07 | {1,2} | | {1,2}
+(1 row)
+
+CREATE VIEW safecastview1 AS
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS d_int_arr
+ DEFAULT '{41,43}' ON CONVERSION ERROR) as cast1,
+ CAST(ARRAY[['1', '2', 1.1], ['three', true, B'01']] AS d_int_arr
+ DEFAULT '{41,43}' ON CONVERSION ERROR) as cast2;
+\sv safecastview1
+CREATE OR REPLACE VIEW public.safecastview1 AS
+ SELECT CAST(ARRAY[ARRAY[1], ARRAY['three'], ARRAY['a']] AS d_int_arr DEFAULT '{41,43}'::integer[]::d_int_arr ON CONVERSION ERROR) AS cast1,
+ CAST(ARRAY[ARRAY[1, 2, 1.1::integer], ARRAY['three', true, '01'::"bit"]] AS d_int_arr DEFAULT '{41,43}'::integer[]::d_int_arr ON CONVERSION ERROR) AS cast2
+SELECT * FROM safecastview1;
+ cast1 | cast2
+---------+---------
+ {41,43} | {41,43}
+(1 row)
+
+RESET datestyle;
+--test CAST DEFAULT expression mutability
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as int DEFAULT random(min=>1, max=>1) ON CONVERSION ERROR)));
+ERROR: functions in index expression must be marked IMMUTABLE
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as xid DEFAULT NULL ON CONVERSION ERROR)));
+ERROR: data type xid has no default operator class for access method "btree"
+HINT: You must specify an operator class for the index or define a default operator class for the data type.
+CREATE INDEX test_safecast3_idx ON test_safecast3((CAST(col0 as int DEFAULT NULL ON CONVERSION ERROR))); --ok
+SELECT pg_get_indexdef('test_safecast3_idx'::regclass);
+ pg_get_indexdef
+------------------------------------------------------------------------------------------------------------------------------------------
+ CREATE INDEX test_safecast3_idx ON public.test_safecast3 USING btree ((CAST(col0 AS integer DEFAULT NULL::integer ON CONVERSION ERROR)))
+(1 row)
+
+DROP TABLE test_safecast;
+DROP TABLE test_safecast1;
+DROP TABLE test_safecast2;
+DROP TABLE test_safecast3;
+DROP TABLE tcast;
+DROP FUNCTION ret_int8;
+DROP FUNCTION ret_setint;
+DROP TYPE comp_domain_with_typmod;
+DROP TYPE comp2;
+DROP DOMAIN d_varchar;
+DROP DOMAIN d_int42;
+DROP DOMAIN d_char3_not_null;
+RESET extra_float_digits;
diff --git a/src/test/regress/expected/create_cast.out b/src/test/regress/expected/create_cast.out
index 0e69644bca2..0054ed0ef67 100644
--- a/src/test/regress/expected/create_cast.out
+++ b/src/test/regress/expected/create_cast.out
@@ -88,6 +88,11 @@ SELECT 1234::int4::casttesttype; -- Should work now
bar1234
(1 row)
+SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- error
+ERROR: cannot cast type integer to casttesttype when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVE...
+ ^
+HINT: Safe type cast for user-defined types are not yet supported
-- check dependencies generated for that
SELECT pg_describe_object(classid, objid, objsubid) as obj,
pg_describe_object(refclassid, refobjid, refobjsubid) as objref,
diff --git a/src/test/regress/expected/equivclass.out b/src/test/regress/expected/equivclass.out
index ad8ab294ff6..5aee2c7a9fb 100644
--- a/src/test/regress/expected/equivclass.out
+++ b/src/test/regress/expected/equivclass.out
@@ -95,6 +95,13 @@ create function int8alias1cmp(int8, int8alias1) returns int
strict immutable language internal as 'btint8cmp';
alter operator family integer_ops using btree add
function 1 int8alias1cmp (int8, int8alias1);
+-- int8alias2 binary-coercible to int8. so this is ok
+select cast('1'::int8 as int8alias2 default null on conversion error);
+ int8alias2
+------------
+ 1
+(1 row)
+
create table ec0 (ff int8 primary key, f1 int8, f2 int8);
create table ec1 (ff int8 primary key, f1 int8alias1, f2 int8alias2);
create table ec2 (xf int8 primary key, x1 int8alias1, x2 int8alias2);
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index cc6d799bcea..0b031a37c36 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -81,7 +81,7 @@ test: create_table_like alter_generic alter_operator misc async dbsize merge mis
# collate.linux.utf8 and collate.icu.utf8 tests cannot be run in parallel with each other
# psql depends on create_am
# amutils depends on geometry, create_index_spgist, hash_index, brin
-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 00000000000..9b6f3cc0649
--- /dev/null
+++ b/src/test/regress/sql/cast.sql
@@ -0,0 +1,350 @@
+SET extra_float_digits = 0;
+
+-- CAST DEFAULT ON CONVERSION ERROR
+SELECT CAST(B'01' AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(BIT'01' AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(TRUE AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(1.1 AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(1 AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(1111 AS "char" DEFAULT 'A' ON CONVERSION ERROR);
+
+--test source expression is a unknown const
+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('a' as int DEFAULT 18 ON CONVERSION 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 (SELECT NULL) ON CONVERSION ERROR); --error
+SELECT CAST('a' as int DEFAULT 'b' ON CONVERSION ERROR); --error
+SELECT CAST('a'::int as int DEFAULT NULL ON CONVERSION ERROR); --error
+
+--the default expression’s collation should match the collation of the cast
+--target type
+VALUES (CAST('error' AS text DEFAULT '1' COLLATE "C" ON CONVERSION ERROR));
+
+VALUES (CAST('error' AS int2vector DEFAULT '1 3' ON CONVERSION ERROR));
+VALUES (CAST('error' AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR));
+
+-- test source expression contain subquery
+SELECT CAST((SELECT b FROM generate_series(1, 1), (VALUES('H')) s(b))
+ AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR);
+SELECT CAST((SELECT ARRAY((SELECT b FROM generate_series(1, 2) g, (VALUES('H')) s(b))))
+ AS INT[]
+ DEFAULT NULL ON CONVERSION ERROR);
+
+CREATE FUNCTION ret_int8() RETURNS BIGINT AS
+$$
+BEGIN RETURN 2147483648; END;
+$$
+LANGUAGE plpgsql IMMUTABLE;
+
+SELECT CAST('a' as int DEFAULT ret_int8() ON CONVERSION ERROR); --error
+SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERROR); --error
+
+-- DEFAULT expression can not be set-returning
+CREATE 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);
+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(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 user-defined type, domain, array over domain, domain over array
+CREATE DOMAIN d_int42 as int check (value = 42) NOT NULL;
+CREATE DOMAIN d_char3_not_null as char(3) NOT NULL;
+CREATE DOMAIN d_varchar as varchar(3) NOT NULL;
+CREATE DOMAIN d_int_arr as int[] check (value = '{41, 43}') NOT NULL;
+CREATE TYPE comp_domain_with_typmod AS (a d_char3_not_null, b int);
+CREATE TYPE comp2 AS (a d_varchar);
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION ERROR); --error
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(123)' ON CONVERSION ERROR);
+SELECT CAST(11 AS d_int42 DEFAULT 41 ON CONVERSION ERROR); --error
+SELECT CAST(11 AS d_int42 DEFAULT 42 ON CONVERSION ERROR);
+SELECT CAST(NULL AS d_int42 DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(NULL AS d_int42 DEFAULT 42 ON CONVERSION ERROR);
+SELECT CAST('(,42)' AS comp_domain_with_typmod DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1,2)' ON CONVERSION ERROR);
+SELECT CAST('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1234,2)' ON CONVERSION ERROR); --error
+
+SELECT CAST(ARRAY[42,41] AS d_int42[] DEFAULT '{42, 42}' ON CONVERSION ERROR);
+SELECT CAST(ARRAY[42,41] AS d_int_arr DEFAULT '{41, 43}' ON CONVERSION ERROR);
+
+-- test array coerce
+SELECT CAST(array['a'::text] AS int[] DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(array['a'] AS int[] DEFAULT ARRAY[1] ON CONVERSION ERROR);
+SELECT CAST(array[11.12] AS date[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(array[11111111111111111] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(array[['abc'],[456]] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('{123,abc,456}' AS int[] 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 int[] DEFAULT '{1,2}' ON CONVERSION ERROR);
+SELECT CAST(ARRAY[['1', '2'], ['three', 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --ok
+SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --error
+SELECT CAST(ARRAY[1, 'three'::int] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --error
+
+-----safe cast with geometry data type
+SELECT CAST('(1,2)'::point AS box DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[(NaN,1),(NaN,infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[(1e+300,Infinity),(1e+300,Infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[(1,2),(3,4)]'::path as polygon DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NaN,1.0,NaN,infinity)'::box AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS lseg DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS polygon DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS path DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,infinity,NaN,infinity)'::box AS circle DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NaN,0.0),(2.0,4.0),(0.0,infinity)'::polygon AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS path DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS box DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NaN,infinity),(2.0,4.0),(0.0,infinity)'::polygon AS circle DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('<(5,1),3>'::circle AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('<(3,5),0>'::circle as box DEFAULT NULL ON CONVERSION ERROR);
+
+-- not supported because the cast from circle to polygon is implemented as a SQL
+-- function, which cannot be error-safe.
+SELECT CAST('<(3,5),0>'::circle as polygon DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast from/to money type is not supported, all the following would result error
+SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::money[] AS numeric[] DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast from bytea type to other data types
+SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT 19 ON CONVERSION ERROR);
+SELECT CAST('\x123456789A'::bytea AS int4 DEFAULT 20 ON CONVERSION ERROR);
+SELECT CAST('\x123456'::bytea AS int2 DEFAULT 21 ON CONVERSION ERROR);
+
+-----safe cast from bit type to other data types
+SELECT CAST('111111111100001'::bit(100) AS INT DEFAULT 22 ON CONVERSION ERROR);
+SELECT CAST ('111111111100001'::bit(100) AS INT8 DEFAULT 23 ON CONVERSION ERROR);
+
+-----safe cast from text type to other data types
+select CAST('a.b.c.d'::text as regclass default NULL on conversion error);
+
+CREATE TABLE test_safecast(col0 text);
+INSERT INTO test_safecast(col0) VALUES ('<value>one</value');
+
+SELECT col0 as text,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) as to_regclass,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) IS NULL as expect_true,
+ CAST(col0 AS "char" DEFAULT NULL ON CONVERSION ERROR) as to_char,
+ CAST(col0 AS name DEFAULT NULL ON CONVERSION ERROR) as to_name,
+ CAST(col0 AS xml DEFAULT NULL ON CONVERSION ERROR) as to_xml
+FROM test_safecast;
+
+SELECT CAST('192.168.1.x' as inet DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('192.168.1.2/30' as cidr DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('22:00:5c:08:55:08:01:02'::macaddr8 as macaddr DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast betweeen range data types
+SELECT CAST('[1,2]'::int4range AS int4multirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[1,2]'::int8range AS int8multirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[1,2]'::numrange AS nummultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::daterange AS datemultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::tsrange AS tsmultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::tstzrange AS tstzmultirange DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast betweeen numeric data types
+CREATE TABLE test_safecast1(
+ col1 float4, col2 float8, col3 numeric, col4 numeric[],
+ col5 int2 default 32767,
+ col6 int4 default 32768,
+ col7 int8 default 4294967296);
+INSERT INTO test_safecast1 VALUES('11.1234', '11.1234', '9223372036854775808', '{11.1234}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('inf', 'inf', 'inf', '{11.1234, 12, inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('-inf', '-inf', '-inf', '{11.1234, 12, -inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('NaN', 'NaN', 'NaN', '{11.1234, 12, -inf, NaN}'::numeric[]);
+
+SELECT col5 as int2,
+ CAST(col5 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col5 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col5 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col5 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col5 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col5 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col5 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col5 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col6 as int4,
+ CAST(col6 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col6 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col6 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col6 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col6 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col6 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col6 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col6 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col7 as int8,
+ CAST(col7 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col7 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col7 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col7 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col7 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col7 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col7 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col7 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col3 as numeric,
+ CAST(col3 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col3 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col3 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col3 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col3 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col3 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col3 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col3 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col4 as num_arr,
+ CAST(col4 AS int2[] DEFAULT NULL ON CONVERSION ERROR) as int2arr,
+ CAST(col4 AS int4[] DEFAULT NULL ON CONVERSION ERROR) as int4arr,
+ CAST(col4 as int8[] DEFAULT NULL ON CONVERSION ERROR) as int8arr,
+ CAST(col4 as float4[] DEFAULT NULL ON CONVERSION ERROR) as f4arr,
+ CAST(col4 as float8[] DEFAULT NULL ON CONVERSION ERROR) as f8arr,
+ CAST(col4 as numeric(10,1)[] DEFAULT NULL ON CONVERSION ERROR) as numarr
+FROM test_safecast1;
+
+SELECT col1 as float4,
+ CAST(col1 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col1 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col1 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col1 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col1 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col1 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col1 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col1 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col2 as float8,
+ CAST(col2 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col2 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col2 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col2 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col2 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col2 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col2 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col2 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+-- date/timestamp/interval related safe type cast
+CREATE TABLE test_safecast2(
+ col0 date, col1 timestamp, col2 timestamptz,
+ col3 interval, col4 time, col5 timetz);
+INSERT INTO test_safecast2 VALUES
+('-infinity', '-infinity', '-infinity', '-infinity',
+ '2003-03-07 15:36:39 America/New_York', '2003-07-07 15:36:39 America/New_York'),
+('-infinity', 'infinity', 'infinity', 'infinity',
+ '11:59:59.99 PM', '11:59:59.99 PM PDT');
+
+SELECT col0 as date,
+ CAST(col0 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col0 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col0 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col0 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col0 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+
+SELECT col1 as timestamp,
+ CAST(col1 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col1 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col1 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col1 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col1 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+
+SELECT col2 as timestamptz,
+ CAST(col2 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col2 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col2 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col2 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col2 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+
+SELECT col3 as interval,
+ CAST(col3 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col3 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col3 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col3 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col3 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale,
+ CAST(col3 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+
+SELECT col4 as time,
+ CAST(col4 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col4 AS timetz DEFAULT NULL ON CONVERSION ERROR) IS NOT NULL as to_timetz,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+
+SELECT col5 as timetz,
+ CAST(col5 AS time DEFAULT NULL ON CONVERSION ERROR) as to_time,
+ CAST(col5 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_time_scale,
+ CAST(col5 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col5 AS timetz(6) DEFAULT NULL ON CONVERSION ERROR) as to_timetz_scale,
+ CAST(col5 AS interval DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col5 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+
+CREATE TABLE test_safecast3(col0 jsonb);
+INSERT INTO test_safecast3(col0) VALUES ('"test"');
+SELECT col0 as jsonb,
+ CAST(col0 AS integer DEFAULT NULL ON CONVERSION ERROR) as to_integer,
+ CAST(col0 AS numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col0 AS bigint DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col0 AS float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col0 AS float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col0 AS boolean DEFAULT NULL ON CONVERSION ERROR) as to_bool,
+ CAST(col0 AS smallint DEFAULT NULL ON CONVERSION ERROR) as to_smallint
+FROM test_safecast3;
+
+-- test deparse
+SET datestyle TO ISO, YMD;
+CREATE VIEW safecastview AS
+SELECT CAST('1234' as char(3) DEFAULT -1111 ON CONVERSION ERROR),
+ CAST(1 as date DEFAULT (('2025-Dec-06'::date + random(min=>1, max=>1::int))) ON CONVERSION ERROR) as cast0,
+ CAST(ARRAY[['1'], ['three'],['a']] AS int[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1,
+ CAST(ARRAY[['1', '2'], ['three', 'a']] AS date[] DEFAULT NULL ON CONVERSION ERROR) as cast2,
+ CAST(ARRAY['three'] AS INT[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast3;
+\sv safecastview
+SELECT * FROM safecastview;
+
+CREATE VIEW safecastview1 AS
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS d_int_arr
+ DEFAULT '{41,43}' ON CONVERSION ERROR) as cast1,
+ CAST(ARRAY[['1', '2', 1.1], ['three', true, B'01']] AS d_int_arr
+ DEFAULT '{41,43}' ON CONVERSION ERROR) as cast2;
+\sv safecastview1
+SELECT * FROM safecastview1;
+
+RESET datestyle;
+
+--test CAST DEFAULT expression mutability
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as int DEFAULT random(min=>1, max=>1) ON CONVERSION ERROR)));
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as xid DEFAULT NULL ON CONVERSION ERROR)));
+CREATE INDEX test_safecast3_idx ON test_safecast3((CAST(col0 as int DEFAULT NULL ON CONVERSION ERROR))); --ok
+SELECT pg_get_indexdef('test_safecast3_idx'::regclass);
+
+DROP TABLE test_safecast;
+DROP TABLE test_safecast1;
+DROP TABLE test_safecast2;
+DROP TABLE test_safecast3;
+DROP TABLE tcast;
+DROP FUNCTION ret_int8;
+DROP FUNCTION ret_setint;
+DROP TYPE comp_domain_with_typmod;
+DROP TYPE comp2;
+DROP DOMAIN d_varchar;
+DROP DOMAIN d_int42;
+DROP DOMAIN d_char3_not_null;
+RESET extra_float_digits;
diff --git a/src/test/regress/sql/create_cast.sql b/src/test/regress/sql/create_cast.sql
index 32187853cc7..0a15a795d87 100644
--- a/src/test/regress/sql/create_cast.sql
+++ b/src/test/regress/sql/create_cast.sql
@@ -62,6 +62,7 @@ $$ SELECT ('bar'::text || $1::text); $$;
CREATE CAST (int4 AS casttesttype) WITH FUNCTION bar_int4_text(int4) AS IMPLICIT;
SELECT 1234::int4::casttesttype; -- Should work now
+SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- error
-- check dependencies generated for that
SELECT pg_describe_object(classid, objid, objsubid) as obj,
diff --git a/src/test/regress/sql/equivclass.sql b/src/test/regress/sql/equivclass.sql
index 7fc2159349b..5ad1d26311d 100644
--- a/src/test/regress/sql/equivclass.sql
+++ b/src/test/regress/sql/equivclass.sql
@@ -98,6 +98,9 @@ create function int8alias1cmp(int8, int8alias1) returns int
alter operator family integer_ops using btree add
function 1 int8alias1cmp (int8, int8alias1);
+-- int8alias2 binary-coercible to int8. so this is ok
+select cast('1'::int8 as int8alias2 default null on conversion error);
+
create table ec0 (ff int8 primary key, f1 int8, f2 int8);
create table ec1 (ff int8 primary key, f1 int8alias1, f2 int8alias2);
create table ec2 (xf int8 primary key, x1 int8alias1, x2 int8alias2);
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 6e2ed0c8825..f196d3671c5 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2673,6 +2673,9 @@ STRLEN
SV
SYNCHRONIZATION_BARRIER
SYSTEM_INFO
+SafeTypeCast
+SafeTypeCastExpr
+SafeTypeCastState
SampleScan
SampleScanGetSampleSize_function
SampleScanState
--
2.34.1
v15-0016-error-safe-for-casting-jsonb-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v15-0016-error-safe-for-casting-jsonb-to-other-types-per-pg_cast.patchDownload
From e9c2d202693dbea0610b30dc32ee75c11548d564 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Mon, 24 Nov 2025 14:26:53 +0800
Subject: [PATCH v15 16/22] error safe for casting jsonb to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'jsonb'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+---------------+---------
jsonb | boolean | 3556 | e | f | jsonb_bool | bool
jsonb | numeric | 3449 | e | f | jsonb_numeric | numeric
jsonb | smallint | 3450 | e | f | jsonb_int2 | int2
jsonb | integer | 3451 | e | f | jsonb_int4 | int4
jsonb | bigint | 3452 | e | f | jsonb_int8 | int8
jsonb | real | 3453 | e | f | jsonb_float4 | float4
jsonb | double precision | 2580 | e | f | jsonb_float8 | float8
(7 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/jsonb.c | 74 +++++++++++++++++++++++++++--------
1 file changed, 58 insertions(+), 16 deletions(-)
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index c4fe6e00dcd..1600204fa4a 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -1815,7 +1815,7 @@ JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res)
* Emit correct, translatable cast error message
*/
static void
-cannotCastJsonbValue(enum jbvType type, const char *sqltype)
+cannotCastJsonbValue(enum jbvType type, const char *sqltype, Node *escontext)
{
static const struct
{
@@ -1836,7 +1836,7 @@ cannotCastJsonbValue(enum jbvType type, const char *sqltype)
for (i = 0; i < lengthof(messages); i++)
if (messages[i].type == type)
- ereport(ERROR,
+ ereturn(escontext,,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(messages[i].msg, sqltype)));
@@ -1851,7 +1851,10 @@ jsonb_bool(PG_FUNCTION_ARGS)
JsonbValue v;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "boolean");
+ {
+ cannotCastJsonbValue(v.type, "boolean", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -1860,7 +1863,10 @@ jsonb_bool(PG_FUNCTION_ARGS)
}
if (v.type != jbvBool)
- cannotCastJsonbValue(v.type, "boolean");
+ {
+ cannotCastJsonbValue(v.type, "boolean", fcinfo->context);
+ PG_RETURN_NULL();
+ }
PG_FREE_IF_COPY(in, 0);
@@ -1875,7 +1881,10 @@ jsonb_numeric(PG_FUNCTION_ARGS)
Numeric retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "numeric");
+ {
+ cannotCastJsonbValue(v.type, "numeric", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -1884,7 +1893,10 @@ jsonb_numeric(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "numeric");
+ {
+ cannotCastJsonbValue(v.type, "numeric", fcinfo->context);
+ PG_RETURN_NULL();
+ }
/*
* v.val.numeric points into jsonb body, so we need to make a copy to
@@ -1905,7 +1917,10 @@ jsonb_int2(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "smallint");
+ {
+ cannotCastJsonbValue(v.type, "smallint", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -1914,7 +1929,10 @@ jsonb_int2(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "smallint");
+ {
+ cannotCastJsonbValue(v.type, "smallint", fcinfo->context);
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_int2,
NumericGetDatum(v.val.numeric));
@@ -1932,7 +1950,10 @@ jsonb_int4(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "integer");
+ {
+ cannotCastJsonbValue(v.type, "integer", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -1941,7 +1962,10 @@ jsonb_int4(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "integer");
+ {
+ cannotCastJsonbValue(v.type, "integer", fcinfo->context);
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_int4,
NumericGetDatum(v.val.numeric));
@@ -1959,7 +1983,10 @@ jsonb_int8(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "bigint");
+ {
+ cannotCastJsonbValue(v.type, "bigint", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -1968,7 +1995,10 @@ jsonb_int8(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "bigint");
+ {
+ cannotCastJsonbValue(v.type, "bigint", fcinfo->context);
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_int8,
NumericGetDatum(v.val.numeric));
@@ -1986,7 +2016,10 @@ jsonb_float4(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "real");
+ {
+ cannotCastJsonbValue(v.type, "real", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -1995,7 +2028,10 @@ jsonb_float4(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "real");
+ {
+ cannotCastJsonbValue(v.type, "real", fcinfo->context);
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_float4,
NumericGetDatum(v.val.numeric));
@@ -2013,7 +2049,10 @@ jsonb_float8(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "double precision");
+ {
+ cannotCastJsonbValue(v.type, "double precision", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2022,7 +2061,10 @@ jsonb_float8(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "double precision");
+ {
+ cannotCastJsonbValue(v.type, "double precision", fcinfo->context);
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_float8,
NumericGetDatum(v.val.numeric));
--
2.34.1
v15-0017-refactor-float_overflow_error-float_underflow_error-float_zero_d.patchtext/x-patch; charset=US-ASCII; name=v15-0017-refactor-float_overflow_error-float_underflow_error-float_zero_d.patchDownload
From d357e4541ca62c39982e886df927184714e9d088 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Tue, 9 Dec 2025 14:36:39 +0800
Subject: [PATCH v15 17/22] refactor
float_overflow_error,float_underflow_error,float_zero_divide_error
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
contrib/btree_gist/btree_float4.c | 2 +-
contrib/btree_gist/btree_float8.c | 4 +-
src/backend/utils/adt/float.c | 104 +++++++++++++++---------------
src/include/utils/float.h | 34 +++++-----
4 files changed, 72 insertions(+), 72 deletions(-)
diff --git a/contrib/btree_gist/btree_float4.c b/contrib/btree_gist/btree_float4.c
index d9c859835da..a7325a7bb29 100644
--- a/contrib/btree_gist/btree_float4.c
+++ b/contrib/btree_gist/btree_float4.c
@@ -101,7 +101,7 @@ float4_dist(PG_FUNCTION_ARGS)
r = a - b;
if (unlikely(isinf(r)) && !isinf(a) && !isinf(b))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT4(fabsf(r));
}
diff --git a/contrib/btree_gist/btree_float8.c b/contrib/btree_gist/btree_float8.c
index 567beede178..7c99b84de35 100644
--- a/contrib/btree_gist/btree_float8.c
+++ b/contrib/btree_gist/btree_float8.c
@@ -79,7 +79,7 @@ gbt_float8_dist(const void *a, const void *b, FmgrInfo *flinfo)
r = arg1 - arg2;
if (unlikely(isinf(r)) && !isinf(arg1) && !isinf(arg2))
- float_overflow_error();
+ float_overflow_error(NULL);
return fabs(r);
}
@@ -109,7 +109,7 @@ float8_dist(PG_FUNCTION_ARGS)
r = a - b;
if (unlikely(isinf(r)) && !isinf(a) && !isinf(b))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(fabs(r));
}
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index 55c7030ba81..2710e42f5bb 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -83,25 +83,25 @@ static void init_degree_constants(void);
* This does mean that you don't get a useful error location indicator.
*/
pg_noinline void
-float_overflow_error(void)
+float_overflow_error(struct Node *escontext)
{
- ereport(ERROR,
+ errsave(escontext,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("value out of range: overflow")));
}
pg_noinline void
-float_underflow_error(void)
+float_underflow_error(struct Node *escontext)
{
- ereport(ERROR,
+ errsave(escontext,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("value out of range: underflow")));
}
pg_noinline void
-float_zero_divide_error(void)
+float_zero_divide_error(struct Node *escontext)
{
- ereport(ERROR,
+ errsave(escontext,
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
}
@@ -1460,9 +1460,9 @@ dsqrt(PG_FUNCTION_ARGS)
result = sqrt(arg1);
if (unlikely(isinf(result)) && !isinf(arg1))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0) && arg1 != 0.0)
- float_underflow_error();
+ float_underflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1479,9 +1479,9 @@ dcbrt(PG_FUNCTION_ARGS)
result = cbrt(arg1);
if (unlikely(isinf(result)) && !isinf(arg1))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0) && arg1 != 0.0)
- float_underflow_error();
+ float_underflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1617,24 +1617,24 @@ dpow(PG_FUNCTION_ARGS)
if (absx == 1.0)
result = 1.0;
else if (arg2 >= 0.0 ? (absx > 1.0) : (absx < 1.0))
- float_overflow_error();
+ float_overflow_error(NULL);
else
- float_underflow_error();
+ float_underflow_error(NULL);
}
}
else if (errno == ERANGE)
{
if (result != 0.0)
- float_overflow_error();
+ float_overflow_error(NULL);
else
- float_underflow_error();
+ float_underflow_error(NULL);
}
else
{
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0) && arg1 != 0.0)
- float_underflow_error();
+ float_underflow_error(NULL);
}
}
@@ -1674,14 +1674,14 @@ dexp(PG_FUNCTION_ARGS)
if (unlikely(errno == ERANGE))
{
if (result != 0.0)
- float_overflow_error();
+ float_overflow_error(NULL);
else
- float_underflow_error();
+ float_underflow_error(NULL);
}
else if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
else if (unlikely(result == 0.0))
- float_underflow_error();
+ float_underflow_error(NULL);
}
PG_RETURN_FLOAT8(result);
@@ -1712,9 +1712,9 @@ dlog1(PG_FUNCTION_ARGS)
result = log(arg1);
if (unlikely(isinf(result)) && !isinf(arg1))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0) && arg1 != 1.0)
- float_underflow_error();
+ float_underflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1745,9 +1745,9 @@ dlog10(PG_FUNCTION_ARGS)
result = log10(arg1);
if (unlikely(isinf(result)) && !isinf(arg1))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0) && arg1 != 1.0)
- float_underflow_error();
+ float_underflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1778,7 +1778,7 @@ dacos(PG_FUNCTION_ARGS)
result = acos(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1809,7 +1809,7 @@ dasin(PG_FUNCTION_ARGS)
result = asin(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1835,7 +1835,7 @@ datan(PG_FUNCTION_ARGS)
*/
result = atan(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1861,7 +1861,7 @@ datan2(PG_FUNCTION_ARGS)
*/
result = atan2(arg1, arg2);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1902,7 +1902,7 @@ dcos(PG_FUNCTION_ARGS)
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("input is out of range")));
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1957,7 +1957,7 @@ dsin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("input is out of range")));
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2137,7 +2137,7 @@ dacosd(PG_FUNCTION_ARGS)
result = 90.0 + asind_q1(-arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2174,7 +2174,7 @@ dasind(PG_FUNCTION_ARGS)
result = -asind_q1(-arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2206,7 +2206,7 @@ datand(PG_FUNCTION_ARGS)
result = (atan_arg1 / atan_1_0) * 45.0;
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2242,7 +2242,7 @@ datan2d(PG_FUNCTION_ARGS)
result = (atan2_arg1_arg2 / atan_1_0) * 45.0;
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2365,7 +2365,7 @@ dcosd(PG_FUNCTION_ARGS)
result = sign * cosd_q1(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2487,7 +2487,7 @@ dsind(PG_FUNCTION_ARGS)
result = sign * sind_q1(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2645,7 +2645,7 @@ dcosh(PG_FUNCTION_ARGS)
result = get_float8_infinity();
if (unlikely(result == 0.0))
- float_underflow_error();
+ float_underflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2665,7 +2665,7 @@ dtanh(PG_FUNCTION_ARGS)
result = tanh(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2765,7 +2765,7 @@ derf(PG_FUNCTION_ARGS)
result = erf(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2785,7 +2785,7 @@ derfc(PG_FUNCTION_ARGS)
result = erfc(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2814,7 +2814,7 @@ dgamma(PG_FUNCTION_ARGS)
/* Per POSIX, an input of -Inf causes a domain error */
if (arg1 < 0)
{
- float_overflow_error();
+ float_overflow_error(NULL);
result = get_float8_nan(); /* keep compiler quiet */
}
else
@@ -2836,12 +2836,12 @@ dgamma(PG_FUNCTION_ARGS)
if (errno != 0 || isinf(result) || isnan(result))
{
if (result != 0.0)
- float_overflow_error();
+ float_overflow_error(NULL);
else
- float_underflow_error();
+ float_underflow_error(NULL);
}
else if (result == 0.0)
- float_underflow_error();
+ float_underflow_error(NULL);
}
PG_RETURN_FLOAT8(result);
@@ -2873,7 +2873,7 @@ dlgamma(PG_FUNCTION_ARGS)
* to report overflow, but it should never underflow.
*/
if (errno == ERANGE || (isinf(result) && !isinf(arg1)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -3013,7 +3013,7 @@ float8_combine(PG_FUNCTION_ARGS)
tmp = Sx1 / N1 - Sx2 / N2;
Sxx = Sxx1 + Sxx2 + N1 * N2 * tmp * tmp / N;
if (unlikely(isinf(Sxx)) && !isinf(Sxx1) && !isinf(Sxx2))
- float_overflow_error();
+ float_overflow_error(NULL);
}
/*
@@ -3080,7 +3080,7 @@ float8_accum(PG_FUNCTION_ARGS)
if (isinf(Sx) || isinf(Sxx))
{
if (!isinf(transvalues[1]) && !isinf(newval))
- float_overflow_error();
+ float_overflow_error(NULL);
Sxx = get_float8_nan();
}
@@ -3163,7 +3163,7 @@ float4_accum(PG_FUNCTION_ARGS)
if (isinf(Sx) || isinf(Sxx))
{
if (!isinf(transvalues[1]) && !isinf(newval))
- float_overflow_error();
+ float_overflow_error(NULL);
Sxx = get_float8_nan();
}
@@ -3430,7 +3430,7 @@ float8_regr_accum(PG_FUNCTION_ARGS)
(isinf(Sxy) &&
!isinf(transvalues[1]) && !isinf(newvalX) &&
!isinf(transvalues[3]) && !isinf(newvalY)))
- float_overflow_error();
+ float_overflow_error(NULL);
if (isinf(Sxx))
Sxx = get_float8_nan();
@@ -3603,15 +3603,15 @@ float8_regr_combine(PG_FUNCTION_ARGS)
tmp1 = Sx1 / N1 - Sx2 / N2;
Sxx = Sxx1 + Sxx2 + N1 * N2 * tmp1 * tmp1 / N;
if (unlikely(isinf(Sxx)) && !isinf(Sxx1) && !isinf(Sxx2))
- float_overflow_error();
+ float_overflow_error(NULL);
Sy = float8_pl(Sy1, Sy2);
tmp2 = Sy1 / N1 - Sy2 / N2;
Syy = Syy1 + Syy2 + N1 * N2 * tmp2 * tmp2 / N;
if (unlikely(isinf(Syy)) && !isinf(Syy1) && !isinf(Syy2))
- float_overflow_error();
+ float_overflow_error(NULL);
Sxy = Sxy1 + Sxy2 + N1 * N2 * tmp1 * tmp2 / N;
if (unlikely(isinf(Sxy)) && !isinf(Sxy1) && !isinf(Sxy2))
- float_overflow_error();
+ float_overflow_error(NULL);
if (float8_eq(Cx1, Cx2))
Cx = Cx1;
else
diff --git a/src/include/utils/float.h b/src/include/utils/float.h
index fc2a9cf6475..1d0cb026d4e 100644
--- a/src/include/utils/float.h
+++ b/src/include/utils/float.h
@@ -30,9 +30,9 @@ extern PGDLLIMPORT int extra_float_digits;
/*
* Utility functions in float.c
*/
-pg_noreturn extern void float_overflow_error(void);
-pg_noreturn extern void float_underflow_error(void);
-pg_noreturn extern void float_zero_divide_error(void);
+extern void float_overflow_error(struct Node *escontext);
+extern void float_underflow_error(struct Node *escontext);
+extern void float_zero_divide_error(struct Node *escontext);
extern int is_infinite(float8 val);
extern float8 float8in_internal(char *num, char **endptr_p,
const char *type_name, const char *orig_string,
@@ -104,7 +104,7 @@ float4_pl(const float4 val1, const float4 val2)
result = val1 + val2;
if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error();
+ float_overflow_error(NULL);
return result;
}
@@ -116,7 +116,7 @@ float8_pl(const float8 val1, const float8 val2)
result = val1 + val2;
if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error();
+ float_overflow_error(NULL);
return result;
}
@@ -128,7 +128,7 @@ float4_mi(const float4 val1, const float4 val2)
result = val1 - val2;
if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error();
+ float_overflow_error(NULL);
return result;
}
@@ -140,7 +140,7 @@ float8_mi(const float8 val1, const float8 val2)
result = val1 - val2;
if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error();
+ float_overflow_error(NULL);
return result;
}
@@ -152,9 +152,9 @@ float4_mul(const float4 val1, const float4 val2)
result = val1 * val2;
if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0f) && val1 != 0.0f && val2 != 0.0f)
- float_underflow_error();
+ float_underflow_error(NULL);
return result;
}
@@ -166,9 +166,9 @@ float8_mul(const float8 val1, const float8 val2)
result = val1 * val2;
if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0) && val1 != 0.0 && val2 != 0.0)
- float_underflow_error();
+ float_underflow_error(NULL);
return result;
}
@@ -179,12 +179,12 @@ float4_div(const float4 val1, const float4 val2)
float4 result;
if (unlikely(val2 == 0.0f) && !isnan(val1))
- float_zero_divide_error();
+ float_zero_divide_error(NULL);
result = val1 / val2;
if (unlikely(isinf(result)) && !isinf(val1))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0f) && val1 != 0.0f && !isinf(val2))
- float_underflow_error();
+ float_underflow_error(NULL);
return result;
}
@@ -195,12 +195,12 @@ float8_div(const float8 val1, const float8 val2)
float8 result;
if (unlikely(val2 == 0.0) && !isnan(val1))
- float_zero_divide_error();
+ float_zero_divide_error(NULL);
result = val1 / val2;
if (unlikely(isinf(result)) && !isinf(val1))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0) && val1 != 0.0 && !isinf(val2))
- float_underflow_error();
+ float_underflow_error(NULL);
return result;
}
--
2.34.1
v15-0015-error-safe-for-casting-timestamp-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v15-0015-error-safe-for-casting-timestamp-to-other-types-per-pg_cast.patchDownload
From 6dce08e996087a3bba2f766b74b584fef78db9c1 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Tue, 2 Dec 2025 12:14:36 +0800
Subject: [PATCH v15 15/22] error safe for casting timestamp to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc
and pc.castfunc > 0 and (castsource::regtype ='timestamp'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
-----------------------------+-----------------------------+----------+-------------+------------+-----------------------+-------------
timestamp without time zone | date | 2029 | a | f | timestamp_date | date
timestamp without time zone | time without time zone | 1316 | a | f | timestamp_time | time
timestamp without time zone | timestamp with time zone | 2028 | i | f | timestamp_timestamptz | timestamptz
timestamp without time zone | timestamp without time zone | 1961 | i | f | timestamp_scale | timestamp
(4 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 7 +++++--
src/backend/utils/adt/timestamp.c | 10 ++++++++--
2 files changed, 13 insertions(+), 4 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index bbc864a80cd..91cc8cc85f0 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -1330,7 +1330,10 @@ timestamp_date(PG_FUNCTION_ARGS)
Timestamp timestamp = PG_GETARG_TIMESTAMP(0);
DateADT result;
- result = timestamp2date_safe(timestamp, NULL);
+ result = timestamp2date_safe(timestamp, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
+
PG_RETURN_DATEADT(result);
}
@@ -2008,7 +2011,7 @@ timestamp_time(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index c144caf2458..e84cf4b25f8 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -352,7 +352,8 @@ timestamp_scale(PG_FUNCTION_ARGS)
result = timestamp;
- AdjustTimestampForTypmod(&result, typmod, NULL);
+ if (!AdjustTimestampForTypmod(&result, typmod, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_TIMESTAMP(result);
}
@@ -6432,8 +6433,13 @@ Datum
timestamp_timestamptz(PG_FUNCTION_ARGS)
{
Timestamp timestamp = PG_GETARG_TIMESTAMP(0);
+ TimestampTz result;
- PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(timestamp));
+ result = timestamp2timestamptz_safe(timestamp, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
+
+ PG_RETURN_TIMESTAMPTZ(result);
}
/*
--
2.34.1
v15-0014-error-safe-for-casting-timestamptz-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v15-0014-error-safe-for-casting-timestamptz-to-other-types-per-pg_cast.patchDownload
From 978901cde740075e912ea9a24a82e20f1ec8798a Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Tue, 2 Dec 2025 12:04:40 +0800
Subject: [PATCH v15 14/22] error safe for casting timestamptz to other types
per pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
and pc.castfunc > 0 and (castsource::regtype ='timestamptz'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
--------------------------+-----------------------------+----------+-------------+------------+-----------------------+-------------
timestamp with time zone | date | 1178 | a | f | timestamptz_date | date
timestamp with time zone | time without time zone | 2019 | a | f | timestamptz_time | time
timestamp with time zone | timestamp without time zone | 2027 | a | f | timestamptz_timestamp | timestamp
timestamp with time zone | time with time zone | 1388 | a | f | timestamptz_timetz | timetz
timestamp with time zone | timestamp with time zone | 1967 | i | f | timestamptz_scale | timestamptz
(5 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 9 ++++++---
src/backend/utils/adt/timestamp.c | 10 ++++++++--
2 files changed, 14 insertions(+), 5 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 8fa336da250..bbc864a80cd 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -1406,7 +1406,10 @@ timestamptz_date(PG_FUNCTION_ARGS)
TimestampTz timestamp = PG_GETARG_TIMESTAMP(0);
DateADT result;
- result = timestamptz2date_safe(timestamp, NULL);
+ result = timestamptz2date_safe(timestamp, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->args))
+ PG_RETURN_NULL();
+
PG_RETURN_DATEADT(result);
}
@@ -2036,7 +2039,7 @@ timestamptz_time(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
@@ -2955,7 +2958,7 @@ timestamptz_timetz(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 1e8b859ff29..c144caf2458 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -875,7 +875,8 @@ timestamptz_scale(PG_FUNCTION_ARGS)
result = timestamp;
- AdjustTimestampForTypmod(&result, typmod, NULL);
+ if (!AdjustTimestampForTypmod(&result, typmod, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_TIMESTAMPTZ(result);
}
@@ -6494,8 +6495,13 @@ Datum
timestamptz_timestamp(PG_FUNCTION_ARGS)
{
TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(0);
+ Timestamp result;
- PG_RETURN_TIMESTAMP(timestamptz2timestamp(timestamp));
+ result = timestamptz2timestamp_safe(timestamp, fcinfo->context);
+ if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
+
+ PG_RETURN_TIMESTAMP(result);
}
/*
--
2.34.1
v15-0013-error-safe-for-casting-interval-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v15-0013-error-safe-for-casting-interval-to-other-types-per-pg_cast.patchDownload
From 5f38709a59f0e7a00ec09adb9c935056f78d4028 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 9 Oct 2025 18:43:29 +0800
Subject: [PATCH v15 13/22] error safe for casting interval to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'interval'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------------+----------+-------------+------------+----------------+----------
interval | time without time zone | 1419 | a | f | interval_time | time
interval | interval | 1200 | i | f | interval_scale | interval
(2 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 2 +-
src/backend/utils/adt/timestamp.c | 3 ++-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index cf241ea9794..8fa336da250 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2106,7 +2106,7 @@ interval_time(PG_FUNCTION_ARGS)
TimeADT result;
if (INTERVAL_NOT_FINITE(span))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("cannot convert infinite interval to time")));
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 2dc90a2b8a9..1e8b859ff29 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1334,7 +1334,8 @@ interval_scale(PG_FUNCTION_ARGS)
result = palloc(sizeof(Interval));
*result = *interval;
- AdjustIntervalForTypmod(result, typmod, NULL);
+ if (!AdjustIntervalForTypmod(result, typmod, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_INTERVAL_P(result);
}
--
2.34.1
v15-0012-error-safe-for-casting-date-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v15-0012-error-safe-for-casting-date-to-other-types-per-pg_cast.patchDownload
From 42d29ceea40e0d1f006f59bfa3b4f55b3cbe454c Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Tue, 2 Dec 2025 13:15:32 +0800
Subject: [PATCH v15 12/22] error safe for casting date to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'date'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+-----------------------------+----------+-------------+------------+------------------+-------------
date | timestamp without time zone | 2024 | i | f | date_timestamp | timestamp
date | timestamp with time zone | 1174 | i | f | date_timestamptz | timestamptz
(2 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 17 ++++++-----------
1 file changed, 6 insertions(+), 11 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index c4b8125dd66..cf241ea9794 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -730,15 +730,6 @@ date2timestamptz_safe(DateADT dateVal, Node *escontext)
return result;
}
-/*
- * Promote date to timestamptz, throwing error for overflow.
- */
-static TimestampTz
-date2timestamptz(DateADT dateVal)
-{
- return date2timestamptz_safe(dateVal, NULL);
-}
-
/*
* date2timestamp_no_overflow
*
@@ -1323,7 +1314,9 @@ date_timestamp(PG_FUNCTION_ARGS)
DateADT dateVal = PG_GETARG_DATEADT(0);
Timestamp result;
- result = date2timestamp(dateVal);
+ result = date2timestamp_safe(dateVal, fcinfo->context);
+ if(SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_TIMESTAMP(result);
}
@@ -1396,7 +1389,9 @@ date_timestamptz(PG_FUNCTION_ARGS)
DateADT dateVal = PG_GETARG_DATEADT(0);
TimestampTz result;
- result = date2timestamptz(dateVal);
+ result = date2timestamptz_safe(dateVal, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_TIMESTAMP(result);
}
--
2.34.1
v15-0011-error-safe-for-casting-float8-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v15-0011-error-safe-for-casting-float8-to-other-types-per-pg_cast.patchDownload
From 031b8a5ea41f3ac8ffb35235969b313af01e7ec3 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:31:53 +0800
Subject: [PATCH v15 11/22] error safe for casting float8 to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'float8'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------------+------------+----------+-------------+------------+----------------+---------
double precision | bigint | 483 | a | f | dtoi8 | int8
double precision | smallint | 237 | a | f | dtoi2 | int2
double precision | integer | 317 | a | f | dtoi4 | int4
double precision | real | 312 | a | f | dtof | float4
double precision | numeric | 1743 | a | f | float8_numeric | numeric
(5 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/float.c | 13 +++++++++----
src/backend/utils/adt/int8.c | 2 +-
src/backend/utils/adt/numeric.c | 3 ++-
3 files changed, 12 insertions(+), 6 deletions(-)
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index d5f15bfa7de..55c7030ba81 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -1199,9 +1199,14 @@ 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 +1229,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 +1254,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")));
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 68299e91512..7ff7f267b7e 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")));
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index c9d6669a7de..11c76ee40b5 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -4560,7 +4560,8 @@ float8_numeric(PG_FUNCTION_ARGS)
init_var(&result);
/* Assume we need not worry about leading/trailing spaces */
- (void) set_var_from_str(buf, buf, &result, &endptr, NULL);
+ if (!set_var_from_str(buf, buf, &result, &endptr, fcinfo->context))
+ PG_RETURN_NULL();
res = make_result(&result);
--
2.34.1
v15-0010-error-safe-for-casting-float4-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v15-0010-error-safe-for-casting-float4-to-other-types-per-pg_cast.patchDownload
From bc44fa418746256b09c6c1d5ce7c839e10374aa2 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:28:20 +0800
Subject: [PATCH v15 10/22] error safe for casting float4 to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'float4'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+----------------+---------
real | bigint | 653 | a | f | ftoi8 | int8
real | smallint | 238 | a | f | ftoi2 | int2
real | integer | 319 | a | f | ftoi4 | int4
real | double precision | 311 | i | f | ftod | float8
real | numeric | 1742 | a | f | float4_numeric | numeric
(5 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/float.c | 4 ++--
src/backend/utils/adt/int8.c | 2 +-
src/backend/utils/adt/numeric.c | 3 ++-
3 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index 849639fda9f..d5f15bfa7de 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -1298,7 +1298,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 +1323,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 d5631f35465..68299e91512 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -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 3e78cdf4ea0..c9d6669a7de 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -4658,7 +4658,8 @@ float4_numeric(PG_FUNCTION_ARGS)
init_var(&result);
/* Assume we need not worry about leading/trailing spaces */
- (void) set_var_from_str(buf, buf, &result, &endptr, NULL);
+ if (!set_var_from_str(buf, buf, &result, &endptr, fcinfo->context))
+ PG_RETURN_NULL();
res = make_result(&result);
--
2.34.1
v15-0009-error-safe-for-casting-numeric-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v15-0009-error-safe-for-casting-numeric-to-other-types-per-pg_cast.patchDownload
From 1b8c0e0ce6c3d9f45c451de3ae155b07b81f51b8 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:25:37 +0800
Subject: [PATCH v15 09/22] error safe for casting numeric to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'numeric'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+----------------+---------
numeric | bigint | 1779 | a | f | numeric_int8 | int8
numeric | smallint | 1783 | a | f | numeric_int2 | int2
numeric | integer | 1744 | a | f | numeric_int4 | int4
numeric | real | 1745 | i | f | numeric_float4 | float4
numeric | double precision | 1746 | i | f | numeric_float8 | float8
numeric | money | 3824 | a | f | numeric_cash | money
numeric | numeric | 1703 | i | f | numeric | numeric
(7 rows)
discussion: https://postgr.es/m/CACJufxHCMzrHOW=wRe8L30rMhB3sjwAv1LE928Fa7sxMu1Tx-g@mail.gmail.com
---
src/backend/utils/adt/numeric.c | 58 ++++++++++++++++++++++++---------
1 file changed, 43 insertions(+), 15 deletions(-)
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 1d626aecbe7..3e78cdf4ea0 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -1244,7 +1244,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));
}
@@ -1295,8 +1296,9 @@ numeric (PG_FUNCTION_ARGS)
init_var(&var);
set_var_from_num(num, &var);
- (void) apply_typmod(&var, typmod, NULL);
- new = make_result(&var);
+ if (!apply_typmod(&var, typmod, fcinfo->context))
+ PG_RETURN_NULL();
+ new = make_result_safe(&var, fcinfo->context);
free_var(&var);
@@ -3019,7 +3021,10 @@ numeric_mul(PG_FUNCTION_ARGS)
Numeric num2 = PG_GETARG_NUMERIC(1);
Numeric res;
- res = numeric_mul_safe(num1, num2, NULL);
+ res = numeric_mul_safe(num1, num2, fcinfo->context);
+
+ if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
PG_RETURN_NUMERIC(res);
}
@@ -4393,9 +4398,15 @@ numeric_int4_safe(Numeric num, Node *escontext)
Datum
numeric_int4(PG_FUNCTION_ARGS)
{
+ int32 result;
Numeric num = PG_GETARG_NUMERIC(0);
- PG_RETURN_INT32(numeric_int4_safe(num, NULL));
+ result = numeric_int4_safe(num, fcinfo->context);
+
+ if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
+
+ PG_RETURN_INT32(result);
}
/*
@@ -4463,9 +4474,15 @@ numeric_int8_safe(Numeric num, Node *escontext)
Datum
numeric_int8(PG_FUNCTION_ARGS)
{
+ int64 result;
Numeric num = PG_GETARG_NUMERIC(0);
- PG_RETURN_INT64(numeric_int8_safe(num, NULL));
+ result = numeric_int8_safe(num, fcinfo->context);
+
+ if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
+
+ PG_RETURN_INT64(result);
}
@@ -4489,11 +4506,11 @@ numeric_int2(PG_FUNCTION_ARGS)
if (NUMERIC_IS_SPECIAL(num))
{
if (NUMERIC_IS_NAN(num))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert NaN to %s", "smallint")));
else
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert infinity to %s", "smallint")));
}
@@ -4502,12 +4519,12 @@ numeric_int2(PG_FUNCTION_ARGS)
init_var_from_num(num, &x);
if (!numericvar_to_int64(&x, &val))
- ereport(ERROR,
+ ereturn(fcinfo->context, (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(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
@@ -4572,10 +4589,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);
}
@@ -4667,7 +4688,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
v15-0007-error-safe-for-casting-integer-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v15-0007-error-safe-for-casting-integer-to-other-types-per-pg_cast.patchDownload
From fd1a8676cf8ba7bff310337b97011a784d2174d4 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:20:10 +0800
Subject: [PATCH v15 07/22] error safe for casting integer to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'integer'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+--------------+---------
integer | bigint | 481 | i | f | int48 | int8
integer | smallint | 314 | a | f | i4toi2 | int2
integer | real | 318 | i | f | i4tof | float4
integer | double precision | 316 | i | f | i4tod | float8
integer | numeric | 1740 | i | f | int4_numeric | numeric
integer | money | 3811 | a | f | int4_cash | money
integer | boolean | 2557 | e | f | int4_bool | bool
integer | bytea | 6368 | e | f | int4_bytea | bytea
integer | "char" | 78 | e | f | i4tochar | char
integer | bit | 1683 | e | f | bitfromint4 | bit
(10 rows)
only int4_cash, i4toi2, i4tochar need take care of error handling. but support
for cash data type is not easy, so only i4toi2, i4tochar function refactoring.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/char.c | 2 +-
src/backend/utils/adt/int.c | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/backend/utils/adt/char.c b/src/backend/utils/adt/char.c
index 22dbfc950b1..e90844a29f0 100644
--- a/src/backend/utils/adt/char.c
+++ b/src/backend/utils/adt/char.c
@@ -192,7 +192,7 @@ i4tochar(PG_FUNCTION_ARGS)
int32 arg1 = PG_GETARG_INT32(0);
if (arg1 < SCHAR_MIN || arg1 > SCHAR_MAX)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("\"char\" out of range")));
diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c
index b5781989a64..b45599d402d 100644
--- a/src/backend/utils/adt/int.c
+++ b/src/backend/utils/adt/int.c
@@ -350,7 +350,7 @@ i4toi2(PG_FUNCTION_ARGS)
int32 arg1 = PG_GETARG_INT32(0);
if (unlikely(arg1 < SHRT_MIN) || unlikely(arg1 > SHRT_MAX))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
--
2.34.1
v15-0008-error-safe-for-casting-bigint-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v15-0008-error-safe-for-casting-bigint-to-other-types-per-pg_cast.patchDownload
From 4623763149ef4c2aaf822aaa0fc6bead2611e39c Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:22:00 +0800
Subject: [PATCH v15 08/22] error safe for casting bigint to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'bigint'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+--------------+---------
bigint | smallint | 714 | a | f | int82 | int2
bigint | integer | 480 | a | f | int84 | int4
bigint | real | 652 | i | f | i8tof | float4
bigint | double precision | 482 | i | f | i8tod | float8
bigint | numeric | 1781 | i | f | int8_numeric | numeric
bigint | money | 3812 | a | f | int8_cash | money
bigint | oid | 1287 | i | f | i8tooid | oid
bigint | regproc | 1287 | i | f | i8tooid | oid
bigint | regprocedure | 1287 | i | f | i8tooid | oid
bigint | regoper | 1287 | i | f | i8tooid | oid
bigint | regoperator | 1287 | i | f | i8tooid | oid
bigint | regclass | 1287 | i | f | i8tooid | oid
bigint | regcollation | 1287 | i | f | i8tooid | oid
bigint | regtype | 1287 | i | f | i8tooid | oid
bigint | regconfig | 1287 | i | f | i8tooid | oid
bigint | regdictionary | 1287 | i | f | i8tooid | oid
bigint | regrole | 1287 | i | f | i8tooid | oid
bigint | regnamespace | 1287 | i | f | i8tooid | oid
bigint | regdatabase | 1287 | i | f | i8tooid | oid
bigint | bytea | 6369 | e | f | int8_bytea | bytea
bigint | bit | 2075 | e | f | bitfromint8 | bit
(21 rows)
already error safe: i8tof, i8tod, int8_numeric, int8_bytea, bitfromint8
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/int8.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 9cd420b4b9d..d5631f35465 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -1251,7 +1251,7 @@ int84(PG_FUNCTION_ARGS)
int64 arg = PG_GETARG_INT64(0);
if (unlikely(arg < PG_INT32_MIN) || unlikely(arg > PG_INT32_MAX))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
@@ -1272,7 +1272,7 @@ int82(PG_FUNCTION_ARGS)
int64 arg = PG_GETARG_INT64(0);
if (unlikely(arg < PG_INT16_MIN) || unlikely(arg > PG_INT16_MAX))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
@@ -1355,7 +1355,7 @@ i8tooid(PG_FUNCTION_ARGS)
int64 arg = PG_GETARG_INT64(0);
if (unlikely(arg < 0) || unlikely(arg > PG_UINT32_MAX))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("OID out of range")));
--
2.34.1
v15-0005-error-safe-for-casting-inet-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v15-0005-error-safe-for-casting-inet-to-other-types-per-pg_cast.patchDownload
From 287ab2b7d17c850b69ff295f774ed9da6817f84c Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 10:28:54 +0800
Subject: [PATCH v15 05/22] error safe for casting inet to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'inet'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+-------------------+----------+-------------+------------+--------------+---------
inet | cidr | 1715 | a | f | inet_to_cidr | cidr
inet | text | 730 | a | f | network_show | text
inet | character varying | 730 | a | f | network_show | text
inet | character | 730 | a | f | network_show | text
(4 rows)
inet_to_cidr is already error safe.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/network.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/backend/utils/adt/network.c b/src/backend/utils/adt/network.c
index 3cb0ab6829a..648c8d95f51 100644
--- a/src/backend/utils/adt/network.c
+++ b/src/backend/utils/adt/network.c
@@ -1137,7 +1137,7 @@ network_show(PG_FUNCTION_ARGS)
if (pg_inet_net_ntop(ip_family(ip), ip_addr(ip), ip_maxbits(ip),
tmp, sizeof(tmp)) == NULL)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
errmsg("could not format inet value: %m")));
--
2.34.1
v15-0006-error-safe-for-casting-macaddr8-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v15-0006-error-safe-for-casting-macaddr8-to-other-types-per-pg_cast.patchDownload
From fef9b1d6ffb6b1e48c5536aefc0fd981b911fa05 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:17:11 +0800
Subject: [PATCH v15 06/22] error safe for casting macaddr8 to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
and pc.castfunc > 0 and castsource::regtype ='macaddr8'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------+----------+-------------+------------+-------------------+---------
macaddr8 | macaddr | 4124 | i | f | macaddr8tomacaddr | macaddr
(1 row)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/mac8.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/backend/utils/adt/mac8.c b/src/backend/utils/adt/mac8.c
index 08e41ba4eea..1c903f152de 100644
--- a/src/backend/utils/adt/mac8.c
+++ b/src/backend/utils/adt/mac8.c
@@ -550,7 +550,7 @@ macaddr8tomacaddr(PG_FUNCTION_ARGS)
result = (macaddr *) palloc0(sizeof(macaddr));
if ((addr->d != 0xFF) || (addr->e != 0xFE))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("macaddr8 data out of range to convert to macaddr"),
errhint("Only addresses that have FF and FE as values in the "
--
2.34.1
v15-0004-error-safe-for-casting-character-varying-to-other-types-per-pg_c.patchtext/x-patch; charset=US-ASCII; name=v15-0004-error-safe-for-casting-character-varying-to-other-types-per-pg_c.patchDownload
From fbc8f5c3c53eafddc10b6d8d9b7f1ea2d67f3c30 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:13:45 +0800
Subject: [PATCH v15 04/22] error safe for casting character varying to other
types per pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc and pc.castfunc > 0 and
(castsource::regtype = 'character varying'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
-------------------+-------------------+----------+-------------+------------+---------------+----------
character varying | regclass | 1079 | i | f | text_regclass | regclass
character varying | "char" | 944 | a | f | text_char | char
character varying | name | 1400 | i | f | text_name | name
character varying | xml | 2896 | e | f | texttoxml | xml
character varying | character varying | 669 | i | f | varchar | varchar
(5 rows)
texttoxml, text_regclass was refactored as error safe in prior patch.
text_char, text_name is already error safe.
so here we only need handle function "varchar".
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/varchar.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c
index 5cb5c8c46f9..08f1bf5a24d 100644
--- a/src/backend/utils/adt/varchar.c
+++ b/src/backend/utils/adt/varchar.c
@@ -634,7 +634,7 @@ varchar(PG_FUNCTION_ARGS)
{
for (i = maxmblen; i < len; i++)
if (s_data[i] != ' ')
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
errmsg("value too long for type character varying(%d)",
maxlen)));
--
2.34.1
v15-0002-error-safe-for-casting-character-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v15-0002-error-safe-for-casting-character-to-other-types-per-pg_cast.patchDownload
From 78f154c0f30f7cbc209916116638a245984c2a2e Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Mon, 24 Nov 2025 12:52:16 +0800
Subject: [PATCH v15 02/22] error safe for casting character to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype ='character'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+-------------------+----------+-------------+------------+-------------+---------
character | text | 401 | i | f | rtrim1 | text
character | character varying | 401 | i | f | rtrim1 | text
character | "char" | 944 | a | f | text_char | char
character | name | 409 | i | f | bpchar_name | name
character | xml | 2896 | e | f | texttoxml | xml
character | character | 668 | i | f | bpchar | bpchar
(6 rows)
only texttoxml, bpchar(PG_FUNCTION_ARGS) need take care of error handling.
other functions already error safe.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/executor/execExprInterp.c | 2 +-
src/backend/utils/adt/varchar.c | 2 +-
src/backend/utils/adt/xml.c | 18 ++++++++++++------
src/include/utils/xml.h | 2 +-
4 files changed, 15 insertions(+), 9 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 5e7bd933afc..1d88cdd2cb4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -4542,7 +4542,7 @@ ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op)
*op->resvalue = PointerGetDatum(xmlparse(data,
xexpr->xmloption,
- preserve_whitespace));
+ preserve_whitespace, NULL));
*op->resnull = false;
}
break;
diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c
index 3f40c9da1a0..5cb5c8c46f9 100644
--- a/src/backend/utils/adt/varchar.c
+++ b/src/backend/utils/adt/varchar.c
@@ -307,7 +307,7 @@ bpchar(PG_FUNCTION_ARGS)
{
for (i = maxmblen; i < len; i++)
if (s[i] != ' ')
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
errmsg("value too long for type character(%d)",
maxlen)));
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 41e775570ec..9e8016456ce 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -659,7 +659,7 @@ texttoxml(PG_FUNCTION_ARGS)
{
text *data = PG_GETARG_TEXT_PP(0);
- PG_RETURN_XML_P(xmlparse(data, xmloption, true));
+ PG_RETURN_XML_P(xmlparse(data, xmloption, true, fcinfo->context));
}
@@ -1028,19 +1028,25 @@ xmlelement(XmlExpr *xexpr,
xmltype *
-xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace)
+xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, Node *escontext)
{
#ifdef USE_LIBXML
xmlDocPtr doc;
doc = xml_parse(data, xmloption_arg, preserve_whitespace,
- GetDatabaseEncoding(), NULL, NULL, NULL);
- xmlFreeDoc(doc);
+ GetDatabaseEncoding(), NULL, NULL, escontext);
+ if (doc)
+ xmlFreeDoc(doc);
+
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return NULL;
return (xmltype *) data;
#else
- NO_XML_SUPPORT();
- return NULL;
+ ereturn(escontext, NULL
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unsupported XML feature"),
+ errdetail("This functionality requires the server to be built with libxml support."));
#endif
}
diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h
index 732dac47bc4..b15168c430e 100644
--- a/src/include/utils/xml.h
+++ b/src/include/utils/xml.h
@@ -73,7 +73,7 @@ extern xmltype *xmlconcat(List *args);
extern xmltype *xmlelement(XmlExpr *xexpr,
const Datum *named_argvalue, const bool *named_argnull,
const Datum *argvalue, const bool *argnull);
-extern xmltype *xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace);
+extern xmltype *xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, Node *escontext);
extern xmltype *xmlpi(const char *target, text *arg, bool arg_is_null, bool *result_is_null);
extern xmltype *xmlroot(xmltype *data, text *version, int standalone);
extern bool xml_is_document(xmltype *arg);
--
2.34.1
v15-0001-error-safe-for-casting-bit-varbit-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v15-0001-error-safe-for-casting-bit-varbit-to-other-types-per-pg_cast.patchDownload
From 70416850ab1ef1ef8244e51b63e9bf9cbe335300 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 10:33:08 +0800
Subject: [PATCH v15 01/22] error safe for casting bit/varbit to other types
per pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
where pc.castfunc > 0 and (castsource::regtype ='bit'::regtype or
castsource::regtype ='varbit'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
-------------+-------------+----------+-------------+------------+-----------+---------
bit | bigint | 2076 | e | f | bittoint8 | int8
bit | integer | 1684 | e | f | bittoint4 | int4
bit | bit | 1685 | i | f | bit | bit
bit varying | bit varying | 1687 | i | f | varbit | varbit
(4 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/varbit.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/backend/utils/adt/varbit.c b/src/backend/utils/adt/varbit.c
index 205a67dafc5..6e9b808e20a 100644
--- a/src/backend/utils/adt/varbit.c
+++ b/src/backend/utils/adt/varbit.c
@@ -401,7 +401,7 @@ bit(PG_FUNCTION_ARGS)
PG_RETURN_VARBIT_P(arg);
if (!isExplicit)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_STRING_DATA_LENGTH_MISMATCH),
errmsg("bit string length %d does not match type bit(%d)",
VARBITLEN(arg), len)));
@@ -752,7 +752,7 @@ varbit(PG_FUNCTION_ARGS)
PG_RETURN_VARBIT_P(arg);
if (!isExplicit)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
errmsg("bit string too long for type bit varying(%d)",
len)));
@@ -1591,7 +1591,7 @@ bittoint4(PG_FUNCTION_ARGS)
/* Check that the bit string is not too long */
if (VARBITLEN(arg) > sizeof(result) * BITS_PER_BYTE)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
@@ -1671,7 +1671,7 @@ bittoint8(PG_FUNCTION_ARGS)
/* Check that the bit string is not too long */
if (VARBITLEN(arg) > sizeof(result) * BITS_PER_BYTE)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("bigint out of range")));
--
2.34.1
v15-0003-error-safe-for-casting-character-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v15-0003-error-safe-for-casting-character-to-other-types-per-pg_cast.patchDownload
From 7867024f1ec405db6aa0c91de715bd8707550f3f Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Tue, 25 Nov 2025 20:07:28 +0800
Subject: [PATCH v15 03/22] error safe for casting character to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype ='character'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+-------------------+----------+-------------+------------+-------------+---------
character | text | 401 | i | f | rtrim1 | text
character | character varying | 401 | i | f | rtrim1 | text
character | "char" | 944 | a | f | text_char | char
character | name | 409 | i | f | bpchar_name | name
character | xml | 2896 | e | f | texttoxml | xml
character | character | 668 | i | f | bpchar | bpchar
(6 rows)
only texttoxml, bpchar(PG_FUNCTION_ARGS) need take care of error handling.
other functions already error safe.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/catalog/namespace.c | 58 ++++++++++++++++++++++++++-------
src/backend/utils/adt/regproc.c | 13 ++++++--
src/backend/utils/adt/varlena.c | 10 ++++--
src/backend/utils/adt/xml.c | 2 +-
src/include/catalog/namespace.h | 6 ++++
src/include/utils/varlena.h | 1 +
6 files changed, 73 insertions(+), 17 deletions(-)
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index d23474da4fb..bef2de5dd39 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -440,6 +440,16 @@ Oid
RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
uint32 flags,
RangeVarGetRelidCallback callback, void *callback_arg)
+{
+ return RangeVarGetRelidExtendedSafe(relation, lockmode, flags,
+ callback, callback_arg,
+ NULL);
+}
+
+Oid
+RangeVarGetRelidExtendedSafe(const RangeVar *relation, LOCKMODE lockmode, uint32 flags,
+ RangeVarGetRelidCallback callback, void *callback_arg,
+ Node *escontext)
{
uint64 inval_count;
Oid relId;
@@ -456,7 +466,7 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
if (relation->catalogname)
{
if (strcmp(relation->catalogname, get_database_name(MyDatabaseId)) != 0)
- ereport(ERROR,
+ ereturn(escontext, InvalidOid,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cross-database references are not implemented: \"%s.%s.%s\"",
relation->catalogname, relation->schemaname,
@@ -513,7 +523,7 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
* return InvalidOid.
*/
if (namespaceId != myTempNamespace)
- ereport(ERROR,
+ ereturn(escontext, InvalidOid,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("temporary tables cannot specify a schema name")));
}
@@ -593,13 +603,23 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
{
int elevel = (flags & RVR_SKIP_LOCKED) ? DEBUG1 : ERROR;
- if (relation->schemaname)
- ereport(elevel,
+ if (relation->schemaname && elevel == DEBUG1)
+ ereport(DEBUG1,
(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
errmsg("could not obtain lock on relation \"%s.%s\"",
relation->schemaname, relation->relname)));
- else
- ereport(elevel,
+ else if (relation->schemaname && elevel == ERROR)
+ ereturn(escontext, InvalidOid,
+ errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s.%s\"",
+ relation->schemaname, relation->relname));
+ else if (elevel == DEBUG1)
+ ereport(DEBUG1,
+ errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s\"",
+ relation->relname));
+ else if (elevel == ERROR)
+ ereturn(escontext, InvalidOid,
(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
errmsg("could not obtain lock on relation \"%s\"",
relation->relname)));
@@ -626,13 +646,23 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
{
int elevel = missing_ok ? DEBUG1 : ERROR;
- if (relation->schemaname)
- ereport(elevel,
+ if (relation->schemaname && elevel == DEBUG1)
+ ereport(DEBUG1,
(errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("relation \"%s.%s\" does not exist",
relation->schemaname, relation->relname)));
- else
- ereport(elevel,
+ else if (relation->schemaname && elevel == ERROR)
+ ereturn(escontext, InvalidOid,
+ errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s.%s\" does not exist",
+ relation->schemaname, relation->relname));
+ else if (elevel == DEBUG1)
+ ereport(DEBUG1,
+ errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" does not exist",
+ relation->relname));
+ else if (elevel == ERROR)
+ ereturn(escontext, InvalidOid,
(errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("relation \"%s\" does not exist",
relation->relname)));
@@ -3622,6 +3652,12 @@ get_namespace_oid(const char *nspname, bool missing_ok)
*/
RangeVar *
makeRangeVarFromNameList(const List *names)
+{
+ return makeRangeVarFromNameListSafe(names, NULL);
+}
+
+RangeVar *
+makeRangeVarFromNameListSafe(const List *names, Node *escontext)
{
RangeVar *rel = makeRangeVar(NULL, NULL, -1);
@@ -3640,7 +3676,7 @@ makeRangeVarFromNameList(const List *names)
rel->relname = strVal(lthird(names));
break;
default:
- ereport(ERROR,
+ ereturn(escontext, NULL,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("improper relation name (too many dotted names): %s",
NameListToString(names))));
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index e5c2246f2c9..59cc508f805 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -1901,12 +1901,19 @@ text_regclass(PG_FUNCTION_ARGS)
text *relname = PG_GETARG_TEXT_PP(0);
Oid result;
RangeVar *rv;
+ List *namelist;
- rv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
+ namelist = textToQualifiedNameListSafe(relname, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
+
+ rv = makeRangeVarFromNameListSafe(namelist, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
/* We might not even have permissions on this relation; don't lock it. */
- result = RangeVarGetRelid(rv, NoLock, false);
-
+ result = RangeVarGetRelidExtendedSafe(rv, NoLock, 0, NULL, NULL,
+ fcinfo->context);
PG_RETURN_OID(result);
}
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index f202b8df4e2..c4b8f61f5d6 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -2695,6 +2695,12 @@ name_text(PG_FUNCTION_ARGS)
*/
List *
textToQualifiedNameList(text *textval)
+{
+ return textToQualifiedNameListSafe(textval, NULL);
+}
+
+List *
+textToQualifiedNameListSafe(text *textval, Node *escontext)
{
char *rawname;
List *result = NIL;
@@ -2706,12 +2712,12 @@ textToQualifiedNameList(text *textval)
rawname = text_to_cstring(textval);
if (!SplitIdentifierString(rawname, '.', &namelist))
- ereport(ERROR,
+ ereturn(escontext, NIL,
(errcode(ERRCODE_INVALID_NAME),
errmsg("invalid name syntax")));
if (namelist == NIL)
- ereport(ERROR,
+ ereturn(escontext, NIL,
(errcode(ERRCODE_INVALID_NAME),
errmsg("invalid name syntax")));
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 9e8016456ce..8de1f1fc741 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -1043,7 +1043,7 @@ xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, Node
return (xmltype *) data;
#else
- ereturn(escontext, NULL
+ ereturn(escontext, NULL,
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("unsupported XML feature"),
errdetail("This functionality requires the server to be built with libxml support."));
diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h
index f1423f28c32..ab61af55ddc 100644
--- a/src/include/catalog/namespace.h
+++ b/src/include/catalog/namespace.h
@@ -103,6 +103,11 @@ extern Oid RangeVarGetRelidExtended(const RangeVar *relation,
LOCKMODE lockmode, uint32 flags,
RangeVarGetRelidCallback callback,
void *callback_arg);
+extern Oid RangeVarGetRelidExtendedSafe(const RangeVar *relation,
+ LOCKMODE lockmode, uint32 flags,
+ RangeVarGetRelidCallback callback,
+ void *callback_arg,
+ Node *escontext);
extern Oid RangeVarGetCreationNamespace(const RangeVar *newRelation);
extern Oid RangeVarGetAndCheckCreationNamespace(RangeVar *relation,
LOCKMODE lockmode,
@@ -168,6 +173,7 @@ extern Oid LookupCreationNamespace(const char *nspname);
extern void CheckSetNamespace(Oid oldNspOid, Oid nspOid);
extern Oid QualifiedNameGetCreationNamespace(const List *names, char **objname_p);
extern RangeVar *makeRangeVarFromNameList(const List *names);
+extern RangeVar *makeRangeVarFromNameListSafe(const List *names, Node *escontext);
extern char *NameListToString(const List *names);
extern char *NameListToQuotedString(const List *names);
diff --git a/src/include/utils/varlena.h b/src/include/utils/varlena.h
index db9fdf72941..0cf01ae5281 100644
--- a/src/include/utils/varlena.h
+++ b/src/include/utils/varlena.h
@@ -27,6 +27,7 @@ extern int varstr_levenshtein_less_equal(const char *source, int slen,
int ins_c, int del_c, int sub_c,
int max_d, bool trusted);
extern List *textToQualifiedNameList(text *textval);
+extern List *textToQualifiedNameListSafe(text *textval, Node *escontext);
extern bool SplitIdentifierString(char *rawstring, char separator,
List **namelist);
extern bool SplitDirectoriesString(char *rawstring, char separator,
--
2.34.1
On Wed, 10 Dec 2025 at 13:58, jian he <jian.universality@gmail.com> wrote:
On Tue, Dec 9, 2025 at 11:39 AM jian he <jian.universality@gmail.com> wrote:
On Mon, Dec 1, 2025 at 1:41 PM Corey Huinker <corey.huinker@gmail.com> wrote:
No, I meant implementing the syntax for being able to declare a custom CAST function as safe (or not). Basically adding the [SAFE] to
CREATE CAST (source_type AS target_type)
WITH [SAFE] FUNCTION function_name [ (argument_type [, ...]) ]I'm not tied to this syntax choice, but this one seemed the most obvious and least invasive.
hi.
please see the attached v15.
the primary implementation of CAST DEFAULT is contained in V15-0021.changes compared to v14. 1. separate patch (v15-0017) for float error. -pg_noreturn extern void float_overflow_error(void); -pg_noreturn extern void float_underflow_error(void); -pg_noreturn extern void float_zero_divide_error(void); +extern void float_overflow_error(struct Node *escontext); +extern void float_underflow_error(struct Node *escontext); +extern void float_zero_divide_error(struct Node *escontext);2. separate patch (v15-0018) for newly added float8 functions:
float8_pl_safe
float8_mi_safe
float8_mul_safe
float8_div_safe
refactoring existing functions is too invasive, I choose not to.3. refactor point_dt (v15-0019). This is necessary for making geometry data type error-safe, separate from the main patch (v15-0020). I hope to make it easier to review. -static inline float8 point_dt(Point *pt1, Point *pt2); +static inline float8 point_dt(Point *pt1, Point *pt2, Node *escontext);4. skip compile DEFAULT expression (ExecInitExprRec) for binary coercion cast,
as mentioned before. See ExecInitSafeTypeCastExpr.5. Support user-defined type cast error-safe, see v15-0022.
user-defined error-safe cast syntax:
CREATE CAST (source_type AS target_type)
WITH [SAFE] FUNCTION function_name [ (argument_type [, ...]) ]
[ AS ASSIGNMENT | AS IMPLICIT ]this only adds a new keyword SAFE.
This works for C and internal language functions only now.
To make it really usable, I have made citext, hstore module castfunc error safe.
A new column: pg_cast.casterrorsafe was added, this is needed for
CREATE CAST WITH SAFE FUNCTION.+select CAST(ARRAY['a','g','b','h',null,'i'] AS hstore + DEFAULT NULL ON CONVERSION ERROR); + array +------- + +(1 row) +6. slightly polished the doc.
Hi!
Overall, I think this patch is doing a good thing. Also, are we
holding it until the next SQL standard release, because sql/23 leaks
this feature?
Below are my 2c.
1)
First of all, I would prefer the `Bumps catversion` comment in the
commit msg of v15-0022.
2)
In v15-0006, if dont understand when memory allocated by
`result = (macaddr *) palloc0(sizeof(macaddr));` will be freed. Does
it persist until the query ends? I tried to get OOM with a query that
errors out macaddr8 casts repeatedly, but failed.
3)
* When error_safe set to true, we will evaluate the constant expression in a
* error safe way. If the evaluation fails, return NULL instead of throwing
* error.
Somebody has to say it - s/error_safe set/error_safe is set/, also
s/throwing error/throwing an error/
--
Best regards,
Kirill Reshke
Overall, I think this patch is doing a good thing. Also, are we
holding it until the next SQL standard release, because sql/23 leaks
this feature?
The impression that I get is that the SQL Standard has become more
descriptive and less prescriptive. It takes things that exist and
builds the standard around those, so by implementing this draft feature, we
help ensure that it makes it into the standard.
This might be counter-intuitive, but that's what dictionaries do: they
describe how the language is used (descriptive), rather than dictate how it
should be used (prescriptive).
hi.
On Wed, Dec 10, 2025 at 9:32 PM Kirill Reshke <reshkekirill@gmail.com> wrote:
Hi!
Overall, I think this patch is doing a good thing. Also, are we
holding it until the next SQL standard release, because sql/23 leaks
this feature?Below are my 2c.
1)
First of all, I would prefer the `Bumps catversion` comment in the
commit msg of v15-0022.
ok.
2)
In v15-0006, if dont understand when memory allocated by
`result = (macaddr *) palloc0(sizeof(macaddr));` will be freed. Does
it persist until the query ends? I tried to get OOM with a query that
errors out macaddr8 casts repeatedly, but failed.
if ((addr->d != 0xFF) || (addr->e != 0xFE))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("macaddr8 data out of range to convert to macaddr"),
errhint("Only addresses that have FF and FE as values in the "
the change is minor, changing ereport to ereturn.
The whole refactoring does not related to OOM errror,
OOM errors will behave exactly as they did previously.
3)
* When error_safe set to true, we will evaluate the constant expression in a
* error safe way. If the evaluation fails, return NULL instead of throwing
* error.Somebody has to say it - s/error_safe set/error_safe is set/, also
s/throwing error/throwing an error/
sure.
mainly rebase due to recent palloc_object, palloc_array conflict.
Attachments:
v16-0023-error-safe-for-user-defined-CREATE-CAST.patchapplication/x-patch; name=v16-0023-error-safe-for-user-defined-CREATE-CAST.patchDownload
From 61a8a17e8eac8e131e751a643e69555eda34c8fe Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Wed, 10 Dec 2025 16:15:37 +0800
Subject: [PATCH v16 23/23] error safe for user defined CREATE CAST
pg_cast.casterrorsafe column to indicate castfunc is error safe or not.
change src/include/catalog/pg_cast.dat to indicate that most of the system cast
function support soft error evaluation.
The SAFE keyword is introduced for allow user-defined CREATE CAST can also be
evaluated in soft-error. now the synopsis of CREATE CAST is:
CREATE CAST (source_type AS target_type)
WITH [SAFE] FUNCTION function_name [ (argument_type [, ...]) ]
[ AS ASSIGNMENT | AS IMPLICIT ]
The following cast in citext, hstore module refactored to error safe:
CAST (bpchar AS citext)
CAST (boolean AS citext)
CAST (inet AS citext)
CAST (text[] AS hstore)
CAST (hstore AS json)
CAST (hstore AS jsonb)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
contrib/citext/citext--1.4.sql | 6 +-
contrib/citext/expected/citext.out | 24 +-
contrib/citext/expected/citext_1.out | 24 +-
contrib/citext/sql/citext.sql | 5 +-
contrib/hstore/expected/hstore.out | 37 +++
contrib/hstore/hstore--1.2--1.3.sql | 2 +-
contrib/hstore/hstore--1.4.sql | 6 +-
contrib/hstore/hstore_io.c | 8 +-
contrib/hstore/sql/hstore.sql | 11 +
doc/src/sgml/catalogs.sgml | 15 +
doc/src/sgml/ref/create_cast.sgml | 14 +-
doc/src/sgml/syntax.sgml | 3 +-
src/backend/catalog/pg_cast.c | 4 +-
src/backend/commands/functioncmds.c | 9 +-
src/backend/commands/typecmds.c | 1 +
src/backend/parser/gram.y | 18 +-
src/backend/parser/parse_expr.c | 10 +-
src/include/catalog/pg_cast.dat | 330 +++++++++++-----------
src/include/catalog/pg_cast.h | 5 +
src/include/nodes/parsenodes.h | 1 +
src/include/parser/kwlist.h | 1 +
src/test/regress/expected/create_cast.out | 8 +-
src/test/regress/expected/opr_sanity.out | 24 +-
src/test/regress/sql/create_cast.sql | 5 +
24 files changed, 353 insertions(+), 218 deletions(-)
diff --git a/contrib/citext/citext--1.4.sql b/contrib/citext/citext--1.4.sql
index 7b061989352..5c87820388f 100644
--- a/contrib/citext/citext--1.4.sql
+++ b/contrib/citext/citext--1.4.sql
@@ -85,9 +85,9 @@ CREATE CAST (citext AS varchar) WITHOUT FUNCTION AS IMPLICIT;
CREATE CAST (citext AS bpchar) WITHOUT FUNCTION AS ASSIGNMENT;
CREATE CAST (text AS citext) WITHOUT FUNCTION AS ASSIGNMENT;
CREATE CAST (varchar AS citext) WITHOUT FUNCTION AS ASSIGNMENT;
-CREATE CAST (bpchar AS citext) WITH FUNCTION citext(bpchar) AS ASSIGNMENT;
-CREATE CAST (boolean AS citext) WITH FUNCTION citext(boolean) AS ASSIGNMENT;
-CREATE CAST (inet AS citext) WITH FUNCTION citext(inet) AS ASSIGNMENT;
+CREATE CAST (bpchar AS citext) WITH SAFE FUNCTION citext(bpchar) AS ASSIGNMENT;
+CREATE CAST (boolean AS citext) WITH SAFE FUNCTION citext(boolean) AS ASSIGNMENT;
+CREATE CAST (inet AS citext) WITH SAFE FUNCTION citext(inet) AS ASSIGNMENT;
--
-- Operator Functions.
diff --git a/contrib/citext/expected/citext.out b/contrib/citext/expected/citext.out
index 33da19d8df4..be328715492 100644
--- a/contrib/citext/expected/citext.out
+++ b/contrib/citext/expected/citext.out
@@ -10,11 +10,12 @@ WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid);
--------+---------
(0 rows)
-SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR); --error
-ERROR: cannot cast type character to citext when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
-LINE 1: SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSI...
- ^
-HINT: Safe type cast for user-defined types are not yet supported
+SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR);
+ citext
+--------
+ abc
+(1 row)
+
-- Test the operators and indexing functions
-- Test = and <>.
SELECT 'a'::citext = 'a'::citext AS t;
@@ -523,6 +524,12 @@ SELECT true::citext = 'true' AS t;
t
(1 row)
+SELECT CAST(true AS citext DEFAULT NULL ON CONVERSION ERROR);
+ citext
+--------
+ true
+(1 row)
+
SELECT 'true'::citext::boolean = true AS t;
t
---
@@ -787,6 +794,13 @@ SELECT '192.168.100.128'::citext::inet = '192.168.100.128'::inet AS t;
t
(1 row)
+SELECT CAST(inet '192.168.100.128' AS citext
+ DEFAULT NULL ON CONVERSION ERROR) = '192.168.100.128/32' AS t;
+ t
+---
+ t
+(1 row)
+
SELECT '08:00:2b:01:02:03'::macaddr::citext = '08:00:2b:01:02:03' AS t;
t
---
diff --git a/contrib/citext/expected/citext_1.out b/contrib/citext/expected/citext_1.out
index 647eea19142..e9f8454c662 100644
--- a/contrib/citext/expected/citext_1.out
+++ b/contrib/citext/expected/citext_1.out
@@ -10,11 +10,12 @@ WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid);
--------+---------
(0 rows)
-SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR); --error
-ERROR: cannot cast type character to citext when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
-LINE 1: SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSI...
- ^
-HINT: Safe type cast for user-defined types are not yet supported
+SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR);
+ citext
+--------
+ abc
+(1 row)
+
-- Test the operators and indexing functions
-- Test = and <>.
SELECT 'a'::citext = 'a'::citext AS t;
@@ -523,6 +524,12 @@ SELECT true::citext = 'true' AS t;
t
(1 row)
+SELECT CAST(true AS citext DEFAULT NULL ON CONVERSION ERROR);
+ citext
+--------
+ true
+(1 row)
+
SELECT 'true'::citext::boolean = true AS t;
t
---
@@ -787,6 +794,13 @@ SELECT '192.168.100.128'::citext::inet = '192.168.100.128'::inet AS t;
t
(1 row)
+SELECT CAST(inet '192.168.100.128' AS citext
+ DEFAULT NULL ON CONVERSION ERROR) = '192.168.100.128/32' AS t;
+ t
+---
+ t
+(1 row)
+
SELECT '08:00:2b:01:02:03'::macaddr::citext = '08:00:2b:01:02:03' AS t;
t
---
diff --git a/contrib/citext/sql/citext.sql b/contrib/citext/sql/citext.sql
index 99794497d47..62f83d749f9 100644
--- a/contrib/citext/sql/citext.sql
+++ b/contrib/citext/sql/citext.sql
@@ -9,7 +9,7 @@ SELECT amname, opcname
FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod
WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid);
-SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR);
-- Test the operators and indexing functions
@@ -165,6 +165,7 @@ SELECT name FROM srt WHERE name SIMILAR TO '%A.*';
-- Explicit casts.
SELECT true::citext = 'true' AS t;
+SELECT CAST(true AS citext DEFAULT NULL ON CONVERSION ERROR);
SELECT 'true'::citext::boolean = true AS t;
SELECT 4::citext = '4' AS t;
@@ -224,6 +225,8 @@ SELECT '192.168.100.128/25'::citext::cidr = '192.168.100.128/25'::cidr AS t;
SELECT '192.168.100.128'::inet::citext = '192.168.100.128/32' AS t;
SELECT '192.168.100.128'::citext::inet = '192.168.100.128'::inet AS t;
+SELECT CAST(inet '192.168.100.128' AS citext
+ DEFAULT NULL ON CONVERSION ERROR) = '192.168.100.128/32' AS t;
SELECT '08:00:2b:01:02:03'::macaddr::citext = '08:00:2b:01:02:03' AS t;
SELECT '08:00:2b:01:02:03'::citext::macaddr = '08:00:2b:01:02:03'::macaddr AS t;
diff --git a/contrib/hstore/expected/hstore.out b/contrib/hstore/expected/hstore.out
index 1836c9acf39..2622137cbf9 100644
--- a/contrib/hstore/expected/hstore.out
+++ b/contrib/hstore/expected/hstore.out
@@ -841,12 +841,28 @@ select '{}'::text[]::hstore;
select ARRAY['a','g','b','h','asd']::hstore;
ERROR: array must have even number of elements
+select CAST(ARRAY['a','g','b','h','asd'] AS hstore
+ DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
select ARRAY['a','g','b','h','asd','i']::hstore;
array
--------------------------------
"a"=>"g", "b"=>"h", "asd"=>"i"
(1 row)
+select ARRAY['a','g','b','h',null,'i']::hstore;
+ERROR: null value not allowed for hstore key
+select CAST(ARRAY['a','g','b','h',null,'i'] AS hstore
+ DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
select ARRAY[['a','g'],['b','h'],['asd','i']]::hstore;
array
--------------------------------
@@ -855,6 +871,13 @@ select ARRAY[['a','g'],['b','h'],['asd','i']]::hstore;
select ARRAY[['a','g','b'],['h','asd','i']]::hstore;
ERROR: array must have two columns
+select CAST(ARRAY[['a','g','b'],['h','asd','i']] AS hstore
+ DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
select ARRAY[[['a','g'],['b','h'],['asd','i']]]::hstore;
ERROR: wrong number of array subscripts
select hstore('{}'::text[]);
@@ -1553,6 +1576,13 @@ select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=
{"b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4", "a key": "1"}
(1 row)
+select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as json
+ default null on conversion error);
+ json
+-------------------------------------------------------------------------------------------------
+ {"b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4", "a key": "1"}
+(1 row)
+
select hstore_to_json_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4, h=> "2016-01-01"');
hstore_to_json_loose
-------------------------------------------------------------------------------------------------------------
@@ -1571,6 +1601,13 @@ select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=
{"b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4", "a key": "1"}
(1 row)
+select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as jsonb
+ default null on conversion error);
+ jsonb
+-------------------------------------------------------------------------------------------------
+ {"b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4", "a key": "1"}
+(1 row)
+
select hstore_to_jsonb_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4, h=> "2016-01-01"');
hstore_to_jsonb_loose
----------------------------------------------------------------------------------------------------------
diff --git a/contrib/hstore/hstore--1.2--1.3.sql b/contrib/hstore/hstore--1.2--1.3.sql
index 0a7056015b7..cbb1a139e69 100644
--- a/contrib/hstore/hstore--1.2--1.3.sql
+++ b/contrib/hstore/hstore--1.2--1.3.sql
@@ -9,7 +9,7 @@ AS 'MODULE_PATHNAME', 'hstore_to_jsonb'
LANGUAGE C IMMUTABLE STRICT;
CREATE CAST (hstore AS jsonb)
- WITH FUNCTION hstore_to_jsonb(hstore);
+ WITH SAFE FUNCTION hstore_to_jsonb(hstore);
CREATE FUNCTION hstore_to_jsonb_loose(hstore)
RETURNS jsonb
diff --git a/contrib/hstore/hstore--1.4.sql b/contrib/hstore/hstore--1.4.sql
index 4294d14ceb5..451c2ed8187 100644
--- a/contrib/hstore/hstore--1.4.sql
+++ b/contrib/hstore/hstore--1.4.sql
@@ -232,7 +232,7 @@ AS 'MODULE_PATHNAME', 'hstore_from_array'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE CAST (text[] AS hstore)
- WITH FUNCTION hstore(text[]);
+ WITH SAFE FUNCTION hstore(text[]);
CREATE FUNCTION hstore_to_json(hstore)
RETURNS json
@@ -240,7 +240,7 @@ AS 'MODULE_PATHNAME', 'hstore_to_json'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE CAST (hstore AS json)
- WITH FUNCTION hstore_to_json(hstore);
+ WITH SAFE FUNCTION hstore_to_json(hstore);
CREATE FUNCTION hstore_to_json_loose(hstore)
RETURNS json
@@ -253,7 +253,7 @@ AS 'MODULE_PATHNAME', 'hstore_to_jsonb'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE CAST (hstore AS jsonb)
- WITH FUNCTION hstore_to_jsonb(hstore);
+ WITH SAFE FUNCTION hstore_to_jsonb(hstore);
CREATE FUNCTION hstore_to_jsonb_loose(hstore)
RETURNS jsonb
diff --git a/contrib/hstore/hstore_io.c b/contrib/hstore/hstore_io.c
index 34e3918811c..f5166679783 100644
--- a/contrib/hstore/hstore_io.c
+++ b/contrib/hstore/hstore_io.c
@@ -738,20 +738,20 @@ hstore_from_array(PG_FUNCTION_ARGS)
case 1:
if ((ARR_DIMS(in_array)[0]) % 2)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
errmsg("array must have even number of elements")));
break;
case 2:
if ((ARR_DIMS(in_array)[1]) != 2)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
errmsg("array must have two columns")));
break;
default:
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
errmsg("wrong number of array subscripts")));
}
@@ -772,7 +772,7 @@ hstore_from_array(PG_FUNCTION_ARGS)
for (i = 0; i < count; ++i)
{
if (in_nulls[i * 2])
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("null value not allowed for hstore key")));
diff --git a/contrib/hstore/sql/hstore.sql b/contrib/hstore/sql/hstore.sql
index efef91292a3..8fa46630d6d 100644
--- a/contrib/hstore/sql/hstore.sql
+++ b/contrib/hstore/sql/hstore.sql
@@ -192,9 +192,16 @@ select pg_column_size(slice(hstore 'aa=>1, b=>2, c=>3', ARRAY['c','b','aa']))
-- array input
select '{}'::text[]::hstore;
select ARRAY['a','g','b','h','asd']::hstore;
+select CAST(ARRAY['a','g','b','h','asd'] AS hstore
+ DEFAULT NULL ON CONVERSION ERROR);
select ARRAY['a','g','b','h','asd','i']::hstore;
+select ARRAY['a','g','b','h',null,'i']::hstore;
+select CAST(ARRAY['a','g','b','h',null,'i'] AS hstore
+ DEFAULT NULL ON CONVERSION ERROR);
select ARRAY[['a','g'],['b','h'],['asd','i']]::hstore;
select ARRAY[['a','g','b'],['h','asd','i']]::hstore;
+select CAST(ARRAY[['a','g','b'],['h','asd','i']] AS hstore
+ DEFAULT NULL ON CONVERSION ERROR);
select ARRAY[[['a','g'],['b','h'],['asd','i']]]::hstore;
select hstore('{}'::text[]);
select hstore(ARRAY['a','g','b','h','asd']);
@@ -363,10 +370,14 @@ select count(*) from testhstore where h = 'pos=>98, line=>371, node=>CBA, indexe
-- json and jsonb
select hstore_to_json('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4');
select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as json);
+select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as json
+ default null on conversion error);
select hstore_to_json_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4, h=> "2016-01-01"');
select hstore_to_jsonb('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4');
select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as jsonb);
+select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as jsonb
+ default null on conversion error);
select hstore_to_jsonb_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4, h=> "2016-01-01"');
create table test_json_agg (f1 text, f2 hstore);
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2fc63442980..8fca3534f32 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1849,6 +1849,21 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<literal>b</literal> means that the types are binary-coercible, thus no conversion is required.
</para></entry>
</row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>casterrorsafe</structfield> <type>bool</type>
+ </para>
+ <para>
+ This flag indicates whether the <structfield>castfunc</structfield> function
+ is error-safe. It is meaningful only when <structfield>castfunc</structfield>
+ is not zero. User-defined casts can set it
+ to <literal>true</literal> via <link linkend="sql-createcast">CREATE CAST</link>.
+ For error-safe type cast, see <xref linkend="sql-syntax-type-casts-safe"/>.
+ </para>
+ </entry>
+ </row>
+
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/ref/create_cast.sgml b/doc/src/sgml/ref/create_cast.sgml
index bad75bc1dce..888d7142e42 100644
--- a/doc/src/sgml/ref/create_cast.sgml
+++ b/doc/src/sgml/ref/create_cast.sgml
@@ -22,7 +22,7 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE CAST (<replaceable>source_type</replaceable> AS <replaceable>target_type</replaceable>)
- WITH FUNCTION <replaceable>function_name</replaceable> [ (<replaceable>argument_type</replaceable> [, ...]) ]
+ WITH [SAFE] FUNCTION <replaceable>function_name</replaceable> [ (<replaceable>argument_type</replaceable> [, ...]) ]
[ AS ASSIGNMENT | AS IMPLICIT ]
CREATE CAST (<replaceable>source_type</replaceable> AS <replaceable>target_type</replaceable>)
@@ -194,6 +194,18 @@ SELECT CAST ( 2 AS numeric ) + 4.0;
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>SAFE</literal></term>
+ <listitem>
+ <para>
+ The function used to perform the cast support soft-error evaluation,
+ Currently, only functions written in C or the internal language are supported.
+ An alternate expression can be specified to be evaluated if the cast
+ error occurs. See <link linkend="sql-syntax-type-casts-safe">safe type cast</link>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal><replaceable>function_name</replaceable>[(<replaceable>argument_type</replaceable> [, ...])]</literal></term>
diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml
index 32af9ea061c..24d1dc6de0b 100644
--- a/doc/src/sgml/syntax.sgml
+++ b/doc/src/sgml/syntax.sgml
@@ -2176,8 +2176,7 @@ CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable>
</synopsis>
If the type cast fails, instead of error out, evaluation falls back to the
default <replaceable>expression</replaceable> specified in the <literal>ON ERROR</literal> clause.
- At present, this only support built-in type casts; see <xref linkend="catalog-pg-cast"/>.
- User-defined type casts created with <link linkend="sql-createcast">CREATE CAST</link> are not supported.
+ User-defined type casts created with <link linkend="sql-createcast">CREATE CAST</link> are supported too.
</para>
<para>
diff --git a/src/backend/catalog/pg_cast.c b/src/backend/catalog/pg_cast.c
index 1773c9c5491..4116b1708b0 100644
--- a/src/backend/catalog/pg_cast.c
+++ b/src/backend/catalog/pg_cast.c
@@ -48,7 +48,8 @@
ObjectAddress
CastCreate(Oid sourcetypeid, Oid targettypeid,
Oid funcid, Oid incastid, Oid outcastid,
- char castcontext, char castmethod, DependencyType behavior)
+ char castcontext, char castmethod, bool casterrorsafe,
+ DependencyType behavior)
{
Relation relation;
HeapTuple tuple;
@@ -84,6 +85,7 @@ CastCreate(Oid sourcetypeid, Oid targettypeid,
values[Anum_pg_cast_castfunc - 1] = ObjectIdGetDatum(funcid);
values[Anum_pg_cast_castcontext - 1] = CharGetDatum(castcontext);
values[Anum_pg_cast_castmethod - 1] = CharGetDatum(castmethod);
+ values[Anum_pg_cast_casterrorsafe - 1] = BoolGetDatum(casterrorsafe);
tuple = heap_form_tuple(RelationGetDescr(relation), values, nulls);
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index 8a435cd93db..0bc9373c7f2 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -1667,6 +1667,13 @@ CreateCast(CreateCastStmt *stmt)
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("cast function must not return a set")));
+ if (stmt->safe &&
+ procstruct->prolang != INTERNALlanguageId &&
+ procstruct->prolang != ClanguageId)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Safe type cast functions are only supported for C and internal languages"));
+
ReleaseSysCache(tuple);
}
else
@@ -1795,7 +1802,7 @@ CreateCast(CreateCastStmt *stmt)
}
myself = CastCreate(sourcetypeid, targettypeid, funcid, incastid, outcastid,
- castcontext, castmethod, DEPENDENCY_NORMAL);
+ castcontext, castmethod, stmt->safe, DEPENDENCY_NORMAL);
return myself;
}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 0eb8e0a2bb0..36d443205f4 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -1754,6 +1754,7 @@ DefineRange(ParseState *pstate, CreateRangeStmt *stmt)
/* Create cast from the range type to its multirange type */
CastCreate(typoid, multirangeOid, castFuncOid, InvalidOid, InvalidOid,
COERCION_CODE_EXPLICIT, COERCION_METHOD_FUNCTION,
+ false,
DEPENDENCY_INTERNAL);
pfree(multirangeArrayName);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9e6573444cc..661d11e57dd 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -353,7 +353,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <defelt> drop_option
%type <boolean> opt_or_replace opt_no
opt_grant_grant_option
- opt_nowait opt_if_exists opt_with_data
+ opt_nowait opt_safe opt_if_exists opt_with_data
opt_transaction_chain
%type <list> grant_role_opt_list
%type <defelt> grant_role_opt
@@ -774,7 +774,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
RESET RESPECT_P RESTART RESTRICT RETURN RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
ROUTINE ROUTINES ROW ROWS RULE
- SAVEPOINT SCALAR SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT
+ SAFE SAVEPOINT SCALAR SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT
SEQUENCE SEQUENCES
SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SOURCE SQL_P STABLE STANDALONE_P
@@ -9291,14 +9291,15 @@ dostmt_opt_item:
*****************************************************************************/
CreateCastStmt: CREATE CAST '(' Typename AS Typename ')'
- WITH FUNCTION function_with_argtypes cast_context
+ WITH opt_safe FUNCTION function_with_argtypes cast_context
{
CreateCastStmt *n = makeNode(CreateCastStmt);
n->sourcetype = $4;
n->targettype = $6;
- n->func = $10;
- n->context = (CoercionContext) $11;
+ n->safe = $9;
+ n->func = $11;
+ n->context = (CoercionContext) $12;
n->inout = false;
$$ = (Node *) n;
}
@@ -9309,6 +9310,7 @@ CreateCastStmt: CREATE CAST '(' Typename AS Typename ')'
n->sourcetype = $4;
n->targettype = $6;
+ n->safe = false;
n->func = NULL;
n->context = (CoercionContext) $10;
n->inout = false;
@@ -9321,6 +9323,7 @@ CreateCastStmt: CREATE CAST '(' Typename AS Typename ')'
n->sourcetype = $4;
n->targettype = $6;
+ n->safe = false;
n->func = NULL;
n->context = (CoercionContext) $10;
n->inout = true;
@@ -9333,6 +9336,9 @@ cast_context: AS IMPLICIT_P { $$ = COERCION_IMPLICIT; }
| /*EMPTY*/ { $$ = COERCION_EXPLICIT; }
;
+opt_safe: SAFE { $$ = true; }
+ | /*EMPTY*/ { $$ = false; }
+ ;
DropCastStmt: DROP CAST opt_if_exists '(' Typename AS Typename ')' opt_drop_behavior
{
@@ -18108,6 +18114,7 @@ unreserved_keyword:
| ROUTINES
| ROWS
| RULE
+ | SAFE
| SAVEPOINT
| SCALAR
| SCHEMA
@@ -18744,6 +18751,7 @@ bare_label_keyword:
| ROW
| ROWS
| RULE
+ | SAFE
| SAVEPOINT
| SCALAR
| SCHEMA
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index a10ec64a708..1e1fa5b1f20 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -3051,7 +3051,6 @@ CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr, Node *sourceexpr, Oid
{
HeapTuple tuple;
bool errorsafe = true;
- bool userdefined = false;
Oid inputBaseType;
Oid targetBaseType;
Oid inputElementType;
@@ -3123,11 +3122,8 @@ CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr, Node *sourceexpr, Oid
{
Form_pg_cast castForm = (Form_pg_cast) GETSTRUCT(tuple);
- if (castForm->oid > FirstUnpinnedObjectId)
- {
+ if (OidIsValid(castForm->castfunc) && !castForm->casterrorsafe)
errorsafe = false;
- userdefined = true;
- }
ReleaseSysCache(tuple);
}
@@ -3141,9 +3137,7 @@ CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr, Node *sourceexpr, Oid
format_type_be(targetType),
"DEFAULT",
"CAST ... ON CONVERSION ERROR"),
- userdefined
- ? errhint("Safe type cast for user-defined types are not yet supported")
- : errhint("Explicit cast is defined but definition is not error safe"),
+ errhint("Explicit cast is defined but definition is not error safe"),
parser_errposition(pstate, exprLocation(sourceexpr)));
}
diff --git a/src/include/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat
index fbfd669587f..ca52cfcd086 100644
--- a/src/include/catalog/pg_cast.dat
+++ b/src/include/catalog/pg_cast.dat
@@ -19,65 +19,65 @@
# int2->int4->int8->numeric->float4->float8, while casts in the
# reverse direction are assignment-only.
{ castsource => 'int8', casttarget => 'int2', castfunc => 'int2(int8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'int4', castfunc => 'int4(int8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'float4', castfunc => 'float4(int8)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'float8', castfunc => 'float8(int8)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'numeric', castfunc => 'numeric(int8)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'int8', castfunc => 'int8(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'int4', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'float4', castfunc => 'float4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'float8', castfunc => 'float8(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'numeric', castfunc => 'numeric(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'int8', castfunc => 'int8(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'int2', castfunc => 'int2(int4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'float4', castfunc => 'float4(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'float8', castfunc => 'float8(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'numeric', castfunc => 'numeric(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'int8', castfunc => 'int8(float4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'int2', castfunc => 'int2(float4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'int4', castfunc => 'int4(float4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'float8', castfunc => 'float8(float4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'numeric',
- castfunc => 'numeric(float4)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'numeric(float4)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'int8', castfunc => 'int8(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'int2', castfunc => 'int2(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'int4', castfunc => 'int4(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'float4', castfunc => 'float4(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'numeric',
- castfunc => 'numeric(float8)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'numeric(float8)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'int8', castfunc => 'int8(numeric)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'int2', castfunc => 'int2(numeric)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'int4', castfunc => 'int4(numeric)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'float4',
- castfunc => 'float4(numeric)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'float4(numeric)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'float8',
- castfunc => 'float8(numeric)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'float8(numeric)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'money', casttarget => 'numeric', castfunc => 'numeric(money)',
castcontext => 'a', castmethod => 'f' },
{ castsource => 'numeric', casttarget => 'money', castfunc => 'money(numeric)',
@@ -89,13 +89,13 @@
# Allow explicit coercions between int4 and bool
{ castsource => 'int4', casttarget => 'bool', castfunc => 'bool(int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'int4', castfunc => 'int4(bool)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Allow explicit coercions between xid8 and xid
{ castsource => 'xid8', casttarget => 'xid', castfunc => 'xid(xid8)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# OID category: allow implicit conversion from any integral type (including
# int8, to support OID literals > 2G) to OID, as well as assignment coercion
@@ -106,13 +106,13 @@
# casts from text and varchar to regclass, which exist mainly to support
# legacy forms of nextval() and related functions.
{ castsource => 'int8', casttarget => 'oid', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'oid', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'oid', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regproc', castfunc => '0',
@@ -120,13 +120,13 @@
{ castsource => 'regproc', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regproc', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regproc', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regproc', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regproc', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regproc', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'regproc', casttarget => 'regprocedure', castfunc => '0',
@@ -138,13 +138,13 @@
{ castsource => 'regprocedure', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regprocedure', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regprocedure', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regprocedure', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regprocedure', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regprocedure', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regoper', castfunc => '0',
@@ -152,13 +152,13 @@
{ castsource => 'regoper', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regoper', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regoper', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regoper', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regoper', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regoper', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'regoper', casttarget => 'regoperator', castfunc => '0',
@@ -170,13 +170,13 @@
{ castsource => 'regoperator', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regoperator', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regoperator', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regoperator', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regoperator', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regoperator', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regclass', castfunc => '0',
@@ -184,13 +184,13 @@
{ castsource => 'regclass', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regclass', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regclass', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regclass', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regclass', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regclass', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regcollation', castfunc => '0',
@@ -198,13 +198,13 @@
{ castsource => 'regcollation', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regcollation', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regcollation', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regcollation', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regcollation', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regcollation', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regtype', castfunc => '0',
@@ -212,13 +212,13 @@
{ castsource => 'regtype', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regtype', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regtype', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regtype', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regtype', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regtype', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regconfig', castfunc => '0',
@@ -226,13 +226,13 @@
{ castsource => 'regconfig', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regconfig', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regconfig', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regconfig', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regconfig', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regconfig', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regdictionary', castfunc => '0',
@@ -240,31 +240,31 @@
{ castsource => 'regdictionary', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regdictionary', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regdictionary', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regdictionary', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regdictionary', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regdictionary', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'text', casttarget => 'regclass', castfunc => 'regclass',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'regclass', castfunc => 'regclass',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'oid', casttarget => 'regrole', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regrole', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regrole', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regrole', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regrole', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regrole', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regrole', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regnamespace', castfunc => '0',
@@ -272,13 +272,13 @@
{ castsource => 'regnamespace', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regnamespace', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regnamespace', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regnamespace', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regnamespace', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regnamespace', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regdatabase', castfunc => '0',
@@ -286,13 +286,13 @@
{ castsource => 'regdatabase', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regdatabase', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regdatabase', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regdatabase', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regdatabase', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regdatabase', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
@@ -302,57 +302,57 @@
{ castsource => 'text', casttarget => 'varchar', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'bpchar', casttarget => 'text', castfunc => 'text(bpchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bpchar', casttarget => 'varchar', castfunc => 'text(bpchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'text', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'varchar', casttarget => 'bpchar', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'char', casttarget => 'text', castfunc => 'text(char)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'char', casttarget => 'bpchar', castfunc => 'bpchar(char)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'char', casttarget => 'varchar', castfunc => 'text(char)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'name', casttarget => 'text', castfunc => 'text(name)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'name', casttarget => 'bpchar', castfunc => 'bpchar(name)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'name', casttarget => 'varchar', castfunc => 'varchar(name)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'text', casttarget => 'char', castfunc => 'char(text)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bpchar', casttarget => 'char', castfunc => 'char(text)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'char', castfunc => 'char(text)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'text', casttarget => 'name', castfunc => 'name(text)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bpchar', casttarget => 'name', castfunc => 'name(bpchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'name', castfunc => 'name(varchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
# Allow explicit coercions between bytea and integer types
{ castsource => 'int2', casttarget => 'bytea', castfunc => 'bytea(int2)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'bytea', castfunc => 'bytea(int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'bytea', castfunc => 'bytea(int8)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bytea', casttarget => 'int2', castfunc => 'int2(bytea)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bytea', casttarget => 'int4', castfunc => 'int4(bytea)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bytea', casttarget => 'int8', castfunc => 'int8(bytea)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Allow explicit coercions between int4 and "char"
{ castsource => 'char', casttarget => 'int4', castfunc => 'int4(char)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'char', castfunc => 'char(int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# pg_node_tree can be coerced to, but not from, text
{ castsource => 'pg_node_tree', casttarget => 'text', castfunc => '0',
@@ -378,73 +378,73 @@
# Datetime category
{ castsource => 'date', casttarget => 'timestamp',
- castfunc => 'timestamp(date)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timestamp(date)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'date', casttarget => 'timestamptz',
- castfunc => 'timestamptz(date)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timestamptz(date)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'time', casttarget => 'interval', castfunc => 'interval(time)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'time', casttarget => 'timetz', castfunc => 'timetz(time)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'date',
- castfunc => 'date(timestamp)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'date(timestamp)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'time',
- castfunc => 'time(timestamp)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'time(timestamp)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'timestamptz',
- castfunc => 'timestamptz(timestamp)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timestamptz(timestamp)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'date',
- castfunc => 'date(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'date(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'time',
- castfunc => 'time(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'time(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'timestamp',
- castfunc => 'timestamp(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'timestamp(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'timetz',
- castfunc => 'timetz(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'timetz(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'interval', casttarget => 'time', castfunc => 'time(interval)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timetz', casttarget => 'time', castfunc => 'time(timetz)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
# Geometric category
{ castsource => 'point', casttarget => 'box', castfunc => 'box(point)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'lseg', casttarget => 'point', castfunc => 'point(lseg)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'path', casttarget => 'polygon', castfunc => 'polygon(path)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'box', casttarget => 'point', castfunc => 'point(box)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'box', casttarget => 'lseg', castfunc => 'lseg(box)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'box', casttarget => 'polygon', castfunc => 'polygon(box)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'box', casttarget => 'circle', castfunc => 'circle(box)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'polygon', casttarget => 'point', castfunc => 'point(polygon)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'polygon', casttarget => 'path', castfunc => 'path',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'polygon', casttarget => 'box', castfunc => 'box(polygon)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'polygon', casttarget => 'circle',
- castfunc => 'circle(polygon)', castcontext => 'e', castmethod => 'f' },
+ castfunc => 'circle(polygon)', castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'circle', casttarget => 'point', castfunc => 'point(circle)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'circle', casttarget => 'box', castfunc => 'box(circle)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'circle', casttarget => 'polygon',
- castfunc => 'polygon(circle)', castcontext => 'e', castmethod => 'f' },
+ castfunc => 'polygon(circle)', castcontext => 'e', castmethod => 'f'},
# MAC address category
{ castsource => 'macaddr', casttarget => 'macaddr8', castfunc => 'macaddr8',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'macaddr8', casttarget => 'macaddr', castfunc => 'macaddr',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
# INET category
{ castsource => 'cidr', casttarget => 'inet', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'inet', casttarget => 'cidr', castfunc => 'cidr',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
# BitString category
{ castsource => 'bit', casttarget => 'varbit', castfunc => '0',
@@ -454,13 +454,13 @@
# Cross-category casts between bit and int4, int8
{ castsource => 'int8', casttarget => 'bit', castfunc => 'bit(int8,int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'bit', castfunc => 'bit(int4,int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bit', casttarget => 'int8', castfunc => 'int8(bit)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bit', casttarget => 'int4', castfunc => 'int4(bit)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Cross-category casts to and from TEXT
# We need entries here only for a few specialized cases where the behavior
@@ -471,68 +471,68 @@
# behavior will ensue when the automatic cast is applied instead of the
# pg_cast entry!
{ castsource => 'cidr', casttarget => 'text', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'inet', casttarget => 'text', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'text', castfunc => 'text(bool)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'xml', casttarget => 'text', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'text', casttarget => 'xml', castfunc => 'xml',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Cross-category casts to and from VARCHAR
# We support all the same casts as for TEXT.
{ castsource => 'cidr', casttarget => 'varchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'inet', casttarget => 'varchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'varchar', castfunc => 'text(bool)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'xml', casttarget => 'varchar', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'varchar', casttarget => 'xml', castfunc => 'xml',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Cross-category casts to and from BPCHAR
# We support all the same casts as for TEXT.
{ castsource => 'cidr', casttarget => 'bpchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'inet', casttarget => 'bpchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'bpchar', castfunc => 'text(bool)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'xml', casttarget => 'bpchar', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'bpchar', casttarget => 'xml', castfunc => 'xml',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Length-coercion functions
{ castsource => 'bpchar', casttarget => 'bpchar',
castfunc => 'bpchar(bpchar,int4,bool)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'varchar',
castfunc => 'varchar(varchar,int4,bool)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'time', casttarget => 'time', castfunc => 'time(time,int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'timestamp',
castfunc => 'timestamp(timestamp,int4)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'timestamptz',
castfunc => 'timestamptz(timestamptz,int4)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'interval', casttarget => 'interval',
castfunc => 'interval(interval,int4)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timetz', casttarget => 'timetz',
- castfunc => 'timetz(timetz,int4)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timetz(timetz,int4)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bit', casttarget => 'bit', castfunc => 'bit(bit,int4,bool)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varbit', casttarget => 'varbit', castfunc => 'varbit',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'numeric',
- castfunc => 'numeric(numeric,int4)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'numeric(numeric,int4)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
# json to/from jsonb
{ castsource => 'json', casttarget => 'jsonb', castfunc => '0',
@@ -542,36 +542,36 @@
# jsonb to numeric and bool types
{ castsource => 'jsonb', casttarget => 'bool', castfunc => 'bool(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'numeric', castfunc => 'numeric(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'int2', castfunc => 'int2(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'int4', castfunc => 'int4(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'int8', castfunc => 'int8(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'float4', castfunc => 'float4(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'float8', castfunc => 'float8(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# range to multirange
{ castsource => 'int4range', casttarget => 'int4multirange',
castfunc => 'int4multirange(int4range)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8range', casttarget => 'int8multirange',
castfunc => 'int8multirange(int8range)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numrange', casttarget => 'nummultirange',
castfunc => 'nummultirange(numrange)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'daterange', casttarget => 'datemultirange',
castfunc => 'datemultirange(daterange)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'tsrange', casttarget => 'tsmultirange',
- castfunc => 'tsmultirange(tsrange)', castcontext => 'e', castmethod => 'f' },
+ castfunc => 'tsmultirange(tsrange)', castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'tstzrange', casttarget => 'tstzmultirange',
castfunc => 'tstzmultirange(tstzrange)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
]
diff --git a/src/include/catalog/pg_cast.h b/src/include/catalog/pg_cast.h
index 6a0ca337153..bf47544f675 100644
--- a/src/include/catalog/pg_cast.h
+++ b/src/include/catalog/pg_cast.h
@@ -47,6 +47,10 @@ CATALOG(pg_cast,2605,CastRelationId)
/* cast method */
char castmethod;
+
+ /* cast function error safe */
+ bool casterrorsafe BKI_DEFAULT(f);
+
} FormData_pg_cast;
/* ----------------
@@ -101,6 +105,7 @@ extern ObjectAddress CastCreate(Oid sourcetypeid,
Oid outcastid,
char castcontext,
char castmethod,
+ bool casterrorsafe,
DependencyType behavior);
#endif /* PG_CAST_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 82b0fb83b4b..e99499196cb 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4155,6 +4155,7 @@ typedef struct CreateCastStmt
ObjectWithArgs *func;
CoercionContext context;
bool inout;
+ bool safe;
} CreateCastStmt;
/* ----------------------
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 5d4fe27ef96..f2ff6091fd2 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -396,6 +396,7 @@ PG_KEYWORD("routines", ROUTINES, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("row", ROW, COL_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("rows", ROWS, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("rule", RULE, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("safe", SAFE, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("savepoint", SAVEPOINT, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("scalar", SCALAR, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("schema", SCHEMA, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/test/regress/expected/create_cast.out b/src/test/regress/expected/create_cast.out
index 0054ed0ef67..1e32c041b9f 100644
--- a/src/test/regress/expected/create_cast.out
+++ b/src/test/regress/expected/create_cast.out
@@ -81,6 +81,12 @@ NOTICE: drop cascades to cast from integer to casttesttype
-- Try it with a function that requires an implicit cast
CREATE FUNCTION bar_int4_text(int4) RETURNS text LANGUAGE SQL AS
$$ SELECT ('bar'::text || $1::text); $$;
+CREATE FUNCTION bar_int4_text_plpg(int4) RETURNS text LANGUAGE plpgsql AS
+$$ BEGIN RETURN ('bar'::text || $1::text); END $$;
+CREATE CAST (int4 AS casttesttype) WITH SAFE FUNCTION bar_int4_text(int4) AS IMPLICIT; -- error
+ERROR: Safe type cast functions are only supported for C and internal languages
+CREATE CAST (int4 AS casttesttype) WITH SAFE FUNCTION bar_int4_text_plpg(int4) AS IMPLICIT; -- error
+ERROR: Safe type cast functions are only supported for C and internal languages
CREATE CAST (int4 AS casttesttype) WITH FUNCTION bar_int4_text(int4) AS IMPLICIT;
SELECT 1234::int4::casttesttype; -- Should work now
casttesttype
@@ -92,7 +98,7 @@ SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- err
ERROR: cannot cast type integer to casttesttype when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
LINE 1: SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVE...
^
-HINT: Safe type cast for user-defined types are not yet supported
+HINT: Explicit cast is defined but definition is not error safe
-- check dependencies generated for that
SELECT pg_describe_object(classid, objid, objsubid) as obj,
pg_describe_object(refclassid, refobjid, refobjsubid) as objref,
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index a357e1d0c0e..81ea244859f 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -943,8 +943,8 @@ SELECT *
FROM pg_cast c
WHERE castsource = 0 OR casttarget = 0 OR castcontext NOT IN ('e', 'a', 'i')
OR castmethod NOT IN ('f', 'b' ,'i');
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Check that castfunc is nonzero only for cast methods that need a function,
@@ -953,8 +953,8 @@ SELECT *
FROM pg_cast c
WHERE (castmethod = 'f' AND castfunc = 0)
OR (castmethod IN ('b', 'i') AND castfunc <> 0);
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Look for casts to/from the same type that aren't length coercion functions.
@@ -963,15 +963,15 @@ WHERE (castmethod = 'f' AND castfunc = 0)
SELECT *
FROM pg_cast c
WHERE castsource = casttarget AND castfunc = 0;
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
SELECT c.*
FROM pg_cast c, pg_proc p
WHERE c.castfunc = p.oid AND p.pronargs < 2 AND castsource = casttarget;
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Look for cast functions that don't have the right signature. The
@@ -989,8 +989,8 @@ WHERE c.castfunc = p.oid AND
OR (c.castsource = 'character'::regtype AND
p.proargtypes[0] = 'text'::regtype))
OR NOT binary_coercible(p.prorettype, c.casttarget));
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
SELECT c.*
@@ -998,8 +998,8 @@ FROM pg_cast c, pg_proc p
WHERE c.castfunc = p.oid AND
((p.pronargs > 1 AND p.proargtypes[1] != 'int4'::regtype) OR
(p.pronargs > 2 AND p.proargtypes[2] != 'bool'::regtype));
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Look for binary compatible casts that do not have the reverse
diff --git a/src/test/regress/sql/create_cast.sql b/src/test/regress/sql/create_cast.sql
index 0a15a795d87..30a0ff077c9 100644
--- a/src/test/regress/sql/create_cast.sql
+++ b/src/test/regress/sql/create_cast.sql
@@ -60,6 +60,11 @@ DROP FUNCTION int4_casttesttype(int4) CASCADE;
CREATE FUNCTION bar_int4_text(int4) RETURNS text LANGUAGE SQL AS
$$ SELECT ('bar'::text || $1::text); $$;
+CREATE FUNCTION bar_int4_text_plpg(int4) RETURNS text LANGUAGE plpgsql AS
+$$ BEGIN RETURN ('bar'::text || $1::text); END $$;
+
+CREATE CAST (int4 AS casttesttype) WITH SAFE FUNCTION bar_int4_text(int4) AS IMPLICIT; -- error
+CREATE CAST (int4 AS casttesttype) WITH SAFE FUNCTION bar_int4_text_plpg(int4) AS IMPLICIT; -- error
CREATE CAST (int4 AS casttesttype) WITH FUNCTION bar_int4_text(int4) AS IMPLICIT;
SELECT 1234::int4::casttesttype; -- Should work now
SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- error
--
2.34.1
v16-0021-error-safe-for-casting-geometry-data-type.patchapplication/x-patch; name=v16-0021-error-safe-for-casting-geometry-data-type.patchDownload
From 7ddb181c76d8cfe3b874e17acbb48b8a72fb84a2 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 13 Dec 2025 22:57:21 +0800
Subject: [PATCH v16 21/23] error safe for casting geometry data type
select castsource::regtype, casttarget::regtype, pp.prosrc
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
join pg_type pt on pt.oid = castsource
join pg_type pt1 on pt1.oid = casttarget
and pc.castfunc > 0 and pt.typarray <> 0
and pt.typnamespace = 'pg_catalog'::regnamespace
and pt1.typnamespace = 'pg_catalog'::regnamespace
and (pt.typcategory = 'G' or pt1.typcategory = 'G')
order by castsource::regtype, casttarget::regtype;
castsource | casttarget | prosrc
------------+------------+---------------
point | box | point_box
lseg | point | lseg_center
path | polygon | path_poly
box | point | box_center
box | lseg | box_diagonal
box | polygon | box_poly
box | circle | box_circle
polygon | point | poly_center
polygon | path | poly_path
polygon | box | poly_box
polygon | circle | poly_circle
circle | point | circle_center
circle | box | circle_box
circle | polygon |
(14 rows)
already error safe: point_box, box_diagonal, box_poly, poly_path, poly_box,
circle_center
This patch make these functions error safe: path_poly, lseg_center, box_center,
box_circle, poly_center, poly_circle, circle_box
can not error safe: cast circle to polygon, because it's a SQL function
discussion: https://postgr.es/m/CACJufxHCMzrHOW=wRe8L30rMhB3sjwAv1LE928Fa7sxMu1Tx-g@mail.gmail.com
---
src/backend/utils/adt/geo_ops.c | 194 +++++++++++++++++++++++++-------
1 file changed, 153 insertions(+), 41 deletions(-)
diff --git a/src/backend/utils/adt/geo_ops.c b/src/backend/utils/adt/geo_ops.c
index 5c3b9e2ed60..834f4284159 100644
--- a/src/backend/utils/adt/geo_ops.c
+++ b/src/backend/utils/adt/geo_ops.c
@@ -77,7 +77,8 @@ enum path_delim
/* Routines for points */
static inline void point_construct(Point *result, float8 x, float8 y);
-static inline void point_add_point(Point *result, Point *pt1, Point *pt2);
+static inline void point_add_point(Point *result, Point *pt1, Point *pt2,
+ Node *escontext);
static inline void point_sub_point(Point *result, Point *pt1, Point *pt2);
static inline void point_mul_point(Point *result, Point *pt1, Point *pt2);
static inline void point_div_point(Point *result, Point *pt1, Point *pt2);
@@ -108,7 +109,7 @@ static float8 lseg_closept_lseg(Point *result, LSEG *on_lseg, LSEG *to_lseg);
/* Routines for boxes */
static inline void box_construct(BOX *result, Point *pt1, Point *pt2);
-static void box_cn(Point *center, BOX *box);
+static void box_cn(Point *center, BOX *box, Node* escontext);
static bool box_ov(BOX *box1, BOX *box2);
static float8 box_ar(BOX *box);
static float8 box_ht(BOX *box);
@@ -125,7 +126,7 @@ static float8 circle_ar(CIRCLE *circle);
/* Routines for polygons */
static void make_bound_box(POLYGON *poly);
-static void poly_to_circle(CIRCLE *result, POLYGON *poly);
+static bool poly_to_circle(CIRCLE *result, POLYGON *poly, Node *escontext);
static bool lseg_inside_poly(Point *a, Point *b, POLYGON *poly, int start);
static bool poly_contain_poly(POLYGON *contains_poly, POLYGON *contained_poly);
static bool plist_same(int npts, Point *p1, Point *p2);
@@ -836,8 +837,8 @@ box_distance(PG_FUNCTION_ARGS)
Point a,
b;
- box_cn(&a, box1);
- box_cn(&b, box2);
+ box_cn(&a, box1, NULL);
+ box_cn(&b, box2, NULL);
PG_RETURN_FLOAT8(point_dt(&a, &b, NULL));
}
@@ -851,7 +852,9 @@ box_center(PG_FUNCTION_ARGS)
BOX *box = PG_GETARG_BOX_P(0);
Point *result = palloc_object(Point);
- box_cn(result, box);
+ box_cn(result, box, fcinfo->context);
+ if ((SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
PG_RETURN_POINT_P(result);
}
@@ -869,13 +872,26 @@ box_ar(BOX *box)
/* box_cn - stores the centerpoint of the box into *center.
*/
static void
-box_cn(Point *center, BOX *box)
+box_cn(Point *center, BOX *box, Node *escontext)
{
- center->x = float8_div(float8_pl(box->high.x, box->low.x), 2.0);
- center->y = float8_div(float8_pl(box->high.y, box->low.y), 2.0);
+ float8 x;
+ float8 y;
+
+ x = float8_pl_safe(box->high.x, box->low.x, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return;
+
+ center->x = float8_div_safe(x, 2.0, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return;
+
+ y = float8_pl_safe(box->high.y, box->low.y, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return;
+
+ center->y = float8_div_safe(y, 2.0, escontext);
}
-
/* box_wd - returns the width (length) of the box
* (horizontal magnitude).
*/
@@ -2328,13 +2344,31 @@ lseg_center(PG_FUNCTION_ARGS)
{
LSEG *lseg = PG_GETARG_LSEG_P(0);
Point *result;
+ float8 x;
+ float8 y;
result = palloc_object(Point);
- result->x = float8_div(float8_pl(lseg->p[0].x, lseg->p[1].x), 2.0);
- result->y = float8_div(float8_pl(lseg->p[0].y, lseg->p[1].y), 2.0);
+ x = float8_pl_safe(lseg->p[0].x, lseg->p[1].x, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ result->x = float8_div_safe(x, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ y = float8_pl_safe(lseg->p[0].y, lseg->p[1].y, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ result->y = float8_div_safe(y, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
PG_RETURN_POINT_P(result);
+
+fail:
+ PG_RETURN_NULL();
}
@@ -3288,7 +3322,7 @@ box_interpt_lseg(Point *result, BOX *box, LSEG *lseg)
if (result != NULL)
{
- box_cn(&point, box);
+ box_cn(&point, box, NULL);
lseg_closept_point(result, lseg, &point);
}
@@ -4119,11 +4153,20 @@ construct_point(PG_FUNCTION_ARGS)
static inline void
-point_add_point(Point *result, Point *pt1, Point *pt2)
+point_add_point(Point *result, Point *pt1, Point *pt2, Node *escontext)
{
- point_construct(result,
- float8_pl(pt1->x, pt2->x),
- float8_pl(pt1->y, pt2->y));
+ float8 x;
+ float8 y;
+
+ x = float8_pl_safe(pt1->x, pt2->x, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return;
+
+ y = float8_pl_safe(pt1->y, pt2->y, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return;
+
+ point_construct(result, x, y);
}
Datum
@@ -4135,7 +4178,7 @@ point_add(PG_FUNCTION_ARGS)
result = palloc_object(Point);
- point_add_point(result, p1, p2);
+ point_add_point(result, p1, p2, NULL);
PG_RETURN_POINT_P(result);
}
@@ -4247,8 +4290,8 @@ box_add(PG_FUNCTION_ARGS)
result = palloc_object(BOX);
- point_add_point(&result->high, &box->high, p);
- point_add_point(&result->low, &box->low, p);
+ point_add_point(&result->high, &box->high, p, NULL);
+ point_add_point(&result->low, &box->low, p, NULL);
PG_RETURN_BOX_P(result);
}
@@ -4411,7 +4454,7 @@ path_add_pt(PG_FUNCTION_ARGS)
int i;
for (i = 0; i < path->npts; i++)
- point_add_point(&path->p[i], &path->p[i], point);
+ point_add_point(&path->p[i], &path->p[i], point, NULL);
PG_RETURN_PATH_P(path);
}
@@ -4469,7 +4512,7 @@ path_poly(PG_FUNCTION_ARGS)
/* This is not very consistent --- other similar cases return NULL ... */
if (!path->closed)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("open path cannot be converted to polygon")));
@@ -4519,7 +4562,9 @@ poly_center(PG_FUNCTION_ARGS)
result = palloc_object(Point);
- poly_to_circle(&circle, poly);
+ if (!poly_to_circle(&circle, poly, fcinfo->context))
+ PG_RETURN_NULL();
+
*result = circle.center;
PG_RETURN_POINT_P(result);
@@ -4981,7 +5026,7 @@ circle_add_pt(PG_FUNCTION_ARGS)
result = palloc_object(CIRCLE);
- point_add_point(&result->center, &circle->center, point);
+ point_add_point(&result->center, &circle->center, point, NULL);
result->radius = circle->radius;
PG_RETURN_CIRCLE_P(result);
@@ -5202,14 +5247,30 @@ circle_box(PG_FUNCTION_ARGS)
box = palloc_object(BOX);
- delta = float8_div(circle->radius, sqrt(2.0));
+ delta = float8_div_safe(circle->radius, sqrt(2.0), fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
- box->high.x = float8_pl(circle->center.x, delta);
- box->low.x = float8_mi(circle->center.x, delta);
- box->high.y = float8_pl(circle->center.y, delta);
- box->low.y = float8_mi(circle->center.y, delta);
+ box->high.x = float8_pl_safe(circle->center.x, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ box->low.x = float8_mi_safe(circle->center.x, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ box->high.y = float8_pl_safe(circle->center.y, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ box->low.y = float8_mi_safe(circle->center.y, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
PG_RETURN_BOX_P(box);
+
+fail:
+ PG_RETURN_NULL();
}
/* box_circle()
@@ -5220,15 +5281,35 @@ box_circle(PG_FUNCTION_ARGS)
{
BOX *box = PG_GETARG_BOX_P(0);
CIRCLE *circle;
+ float8 x;
+ float8 y;
circle = palloc_object(CIRCLE);
- circle->center.x = float8_div(float8_pl(box->high.x, box->low.x), 2.0);
- circle->center.y = float8_div(float8_pl(box->high.y, box->low.y), 2.0);
+ x = float8_pl_safe(box->high.x, box->low.x, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
- circle->radius = point_dt(&circle->center, &box->high, NULL);
+ circle->center.x = float8_div_safe(x, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ y = float8_pl_safe(box->high.y, box->low.y, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ circle->center.y = float8_div_safe(y, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ circle->radius = point_dt(&circle->center, &box->high, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
PG_RETURN_CIRCLE_P(circle);
+
+fail:
+ PG_RETURN_NULL();
}
@@ -5292,10 +5373,11 @@ circle_poly(PG_FUNCTION_ARGS)
* XXX This algorithm should use weighted means of line segments
* rather than straight average values of points - tgl 97/01/21.
*/
-static void
-poly_to_circle(CIRCLE *result, POLYGON *poly)
+static bool
+poly_to_circle(CIRCLE *result, POLYGON *poly, Node *escontext)
{
int i;
+ float8 x;
Assert(poly->npts > 0);
@@ -5304,14 +5386,43 @@ poly_to_circle(CIRCLE *result, POLYGON *poly)
result->radius = 0;
for (i = 0; i < poly->npts; i++)
- point_add_point(&result->center, &result->center, &poly->p[i]);
- result->center.x = float8_div(result->center.x, poly->npts);
- result->center.y = float8_div(result->center.y, poly->npts);
+ {
+ point_add_point(&result->center,
+ &result->center,
+ &poly->p[i],
+ escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+ }
+
+ result->center.x = float8_div_safe(result->center.x,
+ poly->npts,
+ escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ result->center.y = float8_div_safe(result->center.y,
+ poly->npts,
+ escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
for (i = 0; i < poly->npts; i++)
- result->radius = float8_pl(result->radius,
- point_dt(&poly->p[i], &result->center, NULL));
- result->radius = float8_div(result->radius, poly->npts);
+ {
+ x = point_dt(&poly->p[i], &result->center, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ result->radius = float8_pl_safe(result->radius, x, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+ }
+
+ result->radius = float8_div_safe(result->radius, poly->npts, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ return true;
}
Datum
@@ -5322,7 +5433,8 @@ poly_circle(PG_FUNCTION_ARGS)
result = palloc_object(CIRCLE);
- poly_to_circle(result, poly);
+ if (!poly_to_circle(result, poly, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_CIRCLE_P(result);
}
--
2.34.1
v16-0007-error-safe-for-casting-macaddr8-to-other-types-per-pg_cast.patchapplication/x-patch; name=v16-0007-error-safe-for-casting-macaddr8-to-other-types-per-pg_cast.patchDownload
From 79ae586e297574202130004333afb9766dc2f0e0 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Fri, 12 Dec 2025 15:03:06 +0800
Subject: [PATCH v16 07/23] error safe for casting macaddr8 to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
and pc.castfunc > 0 and castsource::regtype ='macaddr8'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------+----------+-------------+------------+-------------------+---------
macaddr8 | macaddr | 4124 | i | f | macaddr8tomacaddr | macaddr
(1 row)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/mac8.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/backend/utils/adt/mac8.c b/src/backend/utils/adt/mac8.c
index ea715a7a0d4..02b5edb4960 100644
--- a/src/backend/utils/adt/mac8.c
+++ b/src/backend/utils/adt/mac8.c
@@ -550,7 +550,7 @@ macaddr8tomacaddr(PG_FUNCTION_ARGS)
result = palloc0_object(macaddr);
if ((addr->d != 0xFF) || (addr->e != 0xFE))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("macaddr8 data out of range to convert to macaddr"),
errhint("Only addresses that have FF and FE as values in the "
--
2.34.1
v16-0022-CAST-expr-AS-newtype-DEFAULT-ON-ERROR.patchapplication/x-patch; name=v16-0022-CAST-expr-AS-newtype-DEFAULT-ON-ERROR.patchDownload
From dd49fa74df12eb920faf7a3051e22a186f77edbd Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Mon, 15 Dec 2025 12:28:40 +0800
Subject: [PATCH v16 22/23] CAST(expr AS newtype DEFAULT ON ERROR)
# Bumps catversion required.
* With this patchset, most functions in pg_cast.castfunc are now error-safe.
* CoerceViaIO and CoerceToDomain were already error-safe in the HEAD.
* this patch extends error-safe behavior to ArrayCoerceExpr.
* We also ensure that when a coercion fails, execution falls back to evaluating
the specified default node.
* The doc has been refined, though it may still need more review.
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}
[0]: https://git.postgresql.org/cgit/postgresql.git/commit/?id=aaaf9449ec6be62cb0d30ed3588dc384f56274b
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
contrib/citext/expected/citext.out | 5 +
contrib/citext/expected/citext_1.out | 5 +
contrib/citext/sql/citext.sql | 2 +
.../pg_stat_statements/expected/select.out | 23 +-
contrib/pg_stat_statements/sql/select.sql | 5 +
doc/src/sgml/syntax.sgml | 30 +
src/backend/executor/execExpr.c | 109 ++-
src/backend/executor/execExprInterp.c | 29 +
src/backend/jit/llvm/llvmjit_expr.c | 26 +
src/backend/nodes/nodeFuncs.c | 61 ++
src/backend/optimizer/util/clauses.c | 51 +-
src/backend/parser/gram.y | 22 +
src/backend/parser/parse_agg.c | 9 +
src/backend/parser/parse_coerce.c | 78 +-
src/backend/parser/parse_expr.c | 412 ++++++++-
src/backend/parser/parse_func.c | 3 +
src/backend/parser/parse_target.c | 14 +
src/backend/parser/parse_type.c | 14 +
src/backend/parser/parse_utilcmd.c | 2 +-
src/backend/utils/adt/arrayfuncs.c | 8 +
src/backend/utils/adt/ruleutils.c | 21 +
src/backend/utils/fmgr/fmgr.c | 14 +
src/include/executor/execExpr.h | 7 +
src/include/executor/executor.h | 1 +
src/include/fmgr.h | 3 +
src/include/nodes/execnodes.h | 21 +
src/include/nodes/parsenodes.h | 11 +
src/include/nodes/primnodes.h | 36 +
src/include/optimizer/optimizer.h | 2 +-
src/include/parser/parse_coerce.h | 15 +
src/include/parser/parse_node.h | 2 +
src/include/parser/parse_type.h | 2 +
src/test/regress/expected/cast.out | 810 ++++++++++++++++++
src/test/regress/expected/create_cast.out | 5 +
src/test/regress/expected/equivclass.out | 7 +
src/test/regress/parallel_schedule | 2 +-
src/test/regress/sql/cast.sql | 350 ++++++++
src/test/regress/sql/create_cast.sql | 1 +
src/test/regress/sql/equivclass.sql | 3 +
src/tools/pgindent/typedefs.list | 3 +
40 files changed, 2179 insertions(+), 45 deletions(-)
create mode 100644 src/test/regress/expected/cast.out
create mode 100644 src/test/regress/sql/cast.sql
diff --git a/contrib/citext/expected/citext.out b/contrib/citext/expected/citext.out
index 8c0bf54f0f3..33da19d8df4 100644
--- a/contrib/citext/expected/citext.out
+++ b/contrib/citext/expected/citext.out
@@ -10,6 +10,11 @@ WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid);
--------+---------
(0 rows)
+SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR: cannot cast type character to citext when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSI...
+ ^
+HINT: Safe type cast for user-defined types are not yet supported
-- Test the operators and indexing functions
-- Test = and <>.
SELECT 'a'::citext = 'a'::citext AS t;
diff --git a/contrib/citext/expected/citext_1.out b/contrib/citext/expected/citext_1.out
index c5e5f180f2b..647eea19142 100644
--- a/contrib/citext/expected/citext_1.out
+++ b/contrib/citext/expected/citext_1.out
@@ -10,6 +10,11 @@ WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid);
--------+---------
(0 rows)
+SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR: cannot cast type character to citext when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSI...
+ ^
+HINT: Safe type cast for user-defined types are not yet supported
-- Test the operators and indexing functions
-- Test = and <>.
SELECT 'a'::citext = 'a'::citext AS t;
diff --git a/contrib/citext/sql/citext.sql b/contrib/citext/sql/citext.sql
index aa1cf9abd5c..99794497d47 100644
--- a/contrib/citext/sql/citext.sql
+++ b/contrib/citext/sql/citext.sql
@@ -9,6 +9,8 @@ SELECT amname, opcname
FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod
WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid);
+SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR); --error
+
-- Test the operators and indexing functions
-- Test = and <>.
diff --git a/contrib/pg_stat_statements/expected/select.out b/contrib/pg_stat_statements/expected/select.out
index 75c896f3885..6e67997f0a2 100644
--- a/contrib/pg_stat_statements/expected/select.out
+++ b/contrib/pg_stat_statements/expected/select.out
@@ -73,6 +73,25 @@ SELECT 1 AS "int" OFFSET 2 FETCH FIRST 3 ROW ONLY;
-----
(0 rows)
+--error safe type cast
+SELECT CAST('a' AS int DEFAULT 2 ON CONVERSION ERROR);
+ int4
+------
+ 2
+(1 row)
+
+SELECT CAST('11' AS int DEFAULT 2 ON CONVERSION ERROR);
+ int4
+------
+ 11
+(1 row)
+
+SELECT CAST('12' AS numeric DEFAULT 2 ON CONVERSION ERROR);
+ numeric
+---------
+ 12
+(1 row)
+
-- DISTINCT and ORDER BY patterns
-- Try some query permutations which once produced identical query IDs
SELECT DISTINCT 1 AS "int";
@@ -222,6 +241,8 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
2 | 2 | SELECT $1 AS "int" ORDER BY 1
1 | 2 | SELECT $1 AS i UNION SELECT $2 ORDER BY i
1 | 1 | SELECT $1 || $2
+ 2 | 2 | SELECT CAST($1 AS int DEFAULT $2 ON CONVERSION ERROR)
+ 1 | 1 | SELECT CAST($1 AS numeric DEFAULT $2 ON CONVERSION ERROR)
2 | 2 | SELECT DISTINCT $1 AS "int"
0 | 0 | SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"
1 | 1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
@@ -230,7 +251,7 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
| | ) +
| | SELECT f FROM t ORDER BY f
1 | 1 | select $1::jsonb ? $2
-(17 rows)
+(19 rows)
SELECT pg_stat_statements_reset() IS NOT NULL AS t;
t
diff --git a/contrib/pg_stat_statements/sql/select.sql b/contrib/pg_stat_statements/sql/select.sql
index 11662cde08c..7ee8160fd84 100644
--- a/contrib/pg_stat_statements/sql/select.sql
+++ b/contrib/pg_stat_statements/sql/select.sql
@@ -25,6 +25,11 @@ SELECT 1 AS "int" LIMIT 3 OFFSET 3;
SELECT 1 AS "int" OFFSET 1 FETCH FIRST 2 ROW ONLY;
SELECT 1 AS "int" OFFSET 2 FETCH FIRST 3 ROW ONLY;
+--error safe type cast
+SELECT CAST('a' AS int DEFAULT 2 ON CONVERSION ERROR);
+SELECT CAST('11' AS int DEFAULT 2 ON CONVERSION ERROR);
+SELECT CAST('12' AS numeric DEFAULT 2 ON CONVERSION ERROR);
+
-- DISTINCT and ORDER BY patterns
-- Try some query permutations which once produced identical query IDs
SELECT DISTINCT 1 AS "int";
diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml
index 34c83880a66..32af9ea061c 100644
--- a/doc/src/sgml/syntax.sgml
+++ b/doc/src/sgml/syntax.sgml
@@ -2106,6 +2106,10 @@ CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable>
The <literal>CAST</literal> syntax conforms to SQL; the syntax with
<literal>::</literal> is historical <productname>PostgreSQL</productname>
usage.
+ The equivalent ON CONVERSION ERROR behavior is:
+<synopsis>
+CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable> ERROR ON CONVERSION ERROR )
+</synopsis>
</para>
<para>
@@ -2160,6 +2164,32 @@ CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable>
<xref linkend="sql-createcast"/>.
</para>
</note>
+
+ <sect3 id="sql-syntax-type-casts-safe">
+ <title>Safe Type Cast</title>
+ <para>
+ A type cast may occasionally fail. To guard against such failures, you can
+ provide an <literal>ON ERROR</literal> clause to handle potential errors.
+ The syntax for safe type cast is:
+<synopsis>
+CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable> DEFAULT <replaceable>expression</replaceable> ON CONVERSION ERROR )
+</synopsis>
+ If the type cast fails, instead of error out, evaluation falls back to the
+ default <replaceable>expression</replaceable> specified in the <literal>ON ERROR</literal> clause.
+ At present, this only support built-in type casts; see <xref linkend="catalog-pg-cast"/>.
+ User-defined type casts created with <link linkend="sql-createcast">CREATE CAST</link> are not supported.
+ </para>
+
+ <para>
+ Some examples:
+<screen>
+SELECT CAST(TEXT 'error' AS integer DEFAULT 3 ON CONVERSION ERROR);
+<lineannotation>Result: </lineannotation><computeroutput>3</computeroutput>
+SELECT CAST(TEXT 'error' AS numeric DEFAULT 1.1 ON CONVERSION ERROR);
+<lineannotation>Result: </lineannotation><computeroutput>1.1</computeroutput>
+</screen>
+ </para>
+ </sect3>
</sect2>
<sect2 id="sql-syntax-collate-exprs">
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index c35744b105e..12ec679b90b 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,
@@ -170,6 +173,47 @@ ExecInitExpr(Expr *node, PlanState *parent)
return state;
}
+/*
+ * ExecInitExprSafe: soft error variant of ExecInitExpr.
+ *
+ * use it only for expression nodes support soft errors, not all expression
+ * nodes support it.
+*/
+ExprState *
+ExecInitExprSafe(Expr *node, PlanState *parent)
+{
+ ExprState *state;
+ ExprEvalStep scratch = {0};
+
+ /* Special case: NULL expression produces a NULL ExprState pointer */
+ if (node == NULL)
+ return NULL;
+
+ /* Initialize ExprState with empty step list */
+ state = makeNode(ExprState);
+ state->expr = node;
+ state->parent = parent;
+ state->ext_params = NULL;
+ state->escontext = makeNode(ErrorSaveContext);
+ state->escontext->type = T_ErrorSaveContext;
+ state->escontext->error_occurred = false;
+ state->escontext->details_wanted = false;
+
+ /* Insert setup steps as needed */
+ ExecCreateExprSetupSteps(state, (Node *) node);
+
+ /* Compile the expression proper */
+ ExecInitExprRec(node, state, &state->resvalue, &state->resnull);
+
+ /* Finally, append a DONE step */
+ scratch.opcode = EEOP_DONE_RETURN;
+ ExprEvalPushStep(state, &scratch);
+
+ ExecReadyExpr(state);
+
+ return state;
+}
+
/*
* ExecInitExprWithParams: prepare a standalone expression tree for execution
*
@@ -1701,6 +1745,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
elemstate->innermost_caseval = palloc_object(Datum);
elemstate->innermost_casenull = palloc_object(bool);
+ elemstate->escontext = state->escontext;
ExecInitExprRec(acoerce->elemexpr, elemstate,
&elemstate->resvalue, &elemstate->resnull);
@@ -2176,6 +2221,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;
@@ -2736,7 +2789,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;
@@ -4733,6 +4786,60 @@ ExecBuildParamSetEqual(TupleDesc desc,
return state;
}
+/*
+ * Push steps to evaluate a SafeTypeCastExpr and its various subsidiary
+ * expressions.
+ */
+static void
+ExecInitSafeTypeCastExpr(SafeTypeCastExpr *stcexpr , ExprState *state,
+ Datum *resv, bool *resnull,
+ ExprEvalStep *scratch)
+{
+ /*
+ * If we can not coerce to the target type, fallback to the DEFAULT
+ * expression specified in the ON CONVERSION ERROR clause, and we are done.
+ */
+ if (stcexpr->cast_expr == NULL)
+ {
+ ExecInitExprRec((Expr *) stcexpr->default_expr,
+ state, resv, resnull);
+ return;
+ }
+ else
+ {
+ SafeTypeCastState *stcstate;
+
+ stcstate = palloc0(sizeof(SafeTypeCastState));
+ stcstate->stcexpr = stcexpr;
+ stcstate->escontext.type = T_ErrorSaveContext;
+ state->escontext = &stcstate->escontext;
+
+ /* evaluate argument expression into step's result area */
+ ExecInitExprRec((Expr *) stcexpr->cast_expr,
+ state, resv, resnull);
+ scratch->opcode = EEOP_SAFETYPE_CAST;
+ scratch->d.stcexpr.stcstate = stcstate;
+ ExprEvalPushStep(state, scratch);
+
+ /*
+ * Steps to evaluate the DEFAULT expression. Skip it if this is a
+ * binary coercion cast.
+ */
+ if (!IsA(stcexpr->cast_expr, RelabelType))
+ {
+ ErrorSaveContext *saved_escontext;
+
+ saved_escontext = state->escontext;
+ state->escontext = NULL;
+ ExecInitExprRec((Expr *) stcstate->stcexpr->default_expr,
+ state, resv, resnull);
+ state->escontext = saved_escontext;
+ }
+
+ 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 1d88cdd2cb4..ca6e0fd56cd 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,28 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
EEO_NEXT();
}
+ EEO_CASE(EEOP_SAFETYPE_CAST)
+ {
+ SafeTypeCastState *stcstate = op->d.stcexpr.stcstate;
+
+ if (SOFT_ERROR_OCCURRED(&stcstate->escontext))
+ {
+ *op->resvalue = (Datum) 0;
+ *op->resnull = true;
+
+ /*
+ * Reset for next use such as for catching errors when coercing
+ * a expression.
+ */
+ stcstate->escontext.error_occurred = false;
+ stcstate->escontext.details_wanted = false;
+
+ EEO_NEXT();
+ }
+ else
+ EEO_JUMP(stcstate->jump_end);
+ }
+
EEO_CASE(EEOP_JSONEXPR_PATH)
{
/* too complex for an inline implementation */
@@ -3644,6 +3667,12 @@ 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/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index f9c7f29e728..db9f4928619 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -2256,6 +2256,32 @@ llvm_compile_expr(ExprState *state)
LLVMBuildBr(b, opblocks[opno + 1]);
break;
+ case EEOP_SAFETYPE_CAST:
+ {
+ SafeTypeCastState *stcstate = op->d.stcexpr.stcstate;
+
+ if (SOFT_ERROR_OCCURRED(&stcstate->escontext))
+ {
+ /*
+ * Reset for next use such as for catching errors when
+ * coercing a expression.
+ */
+ stcstate->escontext.error_occurred = false;
+ stcstate->escontext.details_wanted = false;
+
+ /* set resnull to true */
+ LLVMBuildStore(b, l_sbool_const(1), v_resnullp);
+ /* reset resvalue */
+ LLVMBuildStore(b, l_datum_const(0), v_resvaluep);
+
+ LLVMBuildBr(b, opblocks[opno + 1]);
+ }
+ else
+ 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 024a2b2fd84..8a8d15ca6f5 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;
@@ -1550,6 +1561,9 @@ exprLocation(const Node *expr)
/* just use leftmost argument's location */
loc = exprLocation((Node *) ((const RowCompareExpr *) expr)->largs);
break;
+ case T_SafeTypeCastExpr:
+ loc = ((const SafeTypeCastExpr *) expr)->location;
+ break;
case T_CoalesceExpr:
/* COALESCE keyword should always be the first thing */
loc = ((const CoalesceExpr *) expr)->location;
@@ -2321,6 +2335,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:
@@ -3330,6 +3356,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;
@@ -4464,6 +4503,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->raw_default))
+ 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/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index ddafc21c819..54046b12587 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -2447,7 +2447,8 @@ estimate_expression_value(PlannerInfo *root, Node *node)
((Node *) evaluate_expr((Expr *) (node), \
exprType((Node *) (node)), \
exprTypmod((Node *) (node)), \
- exprCollation((Node *) (node))))
+ exprCollation((Node *) (node)), \
+ false))
/*
* Recursive guts of eval_const_expressions/estimate_expression_value
@@ -2958,6 +2959,32 @@ 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);
+
+ /*
+ * We must not reduce any recognizably constant subexpressions
+ * in cast_expr here, since we don’t want it to fail
+ * prematurely.
+ */
+ 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:
@@ -3380,7 +3407,8 @@ eval_const_expressions_mutator(Node *node,
return (Node *) evaluate_expr((Expr *) svf,
svf->type,
svf->typmod,
- InvalidOid);
+ InvalidOid,
+ false);
else
return copyObject((Node *) svf);
}
@@ -4698,7 +4726,7 @@ evaluate_function(Oid funcid, Oid result_type, int32 result_typmod,
newexpr->location = -1;
return evaluate_expr((Expr *) newexpr, result_type, result_typmod,
- result_collid);
+ result_collid, false);
}
/*
@@ -5152,10 +5180,14 @@ sql_inline_error_callback(void *arg)
*
* We use the executor's routine ExecEvalExpr() to avoid duplication of
* code and ensure we get the same result as the executor would get.
+ *
+ * When error_safe is set to true, we will evaluate the constant expression in a
+ * error safe way. If the evaluation fails, return NULL instead of throwing an
+ * error.
*/
Expr *
evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
- Oid result_collation)
+ Oid result_collation, bool error_safe)
{
EState *estate;
ExprState *exprstate;
@@ -5180,7 +5212,10 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
* Prepare expr for execution. (Note: we can't use ExecPrepareExpr
* because it'd result in recursively invoking eval_const_expressions.)
*/
- exprstate = ExecInitExpr(expr, NULL);
+ if (error_safe)
+ exprstate = ExecInitExprSafe(expr, NULL);
+ else
+ exprstate = ExecInitExpr(expr, NULL);
/*
* And evaluate it.
@@ -5200,6 +5235,12 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
/* Get back to outer memory context */
MemoryContextSwitchTo(oldcontext);
+ if (error_safe && SOFT_ERROR_OCCURRED(exprstate->escontext))
+ {
+ FreeExecutorState(estate);
+ return NULL;
+ }
+
/*
* Must copy result out of sub-context used by expression eval.
*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7856ce9d78f..9e6573444cc 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -16013,6 +16013,28 @@ func_expr_common_subexpr:
}
| CAST '(' a_expr AS Typename ')'
{ $$ = makeTypeCast($3, $5, @1); }
+ | CAST '(' a_expr AS Typename ERROR_P ON CONVERSION_P ERROR_P ')'
+ { $$ = makeTypeCast($3, $5, @1); }
+ | CAST '(' a_expr AS Typename NULL_P ON CONVERSION_P ERROR_P ')'
+ {
+ TypeCast *cast = (TypeCast *) makeTypeCast($3, $5, @1);
+
+ SafeTypeCast *safecast = makeNode(SafeTypeCast);
+ safecast->cast = (Node *) cast;
+ safecast->raw_default = makeNullAConst(-1);;
+
+ $$ = (Node *) safecast;
+ }
+ | CAST '(' a_expr AS Typename DEFAULT a_expr ON CONVERSION_P ERROR_P ')'
+ {
+ TypeCast *cast = (TypeCast *) makeTypeCast($3, $5, @1);
+
+ SafeTypeCast *safecast = makeNode(SafeTypeCast);
+ safecast->cast = (Node *) cast;
+ safecast->raw_default = $7;
+
+ $$ = (Node *) safecast;
+ }
| EXTRACT '(' extract_list ')'
{
$$ = (Node *) makeFuncCall(SystemFuncName("extract"),
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index b8340557b34..de77f6e9302 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -490,6 +490,12 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
err = _("grouping operations are not allowed in check constraints");
break;
+ case EXPR_KIND_CAST_DEFAULT:
+ if (isAgg)
+ err = _("aggregate functions are not allowed in CAST DEFAULT expressions");
+ else
+ err = _("grouping operations are not allowed in CAST DEFAULT expressions");
+ break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
@@ -983,6 +989,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_DOMAIN_CHECK:
err = _("window functions are not allowed in check constraints");
break;
+ case EXPR_KIND_CAST_DEFAULT:
+ err = _("window functions are not allowed in CAST DEFAULT expressions");
+ break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
err = _("window functions are not allowed in DEFAULT expressions");
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 78b1e366ad7..4ae87e2030b 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -81,6 +81,31 @@ coerce_to_target_type(ParseState *pstate, Node *expr, Oid exprtype,
CoercionContext ccontext,
CoercionForm cformat,
int location)
+{
+ return coerce_to_target_type_extended(pstate,
+ expr,
+ exprtype,
+ targettype,
+ targettypmod,
+ ccontext,
+ cformat,
+ location,
+ NULL);
+}
+
+/*
+ * escontext: If not NULL, expr (Unknown Const node type) will be coerced to the
+ * target type in an error-safe way. If it fails, NULL is returned.
+ *
+ * For other parameters, see above coerce_to_target_type.
+ */
+Node *
+coerce_to_target_type_extended(ParseState *pstate, Node *expr, Oid exprtype,
+ Oid targettype, int32 targettypmod,
+ CoercionContext ccontext,
+ CoercionForm cformat,
+ int location,
+ Node *escontext)
{
Node *result;
Node *origexpr;
@@ -102,9 +127,12 @@ 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_extend(pstate, expr, exprtype,
+ targettype, targettypmod,
+ ccontext, cformat, location,
+ escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return NULL;
/*
* If the target is a fixed-length type, it may need a length coercion as
@@ -158,6 +186,18 @@ Node *
coerce_type(ParseState *pstate, Node *node,
Oid inputTypeId, Oid targetTypeId, int32 targetTypeMod,
CoercionContext ccontext, CoercionForm cformat, int location)
+{
+ return coerce_type_extend(pstate, node,
+ inputTypeId, targetTypeId, targetTypeMod,
+ ccontext, cformat, location, NULL);
+}
+
+Node *
+coerce_type_extend(ParseState *pstate, Node *node,
+ Oid inputTypeId, Oid targetTypeId, int32 targetTypeMod,
+ CoercionContext ccontext, CoercionForm cformat,
+ int location,
+ Node *escontext)
{
Node *result;
CoercionPathType pathtype;
@@ -256,6 +296,8 @@ coerce_type(ParseState *pstate, Node *node,
int32 inputTypeMod;
Type baseType;
ParseCallbackState pcbstate;
+ char *string;
+ bool coercion_failed = false;
/*
* If the target type is a domain, we want to call its base type's
@@ -308,21 +350,27 @@ 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 (con->constisnull)
+ string = NULL;
else
- newcon->constvalue = stringTypeDatum(baseType,
- NULL,
- inputTypeMod);
+ string = DatumGetCString(con->constvalue);
+
+ if (!stringTypeDatumSafe(baseType,
+ string,
+ inputTypeMod,
+ escontext,
+ &newcon->constvalue))
+ {
+ coercion_failed = true;
+ newcon->constisnull = true;
+ }
/*
* 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 (!coercion_failed && !con->constisnull && newcon->constlen == -1)
newcon->constvalue =
PointerGetDatum(PG_DETOAST_DATUM(newcon->constvalue));
@@ -339,7 +387,7 @@ 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 (!coercion_failed && !con->constisnull && !newcon->constbyval)
{
Datum val2;
@@ -358,8 +406,10 @@ coerce_type(ParseState *pstate, Node *node,
result = (Node *) newcon;
- /* If target is a domain, apply constraints. */
- if (baseTypeId != targetTypeId)
+ if (coercion_failed)
+ result = NULL;
+ else if (baseTypeId != targetTypeId)
+ /* If target is a domain, apply constraints. */
result = coerce_to_domain(result,
baseTypeId, baseTypeMod,
targetTypeId,
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 6b8fa15fca3..a10ec64a708 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -17,6 +17,7 @@
#include "access/htup_details.h"
#include "catalog/pg_aggregate.h"
+#include "catalog/pg_cast.h"
#include "catalog/pg_type.h"
#include "miscadmin.h"
#include "nodes/makefuncs.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,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 *can_coerce);
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 +79,10 @@ 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 void CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr,
+ Node *sourceexpr, Oid inputType,
+ Oid targetType);
static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
static Node *transformJsonObjectConstructor(ParseState *pstate,
JsonObjectConstructor *ctor);
@@ -164,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;
@@ -564,6 +575,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
case EXPR_KIND_VALUES_SINGLE:
case EXPR_KIND_CHECK_CONSTRAINT:
case EXPR_KIND_DOMAIN_CHECK:
+ case EXPR_KIND_CAST_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
case EXPR_KIND_INDEX_EXPRESSION:
case EXPR_KIND_INDEX_PREDICATE:
@@ -1824,6 +1836,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_DOMAIN_CHECK:
err = _("cannot use subquery in check constraint");
break;
+ case EXPR_KIND_CAST_DEFAULT:
+ err = _("cannot use subquery in CAST DEFAULT expression");
+ break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
err = _("cannot use subquery in DEFAULT expression");
@@ -2011,17 +2026,24 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
* 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().
+ *
+ * Most of the time, can_coerce will be NULL. It is not NULL only when
+ * performing parse analysis for CAST(DEFAULT ... ON CONVERSION ERROR)
+ * expression. It's default to true. If coercing array elements fails, it will
+ * be set to 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;
List *newcoercedelems = NIL;
ListCell *element;
Oid coerce_type;
+ Oid coerce_type_coll;
bool coerce_hard;
+ ErrorSaveContext escontext = {T_ErrorSaveContext};
/*
* Transform the element expressions
@@ -2045,9 +2067,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
@@ -2088,6 +2111,9 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
}
else
{
+ /* Target type must valid for CAST DEFAULT */
+ Assert(can_coerce == NULL);
+
/* Can't handle an empty array without a target type */
if (newelems == NIL)
ereport(ERROR,
@@ -2125,6 +2151,8 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
coerce_hard = false;
}
+ coerce_type_coll = get_typcollation(coerce_type);
+
/*
* Coerce elements to target type
*
@@ -2134,28 +2162,82 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
* If the array's type was merely derived from the common type of its
* elements, then the elements are implicitly coerced to the common type.
* This is consistent with other uses of select_common_type().
+ *
+ * If can_coerce is not NULL, we need to safely test whether each element
+ * can be coerced to the target type, similar to what is done in
+ * transformTypeSafeCast.
*/
foreach(element, newelems)
{
Node *e = (Node *) lfirst(element);
- Node *newe;
+ Node *newe = NULL;
if (coerce_hard)
{
- newe = coerce_to_target_type(pstate, e,
- exprType(e),
- coerce_type,
- typmod,
- COERCION_EXPLICIT,
- COERCE_EXPLICIT_CAST,
- -1);
- if (newe == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_CANNOT_COERCE),
- errmsg("cannot cast type %s to %s",
- format_type_be(exprType(e)),
- format_type_be(coerce_type)),
- parser_errposition(pstate, exprLocation(e))));
+ /*
+ * Can not coerce, just append the transformed element expression to
+ * the list.
+ */
+ if (can_coerce && (!*can_coerce))
+ newe = e;
+ else
+ {
+ Node *ecopy = NULL;
+
+ if (can_coerce)
+ ecopy = copyObject(e);
+
+ newe = coerce_to_target_type_extended(pstate, e,
+ exprType(e),
+ coerce_type,
+ typmod,
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ -1,
+ (Node *) &escontext);
+ if (newe == NULL)
+ {
+ if (!can_coerce)
+ ereport(ERROR,
+ (errcode(ERRCODE_CANNOT_COERCE),
+ errmsg("cannot cast type %s to %s",
+ format_type_be(exprType(e)),
+ format_type_be(coerce_type)),
+ parser_errposition(pstate, exprLocation(e))));
+ else
+ {
+ /*
+ * Can not coerce, just append the transformed element
+ * expression to the list.
+ */
+ newe = ecopy;
+ *can_coerce = false;
+ }
+ }
+ else if (can_coerce && IsA(ecopy, Const) && IsA(newe, FuncExpr))
+ {
+ Node *result;
+
+ /*
+ * pre-evaluate simple constant cast expressions in a way
+ * that tolerate errors.
+ */
+ FuncExpr *newexpr = (FuncExpr *) copyObject(newe);
+
+ assign_expr_collations(pstate, (Node *) newexpr);
+
+ result = (Node *) evaluate_expr((Expr *) newexpr,
+ coerce_type,
+ typmod,
+ coerce_type_coll,
+ true);
+ if (result == NULL)
+ {
+ newe = ecopy;
+ *can_coerce = false;
+ }
+ }
+ }
}
else
newe = coerce_to_common_type(pstate, e,
@@ -2743,7 +2825,8 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
(A_ArrayExpr *) arg,
targetBaseType,
elementType,
- targetBaseTypmod);
+ targetBaseTypmod,
+ NULL);
}
else
expr = transformExprRecurse(pstate, arg);
@@ -2780,6 +2863,291 @@ 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;
+ TypeCast *tcast = (TypeCast *) tc->cast;
+ Node *source_expr;
+ Node *srcexpr;
+ Node *defexpr;
+ Node *cast_expr = NULL;
+ Oid inputType;
+ Oid targetType;
+ int32 targetTypmod;
+ Oid targetTypecoll;
+ Oid targetBaseType;
+ int32 targetBaseTypmod;
+ bool can_coerce = true;
+
+ /* Look up the type name first */
+ typenameTypeIdAndMod(pstate, tcast->typeName, &targetType, &targetTypmod);
+ targetBaseTypmod = targetTypmod;
+
+ targetTypecoll = get_typcollation(targetType);
+
+ targetBaseType = getBaseTypeAndTypmod(targetType, &targetBaseTypmod);
+
+ /* now looking at DEFAULT expression */
+ defexpr = transformExpr(pstate, tc->raw_default, EXPR_KIND_CAST_DEFAULT);
+
+ defexpr = coerce_to_target_type(pstate, defexpr, exprType(defexpr),
+ targetType, targetTypmod,
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ exprLocation(defexpr));
+ if (defexpr == NULL)
+ ereport(ERROR,
+ errcode(ERRCODE_CANNOT_COERCE),
+ errmsg("cannot coerce %s expression to type %s",
+ "CAST DEFAULT",
+ format_type_be(targetType)),
+ parser_coercion_errposition(pstate, exprLocation(tc->raw_default), defexpr));
+
+ assign_expr_collations(pstate, defexpr);
+
+ /*
+ * The collation of DEFAULT expression must match the collation of the
+ * target type.
+ */
+ if (OidIsValid(targetTypecoll))
+ {
+ Oid defColl = exprCollation(defexpr);
+
+ if (OidIsValid(defColl) && targetTypecoll != defColl)
+ ereport(ERROR,
+ errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("the collation of CAST DEFAULT expression conflicts with target type collation"),
+ errdetail("\"%s\" versus \"%s\"",
+ get_collation_name(targetTypecoll),
+ get_collation_name(defColl)),
+ parser_errposition(pstate, exprLocation(defexpr)));
+ }
+
+ /*
+ * If the type cast 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(tcast->arg, A_ArrayExpr))
+ {
+ 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.
+ */
+ elementType = get_element_type(targetBaseType);
+
+ if (OidIsValid(elementType))
+ {
+ source_expr = transformArrayExpr(pstate,
+ (A_ArrayExpr *) tcast->arg,
+ targetBaseType,
+ elementType,
+ targetBaseTypmod,
+ &can_coerce);
+ }
+ else
+ source_expr = transformExprRecurse(pstate, tcast->arg);
+ }
+ else
+ source_expr = transformExprRecurse(pstate, tcast->arg);
+
+ /*
+ * Since we can't be certain that coerce_to_target_type_extended won't
+ * modify the source expression, we create a copy of it first. This ensures
+ * the transformed source expression is correctly passed to SafeTypeCastExpr
+ */
+ srcexpr = copyObject(source_expr);
+
+ inputType = exprType(source_expr);
+ if (inputType == InvalidOid)
+ return (Node *) NULL; /* return NULL if NULL input */
+
+ if (can_coerce)
+ {
+ ErrorSaveContext escontext = {T_ErrorSaveContext};
+
+ cast_expr = coerce_to_target_type_extended(pstate, source_expr, inputType,
+ targetType, targetTypmod,
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ tcast->location,
+ (Node *) &escontext);
+ if (cast_expr == NULL)
+ can_coerce = false;
+
+ CoercionErrorSafeCheck(pstate, cast_expr, source_expr, inputType,
+ targetType);
+ }
+
+ if (IsA(srcexpr, Const) && cast_expr && IsA(cast_expr, FuncExpr))
+ {
+ Node *result;
+
+ /*
+ * pre-evaluate simple constant cast expressions in a error safe way
+ *
+ * Rationale:
+ * 1. When deparsing safe cast expressions (or in other cases),
+ * eval_const_expressions might be invoked, but it cannot
+ * handle errors gracefully.
+ * 2. If the cast expression involves only simple constants, we
+ * can safely evaluate it ahead of time. If the evaluation
+ * fails, it indicates that such a cast is not possible, and
+ * we can then fall back to the CAST DEFAULT expression.
+ * 3. Even if FuncExpr (for castfunc) node has three arguments,
+ * the second and third arguments will also be constants per
+ * coerce_to_target_type. Evaluating a FuncExpr whose
+ * arguments are all Const is safe.
+ */
+ FuncExpr *newexpr = (FuncExpr *) copyObject(cast_expr);
+
+ assign_expr_collations(pstate, (Node *) newexpr);
+
+ result = (Node *) evaluate_expr((Expr *) newexpr,
+ targetType,
+ targetTypmod,
+ targetTypecoll,
+ true);
+ if (result == NULL)
+ {
+ /* can not coerce, set can_coerce to false */
+ can_coerce = false;
+ cast_expr = NULL;
+ }
+ }
+
+ Assert(can_coerce || cast_expr == NULL);
+
+ result = makeNode(SafeTypeCastExpr);
+ result->source_expr = srcexpr;
+ result->cast_expr = cast_expr;
+ result->default_expr = defexpr;
+ result->resulttype = targetType;
+ result->resulttypmod = targetTypmod;
+ result->resultcollid = targetTypecoll;
+ result->location = tcast->location;
+
+ return (Node *) result;
+}
+
+/*
+ * Check coercion is error safe or not. If not then report error
+ */
+static void
+CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr, Node *sourceexpr, Oid inputType,
+ Oid targetType)
+{
+ HeapTuple tuple;
+ bool errorsafe = true;
+ bool userdefined = false;
+ Oid inputBaseType;
+ Oid targetBaseType;
+ Oid inputElementType;
+ Oid inputElementBaseType;
+ Oid targetElementType;
+ Oid targetElementBaseType;
+
+ /*
+ * Binary coercion cast is equivalent to no cast at all. CoerceViaIO can
+ * also be evaluated in an error-safe manner. So skip these two cases.
+ */
+ if (!castexpr || IsA(castexpr, RelabelType) ||
+ IsA(castexpr, CoerceViaIO))
+ return;
+
+ /*
+ * Type cast involving with some types is not error safe, do the check now.
+ * We need consider domain over array and array over domain scarenio. We
+ * already checked user-defined type cast above.
+ */
+ inputElementType = get_element_type(inputType);
+
+ if (OidIsValid(inputElementType))
+ inputElementBaseType = getBaseType(inputElementType);
+ else
+ {
+ inputBaseType = getBaseType(inputType);
+
+ inputElementBaseType = get_element_type(inputBaseType);
+ if (!OidIsValid(inputElementBaseType))
+ inputElementBaseType = inputBaseType;
+ }
+
+ targetElementType = get_element_type(targetType);
+
+ if (OidIsValid(targetElementType))
+ targetElementBaseType = getBaseType(targetElementType);
+ else
+ {
+ targetBaseType = getBaseType(targetType);
+
+ targetElementBaseType = get_element_type(targetBaseType);
+ if (!OidIsValid(targetElementBaseType))
+ targetElementBaseType = targetBaseType;
+ }
+
+ if (inputElementBaseType == MONEYOID ||
+ targetElementBaseType == MONEYOID ||
+ (inputElementBaseType == CIRCLEOID &&
+ targetElementBaseType == POLYGONOID))
+ {
+ /*
+ * Casts involving MONEY type are not error safe. The CIRCLE to POLYGON
+ * cast is also unsafe because it relies on a SQL-language function;
+ * only C-language functions are currently supported for error safe
+ * casts.
+ */
+ errorsafe = false;
+ }
+
+ if (errorsafe)
+ {
+ /* Look in pg_cast */
+ tuple = SearchSysCache2(CASTSOURCETARGET,
+ ObjectIdGetDatum(inputElementBaseType),
+ ObjectIdGetDatum(targetElementBaseType));
+
+ if (HeapTupleIsValid(tuple))
+ {
+ Form_pg_cast castForm = (Form_pg_cast) GETSTRUCT(tuple);
+
+ if (castForm->oid > FirstUnpinnedObjectId)
+ {
+ errorsafe = false;
+ userdefined = true;
+ }
+
+ ReleaseSysCache(tuple);
+ }
+ }
+
+ if (!errorsafe)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot cast type %s to %s when %s expression is specified in %s",
+ format_type_be(inputType),
+ format_type_be(targetType),
+ "DEFAULT",
+ "CAST ... ON CONVERSION ERROR"),
+ userdefined
+ ? errhint("Safe type cast for user-defined types are not yet supported")
+ : errhint("Explicit cast is defined but definition is not error safe"),
+ parser_errposition(pstate, exprLocation(sourceexpr)));
+}
+
+
/*
* Handle an explicit COLLATE clause.
*
@@ -3193,6 +3561,8 @@ ParseExprKindName(ParseExprKind exprKind)
case EXPR_KIND_CHECK_CONSTRAINT:
case EXPR_KIND_DOMAIN_CHECK:
return "CHECK";
+ case EXPR_KIND_CAST_DEFAULT:
+ return "CAST DEFAULT";
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
return "DEFAULT";
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 778d69c6f3c..a90705b9847 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2743,6 +2743,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
case EXPR_KIND_DOMAIN_CHECK:
err = _("set-returning functions are not allowed in check constraints");
break;
+ case EXPR_KIND_CAST_DEFAULT:
+ err = _("set-returning functions are not allowed in CAST DEFAULT expressions");
+ break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
err = _("set-returning functions are not allowed in DEFAULT expressions");
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 905c975d83b..dc03cf4ce74 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1822,6 +1822,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 9d7aa6967da..f6825bb9870 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,19 @@ stringTypeDatum(Type tp, char *string, int32 atttypmod)
return OidInputFunctionCall(typinput, string, typioparam, atttypmod);
}
+/* error-safe version of stringTypeDatum */
+bool
+stringTypeDatumSafe(Type tp, char *string, int32 atttypmod,
+ Node *escontext, Datum *result)
+{
+ Form_pg_type typform = (Form_pg_type) GETSTRUCT(tp);
+ Oid typinput = typform->typinput;
+ Oid typioparam = getTypeIOParam(tp);
+
+ return OidInputFunctionCallSafe(typinput, string, typioparam, atttypmod,
+ escontext, result);
+}
+
/*
* Given a typeid, return the type's typrelid (associated relation), if any.
* Returns InvalidOid if type is not a composite type.
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..6df0b975a88 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -4630,7 +4630,7 @@ transformPartitionBoundValue(ParseState *pstate, Node *val,
assign_expr_collations(pstate, value);
value = (Node *) expression_planner((Expr *) value);
value = (Node *) evaluate_expr((Expr *) value, colType, colTypmod,
- partCollation);
+ partCollation, false);
if (!IsA(value, Const))
elog(ERROR, "could not evaluate partition bound expression");
}
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index b67ce57656a..c1027c5157d 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -3289,6 +3289,14 @@ array_map(Datum arrayd,
/* Apply the given expression to source element */
values[i] = ExecEvalExpr(exprstate, econtext, &nulls[i]);
+ /* Exit early if the evaluation fails */
+ 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 9f85eb86da1..17049f05cfc 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -10562,6 +10562,27 @@ get_rule_expr(Node *node, deparse_context *context,
}
break;
+ case T_SafeTypeCastExpr:
+ {
+ SafeTypeCastExpr *stcexpr = castNode(SafeTypeCastExpr, node);
+
+ /*
+ * Here, we cannot deparsing cast_expr directly, since
+ * transformTypeSafeCast may have folded it into a simple
+ * constant or NULL. Instead, we use source_expr and
+ * default_expr to reconstruct the CAST DEFAULT clause.
+ */
+ 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 0fe63c6bb83..c9e5d0de8ed 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -1759,6 +1759,20 @@ OidInputFunctionCall(Oid functionId, char *str, Oid typioparam, int32 typmod)
return InputFunctionCall(&flinfo, str, typioparam, typmod);
}
+/* error-safe version of OidInputFunctionCall */
+bool
+OidInputFunctionCallSafe(Oid functionId, char *str, Oid typioparam,
+ int32 typmod, Node *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 75366203706..937cbf3bc9b 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_SAFETYPE_CAST */
+ struct
+ {
+ struct SafeTypeCastState *stcstate;
+ } stcexpr;
+
/* for EEOP_JSONEXPR_PATH */
struct
{
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 7cd6a49309f..3e8ba551e73 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -324,6 +324,7 @@ ExecProcNode(PlanState *node)
* prototypes from functions in execExpr.c
*/
extern ExprState *ExecInitExpr(Expr *node, PlanState *parent);
+extern ExprState *ExecInitExprSafe(Expr *node, PlanState *parent);
extern ExprState *ExecInitExprWithParams(Expr *node, ParamListInfo ext_params);
extern ExprState *ExecInitQual(List *qual, PlanState *parent);
extern ExprState *ExecInitCheck(List *qual, PlanState *parent);
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index c0dbe85ed1c..a9357b98d70 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, Node *escontext,
+ Datum *result);
extern char *OutputFunctionCall(FmgrInfo *flinfo, Datum val);
extern char *OidOutputFunctionCall(Oid functionId, Datum val);
extern Datum ReceiveFunctionCall(FmgrInfo *flinfo, StringInfo buf,
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 64ff6996431..9018e190cc7 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1059,6 +1059,27 @@ typedef struct DomainConstraintState
ExprState *check_exprstate; /* check_expr's eval state, or NULL */
} DomainConstraintState;
+typedef struct SafeTypeCastState
+{
+ SafeTypeCastExpr *stcexpr;
+
+ /*
+ * Address to jump to when skipping all the steps to evaulate the default
+ * expression.
+ */
+ int jump_end;
+
+ /*
+ * For error-safe evaluation of coercions. 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 d14294a4ece..82b0fb83b4b 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -400,6 +400,17 @@ typedef struct TypeCast
ParseLoc location; /* token location, or -1 if unknown */
} TypeCast;
+/*
+ * SafeTypeCast - a CAST(source_expr AS target_type) DEFAULT ON CONVERSION ERROR
+ * construct
+ */
+typedef struct SafeTypeCast
+{
+ NodeTag type;
+ Node *cast; /* TypeCast expression */
+ Node *raw_default; /* untransformed DEFAULT expression */
+} SafeTypeCast;
+
/*
* CollateClause - a COLLATE expression
*/
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 1b4436f2ff6..1401109fb3c 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -769,6 +769,42 @@ 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
+{
+ Expr xpr;
+
+ /* transformed source expression */
+ Node *source_expr;
+
+ /*
+ * The transformed cast expression; NULL if we can not cocerce source
+ * expression to the target type
+ */
+ Node *cast_expr pg_node_attr(query_jumble_ignore);
+
+ /* Fall back to the default expression if cast evaluation fails */
+ Node *default_expr;
+
+ /* cast result data type */
+ Oid resulttype;
+
+ /* cast result data type typmod (usually -1) */
+ int32 resulttypmod;
+
+ /* cast result data type collation */
+ Oid resultcollid;
+
+ /* Original SafeTypeCastExpr's location */
+ ParseLoc location;
+
+} SafeTypeCastExpr;
+
/*
* FuncExpr - expression node for a function call
*
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index 44ec5296a18..587e711596a 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -143,7 +143,7 @@ extern void convert_saop_to_hashed_saop(Node *node);
extern Node *estimate_expression_value(PlannerInfo *root, Node *node);
extern Expr *evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
- Oid result_collation);
+ Oid result_collation, bool error_safe);
extern bool var_is_nonnullable(PlannerInfo *root, Var *var, bool use_rel_info);
diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h
index 8d775c72c59..1eca1ce727d 100644
--- a/src/include/parser/parse_coerce.h
+++ b/src/include/parser/parse_coerce.h
@@ -43,11 +43,26 @@ extern Node *coerce_to_target_type(ParseState *pstate,
CoercionContext ccontext,
CoercionForm cformat,
int location);
+extern Node *coerce_to_target_type_extended(ParseState *pstate,
+ Node *expr,
+ Oid exprtype,
+ Oid targettype,
+ int32 targettypmod,
+ CoercionContext ccontext,
+ CoercionForm cformat,
+ int location,
+ Node *escontext);
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_extend(ParseState *pstate, Node *node,
+ Oid inputTypeId,
+ Oid targetTypeId, int32 targetTypeMod,
+ CoercionContext ccontext, CoercionForm cformat,
+ int location,
+ Node *escontext);
extern Node *coerce_to_domain(Node *arg, Oid baseTypeId, int32 baseTypeMod,
Oid typeId,
CoercionContext ccontext, CoercionForm cformat, int location,
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index f7d07c84542..9f5b32e0360 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -67,6 +67,8 @@ typedef enum ParseExprKind
EXPR_KIND_VALUES_SINGLE, /* single-row VALUES (in INSERT only) */
EXPR_KIND_CHECK_CONSTRAINT, /* CHECK constraint for a table */
EXPR_KIND_DOMAIN_CHECK, /* CHECK constraint for a domain */
+ EXPR_KIND_CAST_DEFAULT, /* default expression in
+ CAST DEFAULT ON CONVERSION ERROR */
EXPR_KIND_COLUMN_DEFAULT, /* default value for a table column */
EXPR_KIND_FUNCTION_DEFAULT, /* default parameter value for function */
EXPR_KIND_INDEX_EXPRESSION, /* index expression */
diff --git a/src/include/parser/parse_type.h b/src/include/parser/parse_type.h
index 0d919d8bfa2..40aca2b31c9 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,
+ Node *escontext, Datum *result);
extern Oid typeidTypeRelid(Oid type_id);
extern Oid typeOrDomainTypeRelid(Oid type_id);
diff --git a/src/test/regress/expected/cast.out b/src/test/regress/expected/cast.out
new file mode 100644
index 00000000000..b0dc7b8faea
--- /dev/null
+++ b/src/test/regress/expected/cast.out
@@ -0,0 +1,810 @@
+SET extra_float_digits = 0;
+-- CAST DEFAULT ON CONVERSION ERROR
+SELECT CAST(B'01' AS date DEFAULT NULL ON CONVERSION ERROR);
+ date
+------
+
+(1 row)
+
+SELECT CAST(BIT'01' AS date DEFAULT NULL ON CONVERSION ERROR);
+ date
+------
+
+(1 row)
+
+SELECT CAST(TRUE AS date DEFAULT NULL ON CONVERSION ERROR);
+ date
+------
+
+(1 row)
+
+SELECT CAST(1.1 AS date DEFAULT NULL ON CONVERSION ERROR);
+ date
+------
+
+(1 row)
+
+SELECT CAST(1 AS date DEFAULT NULL ON CONVERSION ERROR);
+ date
+------
+
+(1 row)
+
+SELECT CAST(1111 AS "char" DEFAULT 'A' ON CONVERSION ERROR);
+ char
+------
+ A
+(1 row)
+
+--test source expression is a unknown const
+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('a' as int DEFAULT 18 ON CONVERSION ERROR);
+ int4
+------
+ 18
+(1 row)
+
+SELECT CAST('a' as int DEFAULT sum(1) ON CONVERSION ERROR); --error
+ERROR: aggregate functions are not allowed in CAST DEFAULT expressions
+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: window functions are not allowed in CAST DEFAULT expressions
+LINE 1: SELECT CAST('a' as int DEFAULT sum(1) over() ON CONVERSION E...
+ ^
+SELECT CAST('a' as int DEFAULT (SELECT NULL) ON CONVERSION ERROR); --error
+ERROR: cannot use subquery in CAST DEFAULT expression
+LINE 1: SELECT CAST('a' as int DEFAULT (SELECT NULL) 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('a'::int as int DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR: invalid input syntax for type integer: "a"
+LINE 1: SELECT CAST('a'::int as int DEFAULT NULL ON CONVERSION ERROR...
+ ^
+--the default expression’s collation should match the collation of the cast
+--target type
+VALUES (CAST('error' AS text DEFAULT '1' COLLATE "C" ON CONVERSION ERROR));
+ERROR: the collation of CAST DEFAULT expression conflicts with target type collation
+LINE 1: VALUES (CAST('error' AS text DEFAULT '1' COLLATE "C" ON CONV...
+ ^
+DETAIL: "default" versus "C"
+VALUES (CAST('error' AS int2vector DEFAULT '1 3' ON CONVERSION ERROR));
+ column1
+---------
+ 1 3
+(1 row)
+
+VALUES (CAST('error' AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR));
+ column1
+---------
+ {"1 3"}
+(1 row)
+
+-- test source expression contain subquery
+SELECT CAST((SELECT b FROM generate_series(1, 1), (VALUES('H')) s(b))
+ AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR);
+ b
+---------
+ {"1 3"}
+(1 row)
+
+SELECT CAST((SELECT ARRAY((SELECT b FROM generate_series(1, 2) g, (VALUES('H')) s(b))))
+ AS INT[]
+ DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+CREATE FUNCTION ret_int8() RETURNS BIGINT AS
+$$
+BEGIN RETURN 2147483648; END;
+$$
+LANGUAGE plpgsql IMMUTABLE;
+SELECT CAST('a' as int DEFAULT ret_int8() ON CONVERSION ERROR); --error
+ERROR: integer out of range
+SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERROR); --error
+ERROR: cannot coerce CAST DEFAULT expression to type date
+LINE 1: SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERR...
+ ^
+-- DEFAULT expression can not be set-returning
+CREATE 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);
+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: set-returning functions are not allowed in CAST DEFAULT expressions
+LINE 1: SELECT CAST('a' as int DEFAULT ret_setint() ON CONVERSION ER...
+ ^
+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 user-defined type, domain, array over domain, domain over array
+CREATE DOMAIN d_int42 as int check (value = 42) NOT NULL;
+CREATE DOMAIN d_char3_not_null as char(3) NOT NULL;
+CREATE DOMAIN d_varchar as varchar(3) NOT NULL;
+CREATE DOMAIN d_int_arr as int[] check (value = '{41, 43}') NOT NULL;
+CREATE TYPE comp_domain_with_typmod AS (a d_char3_not_null, b int);
+CREATE TYPE comp2 AS (a d_varchar);
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION ERROR); --error
+ERROR: value too long for type character varying(3)
+LINE 1: SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION...
+ ^
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(123)' ON CONVERSION ERROR);
+ comp2
+-------
+ (123)
+(1 row)
+
+SELECT CAST(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);
+ 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);
+ 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)'...
+ ^
+SELECT CAST(ARRAY[42,41] AS d_int42[] DEFAULT '{42, 42}' ON CONVERSION ERROR);
+ array
+---------
+ {42,42}
+(1 row)
+
+SELECT CAST(ARRAY[42,41] AS d_int_arr DEFAULT '{41, 43}' ON CONVERSION ERROR);
+ array
+---------
+ {41,43}
+(1 row)
+
+-- test array coerce
+SELECT CAST(array['a'::text] AS int[] DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR: invalid input syntax for type integer: "a"
+SELECT CAST(array['a'] AS int[] DEFAULT ARRAY[1] ON CONVERSION ERROR);
+ array
+-------
+ {1}
+(1 row)
+
+SELECT CAST(array[11.12] AS date[] DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+SELECT CAST(array[11111111111111111] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+SELECT CAST(array[['abc'],[456]] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+SELECT CAST('{123,abc,456}' AS int[] DEFAULT '{-789}' ON CONVERSION ERROR);
+ int4
+--------
+ {-789}
+(1 row)
+
+SELECT CAST('{234,def,567}'::text[] AS integer[] DEFAULT '{-1011}' ON CONVERSION ERROR);
+ int4
+---------
+ {-1011}
+(1 row)
+
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS int[] DEFAULT '{1,2}' ON CONVERSION ERROR);
+ array
+-------
+ {1,2}
+(1 row)
+
+SELECT CAST(ARRAY[['1', '2'], ['three', 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --ok
+ array
+---------
+ {21,22}
+(1 row)
+
+SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --error
+ERROR: invalid input syntax for type integer: "three"
+LINE 1: SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] ...
+ ^
+SELECT CAST(ARRAY[1, 'three'::int] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --error
+ERROR: invalid input syntax for type integer: "three"
+LINE 1: SELECT CAST(ARRAY[1, 'three'::int] AS int[] DEFAULT '{21,22}...
+ ^
+-----safe cast with geometry data type
+SELECT CAST('(1,2)'::point AS box DEFAULT NULL ON CONVERSION ERROR);
+ box
+-------------
+ (1,2),(1,2)
+(1 row)
+
+SELECT CAST('[(NaN,1),(NaN,infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+----------------
+ (NaN,Infinity)
+(1 row)
+
+SELECT CAST('[(1e+300,Infinity),(1e+300,Infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+-------------------
+ (1e+300,Infinity)
+(1 row)
+
+SELECT CAST('[(1,2),(3,4)]'::path as polygon DEFAULT NULL ON CONVERSION ERROR);
+ polygon
+---------
+
+(1 row)
+
+SELECT CAST('(NaN,1.0,NaN,infinity)'::box AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+----------------
+ (NaN,Infinity)
+(1 row)
+
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS lseg DEFAULT NULL ON CONVERSION ERROR);
+ lseg
+---------------
+ [(2,2),(0,0)]
+(1 row)
+
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS polygon DEFAULT NULL ON CONVERSION ERROR);
+ polygon
+---------------------------
+ ((0,0),(0,2),(2,2),(2,0))
+(1 row)
+
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS path DEFAULT NULL ON CONVERSION ERROR);
+ path
+------
+
+(1 row)
+
+SELECT CAST('(2.0,infinity,NaN,infinity)'::box AS circle DEFAULT NULL ON CONVERSION ERROR);
+ circle
+----------------------
+ <(NaN,Infinity),NaN>
+(1 row)
+
+SELECT CAST('(NaN,0.0),(2.0,4.0),(0.0,infinity)'::polygon AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+----------------
+ (NaN,Infinity)
+(1 row)
+
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS path DEFAULT NULL ON CONVERSION ERROR);
+ path
+---------------------
+ ((2,0),(2,4),(0,0))
+(1 row)
+
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS box DEFAULT NULL ON CONVERSION ERROR);
+ box
+-------------
+ (2,4),(0,0)
+(1 row)
+
+SELECT CAST('(NaN,infinity),(2.0,4.0),(0.0,infinity)'::polygon AS circle DEFAULT NULL ON CONVERSION ERROR);
+ circle
+----------------------
+ <(NaN,Infinity),NaN>
+(1 row)
+
+SELECT CAST('<(5,1),3>'::circle AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+-------
+ (5,1)
+(1 row)
+
+SELECT CAST('<(3,5),0>'::circle as box DEFAULT NULL ON CONVERSION ERROR);
+ box
+-------------
+ (3,5),(3,5)
+(1 row)
+
+-- not supported because the cast from circle to polygon is implemented as a SQL
+-- function, which cannot be error-safe.
+SELECT CAST('<(3,5),0>'::circle as polygon DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type circle to polygon when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST('<(3,5),0>'::circle as polygon DEFAULT NULL ON C...
+ ^
+HINT: Explicit cast is defined but definition is not error safe
+-----safe cast from/to money type is not supported, all the following would result error
+SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type bigint to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION E...
+ ^
+HINT: Explicit cast is defined but definition is not error safe
+SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type integer to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION E...
+ ^
+HINT: Explicit cast is defined but definition is not error safe
+SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type numeric to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSIO...
+ ^
+HINT: Explicit cast is defined but definition is not error safe
+SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type money to numeric when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSIO...
+ ^
+HINT: Explicit cast is defined but definition is not error safe
+SELECT CAST(NULL::money[] AS numeric[] DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type money[] to numeric[] when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::money[] AS numeric[] DEFAULT NULL ON CONVE...
+ ^
+HINT: Explicit cast is defined but definition is not error safe
+-----safe cast from bytea type to other data types
+SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT 19 ON CONVERSION ERROR);
+ int8
+------
+ 19
+(1 row)
+
+SELECT CAST('\x123456789A'::bytea AS int4 DEFAULT 20 ON CONVERSION ERROR);
+ int4
+------
+ 20
+(1 row)
+
+SELECT CAST('\x123456'::bytea AS int2 DEFAULT 21 ON CONVERSION ERROR);
+ int2
+------
+ 21
+(1 row)
+
+-----safe cast from bit type to other data types
+SELECT CAST('111111111100001'::bit(100) AS INT DEFAULT 22 ON CONVERSION ERROR);
+ int4
+------
+ 22
+(1 row)
+
+SELECT CAST ('111111111100001'::bit(100) AS INT8 DEFAULT 23 ON CONVERSION ERROR);
+ int8
+------
+ 23
+(1 row)
+
+-----safe cast from text type to other data types
+select CAST('a.b.c.d'::text as regclass default NULL on conversion error);
+ regclass
+----------
+
+(1 row)
+
+CREATE TABLE test_safecast(col0 text);
+INSERT INTO test_safecast(col0) VALUES ('<value>one</value');
+SELECT col0 as text,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) as to_regclass,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) IS NULL as expect_true,
+ CAST(col0 AS "char" DEFAULT NULL ON CONVERSION ERROR) as to_char,
+ CAST(col0 AS name DEFAULT NULL ON CONVERSION ERROR) as to_name,
+ CAST(col0 AS xml DEFAULT NULL ON CONVERSION ERROR) as to_xml
+FROM test_safecast;
+ text | to_regclass | expect_true | to_char | to_name | to_xml
+-------------------+-------------+-------------+---------+-------------------+--------
+ <value>one</value | | t | < | <value>one</value |
+(1 row)
+
+SELECT CAST('192.168.1.x' as inet DEFAULT NULL ON CONVERSION ERROR);
+ inet
+------
+
+(1 row)
+
+SELECT CAST('192.168.1.2/30' as cidr DEFAULT NULL ON CONVERSION ERROR);
+ cidr
+------
+
+(1 row)
+
+SELECT CAST('22:00:5c:08:55:08:01:02'::macaddr8 as macaddr DEFAULT NULL ON CONVERSION ERROR);
+ macaddr
+---------
+
+(1 row)
+
+-----safe cast betweeen range data types
+SELECT CAST('[1,2]'::int4range AS int4multirange DEFAULT NULL ON CONVERSION ERROR);
+ int4multirange
+----------------
+ {[1,3)}
+(1 row)
+
+SELECT CAST('[1,2]'::int8range AS int8multirange DEFAULT NULL ON CONVERSION ERROR);
+ int8multirange
+----------------
+ {[1,3)}
+(1 row)
+
+SELECT CAST('[1,2]'::numrange AS nummultirange DEFAULT NULL ON CONVERSION ERROR);
+ nummultirange
+---------------
+ {[1,2]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::daterange AS datemultirange DEFAULT NULL ON CONVERSION ERROR);
+ datemultirange
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::tsrange AS tsmultirange DEFAULT NULL ON CONVERSION ERROR);
+ tsmultirange
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::tstzrange AS tstzmultirange DEFAULT NULL ON CONVERSION ERROR);
+ tstzmultirange
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+-----safe cast betweeen numeric data types
+CREATE TABLE test_safecast1(
+ col1 float4, col2 float8, col3 numeric, col4 numeric[],
+ col5 int2 default 32767,
+ col6 int4 default 32768,
+ col7 int8 default 4294967296);
+INSERT INTO test_safecast1 VALUES('11.1234', '11.1234', '9223372036854775808', '{11.1234}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('inf', 'inf', 'inf', '{11.1234, 12, inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('-inf', '-inf', '-inf', '{11.1234, 12, -inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('NaN', 'NaN', 'NaN', '{11.1234, 12, -inf, NaN}'::numeric[]);
+SELECT col5 as int2,
+ CAST(col5 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col5 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col5 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col5 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col5 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col5 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col5 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col5 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ int2 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+(4 rows)
+
+SELECT col6 as int4,
+ CAST(col6 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col6 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col6 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col6 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col6 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col6 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col6 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col6 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ int4 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+(4 rows)
+
+SELECT col7 as int8,
+ CAST(col7 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col7 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col7 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col7 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col7 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col7 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col7 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col7 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ int8 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+------------+---------+---------+--------+------------+-------------+------------+------------+------------------
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+(4 rows)
+
+SELECT col3 as numeric,
+ CAST(col3 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col3 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col3 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col3 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col3 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col3 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col3 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col3 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ numeric | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+---------------------+---------+---------+--------+---------+-------------+----------------------+---------------------+------------------
+ 9223372036854775808 | | | | | 9.22337e+18 | 9.22337203685478e+18 | 9223372036854775808 |
+ Infinity | | | | | Infinity | Infinity | Infinity |
+ -Infinity | | | | | -Infinity | -Infinity | -Infinity |
+ NaN | | | | | NaN | NaN | NaN | NaN
+(4 rows)
+
+SELECT col4 as num_arr,
+ CAST(col4 AS int2[] DEFAULT NULL ON CONVERSION ERROR) as int2arr,
+ CAST(col4 AS int4[] DEFAULT NULL ON CONVERSION ERROR) as int4arr,
+ CAST(col4 as int8[] DEFAULT NULL ON CONVERSION ERROR) as int8arr,
+ CAST(col4 as float4[] DEFAULT NULL ON CONVERSION ERROR) as f4arr,
+ CAST(col4 as float8[] DEFAULT NULL ON CONVERSION ERROR) as f8arr,
+ CAST(col4 as numeric(10,1)[] DEFAULT NULL ON CONVERSION ERROR) as numarr
+FROM test_safecast1;
+ num_arr | int2arr | int4arr | int8arr | f4arr | f8arr | numarr
+----------------------------+---------+---------+---------+----------------------------+----------------------------+--------
+ {11.1234} | {11} | {11} | {11} | {11.1234} | {11.1234} | {11.1}
+ {11.1234,12,Infinity,NaN} | | | | {11.1234,12,Infinity,NaN} | {11.1234,12,Infinity,NaN} |
+ {11.1234,12,-Infinity,NaN} | | | | {11.1234,12,-Infinity,NaN} | {11.1234,12,-Infinity,NaN} |
+ {11.1234,12,-Infinity,NaN} | | | | {11.1234,12,-Infinity,NaN} | {11.1234,12,-Infinity,NaN} |
+(4 rows)
+
+SELECT col1 as float4,
+ CAST(col1 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col1 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col1 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col1 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col1 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col1 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col1 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col1 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ float4 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-----------+---------+---------+--------+---------+-----------+------------------+------------+------------------
+ 11.1234 | 11 | 11 | | 11 | 11.1234 | 11.1233997344971 | 11.1234 | 11.1
+ Infinity | | | | | Infinity | Infinity | Infinity |
+ -Infinity | | | | | -Infinity | -Infinity | -Infinity |
+ NaN | | | | | NaN | NaN | NaN | NaN
+(4 rows)
+
+SELECT col2 as float8,
+ CAST(col2 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col2 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col2 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col2 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col2 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col2 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col2 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col2 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ float8 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-----------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 11.1234 | 11 | 11 | | 11 | 11.1234 | 11.1234 | 11.1234 | 11.1
+ Infinity | | | | | Infinity | Infinity | Infinity |
+ -Infinity | | | | | -Infinity | -Infinity | -Infinity |
+ NaN | | | | | NaN | NaN | NaN | NaN
+(4 rows)
+
+-- date/timestamp/interval related safe type cast
+CREATE TABLE test_safecast2(
+ col0 date, col1 timestamp, col2 timestamptz,
+ col3 interval, col4 time, col5 timetz);
+INSERT INTO test_safecast2 VALUES
+('-infinity', '-infinity', '-infinity', '-infinity',
+ '2003-03-07 15:36:39 America/New_York', '2003-07-07 15:36:39 America/New_York'),
+('-infinity', 'infinity', 'infinity', 'infinity',
+ '11:59:59.99 PM', '11:59:59.99 PM PDT');
+SELECT col0 as date,
+ CAST(col0 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col0 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col0 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col0 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col0 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+ date | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale
+-----------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity | -infinity | | | -infinity
+ -infinity | -infinity | -infinity | | | -infinity
+(2 rows)
+
+SELECT col1 as timestamp,
+ CAST(col1 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col1 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col1 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col1 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col1 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+ timestamp | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale
+-----------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity | -infinity | | | -infinity
+ infinity | infinity | infinity | | | infinity
+(2 rows)
+
+SELECT col2 as timestamptz,
+ CAST(col2 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col2 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col2 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col2 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col2 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+ timestamptz | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale
+-------------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity | -infinity | | | -infinity
+ infinity | infinity | infinity | | | infinity
+(2 rows)
+
+SELECT col3 as interval,
+ CAST(col3 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col3 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col3 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col3 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col3 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale,
+ CAST(col3 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+ interval | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale | to_interval_scale
+-----------+----------------+---------+----------+-----------+--------------------+-------------------
+ -infinity | | | | | | -infinity
+ infinity | | | | | | infinity
+(2 rows)
+
+SELECT col4 as time,
+ CAST(col4 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col4 AS timetz DEFAULT NULL ON CONVERSION ERROR) IS NOT NULL as to_timetz,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+ time | to_times | to_timetz | to_interval | to_interval_scale
+-------------+-------------+-----------+-------------------------------+-------------------------------
+ 15:36:39 | 15:36:39 | t | @ 15 hours 36 mins 39 secs | @ 15 hours 36 mins 39 secs
+ 23:59:59.99 | 23:59:59.99 | t | @ 23 hours 59 mins 59.99 secs | @ 23 hours 59 mins 59.99 secs
+(2 rows)
+
+SELECT col5 as timetz,
+ CAST(col5 AS time DEFAULT NULL ON CONVERSION ERROR) as to_time,
+ CAST(col5 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_time_scale,
+ CAST(col5 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col5 AS timetz(6) DEFAULT NULL ON CONVERSION ERROR) as to_timetz_scale,
+ CAST(col5 AS interval DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col5 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+ timetz | to_time | to_time_scale | to_timetz | to_timetz_scale | to_interval | to_interval_scale
+----------------+-------------+---------------+----------------+-----------------+-------------+-------------------
+ 15:36:39-04 | 15:36:39 | 15:36:39 | 15:36:39-04 | 15:36:39-04 | |
+ 23:59:59.99-07 | 23:59:59.99 | 23:59:59.99 | 23:59:59.99-07 | 23:59:59.99-07 | |
+(2 rows)
+
+CREATE TABLE test_safecast3(col0 jsonb);
+INSERT INTO test_safecast3(col0) VALUES ('"test"');
+SELECT col0 as jsonb,
+ CAST(col0 AS integer DEFAULT NULL ON CONVERSION ERROR) as to_integer,
+ CAST(col0 AS numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col0 AS bigint DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col0 AS float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col0 AS float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col0 AS boolean DEFAULT NULL ON CONVERSION ERROR) as to_bool,
+ CAST(col0 AS smallint DEFAULT NULL ON CONVERSION ERROR) as to_smallint
+FROM test_safecast3;
+ jsonb | to_integer | to_numeric | to_int8 | to_float4 | to_float8 | to_bool | to_smallint
+--------+------------+------------+---------+-----------+-----------+---------+-------------
+ "test" | | | | | | |
+(1 row)
+
+-- test deparse
+SET datestyle TO ISO, YMD;
+CREATE VIEW safecastview AS
+SELECT CAST('1234' as char(3) DEFAULT -1111 ON CONVERSION ERROR),
+ CAST(1 as date DEFAULT (('2025-Dec-06'::date + random(min=>1, max=>1::int))) ON CONVERSION ERROR) as cast0,
+ CAST(ARRAY[['1'], ['three'],['a']] AS int[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1,
+ CAST(ARRAY[['1', '2'], ['three', 'a']] AS date[] DEFAULT NULL ON CONVERSION ERROR) as cast2,
+ CAST(ARRAY['three'] AS INT[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast3;
+\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 '2025-12-06'::date + random(min => 1, max => 1) ON CONVERSION ERROR) AS cast0,
+ CAST(ARRAY[ARRAY[1], ARRAY['three'], ARRAY['a']] AS integer[] DEFAULT '{1,2}'::integer[] ON CONVERSION ERROR) AS cast1,
+ CAST(ARRAY[ARRAY['1', '2'], ARRAY['three', 'a']] AS date[] DEFAULT NULL::date[] ON CONVERSION ERROR) AS cast2,
+ CAST(ARRAY['three'] AS integer[] DEFAULT '{1,2}'::integer[] ON CONVERSION ERROR) AS cast3
+SELECT * FROM safecastview;
+ bpchar | cast0 | cast1 | cast2 | cast3
+--------+------------+-------+-------+-------
+ 123 | 2025-12-07 | {1,2} | | {1,2}
+(1 row)
+
+CREATE VIEW safecastview1 AS
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS d_int_arr
+ DEFAULT '{41,43}' ON CONVERSION ERROR) as cast1,
+ CAST(ARRAY[['1', '2', 1.1], ['three', true, B'01']] AS d_int_arr
+ DEFAULT '{41,43}' ON CONVERSION ERROR) as cast2;
+\sv safecastview1
+CREATE OR REPLACE VIEW public.safecastview1 AS
+ SELECT CAST(ARRAY[ARRAY[1], ARRAY['three'], ARRAY['a']] AS d_int_arr DEFAULT '{41,43}'::integer[]::d_int_arr ON CONVERSION ERROR) AS cast1,
+ CAST(ARRAY[ARRAY[1, 2, 1.1::integer], ARRAY['three', true, '01'::"bit"]] AS d_int_arr DEFAULT '{41,43}'::integer[]::d_int_arr ON CONVERSION ERROR) AS cast2
+SELECT * FROM safecastview1;
+ cast1 | cast2
+---------+---------
+ {41,43} | {41,43}
+(1 row)
+
+RESET datestyle;
+--test CAST DEFAULT expression mutability
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as int DEFAULT random(min=>1, max=>1) ON CONVERSION ERROR)));
+ERROR: functions in index expression must be marked IMMUTABLE
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as xid DEFAULT NULL ON CONVERSION ERROR)));
+ERROR: data type xid has no default operator class for access method "btree"
+HINT: You must specify an operator class for the index or define a default operator class for the data type.
+CREATE INDEX test_safecast3_idx ON test_safecast3((CAST(col0 as int DEFAULT NULL ON CONVERSION ERROR))); --ok
+SELECT pg_get_indexdef('test_safecast3_idx'::regclass);
+ pg_get_indexdef
+------------------------------------------------------------------------------------------------------------------------------------------
+ CREATE INDEX test_safecast3_idx ON public.test_safecast3 USING btree ((CAST(col0 AS integer DEFAULT NULL::integer ON CONVERSION ERROR)))
+(1 row)
+
+DROP TABLE test_safecast;
+DROP TABLE test_safecast1;
+DROP TABLE test_safecast2;
+DROP TABLE test_safecast3;
+DROP TABLE tcast;
+DROP FUNCTION ret_int8;
+DROP FUNCTION ret_setint;
+DROP TYPE comp_domain_with_typmod;
+DROP TYPE comp2;
+DROP DOMAIN d_varchar;
+DROP DOMAIN d_int42;
+DROP DOMAIN d_char3_not_null;
+RESET extra_float_digits;
diff --git a/src/test/regress/expected/create_cast.out b/src/test/regress/expected/create_cast.out
index 0e69644bca2..0054ed0ef67 100644
--- a/src/test/regress/expected/create_cast.out
+++ b/src/test/regress/expected/create_cast.out
@@ -88,6 +88,11 @@ SELECT 1234::int4::casttesttype; -- Should work now
bar1234
(1 row)
+SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- error
+ERROR: cannot cast type integer to casttesttype when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVE...
+ ^
+HINT: Safe type cast for user-defined types are not yet supported
-- check dependencies generated for that
SELECT pg_describe_object(classid, objid, objsubid) as obj,
pg_describe_object(refclassid, refobjid, refobjsubid) as objref,
diff --git a/src/test/regress/expected/equivclass.out b/src/test/regress/expected/equivclass.out
index ad8ab294ff6..5aee2c7a9fb 100644
--- a/src/test/regress/expected/equivclass.out
+++ b/src/test/regress/expected/equivclass.out
@@ -95,6 +95,13 @@ create function int8alias1cmp(int8, int8alias1) returns int
strict immutable language internal as 'btint8cmp';
alter operator family integer_ops using btree add
function 1 int8alias1cmp (int8, int8alias1);
+-- int8alias2 binary-coercible to int8. so this is ok
+select cast('1'::int8 as int8alias2 default null on conversion error);
+ int8alias2
+------------
+ 1
+(1 row)
+
create table ec0 (ff int8 primary key, f1 int8, f2 int8);
create table ec1 (ff int8 primary key, f1 int8alias1, f2 int8alias2);
create table ec2 (xf int8 primary key, x1 int8alias1, x2 int8alias2);
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index cc6d799bcea..0b031a37c36 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -81,7 +81,7 @@ test: create_table_like alter_generic alter_operator misc async dbsize merge mis
# collate.linux.utf8 and collate.icu.utf8 tests cannot be run in parallel with each other
# psql depends on create_am
# amutils depends on geometry, create_index_spgist, hash_index, brin
-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 00000000000..9b6f3cc0649
--- /dev/null
+++ b/src/test/regress/sql/cast.sql
@@ -0,0 +1,350 @@
+SET extra_float_digits = 0;
+
+-- CAST DEFAULT ON CONVERSION ERROR
+SELECT CAST(B'01' AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(BIT'01' AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(TRUE AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(1.1 AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(1 AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(1111 AS "char" DEFAULT 'A' ON CONVERSION ERROR);
+
+--test source expression is a unknown const
+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('a' as int DEFAULT 18 ON CONVERSION 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 (SELECT NULL) ON CONVERSION ERROR); --error
+SELECT CAST('a' as int DEFAULT 'b' ON CONVERSION ERROR); --error
+SELECT CAST('a'::int as int DEFAULT NULL ON CONVERSION ERROR); --error
+
+--the default expression’s collation should match the collation of the cast
+--target type
+VALUES (CAST('error' AS text DEFAULT '1' COLLATE "C" ON CONVERSION ERROR));
+
+VALUES (CAST('error' AS int2vector DEFAULT '1 3' ON CONVERSION ERROR));
+VALUES (CAST('error' AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR));
+
+-- test source expression contain subquery
+SELECT CAST((SELECT b FROM generate_series(1, 1), (VALUES('H')) s(b))
+ AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR);
+SELECT CAST((SELECT ARRAY((SELECT b FROM generate_series(1, 2) g, (VALUES('H')) s(b))))
+ AS INT[]
+ DEFAULT NULL ON CONVERSION ERROR);
+
+CREATE FUNCTION ret_int8() RETURNS BIGINT AS
+$$
+BEGIN RETURN 2147483648; END;
+$$
+LANGUAGE plpgsql IMMUTABLE;
+
+SELECT CAST('a' as int DEFAULT ret_int8() ON CONVERSION ERROR); --error
+SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERROR); --error
+
+-- DEFAULT expression can not be set-returning
+CREATE 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);
+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(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 user-defined type, domain, array over domain, domain over array
+CREATE DOMAIN d_int42 as int check (value = 42) NOT NULL;
+CREATE DOMAIN d_char3_not_null as char(3) NOT NULL;
+CREATE DOMAIN d_varchar as varchar(3) NOT NULL;
+CREATE DOMAIN d_int_arr as int[] check (value = '{41, 43}') NOT NULL;
+CREATE TYPE comp_domain_with_typmod AS (a d_char3_not_null, b int);
+CREATE TYPE comp2 AS (a d_varchar);
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION ERROR); --error
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(123)' ON CONVERSION ERROR);
+SELECT CAST(11 AS d_int42 DEFAULT 41 ON CONVERSION ERROR); --error
+SELECT CAST(11 AS d_int42 DEFAULT 42 ON CONVERSION ERROR);
+SELECT CAST(NULL AS d_int42 DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(NULL AS d_int42 DEFAULT 42 ON CONVERSION ERROR);
+SELECT CAST('(,42)' AS comp_domain_with_typmod DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1,2)' ON CONVERSION ERROR);
+SELECT CAST('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1234,2)' ON CONVERSION ERROR); --error
+
+SELECT CAST(ARRAY[42,41] AS d_int42[] DEFAULT '{42, 42}' ON CONVERSION ERROR);
+SELECT CAST(ARRAY[42,41] AS d_int_arr DEFAULT '{41, 43}' ON CONVERSION ERROR);
+
+-- test array coerce
+SELECT CAST(array['a'::text] AS int[] DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(array['a'] AS int[] DEFAULT ARRAY[1] ON CONVERSION ERROR);
+SELECT CAST(array[11.12] AS date[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(array[11111111111111111] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(array[['abc'],[456]] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('{123,abc,456}' AS int[] 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 int[] DEFAULT '{1,2}' ON CONVERSION ERROR);
+SELECT CAST(ARRAY[['1', '2'], ['three', 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --ok
+SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --error
+SELECT CAST(ARRAY[1, 'three'::int] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --error
+
+-----safe cast with geometry data type
+SELECT CAST('(1,2)'::point AS box DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[(NaN,1),(NaN,infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[(1e+300,Infinity),(1e+300,Infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[(1,2),(3,4)]'::path as polygon DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NaN,1.0,NaN,infinity)'::box AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS lseg DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS polygon DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS path DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,infinity,NaN,infinity)'::box AS circle DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NaN,0.0),(2.0,4.0),(0.0,infinity)'::polygon AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS path DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS box DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NaN,infinity),(2.0,4.0),(0.0,infinity)'::polygon AS circle DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('<(5,1),3>'::circle AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('<(3,5),0>'::circle as box DEFAULT NULL ON CONVERSION ERROR);
+
+-- not supported because the cast from circle to polygon is implemented as a SQL
+-- function, which cannot be error-safe.
+SELECT CAST('<(3,5),0>'::circle as polygon DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast from/to money type is not supported, all the following would result error
+SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::money[] AS numeric[] DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast from bytea type to other data types
+SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT 19 ON CONVERSION ERROR);
+SELECT CAST('\x123456789A'::bytea AS int4 DEFAULT 20 ON CONVERSION ERROR);
+SELECT CAST('\x123456'::bytea AS int2 DEFAULT 21 ON CONVERSION ERROR);
+
+-----safe cast from bit type to other data types
+SELECT CAST('111111111100001'::bit(100) AS INT DEFAULT 22 ON CONVERSION ERROR);
+SELECT CAST ('111111111100001'::bit(100) AS INT8 DEFAULT 23 ON CONVERSION ERROR);
+
+-----safe cast from text type to other data types
+select CAST('a.b.c.d'::text as regclass default NULL on conversion error);
+
+CREATE TABLE test_safecast(col0 text);
+INSERT INTO test_safecast(col0) VALUES ('<value>one</value');
+
+SELECT col0 as text,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) as to_regclass,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) IS NULL as expect_true,
+ CAST(col0 AS "char" DEFAULT NULL ON CONVERSION ERROR) as to_char,
+ CAST(col0 AS name DEFAULT NULL ON CONVERSION ERROR) as to_name,
+ CAST(col0 AS xml DEFAULT NULL ON CONVERSION ERROR) as to_xml
+FROM test_safecast;
+
+SELECT CAST('192.168.1.x' as inet DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('192.168.1.2/30' as cidr DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('22:00:5c:08:55:08:01:02'::macaddr8 as macaddr DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast betweeen range data types
+SELECT CAST('[1,2]'::int4range AS int4multirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[1,2]'::int8range AS int8multirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[1,2]'::numrange AS nummultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::daterange AS datemultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::tsrange AS tsmultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::tstzrange AS tstzmultirange DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast betweeen numeric data types
+CREATE TABLE test_safecast1(
+ col1 float4, col2 float8, col3 numeric, col4 numeric[],
+ col5 int2 default 32767,
+ col6 int4 default 32768,
+ col7 int8 default 4294967296);
+INSERT INTO test_safecast1 VALUES('11.1234', '11.1234', '9223372036854775808', '{11.1234}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('inf', 'inf', 'inf', '{11.1234, 12, inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('-inf', '-inf', '-inf', '{11.1234, 12, -inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('NaN', 'NaN', 'NaN', '{11.1234, 12, -inf, NaN}'::numeric[]);
+
+SELECT col5 as int2,
+ CAST(col5 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col5 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col5 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col5 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col5 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col5 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col5 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col5 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col6 as int4,
+ CAST(col6 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col6 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col6 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col6 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col6 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col6 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col6 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col6 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col7 as int8,
+ CAST(col7 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col7 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col7 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col7 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col7 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col7 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col7 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col7 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col3 as numeric,
+ CAST(col3 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col3 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col3 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col3 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col3 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col3 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col3 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col3 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col4 as num_arr,
+ CAST(col4 AS int2[] DEFAULT NULL ON CONVERSION ERROR) as int2arr,
+ CAST(col4 AS int4[] DEFAULT NULL ON CONVERSION ERROR) as int4arr,
+ CAST(col4 as int8[] DEFAULT NULL ON CONVERSION ERROR) as int8arr,
+ CAST(col4 as float4[] DEFAULT NULL ON CONVERSION ERROR) as f4arr,
+ CAST(col4 as float8[] DEFAULT NULL ON CONVERSION ERROR) as f8arr,
+ CAST(col4 as numeric(10,1)[] DEFAULT NULL ON CONVERSION ERROR) as numarr
+FROM test_safecast1;
+
+SELECT col1 as float4,
+ CAST(col1 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col1 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col1 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col1 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col1 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col1 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col1 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col1 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col2 as float8,
+ CAST(col2 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col2 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col2 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col2 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col2 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col2 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col2 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col2 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+-- date/timestamp/interval related safe type cast
+CREATE TABLE test_safecast2(
+ col0 date, col1 timestamp, col2 timestamptz,
+ col3 interval, col4 time, col5 timetz);
+INSERT INTO test_safecast2 VALUES
+('-infinity', '-infinity', '-infinity', '-infinity',
+ '2003-03-07 15:36:39 America/New_York', '2003-07-07 15:36:39 America/New_York'),
+('-infinity', 'infinity', 'infinity', 'infinity',
+ '11:59:59.99 PM', '11:59:59.99 PM PDT');
+
+SELECT col0 as date,
+ CAST(col0 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col0 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col0 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col0 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col0 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+
+SELECT col1 as timestamp,
+ CAST(col1 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col1 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col1 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col1 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col1 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+
+SELECT col2 as timestamptz,
+ CAST(col2 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col2 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col2 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col2 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col2 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+
+SELECT col3 as interval,
+ CAST(col3 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col3 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col3 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col3 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col3 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale,
+ CAST(col3 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+
+SELECT col4 as time,
+ CAST(col4 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col4 AS timetz DEFAULT NULL ON CONVERSION ERROR) IS NOT NULL as to_timetz,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+
+SELECT col5 as timetz,
+ CAST(col5 AS time DEFAULT NULL ON CONVERSION ERROR) as to_time,
+ CAST(col5 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_time_scale,
+ CAST(col5 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col5 AS timetz(6) DEFAULT NULL ON CONVERSION ERROR) as to_timetz_scale,
+ CAST(col5 AS interval DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col5 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+
+CREATE TABLE test_safecast3(col0 jsonb);
+INSERT INTO test_safecast3(col0) VALUES ('"test"');
+SELECT col0 as jsonb,
+ CAST(col0 AS integer DEFAULT NULL ON CONVERSION ERROR) as to_integer,
+ CAST(col0 AS numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col0 AS bigint DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col0 AS float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col0 AS float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col0 AS boolean DEFAULT NULL ON CONVERSION ERROR) as to_bool,
+ CAST(col0 AS smallint DEFAULT NULL ON CONVERSION ERROR) as to_smallint
+FROM test_safecast3;
+
+-- test deparse
+SET datestyle TO ISO, YMD;
+CREATE VIEW safecastview AS
+SELECT CAST('1234' as char(3) DEFAULT -1111 ON CONVERSION ERROR),
+ CAST(1 as date DEFAULT (('2025-Dec-06'::date + random(min=>1, max=>1::int))) ON CONVERSION ERROR) as cast0,
+ CAST(ARRAY[['1'], ['three'],['a']] AS int[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1,
+ CAST(ARRAY[['1', '2'], ['three', 'a']] AS date[] DEFAULT NULL ON CONVERSION ERROR) as cast2,
+ CAST(ARRAY['three'] AS INT[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast3;
+\sv safecastview
+SELECT * FROM safecastview;
+
+CREATE VIEW safecastview1 AS
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS d_int_arr
+ DEFAULT '{41,43}' ON CONVERSION ERROR) as cast1,
+ CAST(ARRAY[['1', '2', 1.1], ['three', true, B'01']] AS d_int_arr
+ DEFAULT '{41,43}' ON CONVERSION ERROR) as cast2;
+\sv safecastview1
+SELECT * FROM safecastview1;
+
+RESET datestyle;
+
+--test CAST DEFAULT expression mutability
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as int DEFAULT random(min=>1, max=>1) ON CONVERSION ERROR)));
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as xid DEFAULT NULL ON CONVERSION ERROR)));
+CREATE INDEX test_safecast3_idx ON test_safecast3((CAST(col0 as int DEFAULT NULL ON CONVERSION ERROR))); --ok
+SELECT pg_get_indexdef('test_safecast3_idx'::regclass);
+
+DROP TABLE test_safecast;
+DROP TABLE test_safecast1;
+DROP TABLE test_safecast2;
+DROP TABLE test_safecast3;
+DROP TABLE tcast;
+DROP FUNCTION ret_int8;
+DROP FUNCTION ret_setint;
+DROP TYPE comp_domain_with_typmod;
+DROP TYPE comp2;
+DROP DOMAIN d_varchar;
+DROP DOMAIN d_int42;
+DROP DOMAIN d_char3_not_null;
+RESET extra_float_digits;
diff --git a/src/test/regress/sql/create_cast.sql b/src/test/regress/sql/create_cast.sql
index 32187853cc7..0a15a795d87 100644
--- a/src/test/regress/sql/create_cast.sql
+++ b/src/test/regress/sql/create_cast.sql
@@ -62,6 +62,7 @@ $$ SELECT ('bar'::text || $1::text); $$;
CREATE CAST (int4 AS casttesttype) WITH FUNCTION bar_int4_text(int4) AS IMPLICIT;
SELECT 1234::int4::casttesttype; -- Should work now
+SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- error
-- check dependencies generated for that
SELECT pg_describe_object(classid, objid, objsubid) as obj,
diff --git a/src/test/regress/sql/equivclass.sql b/src/test/regress/sql/equivclass.sql
index 7fc2159349b..5ad1d26311d 100644
--- a/src/test/regress/sql/equivclass.sql
+++ b/src/test/regress/sql/equivclass.sql
@@ -98,6 +98,9 @@ create function int8alias1cmp(int8, int8alias1) returns int
alter operator family integer_ops using btree add
function 1 int8alias1cmp (int8, int8alias1);
+-- int8alias2 binary-coercible to int8. so this is ok
+select cast('1'::int8 as int8alias2 default null on conversion error);
+
create table ec0 (ff int8 primary key, f1 int8, f2 int8);
create table ec1 (ff int8 primary key, f1 int8alias1, f2 int8alias2);
create table ec2 (xf int8 primary key, x1 int8alias1, x2 int8alias2);
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 9dd65b10254..44bba2e819f 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2675,6 +2675,9 @@ STRLEN
SV
SYNCHRONIZATION_BARRIER
SYSTEM_INFO
+SafeTypeCast
+SafeTypeCastExpr
+SafeTypeCastState
SampleScan
SampleScanGetSampleSize_function
SampleScanState
--
2.34.1
v16-0020-refactor-point_dt.patchapplication/x-patch; name=v16-0020-refactor-point_dt.patchDownload
From f4dc33307e4f187165df4566714e1ce140528d11 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Wed, 10 Dec 2025 12:00:54 +0800
Subject: [PATCH v16 20/23] refactor point_dt
point_dt used in many places, it will be used in later on patch, thus
refactoring make it error safe.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/geo_ops.c | 77 +++++++++++++++++++--------------
1 file changed, 44 insertions(+), 33 deletions(-)
diff --git a/src/backend/utils/adt/geo_ops.c b/src/backend/utils/adt/geo_ops.c
index 43b7eb43a79..5c3b9e2ed60 100644
--- a/src/backend/utils/adt/geo_ops.c
+++ b/src/backend/utils/adt/geo_ops.c
@@ -82,7 +82,7 @@ static inline void point_sub_point(Point *result, Point *pt1, Point *pt2);
static inline void point_mul_point(Point *result, Point *pt1, Point *pt2);
static inline void point_div_point(Point *result, Point *pt1, Point *pt2);
static inline bool point_eq_point(Point *pt1, Point *pt2);
-static inline float8 point_dt(Point *pt1, Point *pt2);
+static inline float8 point_dt(Point *pt1, Point *pt2, Node *escontext);
static inline float8 point_sl(Point *pt1, Point *pt2);
static int point_inside(Point *p, int npts, Point *plist);
@@ -839,7 +839,7 @@ box_distance(PG_FUNCTION_ARGS)
box_cn(&a, box1);
box_cn(&b, box2);
- PG_RETURN_FLOAT8(point_dt(&a, &b));
+ PG_RETURN_FLOAT8(point_dt(&a, &b, NULL));
}
@@ -1808,7 +1808,7 @@ path_length(PG_FUNCTION_ARGS)
iprev = path->npts - 1; /* include the closure segment */
}
- result = float8_pl(result, point_dt(&path->p[iprev], &path->p[i]));
+ result = float8_pl(result, point_dt(&path->p[iprev], &path->p[i], NULL));
}
PG_RETURN_FLOAT8(result);
@@ -1995,13 +1995,24 @@ point_distance(PG_FUNCTION_ARGS)
Point *pt1 = PG_GETARG_POINT_P(0);
Point *pt2 = PG_GETARG_POINT_P(1);
- PG_RETURN_FLOAT8(point_dt(pt1, pt2));
+ PG_RETURN_FLOAT8(point_dt(pt1, pt2, NULL));
}
static inline float8
-point_dt(Point *pt1, Point *pt2)
+point_dt(Point *pt1, Point *pt2, Node *escontext)
{
- return hypot(float8_mi(pt1->x, pt2->x), float8_mi(pt1->y, pt2->y));
+ float8 x;
+ float8 y;
+
+ x = float8_mi_safe(pt1->x, pt2->x, escontext);
+ if (unlikely(SOFT_ERROR_OCCURRED(escontext)))
+ return 0.0;
+
+ y = float8_mi_safe(pt1->y, pt2->y, escontext);
+ if (unlikely(SOFT_ERROR_OCCURRED(escontext)))
+ return 0.0;
+
+ return hypot(x, y);
}
Datum
@@ -2173,7 +2184,7 @@ lseg_length(PG_FUNCTION_ARGS)
{
LSEG *lseg = PG_GETARG_LSEG_P(0);
- PG_RETURN_FLOAT8(point_dt(&lseg->p[0], &lseg->p[1]));
+ PG_RETURN_FLOAT8(point_dt(&lseg->p[0], &lseg->p[1], NULL));
}
/*----------------------------------------------------------
@@ -2258,8 +2269,8 @@ lseg_lt(PG_FUNCTION_ARGS)
LSEG *l1 = PG_GETARG_LSEG_P(0);
LSEG *l2 = PG_GETARG_LSEG_P(1);
- PG_RETURN_BOOL(FPlt(point_dt(&l1->p[0], &l1->p[1]),
- point_dt(&l2->p[0], &l2->p[1])));
+ PG_RETURN_BOOL(FPlt(point_dt(&l1->p[0], &l1->p[1], NULL),
+ point_dt(&l2->p[0], &l2->p[1], NULL)));
}
Datum
@@ -2268,8 +2279,8 @@ lseg_le(PG_FUNCTION_ARGS)
LSEG *l1 = PG_GETARG_LSEG_P(0);
LSEG *l2 = PG_GETARG_LSEG_P(1);
- PG_RETURN_BOOL(FPle(point_dt(&l1->p[0], &l1->p[1]),
- point_dt(&l2->p[0], &l2->p[1])));
+ PG_RETURN_BOOL(FPle(point_dt(&l1->p[0], &l1->p[1], NULL),
+ point_dt(&l2->p[0], &l2->p[1], NULL)));
}
Datum
@@ -2278,8 +2289,8 @@ lseg_gt(PG_FUNCTION_ARGS)
LSEG *l1 = PG_GETARG_LSEG_P(0);
LSEG *l2 = PG_GETARG_LSEG_P(1);
- PG_RETURN_BOOL(FPgt(point_dt(&l1->p[0], &l1->p[1]),
- point_dt(&l2->p[0], &l2->p[1])));
+ PG_RETURN_BOOL(FPgt(point_dt(&l1->p[0], &l1->p[1], NULL),
+ point_dt(&l2->p[0], &l2->p[1], NULL)));
}
Datum
@@ -2288,8 +2299,8 @@ lseg_ge(PG_FUNCTION_ARGS)
LSEG *l1 = PG_GETARG_LSEG_P(0);
LSEG *l2 = PG_GETARG_LSEG_P(1);
- PG_RETURN_BOOL(FPge(point_dt(&l1->p[0], &l1->p[1]),
- point_dt(&l2->p[0], &l2->p[1])));
+ PG_RETURN_BOOL(FPge(point_dt(&l1->p[0], &l1->p[1], NULL),
+ point_dt(&l2->p[0], &l2->p[1], NULL)));
}
@@ -2743,7 +2754,7 @@ line_closept_point(Point *result, LINE *line, Point *point)
if (result != NULL)
*result = closept;
- return point_dt(&closept, point);
+ return point_dt(&closept, point, NULL);
}
Datum
@@ -2784,7 +2795,7 @@ lseg_closept_point(Point *result, LSEG *lseg, Point *pt)
if (result != NULL)
*result = closept;
- return point_dt(&closept, pt);
+ return point_dt(&closept, pt, NULL);
}
Datum
@@ -3108,9 +3119,9 @@ on_pl(PG_FUNCTION_ARGS)
static bool
lseg_contain_point(LSEG *lseg, Point *pt)
{
- return FPeq(point_dt(pt, &lseg->p[0]) +
- point_dt(pt, &lseg->p[1]),
- point_dt(&lseg->p[0], &lseg->p[1]));
+ return FPeq(point_dt(pt, &lseg->p[0], NULL) +
+ point_dt(pt, &lseg->p[1], NULL),
+ point_dt(&lseg->p[0], &lseg->p[1], NULL));
}
Datum
@@ -3176,11 +3187,11 @@ on_ppath(PG_FUNCTION_ARGS)
if (!path->closed)
{
n = path->npts - 1;
- a = point_dt(pt, &path->p[0]);
+ a = point_dt(pt, &path->p[0], NULL);
for (i = 0; i < n; i++)
{
- b = point_dt(pt, &path->p[i + 1]);
- if (FPeq(float8_pl(a, b), point_dt(&path->p[i], &path->p[i + 1])))
+ b = point_dt(pt, &path->p[i + 1], NULL);
+ if (FPeq(float8_pl(a, b), point_dt(&path->p[i], &path->p[i + 1], NULL)))
PG_RETURN_BOOL(true);
a = b;
}
@@ -4766,7 +4777,7 @@ circle_overlap(PG_FUNCTION_ARGS)
CIRCLE *circle1 = PG_GETARG_CIRCLE_P(0);
CIRCLE *circle2 = PG_GETARG_CIRCLE_P(1);
- PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center),
+ PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center, NULL),
float8_pl(circle1->radius, circle2->radius)));
}
@@ -4828,7 +4839,7 @@ circle_contained(PG_FUNCTION_ARGS)
CIRCLE *circle1 = PG_GETARG_CIRCLE_P(0);
CIRCLE *circle2 = PG_GETARG_CIRCLE_P(1);
- PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center),
+ PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center, NULL),
float8_mi(circle2->radius, circle1->radius)));
}
@@ -4840,7 +4851,7 @@ circle_contain(PG_FUNCTION_ARGS)
CIRCLE *circle1 = PG_GETARG_CIRCLE_P(0);
CIRCLE *circle2 = PG_GETARG_CIRCLE_P(1);
- PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center),
+ PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center, NULL),
float8_mi(circle1->radius, circle2->radius)));
}
@@ -5069,7 +5080,7 @@ circle_distance(PG_FUNCTION_ARGS)
CIRCLE *circle2 = PG_GETARG_CIRCLE_P(1);
float8 result;
- result = float8_mi(point_dt(&circle1->center, &circle2->center),
+ result = float8_mi(point_dt(&circle1->center, &circle2->center, NULL),
float8_pl(circle1->radius, circle2->radius));
if (result < 0.0)
result = 0.0;
@@ -5085,7 +5096,7 @@ circle_contain_pt(PG_FUNCTION_ARGS)
Point *point = PG_GETARG_POINT_P(1);
float8 d;
- d = point_dt(&circle->center, point);
+ d = point_dt(&circle->center, point, NULL);
PG_RETURN_BOOL(d <= circle->radius);
}
@@ -5097,7 +5108,7 @@ pt_contained_circle(PG_FUNCTION_ARGS)
CIRCLE *circle = PG_GETARG_CIRCLE_P(1);
float8 d;
- d = point_dt(&circle->center, point);
+ d = point_dt(&circle->center, point, NULL);
PG_RETURN_BOOL(d <= circle->radius);
}
@@ -5112,7 +5123,7 @@ dist_pc(PG_FUNCTION_ARGS)
CIRCLE *circle = PG_GETARG_CIRCLE_P(1);
float8 result;
- result = float8_mi(point_dt(point, &circle->center),
+ result = float8_mi(point_dt(point, &circle->center, NULL),
circle->radius);
if (result < 0.0)
result = 0.0;
@@ -5130,7 +5141,7 @@ dist_cpoint(PG_FUNCTION_ARGS)
Point *point = PG_GETARG_POINT_P(1);
float8 result;
- result = float8_mi(point_dt(point, &circle->center), circle->radius);
+ result = float8_mi(point_dt(point, &circle->center, NULL), circle->radius);
if (result < 0.0)
result = 0.0;
@@ -5215,7 +5226,7 @@ box_circle(PG_FUNCTION_ARGS)
circle->center.x = float8_div(float8_pl(box->high.x, box->low.x), 2.0);
circle->center.y = float8_div(float8_pl(box->high.y, box->low.y), 2.0);
- circle->radius = point_dt(&circle->center, &box->high);
+ circle->radius = point_dt(&circle->center, &box->high, NULL);
PG_RETURN_CIRCLE_P(circle);
}
@@ -5299,7 +5310,7 @@ poly_to_circle(CIRCLE *result, POLYGON *poly)
for (i = 0; i < poly->npts; i++)
result->radius = float8_pl(result->radius,
- point_dt(&poly->p[i], &result->center));
+ point_dt(&poly->p[i], &result->center, NULL));
result->radius = float8_div(result->radius, poly->npts);
}
--
2.34.1
v16-0019-introduce-float8-safe-function.patchapplication/x-patch; name=v16-0019-introduce-float8-safe-function.patchDownload
From b78c7dbd4a30f9f4edfef384c02867ad3b91475c Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Wed, 10 Dec 2025 11:55:40 +0800
Subject: [PATCH v16 19/23] introduce float8 safe function
this patch introduce the following function:
float8_pl_safe
float8_mi_safe
float8_mul_safe
float8_div_safe
refactoring existing function is be too invasive. thus add these new functions.
It's close to existing non-safe version functions.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/include/utils/float.h | 79 ++++++++++++++++++++++++++++-----------
1 file changed, 58 insertions(+), 21 deletions(-)
diff --git a/src/include/utils/float.h b/src/include/utils/float.h
index 1d0cb026d4e..f46861ab5c3 100644
--- a/src/include/utils/float.h
+++ b/src/include/utils/float.h
@@ -109,16 +109,24 @@ float4_pl(const float4 val1, const float4 val2)
return result;
}
+static inline float8
+float8_pl_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+ float8 result;
+
+ result = val1 + val2;
+ if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
+ {
+ float_overflow_error(escontext);
+ return 0.0;
+ }
+ return result;
+}
+
static inline float8
float8_pl(const float8 val1, const float8 val2)
{
- float8 result;
-
- result = val1 + val2;
- if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error(NULL);
-
- return result;
+ return float8_pl_safe(val1, val2, NULL);;
}
static inline float4
@@ -133,16 +141,24 @@ float4_mi(const float4 val1, const float4 val2)
return result;
}
+static inline float8
+float8_mi_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+ float8 result;
+
+ result = val1 - val2;
+ if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
+ {
+ float_overflow_error(escontext);
+ return 0.0;
+ }
+ return result;
+}
+
static inline float8
float8_mi(const float8 val1, const float8 val2)
{
- float8 result;
-
- result = val1 - val2;
- if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error(NULL);
-
- return result;
+ return float8_mi_safe(val1, val2, NULL);
}
static inline float4
@@ -160,19 +176,33 @@ float4_mul(const float4 val1, const float4 val2)
}
static inline float8
-float8_mul(const float8 val1, const float8 val2)
+float8_mul_safe(const float8 val1, const float8 val2, struct Node *escontext)
{
float8 result;
result = val1 * val2;
if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error(NULL);
+ {
+ float_overflow_error(escontext);
+ return 0.0;
+ }
+
if (unlikely(result == 0.0) && val1 != 0.0 && val2 != 0.0)
- float_underflow_error(NULL);
+ {
+ float_underflow_error(escontext);
+ return 0.0;
+ }
return result;
}
+static inline float8
+float8_mul(const float8 val1, const float8 val2)
+{
+ return float8_mul_safe(val1, val2, NULL);
+}
+
+
static inline float4
float4_div(const float4 val1, const float4 val2)
{
@@ -190,21 +220,28 @@ float4_div(const float4 val1, const float4 val2)
}
static inline float8
-float8_div(const float8 val1, const float8 val2)
+float8_div_safe(const float8 val1, const float8 val2, struct Node *escontext)
{
float8 result;
if (unlikely(val2 == 0.0) && !isnan(val1))
- float_zero_divide_error(NULL);
+ float_zero_divide_error(escontext);
result = val1 / val2;
if (unlikely(isinf(result)) && !isinf(val1))
- float_overflow_error(NULL);
+ float_overflow_error(escontext);
if (unlikely(result == 0.0) && val1 != 0.0 && !isinf(val2))
- float_underflow_error(NULL);
+ float_underflow_error(escontext);
return result;
}
+static inline float8
+float8_div(const float8 val1, const float8 val2)
+{
+ return float8_div_safe(val1, val2, NULL);
+}
+
+
/*
* Routines for NaN-aware comparisons
*
--
2.34.1
v16-0018-refactor-float_overflow_error-float_underflow_error-float_zero_d.patchapplication/x-patch; name=v16-0018-refactor-float_overflow_error-float_underflow_error-float_zero_d.patchDownload
From c968d0c06ec8aefb8791d93739d3b22bf9f803a1 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Tue, 9 Dec 2025 14:36:39 +0800
Subject: [PATCH v16 18/23] refactor
float_overflow_error,float_underflow_error,float_zero_divide_error
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
contrib/btree_gist/btree_float4.c | 2 +-
contrib/btree_gist/btree_float8.c | 4 +-
src/backend/utils/adt/float.c | 104 +++++++++++++++---------------
src/include/utils/float.h | 34 +++++-----
4 files changed, 72 insertions(+), 72 deletions(-)
diff --git a/contrib/btree_gist/btree_float4.c b/contrib/btree_gist/btree_float4.c
index d9c859835da..a7325a7bb29 100644
--- a/contrib/btree_gist/btree_float4.c
+++ b/contrib/btree_gist/btree_float4.c
@@ -101,7 +101,7 @@ float4_dist(PG_FUNCTION_ARGS)
r = a - b;
if (unlikely(isinf(r)) && !isinf(a) && !isinf(b))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT4(fabsf(r));
}
diff --git a/contrib/btree_gist/btree_float8.c b/contrib/btree_gist/btree_float8.c
index 567beede178..7c99b84de35 100644
--- a/contrib/btree_gist/btree_float8.c
+++ b/contrib/btree_gist/btree_float8.c
@@ -79,7 +79,7 @@ gbt_float8_dist(const void *a, const void *b, FmgrInfo *flinfo)
r = arg1 - arg2;
if (unlikely(isinf(r)) && !isinf(arg1) && !isinf(arg2))
- float_overflow_error();
+ float_overflow_error(NULL);
return fabs(r);
}
@@ -109,7 +109,7 @@ float8_dist(PG_FUNCTION_ARGS)
r = a - b;
if (unlikely(isinf(r)) && !isinf(a) && !isinf(b))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(fabs(r));
}
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index 55c7030ba81..2710e42f5bb 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -83,25 +83,25 @@ static void init_degree_constants(void);
* This does mean that you don't get a useful error location indicator.
*/
pg_noinline void
-float_overflow_error(void)
+float_overflow_error(struct Node *escontext)
{
- ereport(ERROR,
+ errsave(escontext,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("value out of range: overflow")));
}
pg_noinline void
-float_underflow_error(void)
+float_underflow_error(struct Node *escontext)
{
- ereport(ERROR,
+ errsave(escontext,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("value out of range: underflow")));
}
pg_noinline void
-float_zero_divide_error(void)
+float_zero_divide_error(struct Node *escontext)
{
- ereport(ERROR,
+ errsave(escontext,
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
}
@@ -1460,9 +1460,9 @@ dsqrt(PG_FUNCTION_ARGS)
result = sqrt(arg1);
if (unlikely(isinf(result)) && !isinf(arg1))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0) && arg1 != 0.0)
- float_underflow_error();
+ float_underflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1479,9 +1479,9 @@ dcbrt(PG_FUNCTION_ARGS)
result = cbrt(arg1);
if (unlikely(isinf(result)) && !isinf(arg1))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0) && arg1 != 0.0)
- float_underflow_error();
+ float_underflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1617,24 +1617,24 @@ dpow(PG_FUNCTION_ARGS)
if (absx == 1.0)
result = 1.0;
else if (arg2 >= 0.0 ? (absx > 1.0) : (absx < 1.0))
- float_overflow_error();
+ float_overflow_error(NULL);
else
- float_underflow_error();
+ float_underflow_error(NULL);
}
}
else if (errno == ERANGE)
{
if (result != 0.0)
- float_overflow_error();
+ float_overflow_error(NULL);
else
- float_underflow_error();
+ float_underflow_error(NULL);
}
else
{
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0) && arg1 != 0.0)
- float_underflow_error();
+ float_underflow_error(NULL);
}
}
@@ -1674,14 +1674,14 @@ dexp(PG_FUNCTION_ARGS)
if (unlikely(errno == ERANGE))
{
if (result != 0.0)
- float_overflow_error();
+ float_overflow_error(NULL);
else
- float_underflow_error();
+ float_underflow_error(NULL);
}
else if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
else if (unlikely(result == 0.0))
- float_underflow_error();
+ float_underflow_error(NULL);
}
PG_RETURN_FLOAT8(result);
@@ -1712,9 +1712,9 @@ dlog1(PG_FUNCTION_ARGS)
result = log(arg1);
if (unlikely(isinf(result)) && !isinf(arg1))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0) && arg1 != 1.0)
- float_underflow_error();
+ float_underflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1745,9 +1745,9 @@ dlog10(PG_FUNCTION_ARGS)
result = log10(arg1);
if (unlikely(isinf(result)) && !isinf(arg1))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0) && arg1 != 1.0)
- float_underflow_error();
+ float_underflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1778,7 +1778,7 @@ dacos(PG_FUNCTION_ARGS)
result = acos(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1809,7 +1809,7 @@ dasin(PG_FUNCTION_ARGS)
result = asin(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1835,7 +1835,7 @@ datan(PG_FUNCTION_ARGS)
*/
result = atan(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1861,7 +1861,7 @@ datan2(PG_FUNCTION_ARGS)
*/
result = atan2(arg1, arg2);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1902,7 +1902,7 @@ dcos(PG_FUNCTION_ARGS)
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("input is out of range")));
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1957,7 +1957,7 @@ dsin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("input is out of range")));
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2137,7 +2137,7 @@ dacosd(PG_FUNCTION_ARGS)
result = 90.0 + asind_q1(-arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2174,7 +2174,7 @@ dasind(PG_FUNCTION_ARGS)
result = -asind_q1(-arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2206,7 +2206,7 @@ datand(PG_FUNCTION_ARGS)
result = (atan_arg1 / atan_1_0) * 45.0;
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2242,7 +2242,7 @@ datan2d(PG_FUNCTION_ARGS)
result = (atan2_arg1_arg2 / atan_1_0) * 45.0;
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2365,7 +2365,7 @@ dcosd(PG_FUNCTION_ARGS)
result = sign * cosd_q1(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2487,7 +2487,7 @@ dsind(PG_FUNCTION_ARGS)
result = sign * sind_q1(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2645,7 +2645,7 @@ dcosh(PG_FUNCTION_ARGS)
result = get_float8_infinity();
if (unlikely(result == 0.0))
- float_underflow_error();
+ float_underflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2665,7 +2665,7 @@ dtanh(PG_FUNCTION_ARGS)
result = tanh(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2765,7 +2765,7 @@ derf(PG_FUNCTION_ARGS)
result = erf(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2785,7 +2785,7 @@ derfc(PG_FUNCTION_ARGS)
result = erfc(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2814,7 +2814,7 @@ dgamma(PG_FUNCTION_ARGS)
/* Per POSIX, an input of -Inf causes a domain error */
if (arg1 < 0)
{
- float_overflow_error();
+ float_overflow_error(NULL);
result = get_float8_nan(); /* keep compiler quiet */
}
else
@@ -2836,12 +2836,12 @@ dgamma(PG_FUNCTION_ARGS)
if (errno != 0 || isinf(result) || isnan(result))
{
if (result != 0.0)
- float_overflow_error();
+ float_overflow_error(NULL);
else
- float_underflow_error();
+ float_underflow_error(NULL);
}
else if (result == 0.0)
- float_underflow_error();
+ float_underflow_error(NULL);
}
PG_RETURN_FLOAT8(result);
@@ -2873,7 +2873,7 @@ dlgamma(PG_FUNCTION_ARGS)
* to report overflow, but it should never underflow.
*/
if (errno == ERANGE || (isinf(result) && !isinf(arg1)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -3013,7 +3013,7 @@ float8_combine(PG_FUNCTION_ARGS)
tmp = Sx1 / N1 - Sx2 / N2;
Sxx = Sxx1 + Sxx2 + N1 * N2 * tmp * tmp / N;
if (unlikely(isinf(Sxx)) && !isinf(Sxx1) && !isinf(Sxx2))
- float_overflow_error();
+ float_overflow_error(NULL);
}
/*
@@ -3080,7 +3080,7 @@ float8_accum(PG_FUNCTION_ARGS)
if (isinf(Sx) || isinf(Sxx))
{
if (!isinf(transvalues[1]) && !isinf(newval))
- float_overflow_error();
+ float_overflow_error(NULL);
Sxx = get_float8_nan();
}
@@ -3163,7 +3163,7 @@ float4_accum(PG_FUNCTION_ARGS)
if (isinf(Sx) || isinf(Sxx))
{
if (!isinf(transvalues[1]) && !isinf(newval))
- float_overflow_error();
+ float_overflow_error(NULL);
Sxx = get_float8_nan();
}
@@ -3430,7 +3430,7 @@ float8_regr_accum(PG_FUNCTION_ARGS)
(isinf(Sxy) &&
!isinf(transvalues[1]) && !isinf(newvalX) &&
!isinf(transvalues[3]) && !isinf(newvalY)))
- float_overflow_error();
+ float_overflow_error(NULL);
if (isinf(Sxx))
Sxx = get_float8_nan();
@@ -3603,15 +3603,15 @@ float8_regr_combine(PG_FUNCTION_ARGS)
tmp1 = Sx1 / N1 - Sx2 / N2;
Sxx = Sxx1 + Sxx2 + N1 * N2 * tmp1 * tmp1 / N;
if (unlikely(isinf(Sxx)) && !isinf(Sxx1) && !isinf(Sxx2))
- float_overflow_error();
+ float_overflow_error(NULL);
Sy = float8_pl(Sy1, Sy2);
tmp2 = Sy1 / N1 - Sy2 / N2;
Syy = Syy1 + Syy2 + N1 * N2 * tmp2 * tmp2 / N;
if (unlikely(isinf(Syy)) && !isinf(Syy1) && !isinf(Syy2))
- float_overflow_error();
+ float_overflow_error(NULL);
Sxy = Sxy1 + Sxy2 + N1 * N2 * tmp1 * tmp2 / N;
if (unlikely(isinf(Sxy)) && !isinf(Sxy1) && !isinf(Sxy2))
- float_overflow_error();
+ float_overflow_error(NULL);
if (float8_eq(Cx1, Cx2))
Cx = Cx1;
else
diff --git a/src/include/utils/float.h b/src/include/utils/float.h
index fc2a9cf6475..1d0cb026d4e 100644
--- a/src/include/utils/float.h
+++ b/src/include/utils/float.h
@@ -30,9 +30,9 @@ extern PGDLLIMPORT int extra_float_digits;
/*
* Utility functions in float.c
*/
-pg_noreturn extern void float_overflow_error(void);
-pg_noreturn extern void float_underflow_error(void);
-pg_noreturn extern void float_zero_divide_error(void);
+extern void float_overflow_error(struct Node *escontext);
+extern void float_underflow_error(struct Node *escontext);
+extern void float_zero_divide_error(struct Node *escontext);
extern int is_infinite(float8 val);
extern float8 float8in_internal(char *num, char **endptr_p,
const char *type_name, const char *orig_string,
@@ -104,7 +104,7 @@ float4_pl(const float4 val1, const float4 val2)
result = val1 + val2;
if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error();
+ float_overflow_error(NULL);
return result;
}
@@ -116,7 +116,7 @@ float8_pl(const float8 val1, const float8 val2)
result = val1 + val2;
if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error();
+ float_overflow_error(NULL);
return result;
}
@@ -128,7 +128,7 @@ float4_mi(const float4 val1, const float4 val2)
result = val1 - val2;
if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error();
+ float_overflow_error(NULL);
return result;
}
@@ -140,7 +140,7 @@ float8_mi(const float8 val1, const float8 val2)
result = val1 - val2;
if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error();
+ float_overflow_error(NULL);
return result;
}
@@ -152,9 +152,9 @@ float4_mul(const float4 val1, const float4 val2)
result = val1 * val2;
if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0f) && val1 != 0.0f && val2 != 0.0f)
- float_underflow_error();
+ float_underflow_error(NULL);
return result;
}
@@ -166,9 +166,9 @@ float8_mul(const float8 val1, const float8 val2)
result = val1 * val2;
if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0) && val1 != 0.0 && val2 != 0.0)
- float_underflow_error();
+ float_underflow_error(NULL);
return result;
}
@@ -179,12 +179,12 @@ float4_div(const float4 val1, const float4 val2)
float4 result;
if (unlikely(val2 == 0.0f) && !isnan(val1))
- float_zero_divide_error();
+ float_zero_divide_error(NULL);
result = val1 / val2;
if (unlikely(isinf(result)) && !isinf(val1))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0f) && val1 != 0.0f && !isinf(val2))
- float_underflow_error();
+ float_underflow_error(NULL);
return result;
}
@@ -195,12 +195,12 @@ float8_div(const float8 val1, const float8 val2)
float8 result;
if (unlikely(val2 == 0.0) && !isnan(val1))
- float_zero_divide_error();
+ float_zero_divide_error(NULL);
result = val1 / val2;
if (unlikely(isinf(result)) && !isinf(val1))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0) && val1 != 0.0 && !isinf(val2))
- float_underflow_error();
+ float_underflow_error(NULL);
return result;
}
--
2.34.1
v16-0017-error-safe-for-casting-jsonb-to-other-types-per-pg_cast.patchapplication/x-patch; name=v16-0017-error-safe-for-casting-jsonb-to-other-types-per-pg_cast.patchDownload
From 2dcc56b0e5ac12ae4b07d421c0ed8515568b06e6 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Mon, 24 Nov 2025 14:26:53 +0800
Subject: [PATCH v16 17/23] error safe for casting jsonb to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'jsonb'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+---------------+---------
jsonb | boolean | 3556 | e | f | jsonb_bool | bool
jsonb | numeric | 3449 | e | f | jsonb_numeric | numeric
jsonb | smallint | 3450 | e | f | jsonb_int2 | int2
jsonb | integer | 3451 | e | f | jsonb_int4 | int4
jsonb | bigint | 3452 | e | f | jsonb_int8 | int8
jsonb | real | 3453 | e | f | jsonb_float4 | float4
jsonb | double precision | 2580 | e | f | jsonb_float8 | float8
(7 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/jsonb.c | 74 +++++++++++++++++++++++++++--------
1 file changed, 58 insertions(+), 16 deletions(-)
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index c4fe6e00dcd..1600204fa4a 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -1815,7 +1815,7 @@ JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res)
* Emit correct, translatable cast error message
*/
static void
-cannotCastJsonbValue(enum jbvType type, const char *sqltype)
+cannotCastJsonbValue(enum jbvType type, const char *sqltype, Node *escontext)
{
static const struct
{
@@ -1836,7 +1836,7 @@ cannotCastJsonbValue(enum jbvType type, const char *sqltype)
for (i = 0; i < lengthof(messages); i++)
if (messages[i].type == type)
- ereport(ERROR,
+ ereturn(escontext,,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(messages[i].msg, sqltype)));
@@ -1851,7 +1851,10 @@ jsonb_bool(PG_FUNCTION_ARGS)
JsonbValue v;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "boolean");
+ {
+ cannotCastJsonbValue(v.type, "boolean", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -1860,7 +1863,10 @@ jsonb_bool(PG_FUNCTION_ARGS)
}
if (v.type != jbvBool)
- cannotCastJsonbValue(v.type, "boolean");
+ {
+ cannotCastJsonbValue(v.type, "boolean", fcinfo->context);
+ PG_RETURN_NULL();
+ }
PG_FREE_IF_COPY(in, 0);
@@ -1875,7 +1881,10 @@ jsonb_numeric(PG_FUNCTION_ARGS)
Numeric retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "numeric");
+ {
+ cannotCastJsonbValue(v.type, "numeric", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -1884,7 +1893,10 @@ jsonb_numeric(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "numeric");
+ {
+ cannotCastJsonbValue(v.type, "numeric", fcinfo->context);
+ PG_RETURN_NULL();
+ }
/*
* v.val.numeric points into jsonb body, so we need to make a copy to
@@ -1905,7 +1917,10 @@ jsonb_int2(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "smallint");
+ {
+ cannotCastJsonbValue(v.type, "smallint", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -1914,7 +1929,10 @@ jsonb_int2(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "smallint");
+ {
+ cannotCastJsonbValue(v.type, "smallint", fcinfo->context);
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_int2,
NumericGetDatum(v.val.numeric));
@@ -1932,7 +1950,10 @@ jsonb_int4(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "integer");
+ {
+ cannotCastJsonbValue(v.type, "integer", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -1941,7 +1962,10 @@ jsonb_int4(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "integer");
+ {
+ cannotCastJsonbValue(v.type, "integer", fcinfo->context);
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_int4,
NumericGetDatum(v.val.numeric));
@@ -1959,7 +1983,10 @@ jsonb_int8(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "bigint");
+ {
+ cannotCastJsonbValue(v.type, "bigint", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -1968,7 +1995,10 @@ jsonb_int8(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "bigint");
+ {
+ cannotCastJsonbValue(v.type, "bigint", fcinfo->context);
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_int8,
NumericGetDatum(v.val.numeric));
@@ -1986,7 +2016,10 @@ jsonb_float4(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "real");
+ {
+ cannotCastJsonbValue(v.type, "real", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -1995,7 +2028,10 @@ jsonb_float4(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "real");
+ {
+ cannotCastJsonbValue(v.type, "real", fcinfo->context);
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_float4,
NumericGetDatum(v.val.numeric));
@@ -2013,7 +2049,10 @@ jsonb_float8(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "double precision");
+ {
+ cannotCastJsonbValue(v.type, "double precision", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2022,7 +2061,10 @@ jsonb_float8(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "double precision");
+ {
+ cannotCastJsonbValue(v.type, "double precision", fcinfo->context);
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_float8,
NumericGetDatum(v.val.numeric));
--
2.34.1
v16-0016-error-safe-for-casting-timestamp-to-other-types-per-pg_cast.patchapplication/x-patch; name=v16-0016-error-safe-for-casting-timestamp-to-other-types-per-pg_cast.patchDownload
From dfe03a2d909905c0a15e11b2ba23562f898d39ff Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Tue, 2 Dec 2025 12:14:36 +0800
Subject: [PATCH v16 16/23] error safe for casting timestamp to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc
and pc.castfunc > 0 and (castsource::regtype ='timestamp'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
-----------------------------+-----------------------------+----------+-------------+------------+-----------------------+-------------
timestamp without time zone | date | 2029 | a | f | timestamp_date | date
timestamp without time zone | time without time zone | 1316 | a | f | timestamp_time | time
timestamp without time zone | timestamp with time zone | 2028 | i | f | timestamp_timestamptz | timestamptz
timestamp without time zone | timestamp without time zone | 1961 | i | f | timestamp_scale | timestamp
(4 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 7 +++++--
src/backend/utils/adt/timestamp.c | 10 ++++++++--
2 files changed, 13 insertions(+), 4 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 191f7827372..305e6c052e6 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -1330,7 +1330,10 @@ timestamp_date(PG_FUNCTION_ARGS)
Timestamp timestamp = PG_GETARG_TIMESTAMP(0);
DateADT result;
- result = timestamp2date_safe(timestamp, NULL);
+ result = timestamp2date_safe(timestamp, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
+
PG_RETURN_DATEADT(result);
}
@@ -2008,7 +2011,7 @@ timestamp_time(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index d9181ddecef..7c3930011e7 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -352,7 +352,8 @@ timestamp_scale(PG_FUNCTION_ARGS)
result = timestamp;
- AdjustTimestampForTypmod(&result, typmod, NULL);
+ if (!AdjustTimestampForTypmod(&result, typmod, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_TIMESTAMP(result);
}
@@ -6432,8 +6433,13 @@ Datum
timestamp_timestamptz(PG_FUNCTION_ARGS)
{
Timestamp timestamp = PG_GETARG_TIMESTAMP(0);
+ TimestampTz result;
- PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(timestamp));
+ result = timestamp2timestamptz_safe(timestamp, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
+
+ PG_RETURN_TIMESTAMPTZ(result);
}
/*
--
2.34.1
v16-0015-error-safe-for-casting-timestamptz-to-other-types-per-pg_cast.patchapplication/x-patch; name=v16-0015-error-safe-for-casting-timestamptz-to-other-types-per-pg_cast.patchDownload
From f05ef3ffdab21be380f79bec4faeef873a29c97b Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Tue, 2 Dec 2025 12:04:40 +0800
Subject: [PATCH v16 15/23] error safe for casting timestamptz to other types
per pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
and pc.castfunc > 0 and (castsource::regtype ='timestamptz'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
--------------------------+-----------------------------+----------+-------------+------------+-----------------------+-------------
timestamp with time zone | date | 1178 | a | f | timestamptz_date | date
timestamp with time zone | time without time zone | 2019 | a | f | timestamptz_time | time
timestamp with time zone | timestamp without time zone | 2027 | a | f | timestamptz_timestamp | timestamp
timestamp with time zone | time with time zone | 1388 | a | f | timestamptz_timetz | timetz
timestamp with time zone | timestamp with time zone | 1967 | i | f | timestamptz_scale | timestamptz
(5 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 9 ++++++---
src/backend/utils/adt/timestamp.c | 10 ++++++++--
2 files changed, 14 insertions(+), 5 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index c6ce784b0dc..191f7827372 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -1406,7 +1406,10 @@ timestamptz_date(PG_FUNCTION_ARGS)
TimestampTz timestamp = PG_GETARG_TIMESTAMP(0);
DateADT result;
- result = timestamptz2date_safe(timestamp, NULL);
+ result = timestamptz2date_safe(timestamp, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->args))
+ PG_RETURN_NULL();
+
PG_RETURN_DATEADT(result);
}
@@ -2036,7 +2039,7 @@ timestamptz_time(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
@@ -2955,7 +2958,7 @@ timestamptz_timetz(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 11f896147ea..d9181ddecef 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -875,7 +875,8 @@ timestamptz_scale(PG_FUNCTION_ARGS)
result = timestamp;
- AdjustTimestampForTypmod(&result, typmod, NULL);
+ if (!AdjustTimestampForTypmod(&result, typmod, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_TIMESTAMPTZ(result);
}
@@ -6494,8 +6495,13 @@ Datum
timestamptz_timestamp(PG_FUNCTION_ARGS)
{
TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(0);
+ Timestamp result;
- PG_RETURN_TIMESTAMP(timestamptz2timestamp(timestamp));
+ result = timestamptz2timestamp_safe(timestamp, fcinfo->context);
+ if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
+
+ PG_RETURN_TIMESTAMP(result);
}
/*
--
2.34.1
v16-0014-error-safe-for-casting-interval-to-other-types-per-pg_cast.patchapplication/x-patch; name=v16-0014-error-safe-for-casting-interval-to-other-types-per-pg_cast.patchDownload
From 7662d26340e5e58749f2ac3546cabbda7e02942d Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Fri, 12 Dec 2025 15:05:26 +0800
Subject: [PATCH v16 14/23] error safe for casting interval to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'interval'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------------+----------+-------------+------------+----------------+----------
interval | time without time zone | 1419 | a | f | interval_time | time
interval | interval | 1200 | i | f | interval_scale | interval
(2 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 2 +-
src/backend/utils/adt/timestamp.c | 3 ++-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index d9679aa53ab..c6ce784b0dc 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2106,7 +2106,7 @@ interval_time(PG_FUNCTION_ARGS)
TimeADT result;
if (INTERVAL_NOT_FINITE(span))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("cannot convert infinite interval to time")));
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 3569d201ee1..11f896147ea 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1334,7 +1334,8 @@ interval_scale(PG_FUNCTION_ARGS)
result = palloc_object(Interval);
*result = *interval;
- AdjustIntervalForTypmod(result, typmod, NULL);
+ if (!AdjustIntervalForTypmod(result, typmod, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_INTERVAL_P(result);
}
--
2.34.1
v16-0013-error-safe-for-casting-date-to-other-types-per-pg_cast.patchapplication/x-patch; name=v16-0013-error-safe-for-casting-date-to-other-types-per-pg_cast.patchDownload
From 056fbb400196b350ed6fc138772df1f831e6de77 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Tue, 2 Dec 2025 13:15:32 +0800
Subject: [PATCH v16 13/23] error safe for casting date to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'date'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+-----------------------------+----------+-------------+------------+------------------+-------------
date | timestamp without time zone | 2024 | i | f | date_timestamp | timestamp
date | timestamp with time zone | 1174 | i | f | date_timestamptz | timestamptz
(2 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 17 ++++++-----------
1 file changed, 6 insertions(+), 11 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 421ccc306f6..d9679aa53ab 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -730,15 +730,6 @@ date2timestamptz_safe(DateADT dateVal, Node *escontext)
return result;
}
-/*
- * Promote date to timestamptz, throwing error for overflow.
- */
-static TimestampTz
-date2timestamptz(DateADT dateVal)
-{
- return date2timestamptz_safe(dateVal, NULL);
-}
-
/*
* date2timestamp_no_overflow
*
@@ -1323,7 +1314,9 @@ date_timestamp(PG_FUNCTION_ARGS)
DateADT dateVal = PG_GETARG_DATEADT(0);
Timestamp result;
- result = date2timestamp(dateVal);
+ result = date2timestamp_safe(dateVal, fcinfo->context);
+ if(SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_TIMESTAMP(result);
}
@@ -1396,7 +1389,9 @@ date_timestamptz(PG_FUNCTION_ARGS)
DateADT dateVal = PG_GETARG_DATEADT(0);
TimestampTz result;
- result = date2timestamptz(dateVal);
+ result = date2timestamptz_safe(dateVal, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_TIMESTAMP(result);
}
--
2.34.1
v16-0012-error-safe-for-casting-float8-to-other-types-per-pg_cast.patchapplication/x-patch; name=v16-0012-error-safe-for-casting-float8-to-other-types-per-pg_cast.patchDownload
From 023f98f20f1ed427bca5730d7cefa45894009fb8 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:31:53 +0800
Subject: [PATCH v16 12/23] error safe for casting float8 to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'float8'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------------+------------+----------+-------------+------------+----------------+---------
double precision | bigint | 483 | a | f | dtoi8 | int8
double precision | smallint | 237 | a | f | dtoi2 | int2
double precision | integer | 317 | a | f | dtoi4 | int4
double precision | real | 312 | a | f | dtof | float4
double precision | numeric | 1743 | a | f | float8_numeric | numeric
(5 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/float.c | 13 +++++++++----
src/backend/utils/adt/int8.c | 2 +-
src/backend/utils/adt/numeric.c | 3 ++-
3 files changed, 12 insertions(+), 6 deletions(-)
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index d5f15bfa7de..55c7030ba81 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -1199,9 +1199,14 @@ 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 +1229,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 +1254,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")));
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 1185f975bbd..cb579466b24 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")));
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 78c37c044f8..2564f52a3ff 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -4559,7 +4559,8 @@ float8_numeric(PG_FUNCTION_ARGS)
init_var(&result);
/* Assume we need not worry about leading/trailing spaces */
- (void) set_var_from_str(buf, buf, &result, &endptr, NULL);
+ if (!set_var_from_str(buf, buf, &result, &endptr, fcinfo->context))
+ PG_RETURN_NULL();
res = make_result(&result);
--
2.34.1
v16-0010-error-safe-for-casting-numeric-to-other-types-per-pg_cast.patchapplication/x-patch; name=v16-0010-error-safe-for-casting-numeric-to-other-types-per-pg_cast.patchDownload
From c228a5673f9cebecfd6dc474dd03e34e0829a6fb Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:25:37 +0800
Subject: [PATCH v16 10/23] error safe for casting numeric to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'numeric'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+----------------+---------
numeric | bigint | 1779 | a | f | numeric_int8 | int8
numeric | smallint | 1783 | a | f | numeric_int2 | int2
numeric | integer | 1744 | a | f | numeric_int4 | int4
numeric | real | 1745 | i | f | numeric_float4 | float4
numeric | double precision | 1746 | i | f | numeric_float8 | float8
numeric | money | 3824 | a | f | numeric_cash | money
numeric | numeric | 1703 | i | f | numeric | numeric
(7 rows)
discussion: https://postgr.es/m/CACJufxHCMzrHOW=wRe8L30rMhB3sjwAv1LE928Fa7sxMu1Tx-g@mail.gmail.com
---
src/backend/utils/adt/numeric.c | 58 ++++++++++++++++++++++++---------
1 file changed, 43 insertions(+), 15 deletions(-)
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 2460698df01..86a16908305 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -1244,7 +1244,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));
}
@@ -1295,8 +1296,9 @@ numeric (PG_FUNCTION_ARGS)
init_var(&var);
set_var_from_num(num, &var);
- (void) apply_typmod(&var, typmod, NULL);
- new = make_result(&var);
+ if (!apply_typmod(&var, typmod, fcinfo->context))
+ PG_RETURN_NULL();
+ new = make_result_safe(&var, fcinfo->context);
free_var(&var);
@@ -3018,7 +3020,10 @@ numeric_mul(PG_FUNCTION_ARGS)
Numeric num2 = PG_GETARG_NUMERIC(1);
Numeric res;
- res = numeric_mul_safe(num1, num2, NULL);
+ res = numeric_mul_safe(num1, num2, fcinfo->context);
+
+ if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
PG_RETURN_NUMERIC(res);
}
@@ -4392,9 +4397,15 @@ numeric_int4_safe(Numeric num, Node *escontext)
Datum
numeric_int4(PG_FUNCTION_ARGS)
{
+ int32 result;
Numeric num = PG_GETARG_NUMERIC(0);
- PG_RETURN_INT32(numeric_int4_safe(num, NULL));
+ result = numeric_int4_safe(num, fcinfo->context);
+
+ if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
+
+ PG_RETURN_INT32(result);
}
/*
@@ -4462,9 +4473,15 @@ numeric_int8_safe(Numeric num, Node *escontext)
Datum
numeric_int8(PG_FUNCTION_ARGS)
{
+ int64 result;
Numeric num = PG_GETARG_NUMERIC(0);
- PG_RETURN_INT64(numeric_int8_safe(num, NULL));
+ result = numeric_int8_safe(num, fcinfo->context);
+
+ if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
+
+ PG_RETURN_INT64(result);
}
@@ -4488,11 +4505,11 @@ numeric_int2(PG_FUNCTION_ARGS)
if (NUMERIC_IS_SPECIAL(num))
{
if (NUMERIC_IS_NAN(num))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert NaN to %s", "smallint")));
else
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert infinity to %s", "smallint")));
}
@@ -4501,12 +4518,12 @@ numeric_int2(PG_FUNCTION_ARGS)
init_var_from_num(num, &x);
if (!numericvar_to_int64(&x, &val))
- ereport(ERROR,
+ ereturn(fcinfo->context, (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(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
@@ -4571,10 +4588,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);
}
@@ -4666,7 +4687,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
v16-0011-error-safe-for-casting-float4-to-other-types-per-pg_cast.patchapplication/x-patch; name=v16-0011-error-safe-for-casting-float4-to-other-types-per-pg_cast.patchDownload
From 6c673a7d607c7cfccdb391eec32fbd4012d0e58c Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:28:20 +0800
Subject: [PATCH v16 11/23] error safe for casting float4 to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'float4'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+----------------+---------
real | bigint | 653 | a | f | ftoi8 | int8
real | smallint | 238 | a | f | ftoi2 | int2
real | integer | 319 | a | f | ftoi4 | int4
real | double precision | 311 | i | f | ftod | float8
real | numeric | 1742 | a | f | float4_numeric | numeric
(5 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/float.c | 4 ++--
src/backend/utils/adt/int8.c | 2 +-
src/backend/utils/adt/numeric.c | 3 ++-
3 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index 849639fda9f..d5f15bfa7de 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -1298,7 +1298,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 +1323,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 9e1a2c7792b..1185f975bbd 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -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 86a16908305..78c37c044f8 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -4657,7 +4657,8 @@ float4_numeric(PG_FUNCTION_ARGS)
init_var(&result);
/* Assume we need not worry about leading/trailing spaces */
- (void) set_var_from_str(buf, buf, &result, &endptr, NULL);
+ if (!set_var_from_str(buf, buf, &result, &endptr, fcinfo->context))
+ PG_RETURN_NULL();
res = make_result(&result);
--
2.34.1
v16-0008-error-safe-for-casting-integer-to-other-types-per-pg_cast.patchapplication/x-patch; name=v16-0008-error-safe-for-casting-integer-to-other-types-per-pg_cast.patchDownload
From 3c6e3491cc993222c20a4fe553d0840d37a5f016 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:20:10 +0800
Subject: [PATCH v16 08/23] error safe for casting integer to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'integer'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+--------------+---------
integer | bigint | 481 | i | f | int48 | int8
integer | smallint | 314 | a | f | i4toi2 | int2
integer | real | 318 | i | f | i4tof | float4
integer | double precision | 316 | i | f | i4tod | float8
integer | numeric | 1740 | i | f | int4_numeric | numeric
integer | money | 3811 | a | f | int4_cash | money
integer | boolean | 2557 | e | f | int4_bool | bool
integer | bytea | 6368 | e | f | int4_bytea | bytea
integer | "char" | 78 | e | f | i4tochar | char
integer | bit | 1683 | e | f | bitfromint4 | bit
(10 rows)
only int4_cash, i4toi2, i4tochar need take care of error handling. but support
for cash data type is not easy, so only i4toi2, i4tochar function refactoring.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/char.c | 2 +-
src/backend/utils/adt/int.c | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/backend/utils/adt/char.c b/src/backend/utils/adt/char.c
index 22dbfc950b1..e90844a29f0 100644
--- a/src/backend/utils/adt/char.c
+++ b/src/backend/utils/adt/char.c
@@ -192,7 +192,7 @@ i4tochar(PG_FUNCTION_ARGS)
int32 arg1 = PG_GETARG_INT32(0);
if (arg1 < SCHAR_MIN || arg1 > SCHAR_MAX)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("\"char\" out of range")));
diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c
index 60411ee024d..b4860a94adb 100644
--- a/src/backend/utils/adt/int.c
+++ b/src/backend/utils/adt/int.c
@@ -350,7 +350,7 @@ i4toi2(PG_FUNCTION_ARGS)
int32 arg1 = PG_GETARG_INT32(0);
if (unlikely(arg1 < SHRT_MIN) || unlikely(arg1 > SHRT_MAX))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
--
2.34.1
v16-0009-error-safe-for-casting-bigint-to-other-types-per-pg_cast.patchapplication/x-patch; name=v16-0009-error-safe-for-casting-bigint-to-other-types-per-pg_cast.patchDownload
From 22e978c277e657f482d84624c1968da8aae92cec Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:22:00 +0800
Subject: [PATCH v16 09/23] error safe for casting bigint to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'bigint'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+--------------+---------
bigint | smallint | 714 | a | f | int82 | int2
bigint | integer | 480 | a | f | int84 | int4
bigint | real | 652 | i | f | i8tof | float4
bigint | double precision | 482 | i | f | i8tod | float8
bigint | numeric | 1781 | i | f | int8_numeric | numeric
bigint | money | 3812 | a | f | int8_cash | money
bigint | oid | 1287 | i | f | i8tooid | oid
bigint | regproc | 1287 | i | f | i8tooid | oid
bigint | regprocedure | 1287 | i | f | i8tooid | oid
bigint | regoper | 1287 | i | f | i8tooid | oid
bigint | regoperator | 1287 | i | f | i8tooid | oid
bigint | regclass | 1287 | i | f | i8tooid | oid
bigint | regcollation | 1287 | i | f | i8tooid | oid
bigint | regtype | 1287 | i | f | i8tooid | oid
bigint | regconfig | 1287 | i | f | i8tooid | oid
bigint | regdictionary | 1287 | i | f | i8tooid | oid
bigint | regrole | 1287 | i | f | i8tooid | oid
bigint | regnamespace | 1287 | i | f | i8tooid | oid
bigint | regdatabase | 1287 | i | f | i8tooid | oid
bigint | bytea | 6369 | e | f | int8_bytea | bytea
bigint | bit | 2075 | e | f | bitfromint8 | bit
(21 rows)
already error safe: i8tof, i8tod, int8_numeric, int8_bytea, bitfromint8
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/int8.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 678f971508b..9e1a2c7792b 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -1251,7 +1251,7 @@ int84(PG_FUNCTION_ARGS)
int64 arg = PG_GETARG_INT64(0);
if (unlikely(arg < PG_INT32_MIN) || unlikely(arg > PG_INT32_MAX))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
@@ -1272,7 +1272,7 @@ int82(PG_FUNCTION_ARGS)
int64 arg = PG_GETARG_INT64(0);
if (unlikely(arg < PG_INT16_MIN) || unlikely(arg > PG_INT16_MAX))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
@@ -1355,7 +1355,7 @@ i8tooid(PG_FUNCTION_ARGS)
int64 arg = PG_GETARG_INT64(0);
if (unlikely(arg < 0) || unlikely(arg > PG_UINT32_MAX))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("OID out of range")));
--
2.34.1
v16-0006-error-safe-for-casting-inet-to-other-types-per-pg_cast.patchapplication/x-patch; name=v16-0006-error-safe-for-casting-inet-to-other-types-per-pg_cast.patchDownload
From 91e56d2a18a5a0827e690f8df3cceac3c1aabd27 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 10:28:54 +0800
Subject: [PATCH v16 06/23] error safe for casting inet to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'inet'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+-------------------+----------+-------------+------------+--------------+---------
inet | cidr | 1715 | a | f | inet_to_cidr | cidr
inet | text | 730 | a | f | network_show | text
inet | character varying | 730 | a | f | network_show | text
inet | character | 730 | a | f | network_show | text
(4 rows)
inet_to_cidr is already error safe.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/network.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/backend/utils/adt/network.c b/src/backend/utils/adt/network.c
index 3a2002097dd..c7e0828764e 100644
--- a/src/backend/utils/adt/network.c
+++ b/src/backend/utils/adt/network.c
@@ -1137,7 +1137,7 @@ network_show(PG_FUNCTION_ARGS)
if (pg_inet_net_ntop(ip_family(ip), ip_addr(ip), ip_maxbits(ip),
tmp, sizeof(tmp)) == NULL)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
errmsg("could not format inet value: %m")));
--
2.34.1
v16-0005-error-safe-for-casting-character-varying-to-other-types-per-pg_c.patchapplication/x-patch; name=v16-0005-error-safe-for-casting-character-varying-to-other-types-per-pg_c.patchDownload
From 9565a08b51eb88d6bfc411b35ac45d41965bc2c7 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:13:45 +0800
Subject: [PATCH v16 05/23] error safe for casting character varying to other
types per pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc and pc.castfunc > 0 and
(castsource::regtype = 'character varying'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
-------------------+-------------------+----------+-------------+------------+---------------+----------
character varying | regclass | 1079 | i | f | text_regclass | regclass
character varying | "char" | 944 | a | f | text_char | char
character varying | name | 1400 | i | f | text_name | name
character varying | xml | 2896 | e | f | texttoxml | xml
character varying | character varying | 669 | i | f | varchar | varchar
(5 rows)
texttoxml, text_regclass was refactored as error safe in prior patch.
text_char, text_name is already error safe.
so here we only need handle function "varchar".
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/varchar.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c
index 8618c7a1b13..e853e0d7dbb 100644
--- a/src/backend/utils/adt/varchar.c
+++ b/src/backend/utils/adt/varchar.c
@@ -634,7 +634,7 @@ varchar(PG_FUNCTION_ARGS)
{
for (i = maxmblen; i < len; i++)
if (s_data[i] != ' ')
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
errmsg("value too long for type character varying(%d)",
maxlen)));
--
2.34.1
v16-0004-error-safe-for-casting-text-to-other-types-per-pg_cast.patchapplication/x-patch; name=v16-0004-error-safe-for-casting-text-to-other-types-per-pg_cast.patchDownload
From 3fce1c0eb706c1c6f036e986becb13d7c672d564 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Fri, 12 Dec 2025 15:31:17 +0800
Subject: [PATCH v16 04/23] error safe for casting text to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'text'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------+----------+-------------+------------+---------------+----------
text | regclass | 1079 | i | f | text_regclass | regclass
text | "char" | 944 | a | f | text_char | char
text | name | 407 | i | f | text_name | name
text | xml | 2896 | e | f | texttoxml | xml
(4 rows)
already error safe: text_name, text_char.
texttoxml is refactored in character type error safe patch.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/catalog/namespace.c | 58 ++++++++++++++++++++++++++-------
src/backend/utils/adt/regproc.c | 13 ++++++--
src/backend/utils/adt/varlena.c | 10 ++++--
src/backend/utils/adt/xml.c | 2 +-
src/include/catalog/namespace.h | 6 ++++
src/include/utils/varlena.h | 1 +
6 files changed, 73 insertions(+), 17 deletions(-)
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index c94089caa58..bd25eb4550d 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -440,6 +440,16 @@ Oid
RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
uint32 flags,
RangeVarGetRelidCallback callback, void *callback_arg)
+{
+ return RangeVarGetRelidExtendedSafe(relation, lockmode, flags,
+ callback, callback_arg,
+ NULL);
+}
+
+Oid
+RangeVarGetRelidExtendedSafe(const RangeVar *relation, LOCKMODE lockmode, uint32 flags,
+ RangeVarGetRelidCallback callback, void *callback_arg,
+ Node *escontext)
{
uint64 inval_count;
Oid relId;
@@ -456,7 +466,7 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
if (relation->catalogname)
{
if (strcmp(relation->catalogname, get_database_name(MyDatabaseId)) != 0)
- ereport(ERROR,
+ ereturn(escontext, InvalidOid,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cross-database references are not implemented: \"%s.%s.%s\"",
relation->catalogname, relation->schemaname,
@@ -513,7 +523,7 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
* return InvalidOid.
*/
if (namespaceId != myTempNamespace)
- ereport(ERROR,
+ ereturn(escontext, InvalidOid,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("temporary tables cannot specify a schema name")));
}
@@ -593,13 +603,23 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
{
int elevel = (flags & RVR_SKIP_LOCKED) ? DEBUG1 : ERROR;
- if (relation->schemaname)
- ereport(elevel,
+ if (relation->schemaname && elevel == DEBUG1)
+ ereport(DEBUG1,
(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
errmsg("could not obtain lock on relation \"%s.%s\"",
relation->schemaname, relation->relname)));
- else
- ereport(elevel,
+ else if (relation->schemaname && elevel == ERROR)
+ ereturn(escontext, InvalidOid,
+ errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s.%s\"",
+ relation->schemaname, relation->relname));
+ else if (elevel == DEBUG1)
+ ereport(DEBUG1,
+ errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s\"",
+ relation->relname));
+ else if (elevel == ERROR)
+ ereturn(escontext, InvalidOid,
(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
errmsg("could not obtain lock on relation \"%s\"",
relation->relname)));
@@ -626,13 +646,23 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
{
int elevel = missing_ok ? DEBUG1 : ERROR;
- if (relation->schemaname)
- ereport(elevel,
+ if (relation->schemaname && elevel == DEBUG1)
+ ereport(DEBUG1,
(errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("relation \"%s.%s\" does not exist",
relation->schemaname, relation->relname)));
- else
- ereport(elevel,
+ else if (relation->schemaname && elevel == ERROR)
+ ereturn(escontext, InvalidOid,
+ errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s.%s\" does not exist",
+ relation->schemaname, relation->relname));
+ else if (elevel == DEBUG1)
+ ereport(DEBUG1,
+ errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" does not exist",
+ relation->relname));
+ else if (elevel == ERROR)
+ ereturn(escontext, InvalidOid,
(errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("relation \"%s\" does not exist",
relation->relname)));
@@ -3622,6 +3652,12 @@ get_namespace_oid(const char *nspname, bool missing_ok)
*/
RangeVar *
makeRangeVarFromNameList(const List *names)
+{
+ return makeRangeVarFromNameListSafe(names, NULL);
+}
+
+RangeVar *
+makeRangeVarFromNameListSafe(const List *names, Node *escontext)
{
RangeVar *rel = makeRangeVar(NULL, NULL, -1);
@@ -3640,7 +3676,7 @@ makeRangeVarFromNameList(const List *names)
rel->relname = strVal(lthird(names));
break;
default:
- ereport(ERROR,
+ ereturn(escontext, NULL,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("improper relation name (too many dotted names): %s",
NameListToString(names))));
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index e5c2246f2c9..59cc508f805 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -1901,12 +1901,19 @@ text_regclass(PG_FUNCTION_ARGS)
text *relname = PG_GETARG_TEXT_PP(0);
Oid result;
RangeVar *rv;
+ List *namelist;
- rv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
+ namelist = textToQualifiedNameListSafe(relname, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
+
+ rv = makeRangeVarFromNameListSafe(namelist, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
/* We might not even have permissions on this relation; don't lock it. */
- result = RangeVarGetRelid(rv, NoLock, false);
-
+ result = RangeVarGetRelidExtendedSafe(rv, NoLock, 0, NULL, NULL,
+ fcinfo->context);
PG_RETURN_OID(result);
}
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index baa5b44ea8d..ccd601ec34c 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -2695,6 +2695,12 @@ name_text(PG_FUNCTION_ARGS)
*/
List *
textToQualifiedNameList(text *textval)
+{
+ return textToQualifiedNameListSafe(textval, NULL);
+}
+
+List *
+textToQualifiedNameListSafe(text *textval, Node *escontext)
{
char *rawname;
List *result = NIL;
@@ -2706,12 +2712,12 @@ textToQualifiedNameList(text *textval)
rawname = text_to_cstring(textval);
if (!SplitIdentifierString(rawname, '.', &namelist))
- ereport(ERROR,
+ ereturn(escontext, NIL,
(errcode(ERRCODE_INVALID_NAME),
errmsg("invalid name syntax")));
if (namelist == NIL)
- ereport(ERROR,
+ ereturn(escontext, NIL,
(errcode(ERRCODE_INVALID_NAME),
errmsg("invalid name syntax")));
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 6301115d625..dd0955d1e44 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -1043,7 +1043,7 @@ xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, Node
return (xmltype *) data;
#else
- ereturn(escontext, NULL
+ ereturn(escontext, NULL,
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("unsupported XML feature"),
errdetail("This functionality requires the server to be built with libxml support."));
diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h
index f1423f28c32..ab61af55ddc 100644
--- a/src/include/catalog/namespace.h
+++ b/src/include/catalog/namespace.h
@@ -103,6 +103,11 @@ extern Oid RangeVarGetRelidExtended(const RangeVar *relation,
LOCKMODE lockmode, uint32 flags,
RangeVarGetRelidCallback callback,
void *callback_arg);
+extern Oid RangeVarGetRelidExtendedSafe(const RangeVar *relation,
+ LOCKMODE lockmode, uint32 flags,
+ RangeVarGetRelidCallback callback,
+ void *callback_arg,
+ Node *escontext);
extern Oid RangeVarGetCreationNamespace(const RangeVar *newRelation);
extern Oid RangeVarGetAndCheckCreationNamespace(RangeVar *relation,
LOCKMODE lockmode,
@@ -168,6 +173,7 @@ extern Oid LookupCreationNamespace(const char *nspname);
extern void CheckSetNamespace(Oid oldNspOid, Oid nspOid);
extern Oid QualifiedNameGetCreationNamespace(const List *names, char **objname_p);
extern RangeVar *makeRangeVarFromNameList(const List *names);
+extern RangeVar *makeRangeVarFromNameListSafe(const List *names, Node *escontext);
extern char *NameListToString(const List *names);
extern char *NameListToQuotedString(const List *names);
diff --git a/src/include/utils/varlena.h b/src/include/utils/varlena.h
index db9fdf72941..0cf01ae5281 100644
--- a/src/include/utils/varlena.h
+++ b/src/include/utils/varlena.h
@@ -27,6 +27,7 @@ extern int varstr_levenshtein_less_equal(const char *source, int slen,
int ins_c, int del_c, int sub_c,
int max_d, bool trusted);
extern List *textToQualifiedNameList(text *textval);
+extern List *textToQualifiedNameListSafe(text *textval, Node *escontext);
extern bool SplitIdentifierString(char *rawstring, char separator,
List **namelist);
extern bool SplitDirectoriesString(char *rawstring, char separator,
--
2.34.1
v16-0003-error-safe-for-casting-character-to-other-types-per-pg_cast.patchapplication/x-patch; name=v16-0003-error-safe-for-casting-character-to-other-types-per-pg_cast.patchDownload
From 4199c297a37a256eec3946c3a4992606884ba105 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Mon, 24 Nov 2025 12:52:16 +0800
Subject: [PATCH v16 03/23] error safe for casting character to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype ='character'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+-------------------+----------+-------------+------------+-------------+---------
character | text | 401 | i | f | rtrim1 | text
character | character varying | 401 | i | f | rtrim1 | text
character | "char" | 944 | a | f | text_char | char
character | name | 409 | i | f | bpchar_name | name
character | xml | 2896 | e | f | texttoxml | xml
character | character | 668 | i | f | bpchar | bpchar
(6 rows)
only texttoxml, bpchar(PG_FUNCTION_ARGS) need take care of error handling.
other functions already error safe.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/executor/execExprInterp.c | 2 +-
src/backend/utils/adt/varchar.c | 2 +-
src/backend/utils/adt/xml.c | 18 ++++++++++++------
src/include/utils/xml.h | 2 +-
4 files changed, 15 insertions(+), 9 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 5e7bd933afc..1d88cdd2cb4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -4542,7 +4542,7 @@ ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op)
*op->resvalue = PointerGetDatum(xmlparse(data,
xexpr->xmloption,
- preserve_whitespace));
+ preserve_whitespace, NULL));
*op->resnull = false;
}
break;
diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c
index 39fc27e1f11..8618c7a1b13 100644
--- a/src/backend/utils/adt/varchar.c
+++ b/src/backend/utils/adt/varchar.c
@@ -307,7 +307,7 @@ bpchar(PG_FUNCTION_ARGS)
{
for (i = maxmblen; i < len; i++)
if (s[i] != ' ')
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
errmsg("value too long for type character(%d)",
maxlen)));
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index c8ab9d61c68..6301115d625 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -659,7 +659,7 @@ texttoxml(PG_FUNCTION_ARGS)
{
text *data = PG_GETARG_TEXT_PP(0);
- PG_RETURN_XML_P(xmlparse(data, xmloption, true));
+ PG_RETURN_XML_P(xmlparse(data, xmloption, true, fcinfo->context));
}
@@ -1028,19 +1028,25 @@ xmlelement(XmlExpr *xexpr,
xmltype *
-xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace)
+xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, Node *escontext)
{
#ifdef USE_LIBXML
xmlDocPtr doc;
doc = xml_parse(data, xmloption_arg, preserve_whitespace,
- GetDatabaseEncoding(), NULL, NULL, NULL);
- xmlFreeDoc(doc);
+ GetDatabaseEncoding(), NULL, NULL, escontext);
+ if (doc)
+ xmlFreeDoc(doc);
+
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return NULL;
return (xmltype *) data;
#else
- NO_XML_SUPPORT();
- return NULL;
+ ereturn(escontext, NULL
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unsupported XML feature"),
+ errdetail("This functionality requires the server to be built with libxml support."));
#endif
}
diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h
index 732dac47bc4..b15168c430e 100644
--- a/src/include/utils/xml.h
+++ b/src/include/utils/xml.h
@@ -73,7 +73,7 @@ extern xmltype *xmlconcat(List *args);
extern xmltype *xmlelement(XmlExpr *xexpr,
const Datum *named_argvalue, const bool *named_argnull,
const Datum *argvalue, const bool *argnull);
-extern xmltype *xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace);
+extern xmltype *xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, Node *escontext);
extern xmltype *xmlpi(const char *target, text *arg, bool arg_is_null, bool *result_is_null);
extern xmltype *xmlroot(xmltype *data, text *version, int standalone);
extern bool xml_is_document(xmltype *arg);
--
2.34.1
v16-0002-error-safe-for-casting-bit-varbit-to-other-types-per-pg_cast.patchapplication/x-patch; name=v16-0002-error-safe-for-casting-bit-varbit-to-other-types-per-pg_cast.patchDownload
From 0babb07f5263782103ea149214a44c204efae439 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 10:33:08 +0800
Subject: [PATCH v16 02/23] error safe for casting bit/varbit to other types
per pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
where pc.castfunc > 0 and (castsource::regtype ='bit'::regtype or
castsource::regtype ='varbit'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
-------------+-------------+----------+-------------+------------+-----------+---------
bit | bigint | 2076 | e | f | bittoint8 | int8
bit | integer | 1684 | e | f | bittoint4 | int4
bit | bit | 1685 | i | f | bit | bit
bit varying | bit varying | 1687 | i | f | varbit | varbit
(4 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/varbit.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/backend/utils/adt/varbit.c b/src/backend/utils/adt/varbit.c
index 205a67dafc5..6e9b808e20a 100644
--- a/src/backend/utils/adt/varbit.c
+++ b/src/backend/utils/adt/varbit.c
@@ -401,7 +401,7 @@ bit(PG_FUNCTION_ARGS)
PG_RETURN_VARBIT_P(arg);
if (!isExplicit)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_STRING_DATA_LENGTH_MISMATCH),
errmsg("bit string length %d does not match type bit(%d)",
VARBITLEN(arg), len)));
@@ -752,7 +752,7 @@ varbit(PG_FUNCTION_ARGS)
PG_RETURN_VARBIT_P(arg);
if (!isExplicit)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
errmsg("bit string too long for type bit varying(%d)",
len)));
@@ -1591,7 +1591,7 @@ bittoint4(PG_FUNCTION_ARGS)
/* Check that the bit string is not too long */
if (VARBITLEN(arg) > sizeof(result) * BITS_PER_BYTE)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
@@ -1671,7 +1671,7 @@ bittoint8(PG_FUNCTION_ARGS)
/* Check that the bit string is not too long */
if (VARBITLEN(arg) > sizeof(result) * BITS_PER_BYTE)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("bigint out of range")));
--
2.34.1
v16-0001-error-safe-for-casting-bytea-to-other-types-per-pg_cast.patchapplication/x-patch; name=v16-0001-error-safe-for-casting-bytea-to-other-types-per-pg_cast.patchDownload
From 51e4e2558877ccbb1bff6fcbbff8d92efbacc1fb Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:08:00 +0800
Subject: [PATCH v16 01/23] error safe for casting bytea to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc and pc.castfunc > 0 and castsource::regtype ='bytea'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------+----------+-------------+------------+------------+---------
bytea | smallint | 6370 | e | f | bytea_int2 | int2
bytea | integer | 6371 | e | f | bytea_int4 | int4
bytea | bigint | 6372 | e | f | bytea_int8 | int8
(3 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/bytea.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/backend/utils/adt/bytea.c b/src/backend/utils/adt/bytea.c
index 6e7b914c563..f43ad6b4aff 100644
--- a/src/backend/utils/adt/bytea.c
+++ b/src/backend/utils/adt/bytea.c
@@ -1027,7 +1027,7 @@ bytea_int2(PG_FUNCTION_ARGS)
/* Check that the byte array is not too long */
if (len > sizeof(result))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range"));
@@ -1052,7 +1052,7 @@ bytea_int4(PG_FUNCTION_ARGS)
/* Check that the byte array is not too long */
if (len > sizeof(result))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range"));
@@ -1077,7 +1077,7 @@ bytea_int8(PG_FUNCTION_ARGS)
/* Check that the byte array is not too long */
if (len > sizeof(result))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("bigint out of range"));
--
2.34.1
Hi,
I am still thinking through a design that avoids having two different
code paths for type casting. Can't we avoid adding a new SafeTypeCast
structure by simply adding a raw_default variable (name could be
simply default) to the existing TypeCast structure? If we do that, we
would need to update transformTypeCast() and other places (like
ExecInterpExpr()) to handle the raw_default. This approach would allow
us to avoid the extra code required for a new node structure (e.g.,
T_SafeTypeCastExpr) and a separate EEOP_SAFETYPE_CAST step.
Here are few other comments:
vv16-0019:
+float8_div_safe(const float8 val1, const float8 val2, struct Node *escontext)
Patches show an inconsistent use of Node* and struct Note * for the
escontext argument. I suggest standardising on Note * to maintain
consistency throughout the code.
--
v16-0020:
@@ -839,7 +839,7 @@ box_distance(PG_FUNCTION_ARGS)
box_cn(&a, box1);
box_cn(&b, box2);
- PG_RETURN_FLOAT8(point_dt(&a, &b));
+ PG_RETURN_FLOAT8(point_dt(&a, &b, NULL));
I think user-callable functions that accept PG_FUNCTION_ARGS;
should directly pass fcinfo->context instead of NULL.
--
v16-0022:
+Sometimes a type cast may fail; to avoid such fail case, an
<literal>ON ERROR</literal> clause can be
..
+ default <replaceable>expression</replaceable> in <literal>ON
ERROR</literal> clause.
Shouldn't it be ON CONVERSION ERROR instead of ON ERROR ?
--
+ state->escontext = makeNode(ErrorSaveContext);
+ state->escontext->type = T_ErrorSaveContext;
+ state->escontext->error_occurred = false;
+ state->escontext->details_wanted = false;
No need to assign values to the rest of the escontext members;
makeNode(ErrorSaveContext) is sufficient. I also think
ExecInitExprSafe() should receive escontext from the caller. Instead
of passing an error_safe boolean to evaluate_expr, you can pass the
escontext itself; this can then be passed down to ExecInitExprSafe,
helping capture soft error information at a much higher level.
In that way, you can simply call ExecInitExprSafe() from
ExecInitExpr() and pass NULL for the escontext. This reduces code
duplication, since most of the code is similar except for the
aforementioned initialization lines.
Regards,
Amul
On Fri, Jan 2, 2026 at 2:08 PM Amul Sul <sulamul@gmail.com> wrote:
Hi,
I am still thinking through a design that avoids having two different
code paths for type casting. Can't we avoid adding a new SafeTypeCast
structure by simply adding a raw_default variable (name could be
simply default) to the existing TypeCast structure? If we do that, we
would need to update transformTypeCast() and other places (like
ExecInterpExpr()) to handle the raw_default. This approach would allow
us to avoid the extra code required for a new node structure (e.g.,
T_SafeTypeCastExpr) and a separate EEOP_SAFETYPE_CAST step.
Hi.
transformTypeCast transforms a TypeCast node and may produce one of the
following nodes: FuncExpr, CollateExpr, CoerceToDomain, ArrayCoerceExpr, or
CoerceViaIO.
To avoid EEOP_SAFETY_CAST, the returned node would need an
additional field to store the transformed DEFAULT expression.
This implies adding such a field to the aforementioned node types; otherwise,
the information about the transformed default expression would be lost.
However, adding an extra field to nodes such as FuncExpr seems not doable.
It is not generally applicable to FuncExpr, but rather only relevant to a
specific usage scenario. In addition, it may introduce unnecessary overhead.
T_SafeTypeCastExpr is still needed for holding the transformed cast expression
and default expression, I think.
However, we can add a field to node TypeCast for the raw default expression.
transformTypeSafeCast seems not needed, so I consolidated
the parsing analysis into transformTypeCast.
Here are few other comments:
vv16-0019:
+float8_div_safe(const float8 val1, const float8 val2, struct Node *escontext)
Patches show an inconsistent use of Node* and struct Note * for the
escontext argument. I suggest standardising on Note * to maintain
consistency throughout the code.
--
This inconsistency already exists in the codebase,
we already have many files using "struct Node *escontext".
I guess the reason is to avoid "#include "nodes/nodes.h"
in src/include/utils/float.h
v16-0020:
@@ -839,7 +839,7 @@ box_distance(PG_FUNCTION_ARGS)
box_cn(&a, box1);
box_cn(&b, box2);- PG_RETURN_FLOAT8(point_dt(&a, &b)); + PG_RETURN_FLOAT8(point_dt(&a, &b, NULL));I think user-callable functions that accept PG_FUNCTION_ARGS;
should directly pass fcinfo->context instead of NULL.
--
This seems overall good for me. since next time, if we want
other functions to be error safe, we don't need to do this again.
v16-0022:
+Sometimes a type cast may fail; to avoid such fail case, an <literal>ON ERROR</literal> clause can be .. + default <replaceable>expression</replaceable> in <literal>ON ERROR</literal> clause.Shouldn't it be ON CONVERSION ERROR instead of ON ERROR ?
--
ON CONVERSION ERROR is ok for me.
+ state->escontext = makeNode(ErrorSaveContext); + state->escontext->type = T_ErrorSaveContext; + state->escontext->error_occurred = false; + state->escontext->details_wanted = false;No need to assign values to the rest of the escontext members;
makeNode(ErrorSaveContext) is sufficient. I also think
ExecInitExprSafe() should receive escontext from the caller. Instead
of passing an error_safe boolean to evaluate_expr, you can pass the
escontext itself; this can then be passed down to ExecInitExprSafe,
helping capture soft error information at a much higher level.In that way, you can simply call ExecInitExprSafe() from
ExecInitExpr() and pass NULL for the escontext. This reduces code
duplication, since most of the code is similar except for the
aforementioned initialization lines.
now i changed it to:
ExprState *
ExecInitExpr(Expr *node, PlanState *parent)
{
return ExecInitExprExtended(node, NULL, parent);
}
ExprState *
ExecInitExprExtended(Expr *node, Node *escontext, PlanState *parent)
Attachments:
v17-0022-CAST-expr-AS-newtype-DEFAULT-ON-ERROR.patchtext/x-patch; charset=UTF-8; name=v17-0022-CAST-expr-AS-newtype-DEFAULT-ON-ERROR.patchDownload
From b594d2a41258e828c5953733d47c19a6f1f36e02 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Mon, 5 Jan 2026 12:58:41 +0800
Subject: [PATCH v17 22/23] CAST(expr AS newtype DEFAULT ON ERROR)
# Bumps catversion required
* With this patchset, most functions in pg_cast.castfunc are now error-safe.
* CoerceViaIO and CoerceToDomain were already error-safe in the HEAD.
* this patch extends error-safe behavior to ArrayCoerceExpr.
* We also ensure that when a coercion fails, execution falls back to evaluating
the specified default node.
* The doc has been refined, though it may still need more review.
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}
[0]: https://git.postgresql.org/cgit/postgresql.git/commit/?id=aaaf9449ec6be62cb0d30ed3588dc384f56274b
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
contrib/citext/expected/citext.out | 5 +
contrib/citext/expected/citext_1.out | 5 +
contrib/citext/sql/citext.sql | 2 +
.../pg_stat_statements/expected/select.out | 23 +-
contrib/pg_stat_statements/sql/select.sql | 5 +
doc/src/sgml/syntax.sgml | 33 +
src/backend/executor/execExpr.c | 86 +-
src/backend/executor/execExprInterp.c | 29 +
src/backend/jit/llvm/llvmjit_expr.c | 27 +
src/backend/nodes/nodeFuncs.c | 53 ++
src/backend/optimizer/util/clauses.c | 49 +-
src/backend/parser/gram.y | 23 +
src/backend/parser/parse_agg.c | 9 +
src/backend/parser/parse_coerce.c | 77 +-
src/backend/parser/parse_expr.c | 379 +++++++-
src/backend/parser/parse_func.c | 3 +
src/backend/parser/parse_type.c | 14 +
src/backend/parser/parse_utilcmd.c | 2 +-
src/backend/utils/adt/arrayfuncs.c | 8 +
src/backend/utils/adt/ruleutils.c | 21 +
src/backend/utils/fmgr/fmgr.c | 14 +
src/include/executor/execExpr.h | 7 +
src/include/executor/executor.h | 1 +
src/include/fmgr.h | 3 +
src/include/nodes/execnodes.h | 21 +
src/include/nodes/parsenodes.h | 1 +
src/include/nodes/primnodes.h | 36 +
src/include/optimizer/optimizer.h | 2 +-
src/include/parser/parse_coerce.h | 13 +
src/include/parser/parse_node.h | 2 +
src/include/parser/parse_type.h | 2 +
src/test/regress/expected/cast.out | 810 ++++++++++++++++++
src/test/regress/expected/create_cast.out | 5 +
src/test/regress/expected/equivclass.out | 7 +
src/test/regress/parallel_schedule | 2 +-
src/test/regress/sql/cast.sql | 350 ++++++++
src/test/regress/sql/create_cast.sql | 1 +
src/test/regress/sql/equivclass.sql | 3 +
src/tools/pgindent/typedefs.list | 2 +
39 files changed, 2075 insertions(+), 60 deletions(-)
create mode 100644 src/test/regress/expected/cast.out
create mode 100644 src/test/regress/sql/cast.sql
diff --git a/contrib/citext/expected/citext.out b/contrib/citext/expected/citext.out
index 8c0bf54f0f3..33da19d8df4 100644
--- a/contrib/citext/expected/citext.out
+++ b/contrib/citext/expected/citext.out
@@ -10,6 +10,11 @@ WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid);
--------+---------
(0 rows)
+SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR: cannot cast type character to citext when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSI...
+ ^
+HINT: Safe type cast for user-defined types are not yet supported
-- Test the operators and indexing functions
-- Test = and <>.
SELECT 'a'::citext = 'a'::citext AS t;
diff --git a/contrib/citext/expected/citext_1.out b/contrib/citext/expected/citext_1.out
index c5e5f180f2b..647eea19142 100644
--- a/contrib/citext/expected/citext_1.out
+++ b/contrib/citext/expected/citext_1.out
@@ -10,6 +10,11 @@ WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid);
--------+---------
(0 rows)
+SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR: cannot cast type character to citext when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSI...
+ ^
+HINT: Safe type cast for user-defined types are not yet supported
-- Test the operators and indexing functions
-- Test = and <>.
SELECT 'a'::citext = 'a'::citext AS t;
diff --git a/contrib/citext/sql/citext.sql b/contrib/citext/sql/citext.sql
index aa1cf9abd5c..99794497d47 100644
--- a/contrib/citext/sql/citext.sql
+++ b/contrib/citext/sql/citext.sql
@@ -9,6 +9,8 @@ SELECT amname, opcname
FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod
WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid);
+SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR); --error
+
-- Test the operators and indexing functions
-- Test = and <>.
diff --git a/contrib/pg_stat_statements/expected/select.out b/contrib/pg_stat_statements/expected/select.out
index 75c896f3885..6e67997f0a2 100644
--- a/contrib/pg_stat_statements/expected/select.out
+++ b/contrib/pg_stat_statements/expected/select.out
@@ -73,6 +73,25 @@ SELECT 1 AS "int" OFFSET 2 FETCH FIRST 3 ROW ONLY;
-----
(0 rows)
+--error safe type cast
+SELECT CAST('a' AS int DEFAULT 2 ON CONVERSION ERROR);
+ int4
+------
+ 2
+(1 row)
+
+SELECT CAST('11' AS int DEFAULT 2 ON CONVERSION ERROR);
+ int4
+------
+ 11
+(1 row)
+
+SELECT CAST('12' AS numeric DEFAULT 2 ON CONVERSION ERROR);
+ numeric
+---------
+ 12
+(1 row)
+
-- DISTINCT and ORDER BY patterns
-- Try some query permutations which once produced identical query IDs
SELECT DISTINCT 1 AS "int";
@@ -222,6 +241,8 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
2 | 2 | SELECT $1 AS "int" ORDER BY 1
1 | 2 | SELECT $1 AS i UNION SELECT $2 ORDER BY i
1 | 1 | SELECT $1 || $2
+ 2 | 2 | SELECT CAST($1 AS int DEFAULT $2 ON CONVERSION ERROR)
+ 1 | 1 | SELECT CAST($1 AS numeric DEFAULT $2 ON CONVERSION ERROR)
2 | 2 | SELECT DISTINCT $1 AS "int"
0 | 0 | SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"
1 | 1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
@@ -230,7 +251,7 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
| | ) +
| | SELECT f FROM t ORDER BY f
1 | 1 | select $1::jsonb ? $2
-(17 rows)
+(19 rows)
SELECT pg_stat_statements_reset() IS NOT NULL AS t;
t
diff --git a/contrib/pg_stat_statements/sql/select.sql b/contrib/pg_stat_statements/sql/select.sql
index 11662cde08c..7ee8160fd84 100644
--- a/contrib/pg_stat_statements/sql/select.sql
+++ b/contrib/pg_stat_statements/sql/select.sql
@@ -25,6 +25,11 @@ SELECT 1 AS "int" LIMIT 3 OFFSET 3;
SELECT 1 AS "int" OFFSET 1 FETCH FIRST 2 ROW ONLY;
SELECT 1 AS "int" OFFSET 2 FETCH FIRST 3 ROW ONLY;
+--error safe type cast
+SELECT CAST('a' AS int DEFAULT 2 ON CONVERSION ERROR);
+SELECT CAST('11' AS int DEFAULT 2 ON CONVERSION ERROR);
+SELECT CAST('12' AS numeric DEFAULT 2 ON CONVERSION ERROR);
+
-- DISTINCT and ORDER BY patterns
-- Try some query permutations which once produced identical query IDs
SELECT DISTINCT 1 AS "int";
diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml
index 34c83880a66..d1cc932f7b1 100644
--- a/doc/src/sgml/syntax.sgml
+++ b/doc/src/sgml/syntax.sgml
@@ -2106,6 +2106,10 @@ CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable>
The <literal>CAST</literal> syntax conforms to SQL; the syntax with
<literal>::</literal> is historical <productname>PostgreSQL</productname>
usage.
+ The equivalent ON CONVERSION ERROR behavior is:
+<synopsis>
+CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable> ERROR ON CONVERSION ERROR )
+</synopsis>
</para>
<para>
@@ -2160,6 +2164,35 @@ CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable>
<xref linkend="sql-createcast"/>.
</para>
</note>
+
+ <sect3 id="sql-syntax-type-casts-safe">
+ <title>Safe Type Cast</title>
+ <para>
+ A type cast may occasionally fail. To guard against such failures, you can
+ provide an <literal>ON CONVERSION ERROR</literal> clause to handle potential errors.
+ The syntax for safe type cast is:
+<synopsis>
+CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable> DEFAULT <replaceable>expression</replaceable> ON CONVERSION ERROR )
+</synopsis>
+ If the type cast fails, instead of error out, evaluation falls back to the
+ default <replaceable>expression</replaceable>
+ specified in the <literal>ON CONVERSION ERROR</literal> clause.
+
+ At present, this only support built-in type casts, see <xref linkend="catalog-pg-cast"/>.
+ User-defined type casts created with <link linkend="sql-createcast">CREATE CAST</link>
+ are not supported.
+ </para>
+
+ <para>
+ Some examples:
+<screen>
+SELECT CAST(TEXT 'error' AS integer DEFAULT 3 ON CONVERSION ERROR);
+<lineannotation>Result: </lineannotation><computeroutput>3</computeroutput>
+SELECT CAST(TEXT 'error' AS numeric DEFAULT 1.1 ON CONVERSION ERROR);
+<lineannotation>Result: </lineannotation><computeroutput>1.1</computeroutput>
+</screen>
+ </para>
+ </sect3>
</sect2>
<sect2 id="sql-syntax-collate-exprs">
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index e0a1fb76aa8..d5cbcfbbef6 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,
@@ -141,6 +144,19 @@ static void ExecInitJsonCoercion(ExprState *state, JsonReturning *returning,
*/
ExprState *
ExecInitExpr(Expr *node, PlanState *parent)
+{
+ return ExecInitExprExtended(node, NULL, parent);
+}
+
+/*
+ * ExecInitExprExtended: soft error variant of ExecInitExpr.
+ *
+ * escontext is expected to be non-NULL only for expression nodes that support
+ * soft errors.
+ * Not all expression nodes support this; if in doubt, pass NULL.
+ */
+ExprState *
+ExecInitExprExtended(Expr *node, Node *escontext, PlanState *parent)
{
ExprState *state;
ExprEvalStep scratch = {0};
@@ -154,6 +170,7 @@ ExecInitExpr(Expr *node, PlanState *parent)
state->expr = node;
state->parent = parent;
state->ext_params = NULL;
+ state->escontext = (ErrorSaveContext *) escontext;
/* Insert setup steps as needed */
ExecCreateExprSetupSteps(state, (Node *) node);
@@ -1701,6 +1718,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
elemstate->innermost_caseval = palloc_object(Datum);
elemstate->innermost_casenull = palloc_object(bool);
+ elemstate->escontext = state->escontext;
ExecInitExprRec(acoerce->elemexpr, elemstate,
&elemstate->resvalue, &elemstate->resnull);
@@ -2176,6 +2194,15 @@ 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;
@@ -2736,7 +2763,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;
@@ -4733,6 +4760,63 @@ ExecBuildParamSetEqual(TupleDesc desc,
return state;
}
+/*
+ * Push steps to evaluate a SafeTypeCastExpr and its various subsidiary
+ * expressions.
+ */
+static void
+ExecInitSafeTypeCastExpr(SafeTypeCastExpr *stcexpr, ExprState *state,
+ Datum *resv, bool *resnull,
+ ExprEvalStep *scratch)
+{
+ /*
+ * If we cannot coerce to the target type, fallback to the DEFAULT
+ * expression specified in ON CONVERSION ERROR clause, and we are done.
+ */
+ if (stcexpr->cast_expr == NULL)
+ {
+ ExecInitExprRec((Expr *) stcexpr->default_expr,
+ state, resv, resnull);
+ return;
+ }
+ else
+ {
+ SafeTypeCastState *stcstate;
+
+ stcstate = palloc0(sizeof(SafeTypeCastState));
+ stcstate->stcexpr = stcexpr;
+ stcstate->escontext.type = T_ErrorSaveContext;
+ state->escontext = &stcstate->escontext;
+
+ /* evaluate argument expression into step's result area */
+ ExecInitExprRec((Expr *) stcexpr->cast_expr,
+ state, resv, resnull);
+ scratch->opcode = EEOP_SAFETYPE_CAST;
+ scratch->d.stcexpr.stcstate = stcstate;
+ ExprEvalPushStep(state, scratch);
+
+ /*
+ * Steps to evaluate the DEFAULT expression. Skip it if this is a
+ * binary coercion cast.
+ */
+ if (!IsA(stcexpr->cast_expr, RelabelType))
+ {
+ ErrorSaveContext *saved_escontext;
+
+ saved_escontext = state->escontext;
+
+ state->escontext = NULL;
+
+ ExecInitExprRec((Expr *) stcstate->stcexpr->default_expr,
+ state, resv, resnull);
+
+ state->escontext = saved_escontext;
+ }
+
+ 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 0a2d25c1b62..40623b98491 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,28 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
EEO_NEXT();
}
+ EEO_CASE(EEOP_SAFETYPE_CAST)
+ {
+ SafeTypeCastState *stcstate = op->d.stcexpr.stcstate;
+
+ if (SOFT_ERROR_OCCURRED(&stcstate->escontext))
+ {
+ *op->resvalue = (Datum) 0;
+ *op->resnull = true;
+
+ /*
+ * Reset for next use such as for catching errors when
+ * coercing a expression.
+ */
+ stcstate->escontext.error_occurred = false;
+ stcstate->escontext.details_wanted = false;
+
+ EEO_NEXT();
+ }
+ else
+ EEO_JUMP(stcstate->jump_end);
+ }
+
EEO_CASE(EEOP_JSONEXPR_PATH)
{
/* too complex for an inline implementation */
@@ -3644,6 +3667,12 @@ 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/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index 650f1d42a93..475c03c5488 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -2256,6 +2256,33 @@ llvm_compile_expr(ExprState *state)
LLVMBuildBr(b, opblocks[opno + 1]);
break;
+ case EEOP_SAFETYPE_CAST:
+ {
+ SafeTypeCastState *stcstate = op->d.stcexpr.stcstate;
+
+ if (SOFT_ERROR_OCCURRED(&stcstate->escontext))
+ {
+ /*
+ * Reset for next use such as for catching errors when
+ * coercing a expression.
+ */
+ stcstate->escontext.error_occurred = false;
+ stcstate->escontext.details_wanted = false;
+
+ /* set resnull to true */
+ LLVMBuildStore(b, l_sbool_const(1), v_resnullp);
+
+ /* reset resvalue */
+ LLVMBuildStore(b, l_datum_const(0), v_resvaluep);
+
+ LLVMBuildBr(b, opblocks[opno + 1]);
+ }
+ else
+ 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 c7660df92f4..89fb1b357d5 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;
@@ -1550,6 +1561,9 @@ exprLocation(const Node *expr)
/* just use leftmost argument's location */
loc = exprLocation((Node *) ((const RowCompareExpr *) expr)->largs);
break;
+ case T_SafeTypeCastExpr:
+ loc = ((const SafeTypeCastExpr *) expr)->location;
+ break;
case T_CoalesceExpr:
/* COALESCE keyword should always be the first thing */
loc = ((const CoalesceExpr *) expr)->location;
@@ -2321,6 +2335,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:
@@ -3330,6 +3356,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;
@@ -4462,6 +4501,20 @@ raw_expression_tree_walker_impl(Node *node,
return true;
if (WALK(tc->typeName))
return true;
+ if (WALK(tc->raw_default))
+ 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:
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 39d35827c35..9c118c7abcb 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -2447,7 +2447,8 @@ estimate_expression_value(PlannerInfo *root, Node *node)
((Node *) evaluate_expr((Expr *) (node), \
exprType((Node *) (node)), \
exprTypmod((Node *) (node)), \
- exprCollation((Node *) (node))))
+ exprCollation((Node *) (node)), \
+ NULL))
/*
* Recursive guts of eval_const_expressions/estimate_expression_value
@@ -2958,6 +2959,32 @@ 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);
+
+ /*
+ * We must not reduce any recognizably constant subexpressions
+ * in cast_expr here, since we don’t want it to fail
+ * prematurely.
+ */
+ 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:
@@ -3388,7 +3415,8 @@ eval_const_expressions_mutator(Node *node,
return (Node *) evaluate_expr((Expr *) svf,
svf->type,
svf->typmod,
- InvalidOid);
+ InvalidOid,
+ NULL);
else
return copyObject((Node *) svf);
}
@@ -4828,7 +4856,7 @@ evaluate_function(Oid funcid, Oid result_type, int32 result_typmod,
newexpr->location = -1;
return evaluate_expr((Expr *) newexpr, result_type, result_typmod,
- result_collid);
+ result_collid, NULL);
}
/*
@@ -5282,10 +5310,14 @@ sql_inline_error_callback(void *arg)
*
* We use the executor's routine ExecEvalExpr() to avoid duplication of
* code and ensure we get the same result as the executor would get.
+ *
+ * When escontext is not NULL, we will evaluate the constant expression in a
+ * error safe way. If the evaluation fails, return NULL instead of throwing an
+ * error.
*/
Expr *
evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
- Oid result_collation)
+ Oid result_collation, Node *escontext)
{
EState *estate;
ExprState *exprstate;
@@ -5310,7 +5342,7 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
* Prepare expr for execution. (Note: we can't use ExecPrepareExpr
* because it'd result in recursively invoking eval_const_expressions.)
*/
- exprstate = ExecInitExpr(expr, NULL);
+ exprstate = ExecInitExprExtended(expr, escontext, NULL);
/*
* And evaluate it.
@@ -5330,6 +5362,13 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
/* Get back to outer memory context */
MemoryContextSwitchTo(oldcontext);
+ if (SOFT_ERROR_OCCURRED(exprstate->escontext))
+ {
+ FreeExecutorState(estate);
+
+ return NULL;
+ }
+
/*
* Must copy result out of sub-context used by expression eval.
*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9ea81250ce8..15e4f064782 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -158,6 +158,8 @@ static void updateRawStmtEnd(RawStmt *rs, int end_location);
static Node *makeColumnRef(char *colname, List *indirection,
int location, core_yyscan_t yyscanner);
static Node *makeTypeCast(Node *arg, TypeName *typename, int location);
+static Node *makeTypeCastWithDefault(Node *arg, TypeName *typename,
+ Node *raw_default, int location);
static Node *makeStringConstCast(char *str, int location, TypeName *typename);
static Node *makeIntConst(int val, int location);
static Node *makeFloatConst(char *str, int location);
@@ -16065,6 +16067,12 @@ func_expr_common_subexpr:
}
| CAST '(' a_expr AS Typename ')'
{ $$ = makeTypeCast($3, $5, @1); }
+ | CAST '(' a_expr AS Typename ERROR_P ON CONVERSION_P ERROR_P ')'
+ { $$ = makeTypeCast($3, $5, @1); }
+ | CAST '(' a_expr AS Typename NULL_P ON CONVERSION_P ERROR_P ')'
+ { $$ = makeTypeCastWithDefault($3, $5, makeNullAConst(-1), @1); }
+ | CAST '(' a_expr AS Typename DEFAULT a_expr ON CONVERSION_P ERROR_P ')'
+ { $$ = makeTypeCastWithDefault($3, $5, $7, @1); }
| EXTRACT '(' extract_list ')'
{
$$ = (Node *) makeFuncCall(SystemFuncName("extract"),
@@ -18996,10 +19004,25 @@ makeTypeCast(Node *arg, TypeName *typename, int location)
n->arg = arg;
n->typeName = typename;
+ n->raw_default = NULL;
n->location = location;
return (Node *) n;
}
+static Node *
+makeTypeCastWithDefault(Node *arg, TypeName *typename, Node *raw_default,
+ int location)
+{
+ TypeCast *n = makeNode(TypeCast);
+
+ n->arg = arg;
+ n->typeName = typename;
+ n->raw_default = raw_default;
+ n->location = location;
+
+ return (Node *) n;
+}
+
static Node *
makeStringConstCast(char *str, int location, TypeName *typename)
{
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 25ee0f87d93..0e106fdfced 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -490,6 +490,12 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
err = _("grouping operations are not allowed in check constraints");
break;
+ case EXPR_KIND_CAST_DEFAULT:
+ if (isAgg)
+ err = _("aggregate functions are not allowed in CAST DEFAULT expressions");
+ else
+ err = _("grouping operations are not allowed in CAST DEFAULT expressions");
+ break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
@@ -983,6 +989,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_DOMAIN_CHECK:
err = _("window functions are not allowed in check constraints");
break;
+ case EXPR_KIND_CAST_DEFAULT:
+ err = _("window functions are not allowed in CAST DEFAULT expressions");
+ break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
err = _("window functions are not allowed in DEFAULT expressions");
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 913ca53666f..2ab49a5743d 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -81,6 +81,31 @@ coerce_to_target_type(ParseState *pstate, Node *expr, Oid exprtype,
CoercionContext ccontext,
CoercionForm cformat,
int location)
+{
+ return coerce_to_target_type_extended(pstate,
+ expr,
+ exprtype,
+ targettype,
+ targettypmod,
+ ccontext,
+ cformat,
+ location,
+ NULL);
+}
+
+/*
+ * escontext: If not NULL, expr (Unknown Const node type) will be coerced to the
+ * target type in an error-safe way. If it fails, NULL is returned.
+ *
+ * For other parameters, see above coerce_to_target_type.
+ */
+Node *
+coerce_to_target_type_extended(ParseState *pstate, Node *expr, Oid exprtype,
+ Oid targettype, int32 targettypmod,
+ CoercionContext ccontext,
+ CoercionForm cformat,
+ int location,
+ Node *escontext)
{
Node *result;
Node *origexpr;
@@ -102,9 +127,12 @@ 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_extended(pstate, expr, exprtype,
+ targettype, targettypmod,
+ ccontext, cformat, location,
+ escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return NULL;
/*
* If the target is a fixed-length type, it may need a length coercion as
@@ -158,6 +186,17 @@ Node *
coerce_type(ParseState *pstate, Node *node,
Oid inputTypeId, Oid targetTypeId, int32 targetTypeMod,
CoercionContext ccontext, CoercionForm cformat, int location)
+{
+ return coerce_type_extended(pstate, node,
+ inputTypeId, targetTypeId, targetTypeMod,
+ ccontext, cformat, location, NULL);
+}
+
+Node *
+coerce_type_extended(ParseState *pstate, Node *node,
+ Oid inputTypeId, Oid targetTypeId, int32 targetTypeMod,
+ CoercionContext ccontext, CoercionForm cformat,
+ int location, Node *escontext)
{
Node *result;
CoercionPathType pathtype;
@@ -256,6 +295,8 @@ coerce_type(ParseState *pstate, Node *node,
int32 inputTypeMod;
Type baseType;
ParseCallbackState pcbstate;
+ char *string;
+ bool coercion_failed = false;
/*
* If the target type is a domain, we want to call its base type's
@@ -308,21 +349,27 @@ 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 (con->constisnull)
+ string = NULL;
else
- newcon->constvalue = stringTypeDatum(baseType,
- NULL,
- inputTypeMod);
+ string = DatumGetCString(con->constvalue);
+
+ if (!stringTypeDatumSafe(baseType,
+ string,
+ inputTypeMod,
+ escontext,
+ &newcon->constvalue))
+ {
+ coercion_failed = true;
+ newcon->constisnull = true;
+ }
/*
* 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 (!coercion_failed && !con->constisnull && newcon->constlen == -1)
newcon->constvalue =
PointerGetDatum(PG_DETOAST_DATUM(newcon->constvalue));
@@ -339,7 +386,7 @@ 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 (!coercion_failed && !con->constisnull && !newcon->constbyval)
{
Datum val2;
@@ -358,8 +405,10 @@ coerce_type(ParseState *pstate, Node *node,
result = (Node *) newcon;
- /* If target is a domain, apply constraints. */
- if (baseTypeId != targetTypeId)
+ if (coercion_failed)
+ result = NULL;
+ else if (baseTypeId != targetTypeId)
+ /* If target is a domain, apply constraints. */
result = coerce_to_domain(result,
baseTypeId, baseTypeMod,
targetTypeId,
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 9cba1272253..6fef908804c 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -17,6 +17,7 @@
#include "access/htup_details.h"
#include "catalog/pg_aggregate.h"
+#include "catalog/pg_cast.h"
#include "catalog/pg_type.h"
#include "miscadmin.h"
#include "nodes/makefuncs.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,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 *can_coerce);
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 +79,8 @@ 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 void CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr,
+ Node *sourceexpr, Oid inputType, Oid targetType);
static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
static Node *transformJsonObjectConstructor(ParseState *pstate,
JsonObjectConstructor *ctor);
@@ -164,7 +169,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:
@@ -564,6 +569,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
case EXPR_KIND_VALUES_SINGLE:
case EXPR_KIND_CHECK_CONSTRAINT:
case EXPR_KIND_DOMAIN_CHECK:
+ case EXPR_KIND_CAST_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
case EXPR_KIND_INDEX_EXPRESSION:
case EXPR_KIND_INDEX_PREDICATE:
@@ -1824,6 +1830,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_DOMAIN_CHECK:
err = _("cannot use subquery in check constraint");
break;
+ case EXPR_KIND_CAST_DEFAULT:
+ err = _("cannot use subquery in CAST DEFAULT expression");
+ break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
err = _("cannot use subquery in DEFAULT expression");
@@ -2011,17 +2020,24 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
* 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().
+ *
+ * Most of the time, can_coerce will be NULL. It is not NULL only when
+ * performing parse analysis for CAST(DEFAULT ... ON CONVERSION ERROR)
+ * expression. It's default to true. If coercing array elements fails, it will
+ * be set to 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;
List *newcoercedelems = NIL;
ListCell *element;
Oid coerce_type;
+ Oid coerce_type_coll;
bool coerce_hard;
+ ErrorSaveContext escontext = {T_ErrorSaveContext};
/*
* Transform the element expressions
@@ -2045,9 +2061,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
@@ -2088,6 +2105,9 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
}
else
{
+ /* Target type must valid for CAST DEFAULT */
+ Assert(can_coerce == NULL);
+
/* Can't handle an empty array without a target type */
if (newelems == NIL)
ereport(ERROR,
@@ -2125,6 +2145,8 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
coerce_hard = false;
}
+ coerce_type_coll = get_typcollation(coerce_type);
+
/*
* Coerce elements to target type
*
@@ -2134,28 +2156,82 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
* If the array's type was merely derived from the common type of its
* elements, then the elements are implicitly coerced to the common type.
* This is consistent with other uses of select_common_type().
+ *
+ * If can_coerce is not NULL, we need to safely test whether each element
+ * can be coerced to the target type, similar to what is done in
+ * transformTypeSafeCast.
*/
foreach(element, newelems)
{
Node *e = (Node *) lfirst(element);
- Node *newe;
+ Node *newe = NULL;
if (coerce_hard)
{
- newe = coerce_to_target_type(pstate, e,
- exprType(e),
- coerce_type,
- typmod,
- COERCION_EXPLICIT,
- COERCE_EXPLICIT_CAST,
- -1);
- if (newe == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_CANNOT_COERCE),
- errmsg("cannot cast type %s to %s",
- format_type_be(exprType(e)),
- format_type_be(coerce_type)),
- parser_errposition(pstate, exprLocation(e))));
+ /*
+ * Can not coerce, just append the transformed element expression
+ * to the list.
+ */
+ if (can_coerce && (!*can_coerce))
+ newe = e;
+ else
+ {
+ Node *ecopy = NULL;
+
+ if (can_coerce)
+ ecopy = copyObject(e);
+
+ newe = coerce_to_target_type_extended(pstate, e,
+ exprType(e),
+ coerce_type,
+ typmod,
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ -1,
+ (Node *) &escontext);
+ if (newe == NULL)
+ {
+ if (!can_coerce)
+ ereport(ERROR,
+ (errcode(ERRCODE_CANNOT_COERCE),
+ errmsg("cannot cast type %s to %s",
+ format_type_be(exprType(e)),
+ format_type_be(coerce_type)),
+ parser_errposition(pstate, exprLocation(e))));
+ else
+ {
+ /*
+ * Can not coerce, just append the transformed element
+ * expression to the list.
+ */
+ newe = ecopy;
+ *can_coerce = false;
+ }
+ }
+ else if (can_coerce && IsA(ecopy, Const) && IsA(newe, FuncExpr))
+ {
+ Node *result;
+
+ /*
+ * pre-evaluate simple constant cast expressions in a way
+ * that tolerate errors.
+ */
+ FuncExpr *newexpr = (FuncExpr *) copyObject(newe);
+
+ assign_expr_collations(pstate, (Node *) newexpr);
+
+ result = (Node *) evaluate_expr((Expr *) newexpr,
+ coerce_type,
+ typmod,
+ coerce_type_coll,
+ (Node *) &escontext);
+ if (result == NULL)
+ {
+ newe = ecopy;
+ *can_coerce = false;
+ }
+ }
+ }
}
else
newe = coerce_to_common_type(pstate, e,
@@ -2696,7 +2772,8 @@ transformWholeRowRef(ParseState *pstate, ParseNamespaceItem *nsitem,
}
/*
- * Handle an explicit CAST construct.
+ * Handle an explicit CAST construct or
+ * CAST(... DEFAULT ... ON CONVERSION ERROR) construct.
*
* Transform the argument, look up the type name, and apply any necessary
* coercion function(s).
@@ -2704,17 +2781,64 @@ transformWholeRowRef(ParseState *pstate, ParseNamespaceItem *nsitem,
static Node *
transformTypeCast(ParseState *pstate, TypeCast *tc)
{
- Node *result;
+ SafeTypeCastExpr *stc;
+ Node *cast_expr = NULL;
+ Node *defexpr = NULL;
Node *arg = tc->arg;
+ Node *srcexpr;
Node *expr;
Oid inputType;
Oid targetType;
+ Oid targetTypecoll;
int32 targetTypmod;
int location;
+ bool can_coerce = true;
+ ErrorSaveContext escontext = {T_ErrorSaveContext};
/* Look up the type name first */
typenameTypeIdAndMod(pstate, tc->typeName, &targetType, &targetTypmod);
+ targetTypecoll = get_typcollation(targetType);
+
+ /* now looking at DEFAULT expression */
+ if (tc->raw_default)
+ {
+ Oid defColl;
+
+ defexpr = transformExpr(pstate, tc->raw_default, EXPR_KIND_CAST_DEFAULT);
+
+ defexpr = coerce_to_target_type(pstate, defexpr, exprType(defexpr),
+ targetType, targetTypmod,
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ exprLocation(defexpr));
+ if (defexpr == NULL)
+ ereport(ERROR,
+ errcode(ERRCODE_CANNOT_COERCE),
+ errmsg("cannot coerce %s expression to type %s",
+ "CAST DEFAULT",
+ format_type_be(targetType)),
+ parser_coercion_errposition(pstate, exprLocation(tc->raw_default), defexpr));
+
+ assign_expr_collations(pstate, defexpr);
+
+ /*
+ * The collation of DEFAULT expression must match the collation of the
+ * target type.
+ */
+ defColl = exprCollation(defexpr);
+
+ if (OidIsValid(targetTypecoll) && OidIsValid(defColl) &&
+ targetTypecoll != defColl)
+ ereport(ERROR,
+ errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("the collation of CAST DEFAULT expression conflicts with target type collation"),
+ errdetail("\"%s\" versus \"%s\"",
+ get_collation_name(targetTypecoll),
+ get_collation_name(defColl)),
+ parser_errposition(pstate, exprLocation(defexpr)));
+ }
+
/*
* If the subject of the typecast is an ARRAY[] construct and the target
* type is an array type, we invoke transformArrayExpr() directly so that
@@ -2743,7 +2867,8 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
(A_ArrayExpr *) arg,
targetBaseType,
elementType,
- targetBaseTypmod);
+ targetBaseTypmod,
+ defexpr ? &can_coerce : NULL);
}
else
expr = transformExprRecurse(pstate, arg);
@@ -2764,20 +2889,200 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
if (location < 0)
location = tc->typeName->location;
- result = coerce_to_target_type(pstate, expr, inputType,
- targetType, targetTypmod,
- COERCION_EXPLICIT,
- COERCE_EXPLICIT_CAST,
- location);
- if (result == NULL)
+ /*
+ * coerce_to_target_type_extended is unlikely to modify the source
+ * expression, but we still create a copy beforehand. This allows
+ * SafeTypeCastExpr to receive the transformed source expression
+ * unchanged.
+ */
+ srcexpr = copyObject(expr);
+
+ if (can_coerce)
+ {
+ cast_expr = coerce_to_target_type_extended(pstate, expr, inputType,
+ targetType, targetTypmod,
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ location,
+ defexpr ? (Node *) &escontext : NULL);
+
+ /*
+ * If this is a simple CAST construct, return the cast expression now,
+ * or throw an error.
+ */
+ if (defexpr == NULL)
+ {
+ if (cast_expr == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_CANNOT_COERCE),
+ errmsg("cannot cast type %s to %s",
+ format_type_be(inputType),
+ format_type_be(targetType)),
+ parser_coercion_errposition(pstate, location, expr)));
+
+ return cast_expr;
+ }
+
+ if (cast_expr == NULL)
+ can_coerce = false;
+ }
+
+ /* Further check CAST(... DEFAULT ... ON CONVERSION ERROR) construct */
+ CoercionErrorSafeCheck(pstate, cast_expr, srcexpr, inputType,
+ targetType);
+
+ if (IsA(srcexpr, Const) && cast_expr && IsA(cast_expr, FuncExpr))
+ {
+ Node *result;
+
+ /*
+ * pre-evaluate simple constant cast expression in a error safe way
+ *
+ * Rationale:
+ *
+ * 1. When deparsing safe cast expression (or in other cases),
+ * eval_const_expressions might be invoked, but it cannot handle
+ * errors gracefully.
+ *
+ * 2. Even if FuncExpr (for castfunc) node has three arguments, the
+ * second and third arguments will also be constants per
+ * coerce_to_target_type. Evaluating a FuncExpr whose arguments are
+ * all Const is safe.
+ */
+ FuncExpr *newexpr = (FuncExpr *) copyObject(cast_expr);
+
+ assign_expr_collations(pstate, (Node *) newexpr);
+
+ result = (Node *) evaluate_expr((Expr *) newexpr,
+ targetType,
+ targetTypmod,
+ targetTypecoll,
+ (Node *) &escontext);
+ if (result == NULL)
+ {
+ /* can not coerce, set can_coerce to false */
+ can_coerce = false;
+ cast_expr = NULL;
+ }
+ }
+
+ Assert(can_coerce || cast_expr == NULL);
+
+ stc = makeNode(SafeTypeCastExpr);
+ stc->source_expr = srcexpr;
+ stc->cast_expr = cast_expr;
+ stc->default_expr = defexpr;
+ stc->resulttype = targetType;
+ stc->resulttypmod = targetTypmod;
+ stc->resultcollid = targetTypecoll;
+ stc->location = location;
+
+ return (Node *) stc;
+}
+
+/*
+ * Check type coercion is error safe or not. If not then report error
+ */
+static void
+CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr, Node *sourceexpr,
+ Oid inputType, Oid targetType)
+{
+ HeapTuple tuple;
+ bool errorsafe = true;
+ bool userdefined = false;
+ Oid inputBaseType;
+ Oid targetBaseType;
+ Oid inputElementType;
+ Oid inputElementBaseType;
+ Oid targetElementType;
+ Oid targetElementBaseType;
+
+ /*
+ * Binary coercion cast is equivalent to no cast at all. CoerceViaIO can
+ * also be evaluated in an error-safe manner. So skip these two cases.
+ */
+ if (!castexpr || IsA(castexpr, RelabelType) ||
+ IsA(castexpr, CoerceViaIO))
+ return;
+
+ /*
+ * Type cast involving with some types is not error safe, do the check
+ * now. We need consider domain over array and array over domain scarenio.
+ * We already checked user-defined type cast above.
+ */
+ inputElementType = get_element_type(inputType);
+
+ if (OidIsValid(inputElementType))
+ inputElementBaseType = getBaseType(inputElementType);
+ else
+ {
+ inputBaseType = getBaseType(inputType);
+
+ inputElementBaseType = get_element_type(inputBaseType);
+ if (!OidIsValid(inputElementBaseType))
+ inputElementBaseType = inputBaseType;
+ }
+
+ targetElementType = get_element_type(targetType);
+
+ if (OidIsValid(targetElementType))
+ targetElementBaseType = getBaseType(targetElementType);
+ else
+ {
+ targetBaseType = getBaseType(targetType);
+
+ targetElementBaseType = get_element_type(targetBaseType);
+ if (!OidIsValid(targetElementBaseType))
+ targetElementBaseType = targetBaseType;
+ }
+
+ if (inputElementBaseType == MONEYOID ||
+ targetElementBaseType == MONEYOID ||
+ (inputElementBaseType == CIRCLEOID &&
+ targetElementBaseType == POLYGONOID))
+ {
+ /*
+ * Casts involving MONEY type are not error safe. The CIRCLE to
+ * POLYGON cast is also unsafe because it relies on a SQL-language
+ * function; only C-language functions are currently supported for
+ * error safe casts.
+ */
+ errorsafe = false;
+ }
+
+ if (errorsafe)
+ {
+ /* Look in pg_cast */
+ tuple = SearchSysCache2(CASTSOURCETARGET,
+ ObjectIdGetDatum(inputElementBaseType),
+ ObjectIdGetDatum(targetElementBaseType));
+
+ if (HeapTupleIsValid(tuple))
+ {
+ Form_pg_cast castForm = (Form_pg_cast) GETSTRUCT(tuple);
+
+ if (castForm->oid > FirstUnpinnedObjectId)
+ {
+ errorsafe = false;
+ userdefined = true;
+ }
+
+ ReleaseSysCache(tuple);
+ }
+ }
+
+ if (!errorsafe)
ereport(ERROR,
- (errcode(ERRCODE_CANNOT_COERCE),
- errmsg("cannot cast type %s to %s",
- format_type_be(inputType),
- format_type_be(targetType)),
- parser_coercion_errposition(pstate, location, expr)));
-
- return result;
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot cast type %s to %s when %s expression is specified in %s",
+ format_type_be(inputType),
+ format_type_be(targetType),
+ "DEFAULT",
+ "CAST ... ON CONVERSION ERROR"),
+ userdefined
+ ? errhint("Safe type cast for user-defined types are not yet supported")
+ : errhint("Explicit cast is defined but definition is not error safe"),
+ parser_errposition(pstate, exprLocation(sourceexpr)));
}
/*
@@ -3193,6 +3498,8 @@ ParseExprKindName(ParseExprKind exprKind)
case EXPR_KIND_CHECK_CONSTRAINT:
case EXPR_KIND_DOMAIN_CHECK:
return "CHECK";
+ case EXPR_KIND_CAST_DEFAULT:
+ return "CAST DEFAULT";
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
return "DEFAULT";
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 24f6745923b..590a8a785c9 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2743,6 +2743,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
case EXPR_KIND_DOMAIN_CHECK:
err = _("set-returning functions are not allowed in check constraints");
break;
+ case EXPR_KIND_CAST_DEFAULT:
+ err = _("set-returning functions are not allowed in CAST DEFAULT expressions");
+ break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
err = _("set-returning functions are not allowed in DEFAULT expressions");
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index bb7eccde9fd..eba17cbf241 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,19 @@ stringTypeDatum(Type tp, char *string, int32 atttypmod)
return OidInputFunctionCall(typinput, string, typioparam, atttypmod);
}
+/* error-safe version of stringTypeDatum */
+bool
+stringTypeDatumSafe(Type tp, char *string, int32 atttypmod,
+ Node *escontext, Datum *result)
+{
+ Form_pg_type typform = (Form_pg_type) GETSTRUCT(tp);
+ Oid typinput = typform->typinput;
+ Oid typioparam = getTypeIOParam(tp);
+
+ return OidInputFunctionCallSafe(typinput, string, typioparam, atttypmod,
+ escontext, result);
+}
+
/*
* Given a typeid, return the type's typrelid (associated relation), if any.
* Returns InvalidOid if type is not a composite type.
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 96f442c4145..7ac9486f781 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -4943,7 +4943,7 @@ transformPartitionBoundValue(ParseState *pstate, Node *val,
assign_expr_collations(pstate, value);
value = (Node *) expression_planner((Expr *) value);
value = (Node *) evaluate_expr((Expr *) value, colType, colTypmod,
- partCollation);
+ partCollation, NULL);
if (!IsA(value, Const))
elog(ERROR, "could not evaluate partition bound expression");
}
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index e71d32773b5..c2effe10620 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -3289,6 +3289,14 @@ array_map(Datum arrayd,
/* Apply the given expression to source element */
values[i] = ExecEvalExpr(exprstate, econtext, &nulls[i]);
+ /* Exit early if the evaluation fails */
+ 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 416f1a21ae4..ae1a113b9a5 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -10562,6 +10562,27 @@ get_rule_expr(Node *node, deparse_context *context,
}
break;
+ case T_SafeTypeCastExpr:
+ {
+ SafeTypeCastExpr *stcexpr = castNode(SafeTypeCastExpr, node);
+
+ /*
+ * Here, we cannot deparsing cast_expr directly, since
+ * transformTypeSafeCast may have folded it into a simple
+ * constant or NULL. Instead, we use source_expr and
+ * default_expr to reconstruct the CAST DEFAULT clause.
+ */
+ 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 05984e7ef26..28fed125af7 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -1759,6 +1759,20 @@ OidInputFunctionCall(Oid functionId, char *str, Oid typioparam, int32 typmod)
return InputFunctionCall(&flinfo, str, typioparam, typmod);
}
+/* error-safe version of OidInputFunctionCall */
+bool
+OidInputFunctionCallSafe(Oid functionId, char *str, Oid typioparam,
+ int32 typmod, Node *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 aa9b361fa31..fa9db8fd687 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_SAFETYPE_CAST */
+ struct
+ {
+ struct SafeTypeCastState *stcstate;
+ } stcexpr;
+
/* for EEOP_JSONEXPR_PATH */
struct
{
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 5929aabc353..8ffba99d352 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -324,6 +324,7 @@ ExecProcNode(PlanState *node)
* prototypes from functions in execExpr.c
*/
extern ExprState *ExecInitExpr(Expr *node, PlanState *parent);
+extern ExprState *ExecInitExprExtended(Expr *node, Node *escontext, PlanState *parent);
extern ExprState *ExecInitExprWithParams(Expr *node, ParamListInfo ext_params);
extern ExprState *ExecInitQual(List *qual, PlanState *parent);
extern ExprState *ExecInitCheck(List *qual, PlanState *parent);
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index 22dd6526169..44d9ea07c47 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, Node *escontext,
+ Datum *result);
extern char *OutputFunctionCall(FmgrInfo *flinfo, Datum val);
extern char *OidOutputFunctionCall(Oid functionId, Datum val);
extern Datum ReceiveFunctionCall(FmgrInfo *flinfo, StringInfo buf,
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 02265456978..1842fad8f45 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1059,6 +1059,27 @@ typedef struct DomainConstraintState
ExprState *check_exprstate; /* check_expr's eval state, or NULL */
} DomainConstraintState;
+typedef struct SafeTypeCastState
+{
+ SafeTypeCastExpr *stcexpr;
+
+ /*
+ * Address to jump to when skipping all the steps to evaulate the default
+ * expression.
+ */
+ int jump_end;
+
+ /*
+ * For error-safe evaluation of coercions. 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 896c4f34cc0..62f27d301d0 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -397,6 +397,7 @@ typedef struct TypeCast
NodeTag type;
Node *arg; /* the expression being casted */
TypeName *typeName; /* the target type */
+ Node *raw_default; /* untransformed DEFAULT expression */
ParseLoc location; /* token location, or -1 if unknown */
} TypeCast;
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 5211cadc258..c25227866ca 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -769,6 +769,42 @@ 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
+{
+ Expr xpr;
+
+ /* transformed source expression */
+ Node *source_expr;
+
+ /*
+ * The transformed cast expression; NULL if we can not cocerce source
+ * expression to the target type
+ */
+ Node *cast_expr pg_node_attr(query_jumble_ignore);
+
+ /* Fall back to the default expression if cast evaluation fails */
+ Node *default_expr;
+
+ /* cast result data type */
+ Oid resulttype;
+
+ /* cast result data type typmod (usually -1) */
+ int32 resulttypmod;
+
+ /* cast result data type collation */
+ Oid resultcollid;
+
+ /* Original SafeTypeCastExpr's location */
+ ParseLoc location;
+
+} SafeTypeCastExpr;
+
/*
* FuncExpr - expression node for a function call
*
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index 485f641b3b4..bed86459430 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -143,7 +143,7 @@ extern void convert_saop_to_hashed_saop(Node *node);
extern Node *estimate_expression_value(PlannerInfo *root, Node *node);
extern Expr *evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
- Oid result_collation);
+ Oid result_collation, Node *escontext);
extern bool var_is_nonnullable(PlannerInfo *root, Var *var, bool use_rel_info);
diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h
index aabacd49b65..2c921617762 100644
--- a/src/include/parser/parse_coerce.h
+++ b/src/include/parser/parse_coerce.h
@@ -43,11 +43,24 @@ extern Node *coerce_to_target_type(ParseState *pstate,
CoercionContext ccontext,
CoercionForm cformat,
int location);
+extern Node *coerce_to_target_type_extended(ParseState *pstate,
+ Node *expr,
+ Oid exprtype,
+ Oid targettype,
+ int32 targettypmod,
+ CoercionContext ccontext,
+ CoercionForm cformat,
+ int location,
+ Node *escontext);
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_extended(ParseState *pstate, Node *node,
+ Oid inputTypeId, Oid targetTypeId, int32 targetTypeMod,
+ CoercionContext ccontext, CoercionForm cformat,
+ int location, Node *escontext);
extern Node *coerce_to_domain(Node *arg, Oid baseTypeId, int32 baseTypeMod,
Oid typeId,
CoercionContext ccontext, CoercionForm cformat, int location,
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index a9bffb8a78f..dd72944a5a1 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -67,6 +67,8 @@ typedef enum ParseExprKind
EXPR_KIND_VALUES_SINGLE, /* single-row VALUES (in INSERT only) */
EXPR_KIND_CHECK_CONSTRAINT, /* CHECK constraint for a table */
EXPR_KIND_DOMAIN_CHECK, /* CHECK constraint for a domain */
+ EXPR_KIND_CAST_DEFAULT, /* default expression in
+ CAST DEFAULT ON CONVERSION ERROR */
EXPR_KIND_COLUMN_DEFAULT, /* default value for a table column */
EXPR_KIND_FUNCTION_DEFAULT, /* default parameter value for function */
EXPR_KIND_INDEX_EXPRESSION, /* index expression */
diff --git a/src/include/parser/parse_type.h b/src/include/parser/parse_type.h
index a335807b0b0..e303a1073c1 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,
+ Node *escontext, Datum *result);
extern Oid typeidTypeRelid(Oid type_id);
extern Oid typeOrDomainTypeRelid(Oid type_id);
diff --git a/src/test/regress/expected/cast.out b/src/test/regress/expected/cast.out
new file mode 100644
index 00000000000..b0dc7b8faea
--- /dev/null
+++ b/src/test/regress/expected/cast.out
@@ -0,0 +1,810 @@
+SET extra_float_digits = 0;
+-- CAST DEFAULT ON CONVERSION ERROR
+SELECT CAST(B'01' AS date DEFAULT NULL ON CONVERSION ERROR);
+ date
+------
+
+(1 row)
+
+SELECT CAST(BIT'01' AS date DEFAULT NULL ON CONVERSION ERROR);
+ date
+------
+
+(1 row)
+
+SELECT CAST(TRUE AS date DEFAULT NULL ON CONVERSION ERROR);
+ date
+------
+
+(1 row)
+
+SELECT CAST(1.1 AS date DEFAULT NULL ON CONVERSION ERROR);
+ date
+------
+
+(1 row)
+
+SELECT CAST(1 AS date DEFAULT NULL ON CONVERSION ERROR);
+ date
+------
+
+(1 row)
+
+SELECT CAST(1111 AS "char" DEFAULT 'A' ON CONVERSION ERROR);
+ char
+------
+ A
+(1 row)
+
+--test source expression is a unknown const
+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('a' as int DEFAULT 18 ON CONVERSION ERROR);
+ int4
+------
+ 18
+(1 row)
+
+SELECT CAST('a' as int DEFAULT sum(1) ON CONVERSION ERROR); --error
+ERROR: aggregate functions are not allowed in CAST DEFAULT expressions
+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: window functions are not allowed in CAST DEFAULT expressions
+LINE 1: SELECT CAST('a' as int DEFAULT sum(1) over() ON CONVERSION E...
+ ^
+SELECT CAST('a' as int DEFAULT (SELECT NULL) ON CONVERSION ERROR); --error
+ERROR: cannot use subquery in CAST DEFAULT expression
+LINE 1: SELECT CAST('a' as int DEFAULT (SELECT NULL) 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('a'::int as int DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR: invalid input syntax for type integer: "a"
+LINE 1: SELECT CAST('a'::int as int DEFAULT NULL ON CONVERSION ERROR...
+ ^
+--the default expression’s collation should match the collation of the cast
+--target type
+VALUES (CAST('error' AS text DEFAULT '1' COLLATE "C" ON CONVERSION ERROR));
+ERROR: the collation of CAST DEFAULT expression conflicts with target type collation
+LINE 1: VALUES (CAST('error' AS text DEFAULT '1' COLLATE "C" ON CONV...
+ ^
+DETAIL: "default" versus "C"
+VALUES (CAST('error' AS int2vector DEFAULT '1 3' ON CONVERSION ERROR));
+ column1
+---------
+ 1 3
+(1 row)
+
+VALUES (CAST('error' AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR));
+ column1
+---------
+ {"1 3"}
+(1 row)
+
+-- test source expression contain subquery
+SELECT CAST((SELECT b FROM generate_series(1, 1), (VALUES('H')) s(b))
+ AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR);
+ b
+---------
+ {"1 3"}
+(1 row)
+
+SELECT CAST((SELECT ARRAY((SELECT b FROM generate_series(1, 2) g, (VALUES('H')) s(b))))
+ AS INT[]
+ DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+CREATE FUNCTION ret_int8() RETURNS BIGINT AS
+$$
+BEGIN RETURN 2147483648; END;
+$$
+LANGUAGE plpgsql IMMUTABLE;
+SELECT CAST('a' as int DEFAULT ret_int8() ON CONVERSION ERROR); --error
+ERROR: integer out of range
+SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERROR); --error
+ERROR: cannot coerce CAST DEFAULT expression to type date
+LINE 1: SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERR...
+ ^
+-- DEFAULT expression can not be set-returning
+CREATE 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);
+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: set-returning functions are not allowed in CAST DEFAULT expressions
+LINE 1: SELECT CAST('a' as int DEFAULT ret_setint() ON CONVERSION ER...
+ ^
+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 user-defined type, domain, array over domain, domain over array
+CREATE DOMAIN d_int42 as int check (value = 42) NOT NULL;
+CREATE DOMAIN d_char3_not_null as char(3) NOT NULL;
+CREATE DOMAIN d_varchar as varchar(3) NOT NULL;
+CREATE DOMAIN d_int_arr as int[] check (value = '{41, 43}') NOT NULL;
+CREATE TYPE comp_domain_with_typmod AS (a d_char3_not_null, b int);
+CREATE TYPE comp2 AS (a d_varchar);
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION ERROR); --error
+ERROR: value too long for type character varying(3)
+LINE 1: SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION...
+ ^
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(123)' ON CONVERSION ERROR);
+ comp2
+-------
+ (123)
+(1 row)
+
+SELECT CAST(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);
+ 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);
+ 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)'...
+ ^
+SELECT CAST(ARRAY[42,41] AS d_int42[] DEFAULT '{42, 42}' ON CONVERSION ERROR);
+ array
+---------
+ {42,42}
+(1 row)
+
+SELECT CAST(ARRAY[42,41] AS d_int_arr DEFAULT '{41, 43}' ON CONVERSION ERROR);
+ array
+---------
+ {41,43}
+(1 row)
+
+-- test array coerce
+SELECT CAST(array['a'::text] AS int[] DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR: invalid input syntax for type integer: "a"
+SELECT CAST(array['a'] AS int[] DEFAULT ARRAY[1] ON CONVERSION ERROR);
+ array
+-------
+ {1}
+(1 row)
+
+SELECT CAST(array[11.12] AS date[] DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+SELECT CAST(array[11111111111111111] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+SELECT CAST(array[['abc'],[456]] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+SELECT CAST('{123,abc,456}' AS int[] DEFAULT '{-789}' ON CONVERSION ERROR);
+ int4
+--------
+ {-789}
+(1 row)
+
+SELECT CAST('{234,def,567}'::text[] AS integer[] DEFAULT '{-1011}' ON CONVERSION ERROR);
+ int4
+---------
+ {-1011}
+(1 row)
+
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS int[] DEFAULT '{1,2}' ON CONVERSION ERROR);
+ array
+-------
+ {1,2}
+(1 row)
+
+SELECT CAST(ARRAY[['1', '2'], ['three', 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --ok
+ array
+---------
+ {21,22}
+(1 row)
+
+SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --error
+ERROR: invalid input syntax for type integer: "three"
+LINE 1: SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] ...
+ ^
+SELECT CAST(ARRAY[1, 'three'::int] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --error
+ERROR: invalid input syntax for type integer: "three"
+LINE 1: SELECT CAST(ARRAY[1, 'three'::int] AS int[] DEFAULT '{21,22}...
+ ^
+-----safe cast with geometry data type
+SELECT CAST('(1,2)'::point AS box DEFAULT NULL ON CONVERSION ERROR);
+ box
+-------------
+ (1,2),(1,2)
+(1 row)
+
+SELECT CAST('[(NaN,1),(NaN,infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+----------------
+ (NaN,Infinity)
+(1 row)
+
+SELECT CAST('[(1e+300,Infinity),(1e+300,Infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+-------------------
+ (1e+300,Infinity)
+(1 row)
+
+SELECT CAST('[(1,2),(3,4)]'::path as polygon DEFAULT NULL ON CONVERSION ERROR);
+ polygon
+---------
+
+(1 row)
+
+SELECT CAST('(NaN,1.0,NaN,infinity)'::box AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+----------------
+ (NaN,Infinity)
+(1 row)
+
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS lseg DEFAULT NULL ON CONVERSION ERROR);
+ lseg
+---------------
+ [(2,2),(0,0)]
+(1 row)
+
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS polygon DEFAULT NULL ON CONVERSION ERROR);
+ polygon
+---------------------------
+ ((0,0),(0,2),(2,2),(2,0))
+(1 row)
+
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS path DEFAULT NULL ON CONVERSION ERROR);
+ path
+------
+
+(1 row)
+
+SELECT CAST('(2.0,infinity,NaN,infinity)'::box AS circle DEFAULT NULL ON CONVERSION ERROR);
+ circle
+----------------------
+ <(NaN,Infinity),NaN>
+(1 row)
+
+SELECT CAST('(NaN,0.0),(2.0,4.0),(0.0,infinity)'::polygon AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+----------------
+ (NaN,Infinity)
+(1 row)
+
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS path DEFAULT NULL ON CONVERSION ERROR);
+ path
+---------------------
+ ((2,0),(2,4),(0,0))
+(1 row)
+
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS box DEFAULT NULL ON CONVERSION ERROR);
+ box
+-------------
+ (2,4),(0,0)
+(1 row)
+
+SELECT CAST('(NaN,infinity),(2.0,4.0),(0.0,infinity)'::polygon AS circle DEFAULT NULL ON CONVERSION ERROR);
+ circle
+----------------------
+ <(NaN,Infinity),NaN>
+(1 row)
+
+SELECT CAST('<(5,1),3>'::circle AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+-------
+ (5,1)
+(1 row)
+
+SELECT CAST('<(3,5),0>'::circle as box DEFAULT NULL ON CONVERSION ERROR);
+ box
+-------------
+ (3,5),(3,5)
+(1 row)
+
+-- not supported because the cast from circle to polygon is implemented as a SQL
+-- function, which cannot be error-safe.
+SELECT CAST('<(3,5),0>'::circle as polygon DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type circle to polygon when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST('<(3,5),0>'::circle as polygon DEFAULT NULL ON C...
+ ^
+HINT: Explicit cast is defined but definition is not error safe
+-----safe cast from/to money type is not supported, all the following would result error
+SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type bigint to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION E...
+ ^
+HINT: Explicit cast is defined but definition is not error safe
+SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type integer to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION E...
+ ^
+HINT: Explicit cast is defined but definition is not error safe
+SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type numeric to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSIO...
+ ^
+HINT: Explicit cast is defined but definition is not error safe
+SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type money to numeric when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSIO...
+ ^
+HINT: Explicit cast is defined but definition is not error safe
+SELECT CAST(NULL::money[] AS numeric[] DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type money[] to numeric[] when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::money[] AS numeric[] DEFAULT NULL ON CONVE...
+ ^
+HINT: Explicit cast is defined but definition is not error safe
+-----safe cast from bytea type to other data types
+SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT 19 ON CONVERSION ERROR);
+ int8
+------
+ 19
+(1 row)
+
+SELECT CAST('\x123456789A'::bytea AS int4 DEFAULT 20 ON CONVERSION ERROR);
+ int4
+------
+ 20
+(1 row)
+
+SELECT CAST('\x123456'::bytea AS int2 DEFAULT 21 ON CONVERSION ERROR);
+ int2
+------
+ 21
+(1 row)
+
+-----safe cast from bit type to other data types
+SELECT CAST('111111111100001'::bit(100) AS INT DEFAULT 22 ON CONVERSION ERROR);
+ int4
+------
+ 22
+(1 row)
+
+SELECT CAST ('111111111100001'::bit(100) AS INT8 DEFAULT 23 ON CONVERSION ERROR);
+ int8
+------
+ 23
+(1 row)
+
+-----safe cast from text type to other data types
+select CAST('a.b.c.d'::text as regclass default NULL on conversion error);
+ regclass
+----------
+
+(1 row)
+
+CREATE TABLE test_safecast(col0 text);
+INSERT INTO test_safecast(col0) VALUES ('<value>one</value');
+SELECT col0 as text,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) as to_regclass,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) IS NULL as expect_true,
+ CAST(col0 AS "char" DEFAULT NULL ON CONVERSION ERROR) as to_char,
+ CAST(col0 AS name DEFAULT NULL ON CONVERSION ERROR) as to_name,
+ CAST(col0 AS xml DEFAULT NULL ON CONVERSION ERROR) as to_xml
+FROM test_safecast;
+ text | to_regclass | expect_true | to_char | to_name | to_xml
+-------------------+-------------+-------------+---------+-------------------+--------
+ <value>one</value | | t | < | <value>one</value |
+(1 row)
+
+SELECT CAST('192.168.1.x' as inet DEFAULT NULL ON CONVERSION ERROR);
+ inet
+------
+
+(1 row)
+
+SELECT CAST('192.168.1.2/30' as cidr DEFAULT NULL ON CONVERSION ERROR);
+ cidr
+------
+
+(1 row)
+
+SELECT CAST('22:00:5c:08:55:08:01:02'::macaddr8 as macaddr DEFAULT NULL ON CONVERSION ERROR);
+ macaddr
+---------
+
+(1 row)
+
+-----safe cast betweeen range data types
+SELECT CAST('[1,2]'::int4range AS int4multirange DEFAULT NULL ON CONVERSION ERROR);
+ int4multirange
+----------------
+ {[1,3)}
+(1 row)
+
+SELECT CAST('[1,2]'::int8range AS int8multirange DEFAULT NULL ON CONVERSION ERROR);
+ int8multirange
+----------------
+ {[1,3)}
+(1 row)
+
+SELECT CAST('[1,2]'::numrange AS nummultirange DEFAULT NULL ON CONVERSION ERROR);
+ nummultirange
+---------------
+ {[1,2]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::daterange AS datemultirange DEFAULT NULL ON CONVERSION ERROR);
+ datemultirange
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::tsrange AS tsmultirange DEFAULT NULL ON CONVERSION ERROR);
+ tsmultirange
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::tstzrange AS tstzmultirange DEFAULT NULL ON CONVERSION ERROR);
+ tstzmultirange
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+-----safe cast betweeen numeric data types
+CREATE TABLE test_safecast1(
+ col1 float4, col2 float8, col3 numeric, col4 numeric[],
+ col5 int2 default 32767,
+ col6 int4 default 32768,
+ col7 int8 default 4294967296);
+INSERT INTO test_safecast1 VALUES('11.1234', '11.1234', '9223372036854775808', '{11.1234}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('inf', 'inf', 'inf', '{11.1234, 12, inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('-inf', '-inf', '-inf', '{11.1234, 12, -inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('NaN', 'NaN', 'NaN', '{11.1234, 12, -inf, NaN}'::numeric[]);
+SELECT col5 as int2,
+ CAST(col5 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col5 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col5 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col5 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col5 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col5 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col5 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col5 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ int2 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+(4 rows)
+
+SELECT col6 as int4,
+ CAST(col6 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col6 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col6 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col6 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col6 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col6 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col6 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col6 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ int4 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+(4 rows)
+
+SELECT col7 as int8,
+ CAST(col7 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col7 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col7 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col7 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col7 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col7 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col7 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col7 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ int8 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+------------+---------+---------+--------+------------+-------------+------------+------------+------------------
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+(4 rows)
+
+SELECT col3 as numeric,
+ CAST(col3 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col3 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col3 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col3 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col3 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col3 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col3 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col3 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ numeric | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+---------------------+---------+---------+--------+---------+-------------+----------------------+---------------------+------------------
+ 9223372036854775808 | | | | | 9.22337e+18 | 9.22337203685478e+18 | 9223372036854775808 |
+ Infinity | | | | | Infinity | Infinity | Infinity |
+ -Infinity | | | | | -Infinity | -Infinity | -Infinity |
+ NaN | | | | | NaN | NaN | NaN | NaN
+(4 rows)
+
+SELECT col4 as num_arr,
+ CAST(col4 AS int2[] DEFAULT NULL ON CONVERSION ERROR) as int2arr,
+ CAST(col4 AS int4[] DEFAULT NULL ON CONVERSION ERROR) as int4arr,
+ CAST(col4 as int8[] DEFAULT NULL ON CONVERSION ERROR) as int8arr,
+ CAST(col4 as float4[] DEFAULT NULL ON CONVERSION ERROR) as f4arr,
+ CAST(col4 as float8[] DEFAULT NULL ON CONVERSION ERROR) as f8arr,
+ CAST(col4 as numeric(10,1)[] DEFAULT NULL ON CONVERSION ERROR) as numarr
+FROM test_safecast1;
+ num_arr | int2arr | int4arr | int8arr | f4arr | f8arr | numarr
+----------------------------+---------+---------+---------+----------------------------+----------------------------+--------
+ {11.1234} | {11} | {11} | {11} | {11.1234} | {11.1234} | {11.1}
+ {11.1234,12,Infinity,NaN} | | | | {11.1234,12,Infinity,NaN} | {11.1234,12,Infinity,NaN} |
+ {11.1234,12,-Infinity,NaN} | | | | {11.1234,12,-Infinity,NaN} | {11.1234,12,-Infinity,NaN} |
+ {11.1234,12,-Infinity,NaN} | | | | {11.1234,12,-Infinity,NaN} | {11.1234,12,-Infinity,NaN} |
+(4 rows)
+
+SELECT col1 as float4,
+ CAST(col1 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col1 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col1 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col1 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col1 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col1 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col1 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col1 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ float4 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-----------+---------+---------+--------+---------+-----------+------------------+------------+------------------
+ 11.1234 | 11 | 11 | | 11 | 11.1234 | 11.1233997344971 | 11.1234 | 11.1
+ Infinity | | | | | Infinity | Infinity | Infinity |
+ -Infinity | | | | | -Infinity | -Infinity | -Infinity |
+ NaN | | | | | NaN | NaN | NaN | NaN
+(4 rows)
+
+SELECT col2 as float8,
+ CAST(col2 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col2 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col2 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col2 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col2 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col2 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col2 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col2 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ float8 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-----------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 11.1234 | 11 | 11 | | 11 | 11.1234 | 11.1234 | 11.1234 | 11.1
+ Infinity | | | | | Infinity | Infinity | Infinity |
+ -Infinity | | | | | -Infinity | -Infinity | -Infinity |
+ NaN | | | | | NaN | NaN | NaN | NaN
+(4 rows)
+
+-- date/timestamp/interval related safe type cast
+CREATE TABLE test_safecast2(
+ col0 date, col1 timestamp, col2 timestamptz,
+ col3 interval, col4 time, col5 timetz);
+INSERT INTO test_safecast2 VALUES
+('-infinity', '-infinity', '-infinity', '-infinity',
+ '2003-03-07 15:36:39 America/New_York', '2003-07-07 15:36:39 America/New_York'),
+('-infinity', 'infinity', 'infinity', 'infinity',
+ '11:59:59.99 PM', '11:59:59.99 PM PDT');
+SELECT col0 as date,
+ CAST(col0 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col0 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col0 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col0 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col0 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+ date | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale
+-----------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity | -infinity | | | -infinity
+ -infinity | -infinity | -infinity | | | -infinity
+(2 rows)
+
+SELECT col1 as timestamp,
+ CAST(col1 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col1 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col1 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col1 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col1 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+ timestamp | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale
+-----------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity | -infinity | | | -infinity
+ infinity | infinity | infinity | | | infinity
+(2 rows)
+
+SELECT col2 as timestamptz,
+ CAST(col2 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col2 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col2 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col2 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col2 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+ timestamptz | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale
+-------------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity | -infinity | | | -infinity
+ infinity | infinity | infinity | | | infinity
+(2 rows)
+
+SELECT col3 as interval,
+ CAST(col3 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col3 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col3 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col3 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col3 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale,
+ CAST(col3 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+ interval | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale | to_interval_scale
+-----------+----------------+---------+----------+-----------+--------------------+-------------------
+ -infinity | | | | | | -infinity
+ infinity | | | | | | infinity
+(2 rows)
+
+SELECT col4 as time,
+ CAST(col4 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col4 AS timetz DEFAULT NULL ON CONVERSION ERROR) IS NOT NULL as to_timetz,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+ time | to_times | to_timetz | to_interval | to_interval_scale
+-------------+-------------+-----------+-------------------------------+-------------------------------
+ 15:36:39 | 15:36:39 | t | @ 15 hours 36 mins 39 secs | @ 15 hours 36 mins 39 secs
+ 23:59:59.99 | 23:59:59.99 | t | @ 23 hours 59 mins 59.99 secs | @ 23 hours 59 mins 59.99 secs
+(2 rows)
+
+SELECT col5 as timetz,
+ CAST(col5 AS time DEFAULT NULL ON CONVERSION ERROR) as to_time,
+ CAST(col5 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_time_scale,
+ CAST(col5 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col5 AS timetz(6) DEFAULT NULL ON CONVERSION ERROR) as to_timetz_scale,
+ CAST(col5 AS interval DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col5 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+ timetz | to_time | to_time_scale | to_timetz | to_timetz_scale | to_interval | to_interval_scale
+----------------+-------------+---------------+----------------+-----------------+-------------+-------------------
+ 15:36:39-04 | 15:36:39 | 15:36:39 | 15:36:39-04 | 15:36:39-04 | |
+ 23:59:59.99-07 | 23:59:59.99 | 23:59:59.99 | 23:59:59.99-07 | 23:59:59.99-07 | |
+(2 rows)
+
+CREATE TABLE test_safecast3(col0 jsonb);
+INSERT INTO test_safecast3(col0) VALUES ('"test"');
+SELECT col0 as jsonb,
+ CAST(col0 AS integer DEFAULT NULL ON CONVERSION ERROR) as to_integer,
+ CAST(col0 AS numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col0 AS bigint DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col0 AS float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col0 AS float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col0 AS boolean DEFAULT NULL ON CONVERSION ERROR) as to_bool,
+ CAST(col0 AS smallint DEFAULT NULL ON CONVERSION ERROR) as to_smallint
+FROM test_safecast3;
+ jsonb | to_integer | to_numeric | to_int8 | to_float4 | to_float8 | to_bool | to_smallint
+--------+------------+------------+---------+-----------+-----------+---------+-------------
+ "test" | | | | | | |
+(1 row)
+
+-- test deparse
+SET datestyle TO ISO, YMD;
+CREATE VIEW safecastview AS
+SELECT CAST('1234' as char(3) DEFAULT -1111 ON CONVERSION ERROR),
+ CAST(1 as date DEFAULT (('2025-Dec-06'::date + random(min=>1, max=>1::int))) ON CONVERSION ERROR) as cast0,
+ CAST(ARRAY[['1'], ['three'],['a']] AS int[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1,
+ CAST(ARRAY[['1', '2'], ['three', 'a']] AS date[] DEFAULT NULL ON CONVERSION ERROR) as cast2,
+ CAST(ARRAY['three'] AS INT[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast3;
+\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 '2025-12-06'::date + random(min => 1, max => 1) ON CONVERSION ERROR) AS cast0,
+ CAST(ARRAY[ARRAY[1], ARRAY['three'], ARRAY['a']] AS integer[] DEFAULT '{1,2}'::integer[] ON CONVERSION ERROR) AS cast1,
+ CAST(ARRAY[ARRAY['1', '2'], ARRAY['three', 'a']] AS date[] DEFAULT NULL::date[] ON CONVERSION ERROR) AS cast2,
+ CAST(ARRAY['three'] AS integer[] DEFAULT '{1,2}'::integer[] ON CONVERSION ERROR) AS cast3
+SELECT * FROM safecastview;
+ bpchar | cast0 | cast1 | cast2 | cast3
+--------+------------+-------+-------+-------
+ 123 | 2025-12-07 | {1,2} | | {1,2}
+(1 row)
+
+CREATE VIEW safecastview1 AS
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS d_int_arr
+ DEFAULT '{41,43}' ON CONVERSION ERROR) as cast1,
+ CAST(ARRAY[['1', '2', 1.1], ['three', true, B'01']] AS d_int_arr
+ DEFAULT '{41,43}' ON CONVERSION ERROR) as cast2;
+\sv safecastview1
+CREATE OR REPLACE VIEW public.safecastview1 AS
+ SELECT CAST(ARRAY[ARRAY[1], ARRAY['three'], ARRAY['a']] AS d_int_arr DEFAULT '{41,43}'::integer[]::d_int_arr ON CONVERSION ERROR) AS cast1,
+ CAST(ARRAY[ARRAY[1, 2, 1.1::integer], ARRAY['three', true, '01'::"bit"]] AS d_int_arr DEFAULT '{41,43}'::integer[]::d_int_arr ON CONVERSION ERROR) AS cast2
+SELECT * FROM safecastview1;
+ cast1 | cast2
+---------+---------
+ {41,43} | {41,43}
+(1 row)
+
+RESET datestyle;
+--test CAST DEFAULT expression mutability
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as int DEFAULT random(min=>1, max=>1) ON CONVERSION ERROR)));
+ERROR: functions in index expression must be marked IMMUTABLE
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as xid DEFAULT NULL ON CONVERSION ERROR)));
+ERROR: data type xid has no default operator class for access method "btree"
+HINT: You must specify an operator class for the index or define a default operator class for the data type.
+CREATE INDEX test_safecast3_idx ON test_safecast3((CAST(col0 as int DEFAULT NULL ON CONVERSION ERROR))); --ok
+SELECT pg_get_indexdef('test_safecast3_idx'::regclass);
+ pg_get_indexdef
+------------------------------------------------------------------------------------------------------------------------------------------
+ CREATE INDEX test_safecast3_idx ON public.test_safecast3 USING btree ((CAST(col0 AS integer DEFAULT NULL::integer ON CONVERSION ERROR)))
+(1 row)
+
+DROP TABLE test_safecast;
+DROP TABLE test_safecast1;
+DROP TABLE test_safecast2;
+DROP TABLE test_safecast3;
+DROP TABLE tcast;
+DROP FUNCTION ret_int8;
+DROP FUNCTION ret_setint;
+DROP TYPE comp_domain_with_typmod;
+DROP TYPE comp2;
+DROP DOMAIN d_varchar;
+DROP DOMAIN d_int42;
+DROP DOMAIN d_char3_not_null;
+RESET extra_float_digits;
diff --git a/src/test/regress/expected/create_cast.out b/src/test/regress/expected/create_cast.out
index 0e69644bca2..0054ed0ef67 100644
--- a/src/test/regress/expected/create_cast.out
+++ b/src/test/regress/expected/create_cast.out
@@ -88,6 +88,11 @@ SELECT 1234::int4::casttesttype; -- Should work now
bar1234
(1 row)
+SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- error
+ERROR: cannot cast type integer to casttesttype when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVE...
+ ^
+HINT: Safe type cast for user-defined types are not yet supported
-- check dependencies generated for that
SELECT pg_describe_object(classid, objid, objsubid) as obj,
pg_describe_object(refclassid, refobjid, refobjsubid) as objref,
diff --git a/src/test/regress/expected/equivclass.out b/src/test/regress/expected/equivclass.out
index ad8ab294ff6..5aee2c7a9fb 100644
--- a/src/test/regress/expected/equivclass.out
+++ b/src/test/regress/expected/equivclass.out
@@ -95,6 +95,13 @@ create function int8alias1cmp(int8, int8alias1) returns int
strict immutable language internal as 'btint8cmp';
alter operator family integer_ops using btree add
function 1 int8alias1cmp (int8, int8alias1);
+-- int8alias2 binary-coercible to int8. so this is ok
+select cast('1'::int8 as int8alias2 default null on conversion error);
+ int8alias2
+------------
+ 1
+(1 row)
+
create table ec0 (ff int8 primary key, f1 int8, f2 int8);
create table ec1 (ff int8 primary key, f1 int8alias1, f2 int8alias2);
create table ec2 (xf int8 primary key, x1 int8alias1, x2 int8alias2);
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 905f9bca959..e41b368dd94 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -81,7 +81,7 @@ test: create_table_like alter_generic alter_operator misc async dbsize merge mis
# collate.linux.utf8 and collate.icu.utf8 tests cannot be run in parallel with each other
# psql depends on create_am
# amutils depends on geometry, create_index_spgist, hash_index, brin
-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 00000000000..9b6f3cc0649
--- /dev/null
+++ b/src/test/regress/sql/cast.sql
@@ -0,0 +1,350 @@
+SET extra_float_digits = 0;
+
+-- CAST DEFAULT ON CONVERSION ERROR
+SELECT CAST(B'01' AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(BIT'01' AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(TRUE AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(1.1 AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(1 AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(1111 AS "char" DEFAULT 'A' ON CONVERSION ERROR);
+
+--test source expression is a unknown const
+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('a' as int DEFAULT 18 ON CONVERSION 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 (SELECT NULL) ON CONVERSION ERROR); --error
+SELECT CAST('a' as int DEFAULT 'b' ON CONVERSION ERROR); --error
+SELECT CAST('a'::int as int DEFAULT NULL ON CONVERSION ERROR); --error
+
+--the default expression’s collation should match the collation of the cast
+--target type
+VALUES (CAST('error' AS text DEFAULT '1' COLLATE "C" ON CONVERSION ERROR));
+
+VALUES (CAST('error' AS int2vector DEFAULT '1 3' ON CONVERSION ERROR));
+VALUES (CAST('error' AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR));
+
+-- test source expression contain subquery
+SELECT CAST((SELECT b FROM generate_series(1, 1), (VALUES('H')) s(b))
+ AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR);
+SELECT CAST((SELECT ARRAY((SELECT b FROM generate_series(1, 2) g, (VALUES('H')) s(b))))
+ AS INT[]
+ DEFAULT NULL ON CONVERSION ERROR);
+
+CREATE FUNCTION ret_int8() RETURNS BIGINT AS
+$$
+BEGIN RETURN 2147483648; END;
+$$
+LANGUAGE plpgsql IMMUTABLE;
+
+SELECT CAST('a' as int DEFAULT ret_int8() ON CONVERSION ERROR); --error
+SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERROR); --error
+
+-- DEFAULT expression can not be set-returning
+CREATE 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);
+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(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 user-defined type, domain, array over domain, domain over array
+CREATE DOMAIN d_int42 as int check (value = 42) NOT NULL;
+CREATE DOMAIN d_char3_not_null as char(3) NOT NULL;
+CREATE DOMAIN d_varchar as varchar(3) NOT NULL;
+CREATE DOMAIN d_int_arr as int[] check (value = '{41, 43}') NOT NULL;
+CREATE TYPE comp_domain_with_typmod AS (a d_char3_not_null, b int);
+CREATE TYPE comp2 AS (a d_varchar);
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION ERROR); --error
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(123)' ON CONVERSION ERROR);
+SELECT CAST(11 AS d_int42 DEFAULT 41 ON CONVERSION ERROR); --error
+SELECT CAST(11 AS d_int42 DEFAULT 42 ON CONVERSION ERROR);
+SELECT CAST(NULL AS d_int42 DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(NULL AS d_int42 DEFAULT 42 ON CONVERSION ERROR);
+SELECT CAST('(,42)' AS comp_domain_with_typmod DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1,2)' ON CONVERSION ERROR);
+SELECT CAST('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1234,2)' ON CONVERSION ERROR); --error
+
+SELECT CAST(ARRAY[42,41] AS d_int42[] DEFAULT '{42, 42}' ON CONVERSION ERROR);
+SELECT CAST(ARRAY[42,41] AS d_int_arr DEFAULT '{41, 43}' ON CONVERSION ERROR);
+
+-- test array coerce
+SELECT CAST(array['a'::text] AS int[] DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(array['a'] AS int[] DEFAULT ARRAY[1] ON CONVERSION ERROR);
+SELECT CAST(array[11.12] AS date[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(array[11111111111111111] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(array[['abc'],[456]] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('{123,abc,456}' AS int[] 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 int[] DEFAULT '{1,2}' ON CONVERSION ERROR);
+SELECT CAST(ARRAY[['1', '2'], ['three', 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --ok
+SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --error
+SELECT CAST(ARRAY[1, 'three'::int] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --error
+
+-----safe cast with geometry data type
+SELECT CAST('(1,2)'::point AS box DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[(NaN,1),(NaN,infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[(1e+300,Infinity),(1e+300,Infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[(1,2),(3,4)]'::path as polygon DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NaN,1.0,NaN,infinity)'::box AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS lseg DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS polygon DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS path DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,infinity,NaN,infinity)'::box AS circle DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NaN,0.0),(2.0,4.0),(0.0,infinity)'::polygon AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS path DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS box DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NaN,infinity),(2.0,4.0),(0.0,infinity)'::polygon AS circle DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('<(5,1),3>'::circle AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('<(3,5),0>'::circle as box DEFAULT NULL ON CONVERSION ERROR);
+
+-- not supported because the cast from circle to polygon is implemented as a SQL
+-- function, which cannot be error-safe.
+SELECT CAST('<(3,5),0>'::circle as polygon DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast from/to money type is not supported, all the following would result error
+SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::money[] AS numeric[] DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast from bytea type to other data types
+SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT 19 ON CONVERSION ERROR);
+SELECT CAST('\x123456789A'::bytea AS int4 DEFAULT 20 ON CONVERSION ERROR);
+SELECT CAST('\x123456'::bytea AS int2 DEFAULT 21 ON CONVERSION ERROR);
+
+-----safe cast from bit type to other data types
+SELECT CAST('111111111100001'::bit(100) AS INT DEFAULT 22 ON CONVERSION ERROR);
+SELECT CAST ('111111111100001'::bit(100) AS INT8 DEFAULT 23 ON CONVERSION ERROR);
+
+-----safe cast from text type to other data types
+select CAST('a.b.c.d'::text as regclass default NULL on conversion error);
+
+CREATE TABLE test_safecast(col0 text);
+INSERT INTO test_safecast(col0) VALUES ('<value>one</value');
+
+SELECT col0 as text,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) as to_regclass,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) IS NULL as expect_true,
+ CAST(col0 AS "char" DEFAULT NULL ON CONVERSION ERROR) as to_char,
+ CAST(col0 AS name DEFAULT NULL ON CONVERSION ERROR) as to_name,
+ CAST(col0 AS xml DEFAULT NULL ON CONVERSION ERROR) as to_xml
+FROM test_safecast;
+
+SELECT CAST('192.168.1.x' as inet DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('192.168.1.2/30' as cidr DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('22:00:5c:08:55:08:01:02'::macaddr8 as macaddr DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast betweeen range data types
+SELECT CAST('[1,2]'::int4range AS int4multirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[1,2]'::int8range AS int8multirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[1,2]'::numrange AS nummultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::daterange AS datemultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::tsrange AS tsmultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::tstzrange AS tstzmultirange DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast betweeen numeric data types
+CREATE TABLE test_safecast1(
+ col1 float4, col2 float8, col3 numeric, col4 numeric[],
+ col5 int2 default 32767,
+ col6 int4 default 32768,
+ col7 int8 default 4294967296);
+INSERT INTO test_safecast1 VALUES('11.1234', '11.1234', '9223372036854775808', '{11.1234}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('inf', 'inf', 'inf', '{11.1234, 12, inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('-inf', '-inf', '-inf', '{11.1234, 12, -inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('NaN', 'NaN', 'NaN', '{11.1234, 12, -inf, NaN}'::numeric[]);
+
+SELECT col5 as int2,
+ CAST(col5 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col5 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col5 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col5 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col5 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col5 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col5 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col5 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col6 as int4,
+ CAST(col6 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col6 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col6 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col6 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col6 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col6 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col6 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col6 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col7 as int8,
+ CAST(col7 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col7 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col7 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col7 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col7 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col7 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col7 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col7 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col3 as numeric,
+ CAST(col3 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col3 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col3 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col3 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col3 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col3 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col3 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col3 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col4 as num_arr,
+ CAST(col4 AS int2[] DEFAULT NULL ON CONVERSION ERROR) as int2arr,
+ CAST(col4 AS int4[] DEFAULT NULL ON CONVERSION ERROR) as int4arr,
+ CAST(col4 as int8[] DEFAULT NULL ON CONVERSION ERROR) as int8arr,
+ CAST(col4 as float4[] DEFAULT NULL ON CONVERSION ERROR) as f4arr,
+ CAST(col4 as float8[] DEFAULT NULL ON CONVERSION ERROR) as f8arr,
+ CAST(col4 as numeric(10,1)[] DEFAULT NULL ON CONVERSION ERROR) as numarr
+FROM test_safecast1;
+
+SELECT col1 as float4,
+ CAST(col1 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col1 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col1 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col1 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col1 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col1 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col1 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col1 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col2 as float8,
+ CAST(col2 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col2 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col2 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col2 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col2 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col2 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col2 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col2 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+-- date/timestamp/interval related safe type cast
+CREATE TABLE test_safecast2(
+ col0 date, col1 timestamp, col2 timestamptz,
+ col3 interval, col4 time, col5 timetz);
+INSERT INTO test_safecast2 VALUES
+('-infinity', '-infinity', '-infinity', '-infinity',
+ '2003-03-07 15:36:39 America/New_York', '2003-07-07 15:36:39 America/New_York'),
+('-infinity', 'infinity', 'infinity', 'infinity',
+ '11:59:59.99 PM', '11:59:59.99 PM PDT');
+
+SELECT col0 as date,
+ CAST(col0 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col0 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col0 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col0 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col0 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+
+SELECT col1 as timestamp,
+ CAST(col1 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col1 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col1 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col1 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col1 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+
+SELECT col2 as timestamptz,
+ CAST(col2 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col2 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col2 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col2 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col2 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+
+SELECT col3 as interval,
+ CAST(col3 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col3 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col3 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col3 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col3 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale,
+ CAST(col3 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+
+SELECT col4 as time,
+ CAST(col4 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col4 AS timetz DEFAULT NULL ON CONVERSION ERROR) IS NOT NULL as to_timetz,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+
+SELECT col5 as timetz,
+ CAST(col5 AS time DEFAULT NULL ON CONVERSION ERROR) as to_time,
+ CAST(col5 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_time_scale,
+ CAST(col5 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col5 AS timetz(6) DEFAULT NULL ON CONVERSION ERROR) as to_timetz_scale,
+ CAST(col5 AS interval DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col5 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+
+CREATE TABLE test_safecast3(col0 jsonb);
+INSERT INTO test_safecast3(col0) VALUES ('"test"');
+SELECT col0 as jsonb,
+ CAST(col0 AS integer DEFAULT NULL ON CONVERSION ERROR) as to_integer,
+ CAST(col0 AS numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col0 AS bigint DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col0 AS float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col0 AS float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col0 AS boolean DEFAULT NULL ON CONVERSION ERROR) as to_bool,
+ CAST(col0 AS smallint DEFAULT NULL ON CONVERSION ERROR) as to_smallint
+FROM test_safecast3;
+
+-- test deparse
+SET datestyle TO ISO, YMD;
+CREATE VIEW safecastview AS
+SELECT CAST('1234' as char(3) DEFAULT -1111 ON CONVERSION ERROR),
+ CAST(1 as date DEFAULT (('2025-Dec-06'::date + random(min=>1, max=>1::int))) ON CONVERSION ERROR) as cast0,
+ CAST(ARRAY[['1'], ['three'],['a']] AS int[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1,
+ CAST(ARRAY[['1', '2'], ['three', 'a']] AS date[] DEFAULT NULL ON CONVERSION ERROR) as cast2,
+ CAST(ARRAY['three'] AS INT[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast3;
+\sv safecastview
+SELECT * FROM safecastview;
+
+CREATE VIEW safecastview1 AS
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS d_int_arr
+ DEFAULT '{41,43}' ON CONVERSION ERROR) as cast1,
+ CAST(ARRAY[['1', '2', 1.1], ['three', true, B'01']] AS d_int_arr
+ DEFAULT '{41,43}' ON CONVERSION ERROR) as cast2;
+\sv safecastview1
+SELECT * FROM safecastview1;
+
+RESET datestyle;
+
+--test CAST DEFAULT expression mutability
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as int DEFAULT random(min=>1, max=>1) ON CONVERSION ERROR)));
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as xid DEFAULT NULL ON CONVERSION ERROR)));
+CREATE INDEX test_safecast3_idx ON test_safecast3((CAST(col0 as int DEFAULT NULL ON CONVERSION ERROR))); --ok
+SELECT pg_get_indexdef('test_safecast3_idx'::regclass);
+
+DROP TABLE test_safecast;
+DROP TABLE test_safecast1;
+DROP TABLE test_safecast2;
+DROP TABLE test_safecast3;
+DROP TABLE tcast;
+DROP FUNCTION ret_int8;
+DROP FUNCTION ret_setint;
+DROP TYPE comp_domain_with_typmod;
+DROP TYPE comp2;
+DROP DOMAIN d_varchar;
+DROP DOMAIN d_int42;
+DROP DOMAIN d_char3_not_null;
+RESET extra_float_digits;
diff --git a/src/test/regress/sql/create_cast.sql b/src/test/regress/sql/create_cast.sql
index 32187853cc7..0a15a795d87 100644
--- a/src/test/regress/sql/create_cast.sql
+++ b/src/test/regress/sql/create_cast.sql
@@ -62,6 +62,7 @@ $$ SELECT ('bar'::text || $1::text); $$;
CREATE CAST (int4 AS casttesttype) WITH FUNCTION bar_int4_text(int4) AS IMPLICIT;
SELECT 1234::int4::casttesttype; -- Should work now
+SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- error
-- check dependencies generated for that
SELECT pg_describe_object(classid, objid, objsubid) as obj,
diff --git a/src/test/regress/sql/equivclass.sql b/src/test/regress/sql/equivclass.sql
index 7fc2159349b..5ad1d26311d 100644
--- a/src/test/regress/sql/equivclass.sql
+++ b/src/test/regress/sql/equivclass.sql
@@ -98,6 +98,9 @@ create function int8alias1cmp(int8, int8alias1) returns int
alter operator family integer_ops using btree add
function 1 int8alias1cmp (int8, int8alias1);
+-- int8alias2 binary-coercible to int8. so this is ok
+select cast('1'::int8 as int8alias2 default null on conversion error);
+
create table ec0 (ff int8 primary key, f1 int8, f2 int8);
create table ec1 (ff int8 primary key, f1 int8alias1, f2 int8alias2);
create table ec2 (xf int8 primary key, x1 int8alias1, x2 int8alias2);
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b9e671fcda8..731a9e52416 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2688,6 +2688,8 @@ STRLEN
SV
SYNCHRONIZATION_BARRIER
SYSTEM_INFO
+SafeTypeCastExpr
+SafeTypeCastState
SampleScan
SampleScanGetSampleSize_function
SampleScanState
--
2.34.1
v17-0021-error-safe-for-casting-geometry-data-type.patchtext/x-patch; charset=US-ASCII; name=v17-0021-error-safe-for-casting-geometry-data-type.patchDownload
From 6106bd6bc6fc89751bd35f80ca79e4f8768c67a3 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 3 Jan 2026 14:44:38 +0800
Subject: [PATCH v17 21/23] error safe for casting geometry data type
select castsource::regtype, casttarget::regtype, pp.prosrc
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
join pg_type pt on pt.oid = castsource
join pg_type pt1 on pt1.oid = casttarget
and pc.castfunc > 0 and pt.typarray <> 0
and pt.typnamespace = 'pg_catalog'::regnamespace
and pt1.typnamespace = 'pg_catalog'::regnamespace
and (pt.typcategory = 'G' or pt1.typcategory = 'G')
order by castsource::regtype, casttarget::regtype;
castsource | casttarget | prosrc
------------+------------+---------------
point | box | point_box
lseg | point | lseg_center
path | polygon | path_poly
box | point | box_center
box | lseg | box_diagonal
box | polygon | box_poly
box | circle | box_circle
polygon | point | poly_center
polygon | path | poly_path
polygon | box | poly_box
polygon | circle | poly_circle
circle | point | circle_center
circle | box | circle_box
circle | polygon |
(14 rows)
already error safe: point_box, box_diagonal, box_poly, poly_path, poly_box,
circle_center
This patch make these functions error safe: path_poly, lseg_center, box_center,
box_circle, poly_center, poly_circle, circle_box
can not error safe: cast circle to polygon, because it's a SQL function.
discussion: https://postgr.es/m/CACJufxHCMzrHOW=wRe8L30rMhB3sjwAv1LE928Fa7sxMu1Tx-g@mail.gmail.com
---
src/backend/utils/adt/geo_ops.c | 192 +++++++++++++++++++++++++-------
1 file changed, 152 insertions(+), 40 deletions(-)
diff --git a/src/backend/utils/adt/geo_ops.c b/src/backend/utils/adt/geo_ops.c
index c655b015c14..90228ad2175 100644
--- a/src/backend/utils/adt/geo_ops.c
+++ b/src/backend/utils/adt/geo_ops.c
@@ -77,7 +77,8 @@ enum path_delim
/* Routines for points */
static inline void point_construct(Point *result, float8 x, float8 y);
-static inline void point_add_point(Point *result, Point *pt1, Point *pt2);
+static inline void point_add_point(Point *result, Point *pt1, Point *pt2,
+ Node *escontext);
static inline void point_sub_point(Point *result, Point *pt1, Point *pt2);
static inline void point_mul_point(Point *result, Point *pt1, Point *pt2);
static inline void point_div_point(Point *result, Point *pt1, Point *pt2);
@@ -108,7 +109,7 @@ static float8 lseg_closept_lseg(Point *result, LSEG *on_lseg, LSEG *to_lseg);
/* Routines for boxes */
static inline void box_construct(BOX *result, Point *pt1, Point *pt2);
-static void box_cn(Point *center, BOX *box);
+static void box_cn(Point *center, BOX *box, Node *escontext);
static bool box_ov(BOX *box1, BOX *box2);
static float8 box_ar(BOX *box);
static float8 box_ht(BOX *box);
@@ -125,7 +126,7 @@ static float8 circle_ar(CIRCLE *circle);
/* Routines for polygons */
static void make_bound_box(POLYGON *poly);
-static void poly_to_circle(CIRCLE *result, POLYGON *poly);
+static bool poly_to_circle(CIRCLE *result, POLYGON *poly, Node *escontext);
static bool lseg_inside_poly(Point *a, Point *b, POLYGON *poly, int start);
static bool poly_contain_poly(POLYGON *contains_poly, POLYGON *contained_poly);
static bool plist_same(int npts, Point *p1, Point *p2);
@@ -836,8 +837,8 @@ box_distance(PG_FUNCTION_ARGS)
Point a,
b;
- box_cn(&a, box1);
- box_cn(&b, box2);
+ box_cn(&a, box1, NULL);
+ box_cn(&b, box2, NULL);
PG_RETURN_FLOAT8(point_dt(&a, &b, fcinfo->context));
}
@@ -851,7 +852,9 @@ box_center(PG_FUNCTION_ARGS)
BOX *box = PG_GETARG_BOX_P(0);
Point *result = palloc_object(Point);
- box_cn(result, box);
+ box_cn(result, box, fcinfo->context);
+ if ((SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
PG_RETURN_POINT_P(result);
}
@@ -869,13 +872,26 @@ box_ar(BOX *box)
/* box_cn - stores the centerpoint of the box into *center.
*/
static void
-box_cn(Point *center, BOX *box)
+box_cn(Point *center, BOX *box, Node *escontext)
{
- center->x = float8_div(float8_pl(box->high.x, box->low.x), 2.0);
- center->y = float8_div(float8_pl(box->high.y, box->low.y), 2.0);
+ float8 x;
+ float8 y;
+
+ x = float8_pl_safe(box->high.x, box->low.x, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return;
+
+ center->x = float8_div_safe(x, 2.0, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return;
+
+ y = float8_pl_safe(box->high.y, box->low.y, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return;
+
+ center->y = float8_div_safe(y, 2.0, escontext);
}
-
/* box_wd - returns the width (length) of the box
* (horizontal magnitude).
*/
@@ -2329,13 +2345,31 @@ lseg_center(PG_FUNCTION_ARGS)
{
LSEG *lseg = PG_GETARG_LSEG_P(0);
Point *result;
+ float8 x;
+ float8 y;
result = palloc_object(Point);
- result->x = float8_div(float8_pl(lseg->p[0].x, lseg->p[1].x), 2.0);
- result->y = float8_div(float8_pl(lseg->p[0].y, lseg->p[1].y), 2.0);
+ x = float8_pl_safe(lseg->p[0].x, lseg->p[1].x, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ result->x = float8_div_safe(x, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ y = float8_pl_safe(lseg->p[0].y, lseg->p[1].y, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ result->y = float8_div_safe(y, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
PG_RETURN_POINT_P(result);
+
+fail:
+ PG_RETURN_NULL();
}
@@ -3290,7 +3324,7 @@ box_interpt_lseg(Point *result, BOX *box, LSEG *lseg)
if (result != NULL)
{
- box_cn(&point, box);
+ box_cn(&point, box, NULL);
lseg_closept_point(result, lseg, &point);
}
@@ -4121,11 +4155,20 @@ construct_point(PG_FUNCTION_ARGS)
static inline void
-point_add_point(Point *result, Point *pt1, Point *pt2)
+point_add_point(Point *result, Point *pt1, Point *pt2, Node *escontext)
{
- point_construct(result,
- float8_pl(pt1->x, pt2->x),
- float8_pl(pt1->y, pt2->y));
+ float8 x;
+ float8 y;
+
+ x = float8_pl_safe(pt1->x, pt2->x, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return;
+
+ y = float8_pl_safe(pt1->y, pt2->y, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return;
+
+ point_construct(result, x, y);
}
Datum
@@ -4137,7 +4180,7 @@ point_add(PG_FUNCTION_ARGS)
result = palloc_object(Point);
- point_add_point(result, p1, p2);
+ point_add_point(result, p1, p2, NULL);
PG_RETURN_POINT_P(result);
}
@@ -4249,8 +4292,8 @@ box_add(PG_FUNCTION_ARGS)
result = palloc_object(BOX);
- point_add_point(&result->high, &box->high, p);
- point_add_point(&result->low, &box->low, p);
+ point_add_point(&result->high, &box->high, p, NULL);
+ point_add_point(&result->low, &box->low, p, NULL);
PG_RETURN_BOX_P(result);
}
@@ -4413,7 +4456,7 @@ path_add_pt(PG_FUNCTION_ARGS)
int i;
for (i = 0; i < path->npts; i++)
- point_add_point(&path->p[i], &path->p[i], point);
+ point_add_point(&path->p[i], &path->p[i], point, NULL);
PG_RETURN_PATH_P(path);
}
@@ -4471,7 +4514,7 @@ path_poly(PG_FUNCTION_ARGS)
/* This is not very consistent --- other similar cases return NULL ... */
if (!path->closed)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("open path cannot be converted to polygon")));
@@ -4521,7 +4564,9 @@ poly_center(PG_FUNCTION_ARGS)
result = palloc_object(Point);
- poly_to_circle(&circle, poly);
+ if (!poly_to_circle(&circle, poly, fcinfo->context))
+ PG_RETURN_NULL();
+
*result = circle.center;
PG_RETURN_POINT_P(result);
@@ -4983,7 +5028,7 @@ circle_add_pt(PG_FUNCTION_ARGS)
result = palloc_object(CIRCLE);
- point_add_point(&result->center, &circle->center, point);
+ point_add_point(&result->center, &circle->center, point, NULL);
result->radius = circle->radius;
PG_RETURN_CIRCLE_P(result);
@@ -5204,14 +5249,30 @@ circle_box(PG_FUNCTION_ARGS)
box = palloc_object(BOX);
- delta = float8_div(circle->radius, sqrt(2.0));
+ delta = float8_div_safe(circle->radius, sqrt(2.0), fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
- box->high.x = float8_pl(circle->center.x, delta);
- box->low.x = float8_mi(circle->center.x, delta);
- box->high.y = float8_pl(circle->center.y, delta);
- box->low.y = float8_mi(circle->center.y, delta);
+ box->high.x = float8_pl_safe(circle->center.x, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ box->low.x = float8_mi_safe(circle->center.x, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ box->high.y = float8_pl_safe(circle->center.y, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ box->low.y = float8_mi_safe(circle->center.y, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
PG_RETURN_BOX_P(box);
+
+fail:
+ PG_RETURN_NULL();
}
/* box_circle()
@@ -5222,15 +5283,35 @@ box_circle(PG_FUNCTION_ARGS)
{
BOX *box = PG_GETARG_BOX_P(0);
CIRCLE *circle;
+ float8 x;
+ float8 y;
circle = palloc_object(CIRCLE);
- circle->center.x = float8_div(float8_pl(box->high.x, box->low.x), 2.0);
- circle->center.y = float8_div(float8_pl(box->high.y, box->low.y), 2.0);
+ x = float8_pl_safe(box->high.x, box->low.x, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ circle->center.x = float8_div_safe(x, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ y = float8_pl_safe(box->high.y, box->low.y, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ circle->center.y = float8_div_safe(y, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
circle->radius = point_dt(&circle->center, &box->high, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
PG_RETURN_CIRCLE_P(circle);
+
+fail:
+ PG_RETURN_NULL();
}
@@ -5294,10 +5375,11 @@ circle_poly(PG_FUNCTION_ARGS)
* XXX This algorithm should use weighted means of line segments
* rather than straight average values of points - tgl 97/01/21.
*/
-static void
-poly_to_circle(CIRCLE *result, POLYGON *poly)
+static bool
+poly_to_circle(CIRCLE *result, POLYGON *poly, Node *escontext)
{
int i;
+ float8 x;
Assert(poly->npts > 0);
@@ -5306,14 +5388,43 @@ poly_to_circle(CIRCLE *result, POLYGON *poly)
result->radius = 0;
for (i = 0; i < poly->npts; i++)
- point_add_point(&result->center, &result->center, &poly->p[i]);
- result->center.x = float8_div(result->center.x, poly->npts);
- result->center.y = float8_div(result->center.y, poly->npts);
+ {
+ point_add_point(&result->center,
+ &result->center,
+ &poly->p[i],
+ escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+ }
+
+ result->center.x = float8_div_safe(result->center.x,
+ poly->npts,
+ escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ result->center.y = float8_div_safe(result->center.y,
+ poly->npts,
+ escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
for (i = 0; i < poly->npts; i++)
- result->radius = float8_pl(result->radius,
- point_dt(&poly->p[i], &result->center, NULL));
- result->radius = float8_div(result->radius, poly->npts);
+ {
+ x = point_dt(&poly->p[i], &result->center, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ result->radius = float8_pl_safe(result->radius, x, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+ }
+
+ result->radius = float8_div_safe(result->radius, poly->npts, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ return true;
}
Datum
@@ -5324,7 +5435,8 @@ poly_circle(PG_FUNCTION_ARGS)
result = palloc_object(CIRCLE);
- poly_to_circle(result, poly);
+ if (!poly_to_circle(result, poly, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_CIRCLE_P(result);
}
--
2.34.1
v17-0023-error-safe-for-user-defined-CREATE-CAST.patchtext/x-patch; charset=US-ASCII; name=v17-0023-error-safe-for-user-defined-CREATE-CAST.patchDownload
From 1957f55fb6d8dae7a3955178e00d942b9b182542 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sun, 4 Jan 2026 01:13:57 +0800
Subject: [PATCH v17 23/23] error safe for user defined CREATE CAST
pg_cast.casterrorsafe column to indicate castfunc is error safe or not.
change src/include/catalog/pg_cast.dat to indicate that most of the system cast
function support soft error evaluation.
The SAFE keyword is introduced for allow user-defined CREATE CAST can also be
evaluated in soft-error.
The new synopsis of CREATE CAST is:
CREATE CAST (source_type AS target_type)
WITH [SAFE] FUNCTION function_name [ (argument_type [, ...]) ]
[ AS ASSIGNMENT | AS IMPLICIT ]
The following cast in citext, hstore contrib module refactored to error safe:
CAST (bpchar AS citext)
CAST (boolean AS citext)
CAST (inet AS citext)
CAST (text[] AS hstore)
CAST (hstore AS json)
CAST (hstore AS jsonb)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
contrib/citext/citext--1.4.sql | 6 +-
contrib/citext/expected/citext.out | 24 +-
contrib/citext/expected/citext_1.out | 24 +-
contrib/citext/sql/citext.sql | 5 +-
contrib/hstore/expected/hstore.out | 37 +++
contrib/hstore/hstore--1.2--1.3.sql | 2 +-
contrib/hstore/hstore--1.4.sql | 6 +-
contrib/hstore/hstore_io.c | 8 +-
contrib/hstore/sql/hstore.sql | 11 +
doc/src/sgml/catalogs.sgml | 15 +
doc/src/sgml/ref/create_cast.sgml | 14 +-
doc/src/sgml/syntax.sgml | 3 +-
src/backend/catalog/pg_cast.c | 4 +-
src/backend/commands/functioncmds.c | 9 +-
src/backend/commands/typecmds.c | 1 +
src/backend/parser/gram.y | 18 +-
src/backend/parser/parse_expr.c | 10 +-
src/include/catalog/pg_cast.dat | 330 +++++++++++-----------
src/include/catalog/pg_cast.h | 5 +
src/include/nodes/parsenodes.h | 1 +
src/include/parser/kwlist.h | 1 +
src/test/regress/expected/create_cast.out | 8 +-
src/test/regress/expected/opr_sanity.out | 24 +-
src/test/regress/sql/create_cast.sql | 5 +
24 files changed, 353 insertions(+), 218 deletions(-)
diff --git a/contrib/citext/citext--1.4.sql b/contrib/citext/citext--1.4.sql
index 7b061989352..5c87820388f 100644
--- a/contrib/citext/citext--1.4.sql
+++ b/contrib/citext/citext--1.4.sql
@@ -85,9 +85,9 @@ CREATE CAST (citext AS varchar) WITHOUT FUNCTION AS IMPLICIT;
CREATE CAST (citext AS bpchar) WITHOUT FUNCTION AS ASSIGNMENT;
CREATE CAST (text AS citext) WITHOUT FUNCTION AS ASSIGNMENT;
CREATE CAST (varchar AS citext) WITHOUT FUNCTION AS ASSIGNMENT;
-CREATE CAST (bpchar AS citext) WITH FUNCTION citext(bpchar) AS ASSIGNMENT;
-CREATE CAST (boolean AS citext) WITH FUNCTION citext(boolean) AS ASSIGNMENT;
-CREATE CAST (inet AS citext) WITH FUNCTION citext(inet) AS ASSIGNMENT;
+CREATE CAST (bpchar AS citext) WITH SAFE FUNCTION citext(bpchar) AS ASSIGNMENT;
+CREATE CAST (boolean AS citext) WITH SAFE FUNCTION citext(boolean) AS ASSIGNMENT;
+CREATE CAST (inet AS citext) WITH SAFE FUNCTION citext(inet) AS ASSIGNMENT;
--
-- Operator Functions.
diff --git a/contrib/citext/expected/citext.out b/contrib/citext/expected/citext.out
index 33da19d8df4..be328715492 100644
--- a/contrib/citext/expected/citext.out
+++ b/contrib/citext/expected/citext.out
@@ -10,11 +10,12 @@ WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid);
--------+---------
(0 rows)
-SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR); --error
-ERROR: cannot cast type character to citext when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
-LINE 1: SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSI...
- ^
-HINT: Safe type cast for user-defined types are not yet supported
+SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR);
+ citext
+--------
+ abc
+(1 row)
+
-- Test the operators and indexing functions
-- Test = and <>.
SELECT 'a'::citext = 'a'::citext AS t;
@@ -523,6 +524,12 @@ SELECT true::citext = 'true' AS t;
t
(1 row)
+SELECT CAST(true AS citext DEFAULT NULL ON CONVERSION ERROR);
+ citext
+--------
+ true
+(1 row)
+
SELECT 'true'::citext::boolean = true AS t;
t
---
@@ -787,6 +794,13 @@ SELECT '192.168.100.128'::citext::inet = '192.168.100.128'::inet AS t;
t
(1 row)
+SELECT CAST(inet '192.168.100.128' AS citext
+ DEFAULT NULL ON CONVERSION ERROR) = '192.168.100.128/32' AS t;
+ t
+---
+ t
+(1 row)
+
SELECT '08:00:2b:01:02:03'::macaddr::citext = '08:00:2b:01:02:03' AS t;
t
---
diff --git a/contrib/citext/expected/citext_1.out b/contrib/citext/expected/citext_1.out
index 647eea19142..e9f8454c662 100644
--- a/contrib/citext/expected/citext_1.out
+++ b/contrib/citext/expected/citext_1.out
@@ -10,11 +10,12 @@ WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid);
--------+---------
(0 rows)
-SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR); --error
-ERROR: cannot cast type character to citext when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
-LINE 1: SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSI...
- ^
-HINT: Safe type cast for user-defined types are not yet supported
+SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR);
+ citext
+--------
+ abc
+(1 row)
+
-- Test the operators and indexing functions
-- Test = and <>.
SELECT 'a'::citext = 'a'::citext AS t;
@@ -523,6 +524,12 @@ SELECT true::citext = 'true' AS t;
t
(1 row)
+SELECT CAST(true AS citext DEFAULT NULL ON CONVERSION ERROR);
+ citext
+--------
+ true
+(1 row)
+
SELECT 'true'::citext::boolean = true AS t;
t
---
@@ -787,6 +794,13 @@ SELECT '192.168.100.128'::citext::inet = '192.168.100.128'::inet AS t;
t
(1 row)
+SELECT CAST(inet '192.168.100.128' AS citext
+ DEFAULT NULL ON CONVERSION ERROR) = '192.168.100.128/32' AS t;
+ t
+---
+ t
+(1 row)
+
SELECT '08:00:2b:01:02:03'::macaddr::citext = '08:00:2b:01:02:03' AS t;
t
---
diff --git a/contrib/citext/sql/citext.sql b/contrib/citext/sql/citext.sql
index 99794497d47..62f83d749f9 100644
--- a/contrib/citext/sql/citext.sql
+++ b/contrib/citext/sql/citext.sql
@@ -9,7 +9,7 @@ SELECT amname, opcname
FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod
WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid);
-SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR);
-- Test the operators and indexing functions
@@ -165,6 +165,7 @@ SELECT name FROM srt WHERE name SIMILAR TO '%A.*';
-- Explicit casts.
SELECT true::citext = 'true' AS t;
+SELECT CAST(true AS citext DEFAULT NULL ON CONVERSION ERROR);
SELECT 'true'::citext::boolean = true AS t;
SELECT 4::citext = '4' AS t;
@@ -224,6 +225,8 @@ SELECT '192.168.100.128/25'::citext::cidr = '192.168.100.128/25'::cidr AS t;
SELECT '192.168.100.128'::inet::citext = '192.168.100.128/32' AS t;
SELECT '192.168.100.128'::citext::inet = '192.168.100.128'::inet AS t;
+SELECT CAST(inet '192.168.100.128' AS citext
+ DEFAULT NULL ON CONVERSION ERROR) = '192.168.100.128/32' AS t;
SELECT '08:00:2b:01:02:03'::macaddr::citext = '08:00:2b:01:02:03' AS t;
SELECT '08:00:2b:01:02:03'::citext::macaddr = '08:00:2b:01:02:03'::macaddr AS t;
diff --git a/contrib/hstore/expected/hstore.out b/contrib/hstore/expected/hstore.out
index 1836c9acf39..2622137cbf9 100644
--- a/contrib/hstore/expected/hstore.out
+++ b/contrib/hstore/expected/hstore.out
@@ -841,12 +841,28 @@ select '{}'::text[]::hstore;
select ARRAY['a','g','b','h','asd']::hstore;
ERROR: array must have even number of elements
+select CAST(ARRAY['a','g','b','h','asd'] AS hstore
+ DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
select ARRAY['a','g','b','h','asd','i']::hstore;
array
--------------------------------
"a"=>"g", "b"=>"h", "asd"=>"i"
(1 row)
+select ARRAY['a','g','b','h',null,'i']::hstore;
+ERROR: null value not allowed for hstore key
+select CAST(ARRAY['a','g','b','h',null,'i'] AS hstore
+ DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
select ARRAY[['a','g'],['b','h'],['asd','i']]::hstore;
array
--------------------------------
@@ -855,6 +871,13 @@ select ARRAY[['a','g'],['b','h'],['asd','i']]::hstore;
select ARRAY[['a','g','b'],['h','asd','i']]::hstore;
ERROR: array must have two columns
+select CAST(ARRAY[['a','g','b'],['h','asd','i']] AS hstore
+ DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
select ARRAY[[['a','g'],['b','h'],['asd','i']]]::hstore;
ERROR: wrong number of array subscripts
select hstore('{}'::text[]);
@@ -1553,6 +1576,13 @@ select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=
{"b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4", "a key": "1"}
(1 row)
+select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as json
+ default null on conversion error);
+ json
+-------------------------------------------------------------------------------------------------
+ {"b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4", "a key": "1"}
+(1 row)
+
select hstore_to_json_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4, h=> "2016-01-01"');
hstore_to_json_loose
-------------------------------------------------------------------------------------------------------------
@@ -1571,6 +1601,13 @@ select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=
{"b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4", "a key": "1"}
(1 row)
+select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as jsonb
+ default null on conversion error);
+ jsonb
+-------------------------------------------------------------------------------------------------
+ {"b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4", "a key": "1"}
+(1 row)
+
select hstore_to_jsonb_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4, h=> "2016-01-01"');
hstore_to_jsonb_loose
----------------------------------------------------------------------------------------------------------
diff --git a/contrib/hstore/hstore--1.2--1.3.sql b/contrib/hstore/hstore--1.2--1.3.sql
index 0a7056015b7..cbb1a139e69 100644
--- a/contrib/hstore/hstore--1.2--1.3.sql
+++ b/contrib/hstore/hstore--1.2--1.3.sql
@@ -9,7 +9,7 @@ AS 'MODULE_PATHNAME', 'hstore_to_jsonb'
LANGUAGE C IMMUTABLE STRICT;
CREATE CAST (hstore AS jsonb)
- WITH FUNCTION hstore_to_jsonb(hstore);
+ WITH SAFE FUNCTION hstore_to_jsonb(hstore);
CREATE FUNCTION hstore_to_jsonb_loose(hstore)
RETURNS jsonb
diff --git a/contrib/hstore/hstore--1.4.sql b/contrib/hstore/hstore--1.4.sql
index 4294d14ceb5..451c2ed8187 100644
--- a/contrib/hstore/hstore--1.4.sql
+++ b/contrib/hstore/hstore--1.4.sql
@@ -232,7 +232,7 @@ AS 'MODULE_PATHNAME', 'hstore_from_array'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE CAST (text[] AS hstore)
- WITH FUNCTION hstore(text[]);
+ WITH SAFE FUNCTION hstore(text[]);
CREATE FUNCTION hstore_to_json(hstore)
RETURNS json
@@ -240,7 +240,7 @@ AS 'MODULE_PATHNAME', 'hstore_to_json'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE CAST (hstore AS json)
- WITH FUNCTION hstore_to_json(hstore);
+ WITH SAFE FUNCTION hstore_to_json(hstore);
CREATE FUNCTION hstore_to_json_loose(hstore)
RETURNS json
@@ -253,7 +253,7 @@ AS 'MODULE_PATHNAME', 'hstore_to_jsonb'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE CAST (hstore AS jsonb)
- WITH FUNCTION hstore_to_jsonb(hstore);
+ WITH SAFE FUNCTION hstore_to_jsonb(hstore);
CREATE FUNCTION hstore_to_jsonb_loose(hstore)
RETURNS jsonb
diff --git a/contrib/hstore/hstore_io.c b/contrib/hstore/hstore_io.c
index 34e3918811c..f5166679783 100644
--- a/contrib/hstore/hstore_io.c
+++ b/contrib/hstore/hstore_io.c
@@ -738,20 +738,20 @@ hstore_from_array(PG_FUNCTION_ARGS)
case 1:
if ((ARR_DIMS(in_array)[0]) % 2)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
errmsg("array must have even number of elements")));
break;
case 2:
if ((ARR_DIMS(in_array)[1]) != 2)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
errmsg("array must have two columns")));
break;
default:
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
errmsg("wrong number of array subscripts")));
}
@@ -772,7 +772,7 @@ hstore_from_array(PG_FUNCTION_ARGS)
for (i = 0; i < count; ++i)
{
if (in_nulls[i * 2])
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("null value not allowed for hstore key")));
diff --git a/contrib/hstore/sql/hstore.sql b/contrib/hstore/sql/hstore.sql
index efef91292a3..8fa46630d6d 100644
--- a/contrib/hstore/sql/hstore.sql
+++ b/contrib/hstore/sql/hstore.sql
@@ -192,9 +192,16 @@ select pg_column_size(slice(hstore 'aa=>1, b=>2, c=>3', ARRAY['c','b','aa']))
-- array input
select '{}'::text[]::hstore;
select ARRAY['a','g','b','h','asd']::hstore;
+select CAST(ARRAY['a','g','b','h','asd'] AS hstore
+ DEFAULT NULL ON CONVERSION ERROR);
select ARRAY['a','g','b','h','asd','i']::hstore;
+select ARRAY['a','g','b','h',null,'i']::hstore;
+select CAST(ARRAY['a','g','b','h',null,'i'] AS hstore
+ DEFAULT NULL ON CONVERSION ERROR);
select ARRAY[['a','g'],['b','h'],['asd','i']]::hstore;
select ARRAY[['a','g','b'],['h','asd','i']]::hstore;
+select CAST(ARRAY[['a','g','b'],['h','asd','i']] AS hstore
+ DEFAULT NULL ON CONVERSION ERROR);
select ARRAY[[['a','g'],['b','h'],['asd','i']]]::hstore;
select hstore('{}'::text[]);
select hstore(ARRAY['a','g','b','h','asd']);
@@ -363,10 +370,14 @@ select count(*) from testhstore where h = 'pos=>98, line=>371, node=>CBA, indexe
-- json and jsonb
select hstore_to_json('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4');
select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as json);
+select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as json
+ default null on conversion error);
select hstore_to_json_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4, h=> "2016-01-01"');
select hstore_to_jsonb('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4');
select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as jsonb);
+select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as jsonb
+ default null on conversion error);
select hstore_to_jsonb_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4, h=> "2016-01-01"');
create table test_json_agg (f1 text, f2 hstore);
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2fc63442980..8fca3534f32 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1849,6 +1849,21 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<literal>b</literal> means that the types are binary-coercible, thus no conversion is required.
</para></entry>
</row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>casterrorsafe</structfield> <type>bool</type>
+ </para>
+ <para>
+ This flag indicates whether the <structfield>castfunc</structfield> function
+ is error-safe. It is meaningful only when <structfield>castfunc</structfield>
+ is not zero. User-defined casts can set it
+ to <literal>true</literal> via <link linkend="sql-createcast">CREATE CAST</link>.
+ For error-safe type cast, see <xref linkend="sql-syntax-type-casts-safe"/>.
+ </para>
+ </entry>
+ </row>
+
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/ref/create_cast.sgml b/doc/src/sgml/ref/create_cast.sgml
index bad75bc1dce..888d7142e42 100644
--- a/doc/src/sgml/ref/create_cast.sgml
+++ b/doc/src/sgml/ref/create_cast.sgml
@@ -22,7 +22,7 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE CAST (<replaceable>source_type</replaceable> AS <replaceable>target_type</replaceable>)
- WITH FUNCTION <replaceable>function_name</replaceable> [ (<replaceable>argument_type</replaceable> [, ...]) ]
+ WITH [SAFE] FUNCTION <replaceable>function_name</replaceable> [ (<replaceable>argument_type</replaceable> [, ...]) ]
[ AS ASSIGNMENT | AS IMPLICIT ]
CREATE CAST (<replaceable>source_type</replaceable> AS <replaceable>target_type</replaceable>)
@@ -194,6 +194,18 @@ SELECT CAST ( 2 AS numeric ) + 4.0;
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>SAFE</literal></term>
+ <listitem>
+ <para>
+ The function used to perform the cast support soft-error evaluation,
+ Currently, only functions written in C or the internal language are supported.
+ An alternate expression can be specified to be evaluated if the cast
+ error occurs. See <link linkend="sql-syntax-type-casts-safe">safe type cast</link>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal><replaceable>function_name</replaceable>[(<replaceable>argument_type</replaceable> [, ...])]</literal></term>
diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml
index d1cc932f7b1..6af1d21d465 100644
--- a/doc/src/sgml/syntax.sgml
+++ b/doc/src/sgml/syntax.sgml
@@ -2178,9 +2178,8 @@ CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable>
default <replaceable>expression</replaceable>
specified in the <literal>ON CONVERSION ERROR</literal> clause.
- At present, this only support built-in type casts, see <xref linkend="catalog-pg-cast"/>.
User-defined type casts created with <link linkend="sql-createcast">CREATE CAST</link>
- are not supported.
+ are supported too.
</para>
<para>
diff --git a/src/backend/catalog/pg_cast.c b/src/backend/catalog/pg_cast.c
index 5119c2acda2..9eff941eabb 100644
--- a/src/backend/catalog/pg_cast.c
+++ b/src/backend/catalog/pg_cast.c
@@ -48,7 +48,8 @@
ObjectAddress
CastCreate(Oid sourcetypeid, Oid targettypeid,
Oid funcid, Oid incastid, Oid outcastid,
- char castcontext, char castmethod, DependencyType behavior)
+ char castcontext, char castmethod, bool casterrorsafe,
+ DependencyType behavior)
{
Relation relation;
HeapTuple tuple;
@@ -84,6 +85,7 @@ CastCreate(Oid sourcetypeid, Oid targettypeid,
values[Anum_pg_cast_castfunc - 1] = ObjectIdGetDatum(funcid);
values[Anum_pg_cast_castcontext - 1] = CharGetDatum(castcontext);
values[Anum_pg_cast_castmethod - 1] = CharGetDatum(castmethod);
+ values[Anum_pg_cast_casterrorsafe - 1] = BoolGetDatum(casterrorsafe);
tuple = heap_form_tuple(RelationGetDescr(relation), values, nulls);
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index a516b037dea..b818065af27 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -1667,6 +1667,13 @@ CreateCast(CreateCastStmt *stmt)
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("cast function must not return a set")));
+ if (stmt->safe &&
+ procstruct->prolang != INTERNALlanguageId &&
+ procstruct->prolang != ClanguageId)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Safe type cast functions are only supported for C and internal languages"));
+
ReleaseSysCache(tuple);
}
else
@@ -1795,7 +1802,7 @@ CreateCast(CreateCastStmt *stmt)
}
myself = CastCreate(sourcetypeid, targettypeid, funcid, incastid, outcastid,
- castcontext, castmethod, DEPENDENCY_NORMAL);
+ castcontext, castmethod, stmt->safe, DEPENDENCY_NORMAL);
return myself;
}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index e5fa0578889..078551e45a9 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -1754,6 +1754,7 @@ DefineRange(ParseState *pstate, CreateRangeStmt *stmt)
/* Create cast from the range type to its multirange type */
CastCreate(typoid, multirangeOid, castFuncOid, InvalidOid, InvalidOid,
COERCION_CODE_EXPLICIT, COERCION_METHOD_FUNCTION,
+ false,
DEPENDENCY_INTERNAL);
pfree(multirangeArrayName);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 15e4f064782..89bb718d0f6 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -356,7 +356,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <defelt> drop_option
%type <boolean> opt_or_replace opt_no
opt_grant_grant_option
- opt_nowait opt_if_exists opt_with_data
+ opt_nowait opt_safe opt_if_exists opt_with_data
opt_transaction_chain
%type <list> grant_role_opt_list
%type <defelt> grant_role_opt
@@ -779,7 +779,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
RESET RESPECT_P RESTART RESTRICT RETURN RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
ROUTINE ROUTINES ROW ROWS RULE
- SAVEPOINT SCALAR SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT
+ SAFE SAVEPOINT SCALAR SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT
SEQUENCE SEQUENCES
SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SPLIT SOURCE SQL_P STABLE STANDALONE_P
@@ -9345,14 +9345,15 @@ dostmt_opt_item:
*****************************************************************************/
CreateCastStmt: CREATE CAST '(' Typename AS Typename ')'
- WITH FUNCTION function_with_argtypes cast_context
+ WITH opt_safe FUNCTION function_with_argtypes cast_context
{
CreateCastStmt *n = makeNode(CreateCastStmt);
n->sourcetype = $4;
n->targettype = $6;
- n->func = $10;
- n->context = (CoercionContext) $11;
+ n->safe = $9;
+ n->func = $11;
+ n->context = (CoercionContext) $12;
n->inout = false;
$$ = (Node *) n;
}
@@ -9363,6 +9364,7 @@ CreateCastStmt: CREATE CAST '(' Typename AS Typename ')'
n->sourcetype = $4;
n->targettype = $6;
+ n->safe = false;
n->func = NULL;
n->context = (CoercionContext) $10;
n->inout = false;
@@ -9375,6 +9377,7 @@ CreateCastStmt: CREATE CAST '(' Typename AS Typename ')'
n->sourcetype = $4;
n->targettype = $6;
+ n->safe = false;
n->func = NULL;
n->context = (CoercionContext) $10;
n->inout = true;
@@ -9387,6 +9390,9 @@ cast_context: AS IMPLICIT_P { $$ = COERCION_IMPLICIT; }
| /*EMPTY*/ { $$ = COERCION_EXPLICIT; }
;
+opt_safe: SAFE { $$ = true; }
+ | /*EMPTY*/ { $$ = false; }
+ ;
DropCastStmt: DROP CAST opt_if_exists '(' Typename AS Typename ')' opt_drop_behavior
{
@@ -18147,6 +18153,7 @@ unreserved_keyword:
| ROUTINES
| ROWS
| RULE
+ | SAFE
| SAVEPOINT
| SCALAR
| SCHEMA
@@ -18785,6 +18792,7 @@ bare_label_keyword:
| ROW
| ROWS
| RULE
+ | SAFE
| SAVEPOINT
| SCALAR
| SCHEMA
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 6fef908804c..dab76a0bb66 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -2989,7 +2989,6 @@ CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr, Node *sourceexpr,
{
HeapTuple tuple;
bool errorsafe = true;
- bool userdefined = false;
Oid inputBaseType;
Oid targetBaseType;
Oid inputElementType;
@@ -3061,11 +3060,8 @@ CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr, Node *sourceexpr,
{
Form_pg_cast castForm = (Form_pg_cast) GETSTRUCT(tuple);
- if (castForm->oid > FirstUnpinnedObjectId)
- {
+ if (OidIsValid(castForm->castfunc) && !castForm->casterrorsafe)
errorsafe = false;
- userdefined = true;
- }
ReleaseSysCache(tuple);
}
@@ -3079,9 +3075,7 @@ CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr, Node *sourceexpr,
format_type_be(targetType),
"DEFAULT",
"CAST ... ON CONVERSION ERROR"),
- userdefined
- ? errhint("Safe type cast for user-defined types are not yet supported")
- : errhint("Explicit cast is defined but definition is not error safe"),
+ errhint("Explicit cast is defined but definition is not error safe"),
parser_errposition(pstate, exprLocation(sourceexpr)));
}
diff --git a/src/include/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat
index 1b718a15044..30855e2c864 100644
--- a/src/include/catalog/pg_cast.dat
+++ b/src/include/catalog/pg_cast.dat
@@ -19,65 +19,65 @@
# int2->int4->int8->numeric->float4->float8, while casts in the
# reverse direction are assignment-only.
{ castsource => 'int8', casttarget => 'int2', castfunc => 'int2(int8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'int4', castfunc => 'int4(int8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'float4', castfunc => 'float4(int8)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'float8', castfunc => 'float8(int8)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'numeric', castfunc => 'numeric(int8)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'int8', castfunc => 'int8(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'int4', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'float4', castfunc => 'float4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'float8', castfunc => 'float8(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'numeric', castfunc => 'numeric(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'int8', castfunc => 'int8(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'int2', castfunc => 'int2(int4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'float4', castfunc => 'float4(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'float8', castfunc => 'float8(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'numeric', castfunc => 'numeric(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'int8', castfunc => 'int8(float4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'int2', castfunc => 'int2(float4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'int4', castfunc => 'int4(float4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'float8', castfunc => 'float8(float4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'numeric',
- castfunc => 'numeric(float4)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'numeric(float4)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'int8', castfunc => 'int8(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'int2', castfunc => 'int2(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'int4', castfunc => 'int4(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'float4', castfunc => 'float4(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'numeric',
- castfunc => 'numeric(float8)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'numeric(float8)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'int8', castfunc => 'int8(numeric)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'int2', castfunc => 'int2(numeric)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'int4', castfunc => 'int4(numeric)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'float4',
- castfunc => 'float4(numeric)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'float4(numeric)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'float8',
- castfunc => 'float8(numeric)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'float8(numeric)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'money', casttarget => 'numeric', castfunc => 'numeric(money)',
castcontext => 'a', castmethod => 'f' },
{ castsource => 'numeric', casttarget => 'money', castfunc => 'money(numeric)',
@@ -89,13 +89,13 @@
# Allow explicit coercions between int4 and bool
{ castsource => 'int4', casttarget => 'bool', castfunc => 'bool(int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'int4', castfunc => 'int4(bool)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Allow explicit coercions between xid8 and xid
{ castsource => 'xid8', casttarget => 'xid', castfunc => 'xid(xid8)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# OID category: allow implicit conversion from any integral type (including
# int8, to support OID literals > 2G) to OID, as well as assignment coercion
@@ -106,13 +106,13 @@
# casts from text and varchar to regclass, which exist mainly to support
# legacy forms of nextval() and related functions.
{ castsource => 'int8', casttarget => 'oid', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'oid', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'oid', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regproc', castfunc => '0',
@@ -120,13 +120,13 @@
{ castsource => 'regproc', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regproc', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regproc', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regproc', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regproc', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regproc', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'regproc', casttarget => 'regprocedure', castfunc => '0',
@@ -138,13 +138,13 @@
{ castsource => 'regprocedure', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regprocedure', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regprocedure', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regprocedure', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regprocedure', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regprocedure', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regoper', castfunc => '0',
@@ -152,13 +152,13 @@
{ castsource => 'regoper', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regoper', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regoper', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regoper', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regoper', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regoper', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'regoper', casttarget => 'regoperator', castfunc => '0',
@@ -170,13 +170,13 @@
{ castsource => 'regoperator', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regoperator', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regoperator', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regoperator', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regoperator', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regoperator', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regclass', castfunc => '0',
@@ -184,13 +184,13 @@
{ castsource => 'regclass', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regclass', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regclass', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regclass', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regclass', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regclass', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regcollation', castfunc => '0',
@@ -198,13 +198,13 @@
{ castsource => 'regcollation', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regcollation', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regcollation', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regcollation', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regcollation', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regcollation', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regtype', castfunc => '0',
@@ -212,13 +212,13 @@
{ castsource => 'regtype', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regtype', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regtype', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regtype', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regtype', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regtype', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regconfig', castfunc => '0',
@@ -226,13 +226,13 @@
{ castsource => 'regconfig', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regconfig', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regconfig', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regconfig', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regconfig', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regconfig', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regdictionary', castfunc => '0',
@@ -240,31 +240,31 @@
{ castsource => 'regdictionary', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regdictionary', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regdictionary', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regdictionary', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regdictionary', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regdictionary', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'text', casttarget => 'regclass', castfunc => 'regclass',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'regclass', castfunc => 'regclass',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'oid', casttarget => 'regrole', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regrole', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regrole', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regrole', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regrole', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regrole', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regrole', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regnamespace', castfunc => '0',
@@ -272,13 +272,13 @@
{ castsource => 'regnamespace', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regnamespace', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regnamespace', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regnamespace', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regnamespace', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regnamespace', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regdatabase', castfunc => '0',
@@ -286,13 +286,13 @@
{ castsource => 'regdatabase', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regdatabase', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regdatabase', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regdatabase', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regdatabase', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regdatabase', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
@@ -302,57 +302,57 @@
{ castsource => 'text', casttarget => 'varchar', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'bpchar', casttarget => 'text', castfunc => 'text(bpchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bpchar', casttarget => 'varchar', castfunc => 'text(bpchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'text', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'varchar', casttarget => 'bpchar', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'char', casttarget => 'text', castfunc => 'text(char)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'char', casttarget => 'bpchar', castfunc => 'bpchar(char)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'char', casttarget => 'varchar', castfunc => 'text(char)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'name', casttarget => 'text', castfunc => 'text(name)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'name', casttarget => 'bpchar', castfunc => 'bpchar(name)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'name', casttarget => 'varchar', castfunc => 'varchar(name)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'text', casttarget => 'char', castfunc => 'char(text)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bpchar', casttarget => 'char', castfunc => 'char(text)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'char', castfunc => 'char(text)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'text', casttarget => 'name', castfunc => 'name(text)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bpchar', casttarget => 'name', castfunc => 'name(bpchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'name', castfunc => 'name(varchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
# Allow explicit coercions between bytea and integer types
{ castsource => 'int2', casttarget => 'bytea', castfunc => 'bytea(int2)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'bytea', castfunc => 'bytea(int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'bytea', castfunc => 'bytea(int8)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bytea', casttarget => 'int2', castfunc => 'int2(bytea)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bytea', casttarget => 'int4', castfunc => 'int4(bytea)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bytea', casttarget => 'int8', castfunc => 'int8(bytea)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Allow explicit coercions between int4 and "char"
{ castsource => 'char', casttarget => 'int4', castfunc => 'int4(char)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'char', castfunc => 'char(int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# pg_node_tree can be coerced to, but not from, text
{ castsource => 'pg_node_tree', casttarget => 'text', castfunc => '0',
@@ -378,73 +378,73 @@
# Datetime category
{ castsource => 'date', casttarget => 'timestamp',
- castfunc => 'timestamp(date)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timestamp(date)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'date', casttarget => 'timestamptz',
- castfunc => 'timestamptz(date)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timestamptz(date)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'time', casttarget => 'interval', castfunc => 'interval(time)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'time', casttarget => 'timetz', castfunc => 'timetz(time)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'date',
- castfunc => 'date(timestamp)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'date(timestamp)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'time',
- castfunc => 'time(timestamp)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'time(timestamp)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'timestamptz',
- castfunc => 'timestamptz(timestamp)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timestamptz(timestamp)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'date',
- castfunc => 'date(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'date(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'time',
- castfunc => 'time(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'time(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'timestamp',
- castfunc => 'timestamp(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'timestamp(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'timetz',
- castfunc => 'timetz(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'timetz(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'interval', casttarget => 'time', castfunc => 'time(interval)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timetz', casttarget => 'time', castfunc => 'time(timetz)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
# Geometric category
{ castsource => 'point', casttarget => 'box', castfunc => 'box(point)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'lseg', casttarget => 'point', castfunc => 'point(lseg)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'path', casttarget => 'polygon', castfunc => 'polygon(path)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'box', casttarget => 'point', castfunc => 'point(box)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'box', casttarget => 'lseg', castfunc => 'lseg(box)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'box', casttarget => 'polygon', castfunc => 'polygon(box)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'box', casttarget => 'circle', castfunc => 'circle(box)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'polygon', casttarget => 'point', castfunc => 'point(polygon)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'polygon', casttarget => 'path', castfunc => 'path',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'polygon', casttarget => 'box', castfunc => 'box(polygon)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'polygon', casttarget => 'circle',
- castfunc => 'circle(polygon)', castcontext => 'e', castmethod => 'f' },
+ castfunc => 'circle(polygon)', castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'circle', casttarget => 'point', castfunc => 'point(circle)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'circle', casttarget => 'box', castfunc => 'box(circle)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'circle', casttarget => 'polygon',
- castfunc => 'polygon(circle)', castcontext => 'e', castmethod => 'f' },
+ castfunc => 'polygon(circle)', castcontext => 'e', castmethod => 'f'},
# MAC address category
{ castsource => 'macaddr', casttarget => 'macaddr8', castfunc => 'macaddr8',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'macaddr8', casttarget => 'macaddr', castfunc => 'macaddr',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
# INET category
{ castsource => 'cidr', casttarget => 'inet', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'inet', casttarget => 'cidr', castfunc => 'cidr',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
# BitString category
{ castsource => 'bit', casttarget => 'varbit', castfunc => '0',
@@ -454,13 +454,13 @@
# Cross-category casts between bit and int4, int8
{ castsource => 'int8', casttarget => 'bit', castfunc => 'bit(int8,int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'bit', castfunc => 'bit(int4,int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bit', casttarget => 'int8', castfunc => 'int8(bit)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bit', casttarget => 'int4', castfunc => 'int4(bit)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Cross-category casts to and from TEXT
# We need entries here only for a few specialized cases where the behavior
@@ -471,68 +471,68 @@
# behavior will ensue when the automatic cast is applied instead of the
# pg_cast entry!
{ castsource => 'cidr', casttarget => 'text', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'inet', casttarget => 'text', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'text', castfunc => 'text(bool)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'xml', casttarget => 'text', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'text', casttarget => 'xml', castfunc => 'xml',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Cross-category casts to and from VARCHAR
# We support all the same casts as for TEXT.
{ castsource => 'cidr', casttarget => 'varchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'inet', casttarget => 'varchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'varchar', castfunc => 'text(bool)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'xml', casttarget => 'varchar', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'varchar', casttarget => 'xml', castfunc => 'xml',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Cross-category casts to and from BPCHAR
# We support all the same casts as for TEXT.
{ castsource => 'cidr', casttarget => 'bpchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'inet', casttarget => 'bpchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'bpchar', castfunc => 'text(bool)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'xml', casttarget => 'bpchar', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'bpchar', casttarget => 'xml', castfunc => 'xml',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Length-coercion functions
{ castsource => 'bpchar', casttarget => 'bpchar',
castfunc => 'bpchar(bpchar,int4,bool)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'varchar',
castfunc => 'varchar(varchar,int4,bool)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'time', casttarget => 'time', castfunc => 'time(time,int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'timestamp',
castfunc => 'timestamp(timestamp,int4)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'timestamptz',
castfunc => 'timestamptz(timestamptz,int4)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'interval', casttarget => 'interval',
castfunc => 'interval(interval,int4)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timetz', casttarget => 'timetz',
- castfunc => 'timetz(timetz,int4)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timetz(timetz,int4)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bit', casttarget => 'bit', castfunc => 'bit(bit,int4,bool)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varbit', casttarget => 'varbit', castfunc => 'varbit',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'numeric',
- castfunc => 'numeric(numeric,int4)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'numeric(numeric,int4)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
# json to/from jsonb
{ castsource => 'json', casttarget => 'jsonb', castfunc => '0',
@@ -542,36 +542,36 @@
# jsonb to numeric and bool types
{ castsource => 'jsonb', casttarget => 'bool', castfunc => 'bool(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'numeric', castfunc => 'numeric(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'int2', castfunc => 'int2(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'int4', castfunc => 'int4(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'int8', castfunc => 'int8(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'float4', castfunc => 'float4(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'float8', castfunc => 'float8(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# range to multirange
{ castsource => 'int4range', casttarget => 'int4multirange',
castfunc => 'int4multirange(int4range)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8range', casttarget => 'int8multirange',
castfunc => 'int8multirange(int8range)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numrange', casttarget => 'nummultirange',
castfunc => 'nummultirange(numrange)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'daterange', casttarget => 'datemultirange',
castfunc => 'datemultirange(daterange)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'tsrange', casttarget => 'tsmultirange',
- castfunc => 'tsmultirange(tsrange)', castcontext => 'e', castmethod => 'f' },
+ castfunc => 'tsmultirange(tsrange)', castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'tstzrange', casttarget => 'tstzmultirange',
castfunc => 'tstzmultirange(tstzrange)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
]
diff --git a/src/include/catalog/pg_cast.h b/src/include/catalog/pg_cast.h
index 2c9633a5ecb..068f344e200 100644
--- a/src/include/catalog/pg_cast.h
+++ b/src/include/catalog/pg_cast.h
@@ -47,6 +47,10 @@ CATALOG(pg_cast,2605,CastRelationId)
/* cast method */
char castmethod;
+
+ /* cast function error safe */
+ bool casterrorsafe BKI_DEFAULT(f);
+
} FormData_pg_cast;
/* ----------------
@@ -101,6 +105,7 @@ extern ObjectAddress CastCreate(Oid sourcetypeid,
Oid outcastid,
char castcontext,
char castmethod,
+ bool casterrorsafe,
DependencyType behavior);
#endif /* PG_CAST_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 62f27d301d0..1ef19cbc5e0 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4173,6 +4173,7 @@ typedef struct CreateCastStmt
ObjectWithArgs *func;
CoercionContext context;
bool inout;
+ bool safe;
} CreateCastStmt;
/* ----------------------
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f7753c5c8a8..61b808281df 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -397,6 +397,7 @@ PG_KEYWORD("routines", ROUTINES, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("row", ROW, COL_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("rows", ROWS, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("rule", RULE, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("safe", SAFE, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("savepoint", SAVEPOINT, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("scalar", SCALAR, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("schema", SCHEMA, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/test/regress/expected/create_cast.out b/src/test/regress/expected/create_cast.out
index 0054ed0ef67..1e32c041b9f 100644
--- a/src/test/regress/expected/create_cast.out
+++ b/src/test/regress/expected/create_cast.out
@@ -81,6 +81,12 @@ NOTICE: drop cascades to cast from integer to casttesttype
-- Try it with a function that requires an implicit cast
CREATE FUNCTION bar_int4_text(int4) RETURNS text LANGUAGE SQL AS
$$ SELECT ('bar'::text || $1::text); $$;
+CREATE FUNCTION bar_int4_text_plpg(int4) RETURNS text LANGUAGE plpgsql AS
+$$ BEGIN RETURN ('bar'::text || $1::text); END $$;
+CREATE CAST (int4 AS casttesttype) WITH SAFE FUNCTION bar_int4_text(int4) AS IMPLICIT; -- error
+ERROR: Safe type cast functions are only supported for C and internal languages
+CREATE CAST (int4 AS casttesttype) WITH SAFE FUNCTION bar_int4_text_plpg(int4) AS IMPLICIT; -- error
+ERROR: Safe type cast functions are only supported for C and internal languages
CREATE CAST (int4 AS casttesttype) WITH FUNCTION bar_int4_text(int4) AS IMPLICIT;
SELECT 1234::int4::casttesttype; -- Should work now
casttesttype
@@ -92,7 +98,7 @@ SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- err
ERROR: cannot cast type integer to casttesttype when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
LINE 1: SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVE...
^
-HINT: Safe type cast for user-defined types are not yet supported
+HINT: Explicit cast is defined but definition is not error safe
-- check dependencies generated for that
SELECT pg_describe_object(classid, objid, objsubid) as obj,
pg_describe_object(refclassid, refobjid, refobjsubid) as objref,
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index a357e1d0c0e..81ea244859f 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -943,8 +943,8 @@ SELECT *
FROM pg_cast c
WHERE castsource = 0 OR casttarget = 0 OR castcontext NOT IN ('e', 'a', 'i')
OR castmethod NOT IN ('f', 'b' ,'i');
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Check that castfunc is nonzero only for cast methods that need a function,
@@ -953,8 +953,8 @@ SELECT *
FROM pg_cast c
WHERE (castmethod = 'f' AND castfunc = 0)
OR (castmethod IN ('b', 'i') AND castfunc <> 0);
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Look for casts to/from the same type that aren't length coercion functions.
@@ -963,15 +963,15 @@ WHERE (castmethod = 'f' AND castfunc = 0)
SELECT *
FROM pg_cast c
WHERE castsource = casttarget AND castfunc = 0;
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
SELECT c.*
FROM pg_cast c, pg_proc p
WHERE c.castfunc = p.oid AND p.pronargs < 2 AND castsource = casttarget;
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Look for cast functions that don't have the right signature. The
@@ -989,8 +989,8 @@ WHERE c.castfunc = p.oid AND
OR (c.castsource = 'character'::regtype AND
p.proargtypes[0] = 'text'::regtype))
OR NOT binary_coercible(p.prorettype, c.casttarget));
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
SELECT c.*
@@ -998,8 +998,8 @@ FROM pg_cast c, pg_proc p
WHERE c.castfunc = p.oid AND
((p.pronargs > 1 AND p.proargtypes[1] != 'int4'::regtype) OR
(p.pronargs > 2 AND p.proargtypes[2] != 'bool'::regtype));
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Look for binary compatible casts that do not have the reverse
diff --git a/src/test/regress/sql/create_cast.sql b/src/test/regress/sql/create_cast.sql
index 0a15a795d87..30a0ff077c9 100644
--- a/src/test/regress/sql/create_cast.sql
+++ b/src/test/regress/sql/create_cast.sql
@@ -60,6 +60,11 @@ DROP FUNCTION int4_casttesttype(int4) CASCADE;
CREATE FUNCTION bar_int4_text(int4) RETURNS text LANGUAGE SQL AS
$$ SELECT ('bar'::text || $1::text); $$;
+CREATE FUNCTION bar_int4_text_plpg(int4) RETURNS text LANGUAGE plpgsql AS
+$$ BEGIN RETURN ('bar'::text || $1::text); END $$;
+
+CREATE CAST (int4 AS casttesttype) WITH SAFE FUNCTION bar_int4_text(int4) AS IMPLICIT; -- error
+CREATE CAST (int4 AS casttesttype) WITH SAFE FUNCTION bar_int4_text_plpg(int4) AS IMPLICIT; -- error
CREATE CAST (int4 AS casttesttype) WITH FUNCTION bar_int4_text(int4) AS IMPLICIT;
SELECT 1234::int4::casttesttype; -- Should work now
SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- error
--
2.34.1
v17-0020-refactor-point_dt.patchtext/x-patch; charset=US-ASCII; name=v17-0020-refactor-point_dt.patchDownload
From f966c2b4571325caa55200fcbef03271d09a1696 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Mon, 5 Jan 2026 13:33:51 +0800
Subject: [PATCH v17 20/23] refactor point_dt
point_dt is used in multiple locations and will be needed by a later patch, thus
refactoring make it error safe.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/geo_ops.c | 79 +++++++++++++++++++--------------
1 file changed, 46 insertions(+), 33 deletions(-)
diff --git a/src/backend/utils/adt/geo_ops.c b/src/backend/utils/adt/geo_ops.c
index bfb4859b4cb..c655b015c14 100644
--- a/src/backend/utils/adt/geo_ops.c
+++ b/src/backend/utils/adt/geo_ops.c
@@ -82,7 +82,7 @@ static inline void point_sub_point(Point *result, Point *pt1, Point *pt2);
static inline void point_mul_point(Point *result, Point *pt1, Point *pt2);
static inline void point_div_point(Point *result, Point *pt1, Point *pt2);
static inline bool point_eq_point(Point *pt1, Point *pt2);
-static inline float8 point_dt(Point *pt1, Point *pt2);
+static inline float8 point_dt(Point *pt1, Point *pt2, Node *escontext);
static inline float8 point_sl(Point *pt1, Point *pt2);
static int point_inside(Point *p, int npts, Point *plist);
@@ -839,7 +839,7 @@ box_distance(PG_FUNCTION_ARGS)
box_cn(&a, box1);
box_cn(&b, box2);
- PG_RETURN_FLOAT8(point_dt(&a, &b));
+ PG_RETURN_FLOAT8(point_dt(&a, &b, fcinfo->context));
}
@@ -1808,7 +1808,8 @@ path_length(PG_FUNCTION_ARGS)
iprev = path->npts - 1; /* include the closure segment */
}
- result = float8_pl(result, point_dt(&path->p[iprev], &path->p[i]));
+ result = float8_pl(result,
+ point_dt(&path->p[iprev], &path->p[i], fcinfo->context));
}
PG_RETURN_FLOAT8(result);
@@ -1995,13 +1996,24 @@ point_distance(PG_FUNCTION_ARGS)
Point *pt1 = PG_GETARG_POINT_P(0);
Point *pt2 = PG_GETARG_POINT_P(1);
- PG_RETURN_FLOAT8(point_dt(pt1, pt2));
+ PG_RETURN_FLOAT8(point_dt(pt1, pt2, fcinfo->context));
}
static inline float8
-point_dt(Point *pt1, Point *pt2)
+point_dt(Point *pt1, Point *pt2, Node *escontext)
{
- return hypot(float8_mi(pt1->x, pt2->x), float8_mi(pt1->y, pt2->y));
+ float8 x;
+ float8 y;
+
+ x = float8_mi_safe(pt1->x, pt2->x, escontext);
+ if (unlikely(SOFT_ERROR_OCCURRED(escontext)))
+ return 0.0;
+
+ y = float8_mi_safe(pt1->y, pt2->y, escontext);
+ if (unlikely(SOFT_ERROR_OCCURRED(escontext)))
+ return 0.0;
+
+ return hypot(x, y);
}
Datum
@@ -2173,7 +2185,7 @@ lseg_length(PG_FUNCTION_ARGS)
{
LSEG *lseg = PG_GETARG_LSEG_P(0);
- PG_RETURN_FLOAT8(point_dt(&lseg->p[0], &lseg->p[1]));
+ PG_RETURN_FLOAT8(point_dt(&lseg->p[0], &lseg->p[1], fcinfo->context));
}
/*----------------------------------------------------------
@@ -2258,8 +2270,8 @@ lseg_lt(PG_FUNCTION_ARGS)
LSEG *l1 = PG_GETARG_LSEG_P(0);
LSEG *l2 = PG_GETARG_LSEG_P(1);
- PG_RETURN_BOOL(FPlt(point_dt(&l1->p[0], &l1->p[1]),
- point_dt(&l2->p[0], &l2->p[1])));
+ PG_RETURN_BOOL(FPlt(point_dt(&l1->p[0], &l1->p[1], fcinfo->context),
+ point_dt(&l2->p[0], &l2->p[1], fcinfo->context)));
}
Datum
@@ -2268,8 +2280,8 @@ lseg_le(PG_FUNCTION_ARGS)
LSEG *l1 = PG_GETARG_LSEG_P(0);
LSEG *l2 = PG_GETARG_LSEG_P(1);
- PG_RETURN_BOOL(FPle(point_dt(&l1->p[0], &l1->p[1]),
- point_dt(&l2->p[0], &l2->p[1])));
+ PG_RETURN_BOOL(FPle(point_dt(&l1->p[0], &l1->p[1], fcinfo->context),
+ point_dt(&l2->p[0], &l2->p[1], fcinfo->context)));
}
Datum
@@ -2278,8 +2290,8 @@ lseg_gt(PG_FUNCTION_ARGS)
LSEG *l1 = PG_GETARG_LSEG_P(0);
LSEG *l2 = PG_GETARG_LSEG_P(1);
- PG_RETURN_BOOL(FPgt(point_dt(&l1->p[0], &l1->p[1]),
- point_dt(&l2->p[0], &l2->p[1])));
+ PG_RETURN_BOOL(FPgt(point_dt(&l1->p[0], &l1->p[1], fcinfo->context),
+ point_dt(&l2->p[0], &l2->p[1], fcinfo->context)));
}
Datum
@@ -2288,8 +2300,8 @@ lseg_ge(PG_FUNCTION_ARGS)
LSEG *l1 = PG_GETARG_LSEG_P(0);
LSEG *l2 = PG_GETARG_LSEG_P(1);
- PG_RETURN_BOOL(FPge(point_dt(&l1->p[0], &l1->p[1]),
- point_dt(&l2->p[0], &l2->p[1])));
+ PG_RETURN_BOOL(FPge(point_dt(&l1->p[0], &l1->p[1], fcinfo->context),
+ point_dt(&l2->p[0], &l2->p[1], fcinfo->context)));
}
@@ -2743,7 +2755,7 @@ line_closept_point(Point *result, LINE *line, Point *point)
if (result != NULL)
*result = closept;
- return point_dt(&closept, point);
+ return point_dt(&closept, point, NULL);
}
Datum
@@ -2784,7 +2796,7 @@ lseg_closept_point(Point *result, LSEG *lseg, Point *pt)
if (result != NULL)
*result = closept;
- return point_dt(&closept, pt);
+ return point_dt(&closept, pt, NULL);
}
Datum
@@ -3108,9 +3120,9 @@ on_pl(PG_FUNCTION_ARGS)
static bool
lseg_contain_point(LSEG *lseg, Point *pt)
{
- return FPeq(point_dt(pt, &lseg->p[0]) +
- point_dt(pt, &lseg->p[1]),
- point_dt(&lseg->p[0], &lseg->p[1]));
+ return FPeq(point_dt(pt, &lseg->p[0], NULL) +
+ point_dt(pt, &lseg->p[1], NULL),
+ point_dt(&lseg->p[0], &lseg->p[1], NULL));
}
Datum
@@ -3176,11 +3188,12 @@ on_ppath(PG_FUNCTION_ARGS)
if (!path->closed)
{
n = path->npts - 1;
- a = point_dt(pt, &path->p[0]);
+ a = point_dt(pt, &path->p[0], fcinfo->context);
for (i = 0; i < n; i++)
{
- b = point_dt(pt, &path->p[i + 1]);
- if (FPeq(float8_pl(a, b), point_dt(&path->p[i], &path->p[i + 1])))
+ b = point_dt(pt, &path->p[i + 1], fcinfo->context);
+ if (FPeq(float8_pl(a, b),
+ point_dt(&path->p[i], &path->p[i + 1], fcinfo->context)))
PG_RETURN_BOOL(true);
a = b;
}
@@ -4766,7 +4779,7 @@ circle_overlap(PG_FUNCTION_ARGS)
CIRCLE *circle1 = PG_GETARG_CIRCLE_P(0);
CIRCLE *circle2 = PG_GETARG_CIRCLE_P(1);
- PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center),
+ PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center, fcinfo->context),
float8_pl(circle1->radius, circle2->radius)));
}
@@ -4828,7 +4841,7 @@ circle_contained(PG_FUNCTION_ARGS)
CIRCLE *circle1 = PG_GETARG_CIRCLE_P(0);
CIRCLE *circle2 = PG_GETARG_CIRCLE_P(1);
- PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center),
+ PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center, fcinfo->context),
float8_mi(circle2->radius, circle1->radius)));
}
@@ -4840,7 +4853,7 @@ circle_contain(PG_FUNCTION_ARGS)
CIRCLE *circle1 = PG_GETARG_CIRCLE_P(0);
CIRCLE *circle2 = PG_GETARG_CIRCLE_P(1);
- PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center),
+ PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center, fcinfo->context),
float8_mi(circle1->radius, circle2->radius)));
}
@@ -5069,7 +5082,7 @@ circle_distance(PG_FUNCTION_ARGS)
CIRCLE *circle2 = PG_GETARG_CIRCLE_P(1);
float8 result;
- result = float8_mi(point_dt(&circle1->center, &circle2->center),
+ result = float8_mi(point_dt(&circle1->center, &circle2->center, fcinfo->context),
float8_pl(circle1->radius, circle2->radius));
if (result < 0.0)
result = 0.0;
@@ -5085,7 +5098,7 @@ circle_contain_pt(PG_FUNCTION_ARGS)
Point *point = PG_GETARG_POINT_P(1);
float8 d;
- d = point_dt(&circle->center, point);
+ d = point_dt(&circle->center, point, fcinfo->context);
PG_RETURN_BOOL(d <= circle->radius);
}
@@ -5097,7 +5110,7 @@ pt_contained_circle(PG_FUNCTION_ARGS)
CIRCLE *circle = PG_GETARG_CIRCLE_P(1);
float8 d;
- d = point_dt(&circle->center, point);
+ d = point_dt(&circle->center, point, fcinfo->context);
PG_RETURN_BOOL(d <= circle->radius);
}
@@ -5112,7 +5125,7 @@ dist_pc(PG_FUNCTION_ARGS)
CIRCLE *circle = PG_GETARG_CIRCLE_P(1);
float8 result;
- result = float8_mi(point_dt(point, &circle->center),
+ result = float8_mi(point_dt(point, &circle->center, fcinfo->context),
circle->radius);
if (result < 0.0)
result = 0.0;
@@ -5130,7 +5143,7 @@ dist_cpoint(PG_FUNCTION_ARGS)
Point *point = PG_GETARG_POINT_P(1);
float8 result;
- result = float8_mi(point_dt(point, &circle->center), circle->radius);
+ result = float8_mi(point_dt(point, &circle->center, fcinfo->context), circle->radius);
if (result < 0.0)
result = 0.0;
@@ -5215,7 +5228,7 @@ box_circle(PG_FUNCTION_ARGS)
circle->center.x = float8_div(float8_pl(box->high.x, box->low.x), 2.0);
circle->center.y = float8_div(float8_pl(box->high.y, box->low.y), 2.0);
- circle->radius = point_dt(&circle->center, &box->high);
+ circle->radius = point_dt(&circle->center, &box->high, fcinfo->context);
PG_RETURN_CIRCLE_P(circle);
}
@@ -5299,7 +5312,7 @@ poly_to_circle(CIRCLE *result, POLYGON *poly)
for (i = 0; i < poly->npts; i++)
result->radius = float8_pl(result->radius,
- point_dt(&poly->p[i], &result->center));
+ point_dt(&poly->p[i], &result->center, NULL));
result->radius = float8_div(result->radius, poly->npts);
}
--
2.34.1
v17-0019-introduce-float8-safe-function.patchtext/x-patch; charset=US-ASCII; name=v17-0019-introduce-float8-safe-function.patchDownload
From 885bbfdbcba651dd9bea7e76c8f69f904ddf9565 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Fri, 2 Jan 2026 16:14:28 +0800
Subject: [PATCH v17 19/23] introduce float8 safe function
this patch introduce the following function:
float8_pl_safe
float8_mi_safe
float8_mul_safe
float8_div_safe
refactoring existing function is be too invasive. thus add these new functions.
It's close to existing non-safe version functions.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/include/utils/float.h | 79 ++++++++++++++++++++++++++++-----------
1 file changed, 58 insertions(+), 21 deletions(-)
diff --git a/src/include/utils/float.h b/src/include/utils/float.h
index d2e989960a5..8723b4c00b0 100644
--- a/src/include/utils/float.h
+++ b/src/include/utils/float.h
@@ -109,16 +109,24 @@ float4_pl(const float4 val1, const float4 val2)
return result;
}
+static inline float8
+float8_pl_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+ float8 result;
+
+ result = val1 + val2;
+ if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
+ {
+ float_overflow_error(escontext);
+ return 0.0;
+ }
+ return result;
+}
+
static inline float8
float8_pl(const float8 val1, const float8 val2)
{
- float8 result;
-
- result = val1 + val2;
- if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error(NULL);
-
- return result;
+ return float8_pl_safe(val1, val2, NULL);;
}
static inline float4
@@ -133,16 +141,24 @@ float4_mi(const float4 val1, const float4 val2)
return result;
}
+static inline float8
+float8_mi_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+ float8 result;
+
+ result = val1 - val2;
+ if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
+ {
+ float_overflow_error(escontext);
+ return 0.0;
+ }
+ return result;
+}
+
static inline float8
float8_mi(const float8 val1, const float8 val2)
{
- float8 result;
-
- result = val1 - val2;
- if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error(NULL);
-
- return result;
+ return float8_mi_safe(val1, val2, NULL);
}
static inline float4
@@ -160,19 +176,33 @@ float4_mul(const float4 val1, const float4 val2)
}
static inline float8
-float8_mul(const float8 val1, const float8 val2)
+float8_mul_safe(const float8 val1, const float8 val2, struct Node *escontext)
{
float8 result;
result = val1 * val2;
if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error(NULL);
+ {
+ float_overflow_error(escontext);
+ return 0.0;
+ }
+
if (unlikely(result == 0.0) && val1 != 0.0 && val2 != 0.0)
- float_underflow_error(NULL);
+ {
+ float_underflow_error(escontext);
+ return 0.0;
+ }
return result;
}
+static inline float8
+float8_mul(const float8 val1, const float8 val2)
+{
+ return float8_mul_safe(val1, val2, NULL);
+}
+
+
static inline float4
float4_div(const float4 val1, const float4 val2)
{
@@ -190,21 +220,28 @@ float4_div(const float4 val1, const float4 val2)
}
static inline float8
-float8_div(const float8 val1, const float8 val2)
+float8_div_safe(const float8 val1, const float8 val2, struct Node *escontext)
{
float8 result;
if (unlikely(val2 == 0.0) && !isnan(val1))
- float_zero_divide_error(NULL);
+ float_zero_divide_error(escontext);
result = val1 / val2;
if (unlikely(isinf(result)) && !isinf(val1))
- float_overflow_error(NULL);
+ float_overflow_error(escontext);
if (unlikely(result == 0.0) && val1 != 0.0 && !isinf(val2))
- float_underflow_error(NULL);
+ float_underflow_error(escontext);
return result;
}
+static inline float8
+float8_div(const float8 val1, const float8 val2)
+{
+ return float8_div_safe(val1, val2, NULL);
+}
+
+
/*
* Routines for NaN-aware comparisons
*
--
2.34.1
v17-0017-error-safe-for-casting-jsonb-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v17-0017-error-safe-for-casting-jsonb-to-other-types-per-pg_cast.patchDownload
From ef6e617bca19901053a1bec6e473313a49833a93 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Mon, 24 Nov 2025 14:26:53 +0800
Subject: [PATCH v17 17/23] error safe for casting jsonb to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'jsonb'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+---------------+---------
jsonb | boolean | 3556 | e | f | jsonb_bool | bool
jsonb | numeric | 3449 | e | f | jsonb_numeric | numeric
jsonb | smallint | 3450 | e | f | jsonb_int2 | int2
jsonb | integer | 3451 | e | f | jsonb_int4 | int4
jsonb | bigint | 3452 | e | f | jsonb_int8 | int8
jsonb | real | 3453 | e | f | jsonb_float4 | float4
jsonb | double precision | 2580 | e | f | jsonb_float8 | float8
(7 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/jsonb.c | 74 +++++++++++++++++++++++++++--------
1 file changed, 58 insertions(+), 16 deletions(-)
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index 28e7f80d77f..abcd5baa7c0 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -1818,7 +1818,7 @@ JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res)
* Emit correct, translatable cast error message
*/
static void
-cannotCastJsonbValue(enum jbvType type, const char *sqltype)
+cannotCastJsonbValue(enum jbvType type, const char *sqltype, Node *escontext)
{
static const struct
{
@@ -1839,7 +1839,7 @@ cannotCastJsonbValue(enum jbvType type, const char *sqltype)
for (i = 0; i < lengthof(messages); i++)
if (messages[i].type == type)
- ereport(ERROR,
+ ereturn(escontext,,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(messages[i].msg, sqltype)));
@@ -1854,7 +1854,10 @@ jsonb_bool(PG_FUNCTION_ARGS)
JsonbValue v;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "boolean");
+ {
+ cannotCastJsonbValue(v.type, "boolean", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -1863,7 +1866,10 @@ jsonb_bool(PG_FUNCTION_ARGS)
}
if (v.type != jbvBool)
- cannotCastJsonbValue(v.type, "boolean");
+ {
+ cannotCastJsonbValue(v.type, "boolean", fcinfo->context);
+ PG_RETURN_NULL();
+ }
PG_FREE_IF_COPY(in, 0);
@@ -1878,7 +1884,10 @@ jsonb_numeric(PG_FUNCTION_ARGS)
Numeric retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "numeric");
+ {
+ cannotCastJsonbValue(v.type, "numeric", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -1887,7 +1896,10 @@ jsonb_numeric(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "numeric");
+ {
+ cannotCastJsonbValue(v.type, "numeric", fcinfo->context);
+ PG_RETURN_NULL();
+ }
/*
* v.val.numeric points into jsonb body, so we need to make a copy to
@@ -1908,7 +1920,10 @@ jsonb_int2(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "smallint");
+ {
+ cannotCastJsonbValue(v.type, "smallint", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -1917,7 +1932,10 @@ jsonb_int2(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "smallint");
+ {
+ cannotCastJsonbValue(v.type, "smallint", fcinfo->context);
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_int2,
NumericGetDatum(v.val.numeric));
@@ -1935,7 +1953,10 @@ jsonb_int4(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "integer");
+ {
+ cannotCastJsonbValue(v.type, "integer", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -1944,7 +1965,10 @@ jsonb_int4(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "integer");
+ {
+ cannotCastJsonbValue(v.type, "integer", fcinfo->context);
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_int4,
NumericGetDatum(v.val.numeric));
@@ -1962,7 +1986,10 @@ jsonb_int8(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "bigint");
+ {
+ cannotCastJsonbValue(v.type, "bigint", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -1971,7 +1998,10 @@ jsonb_int8(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "bigint");
+ {
+ cannotCastJsonbValue(v.type, "bigint", fcinfo->context);
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_int8,
NumericGetDatum(v.val.numeric));
@@ -1989,7 +2019,10 @@ jsonb_float4(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "real");
+ {
+ cannotCastJsonbValue(v.type, "real", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -1998,7 +2031,10 @@ jsonb_float4(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "real");
+ {
+ cannotCastJsonbValue(v.type, "real", fcinfo->context);
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_float4,
NumericGetDatum(v.val.numeric));
@@ -2016,7 +2052,10 @@ jsonb_float8(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "double precision");
+ {
+ cannotCastJsonbValue(v.type, "double precision", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2025,7 +2064,10 @@ jsonb_float8(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "double precision");
+ {
+ cannotCastJsonbValue(v.type, "double precision", fcinfo->context);
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_float8,
NumericGetDatum(v.val.numeric));
--
2.34.1
v17-0018-refactor-float_overflow_error-float_underflow_error-float_zero_d.patchtext/x-patch; charset=US-ASCII; name=v17-0018-refactor-float_overflow_error-float_underflow_error-float_zero_d.patchDownload
From 320b55f406a015f527e6ebc02aa139c8684774f8 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Tue, 9 Dec 2025 14:36:39 +0800
Subject: [PATCH v17 18/23] refactor
float_overflow_error,float_underflow_error,float_zero_divide_error
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
contrib/btree_gist/btree_float4.c | 2 +-
contrib/btree_gist/btree_float8.c | 4 +-
src/backend/utils/adt/float.c | 104 +++++++++++++++---------------
src/include/utils/float.h | 34 +++++-----
4 files changed, 72 insertions(+), 72 deletions(-)
diff --git a/contrib/btree_gist/btree_float4.c b/contrib/btree_gist/btree_float4.c
index d9c859835da..a7325a7bb29 100644
--- a/contrib/btree_gist/btree_float4.c
+++ b/contrib/btree_gist/btree_float4.c
@@ -101,7 +101,7 @@ float4_dist(PG_FUNCTION_ARGS)
r = a - b;
if (unlikely(isinf(r)) && !isinf(a) && !isinf(b))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT4(fabsf(r));
}
diff --git a/contrib/btree_gist/btree_float8.c b/contrib/btree_gist/btree_float8.c
index 567beede178..7c99b84de35 100644
--- a/contrib/btree_gist/btree_float8.c
+++ b/contrib/btree_gist/btree_float8.c
@@ -79,7 +79,7 @@ gbt_float8_dist(const void *a, const void *b, FmgrInfo *flinfo)
r = arg1 - arg2;
if (unlikely(isinf(r)) && !isinf(arg1) && !isinf(arg2))
- float_overflow_error();
+ float_overflow_error(NULL);
return fabs(r);
}
@@ -109,7 +109,7 @@ float8_dist(PG_FUNCTION_ARGS)
r = a - b;
if (unlikely(isinf(r)) && !isinf(a) && !isinf(b))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(fabs(r));
}
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index 502398d29ec..5b71c40b00c 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -83,25 +83,25 @@ static void init_degree_constants(void);
* This does mean that you don't get a useful error location indicator.
*/
pg_noinline void
-float_overflow_error(void)
+float_overflow_error(struct Node *escontext)
{
- ereport(ERROR,
+ errsave(escontext,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("value out of range: overflow")));
}
pg_noinline void
-float_underflow_error(void)
+float_underflow_error(struct Node *escontext)
{
- ereport(ERROR,
+ errsave(escontext,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("value out of range: underflow")));
}
pg_noinline void
-float_zero_divide_error(void)
+float_zero_divide_error(struct Node *escontext)
{
- ereport(ERROR,
+ errsave(escontext,
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
}
@@ -1460,9 +1460,9 @@ dsqrt(PG_FUNCTION_ARGS)
result = sqrt(arg1);
if (unlikely(isinf(result)) && !isinf(arg1))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0) && arg1 != 0.0)
- float_underflow_error();
+ float_underflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1479,9 +1479,9 @@ dcbrt(PG_FUNCTION_ARGS)
result = cbrt(arg1);
if (unlikely(isinf(result)) && !isinf(arg1))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0) && arg1 != 0.0)
- float_underflow_error();
+ float_underflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1617,24 +1617,24 @@ dpow(PG_FUNCTION_ARGS)
if (absx == 1.0)
result = 1.0;
else if (arg2 >= 0.0 ? (absx > 1.0) : (absx < 1.0))
- float_overflow_error();
+ float_overflow_error(NULL);
else
- float_underflow_error();
+ float_underflow_error(NULL);
}
}
else if (errno == ERANGE)
{
if (result != 0.0)
- float_overflow_error();
+ float_overflow_error(NULL);
else
- float_underflow_error();
+ float_underflow_error(NULL);
}
else
{
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0) && arg1 != 0.0)
- float_underflow_error();
+ float_underflow_error(NULL);
}
}
@@ -1674,14 +1674,14 @@ dexp(PG_FUNCTION_ARGS)
if (unlikely(errno == ERANGE))
{
if (result != 0.0)
- float_overflow_error();
+ float_overflow_error(NULL);
else
- float_underflow_error();
+ float_underflow_error(NULL);
}
else if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
else if (unlikely(result == 0.0))
- float_underflow_error();
+ float_underflow_error(NULL);
}
PG_RETURN_FLOAT8(result);
@@ -1712,9 +1712,9 @@ dlog1(PG_FUNCTION_ARGS)
result = log(arg1);
if (unlikely(isinf(result)) && !isinf(arg1))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0) && arg1 != 1.0)
- float_underflow_error();
+ float_underflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1745,9 +1745,9 @@ dlog10(PG_FUNCTION_ARGS)
result = log10(arg1);
if (unlikely(isinf(result)) && !isinf(arg1))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0) && arg1 != 1.0)
- float_underflow_error();
+ float_underflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1778,7 +1778,7 @@ dacos(PG_FUNCTION_ARGS)
result = acos(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1809,7 +1809,7 @@ dasin(PG_FUNCTION_ARGS)
result = asin(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1835,7 +1835,7 @@ datan(PG_FUNCTION_ARGS)
*/
result = atan(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1861,7 +1861,7 @@ datan2(PG_FUNCTION_ARGS)
*/
result = atan2(arg1, arg2);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1902,7 +1902,7 @@ dcos(PG_FUNCTION_ARGS)
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("input is out of range")));
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1957,7 +1957,7 @@ dsin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("input is out of range")));
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2137,7 +2137,7 @@ dacosd(PG_FUNCTION_ARGS)
result = 90.0 + asind_q1(-arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2174,7 +2174,7 @@ dasind(PG_FUNCTION_ARGS)
result = -asind_q1(-arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2206,7 +2206,7 @@ datand(PG_FUNCTION_ARGS)
result = (atan_arg1 / atan_1_0) * 45.0;
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2242,7 +2242,7 @@ datan2d(PG_FUNCTION_ARGS)
result = (atan2_arg1_arg2 / atan_1_0) * 45.0;
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2365,7 +2365,7 @@ dcosd(PG_FUNCTION_ARGS)
result = sign * cosd_q1(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2487,7 +2487,7 @@ dsind(PG_FUNCTION_ARGS)
result = sign * sind_q1(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2645,7 +2645,7 @@ dcosh(PG_FUNCTION_ARGS)
result = get_float8_infinity();
if (unlikely(result == 0.0))
- float_underflow_error();
+ float_underflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2665,7 +2665,7 @@ dtanh(PG_FUNCTION_ARGS)
result = tanh(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2765,7 +2765,7 @@ derf(PG_FUNCTION_ARGS)
result = erf(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2785,7 +2785,7 @@ derfc(PG_FUNCTION_ARGS)
result = erfc(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2814,7 +2814,7 @@ dgamma(PG_FUNCTION_ARGS)
/* Per POSIX, an input of -Inf causes a domain error */
if (arg1 < 0)
{
- float_overflow_error();
+ float_overflow_error(NULL);
result = get_float8_nan(); /* keep compiler quiet */
}
else
@@ -2836,12 +2836,12 @@ dgamma(PG_FUNCTION_ARGS)
if (errno != 0 || isinf(result) || isnan(result))
{
if (result != 0.0)
- float_overflow_error();
+ float_overflow_error(NULL);
else
- float_underflow_error();
+ float_underflow_error(NULL);
}
else if (result == 0.0)
- float_underflow_error();
+ float_underflow_error(NULL);
}
PG_RETURN_FLOAT8(result);
@@ -2873,7 +2873,7 @@ dlgamma(PG_FUNCTION_ARGS)
* to report overflow, but it should never underflow.
*/
if (errno == ERANGE || (isinf(result) && !isinf(arg1)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -3013,7 +3013,7 @@ float8_combine(PG_FUNCTION_ARGS)
tmp = Sx1 / N1 - Sx2 / N2;
Sxx = Sxx1 + Sxx2 + N1 * N2 * tmp * tmp / N;
if (unlikely(isinf(Sxx)) && !isinf(Sxx1) && !isinf(Sxx2))
- float_overflow_error();
+ float_overflow_error(NULL);
}
/*
@@ -3080,7 +3080,7 @@ float8_accum(PG_FUNCTION_ARGS)
if (isinf(Sx) || isinf(Sxx))
{
if (!isinf(transvalues[1]) && !isinf(newval))
- float_overflow_error();
+ float_overflow_error(NULL);
Sxx = get_float8_nan();
}
@@ -3163,7 +3163,7 @@ float4_accum(PG_FUNCTION_ARGS)
if (isinf(Sx) || isinf(Sxx))
{
if (!isinf(transvalues[1]) && !isinf(newval))
- float_overflow_error();
+ float_overflow_error(NULL);
Sxx = get_float8_nan();
}
@@ -3430,7 +3430,7 @@ float8_regr_accum(PG_FUNCTION_ARGS)
(isinf(Sxy) &&
!isinf(transvalues[1]) && !isinf(newvalX) &&
!isinf(transvalues[3]) && !isinf(newvalY)))
- float_overflow_error();
+ float_overflow_error(NULL);
if (isinf(Sxx))
Sxx = get_float8_nan();
@@ -3603,15 +3603,15 @@ float8_regr_combine(PG_FUNCTION_ARGS)
tmp1 = Sx1 / N1 - Sx2 / N2;
Sxx = Sxx1 + Sxx2 + N1 * N2 * tmp1 * tmp1 / N;
if (unlikely(isinf(Sxx)) && !isinf(Sxx1) && !isinf(Sxx2))
- float_overflow_error();
+ float_overflow_error(NULL);
Sy = float8_pl(Sy1, Sy2);
tmp2 = Sy1 / N1 - Sy2 / N2;
Syy = Syy1 + Syy2 + N1 * N2 * tmp2 * tmp2 / N;
if (unlikely(isinf(Syy)) && !isinf(Syy1) && !isinf(Syy2))
- float_overflow_error();
+ float_overflow_error(NULL);
Sxy = Sxy1 + Sxy2 + N1 * N2 * tmp1 * tmp2 / N;
if (unlikely(isinf(Sxy)) && !isinf(Sxy1) && !isinf(Sxy2))
- float_overflow_error();
+ float_overflow_error(NULL);
if (float8_eq(Cx1, Cx2))
Cx = Cx1;
else
diff --git a/src/include/utils/float.h b/src/include/utils/float.h
index b340678ca92..d2e989960a5 100644
--- a/src/include/utils/float.h
+++ b/src/include/utils/float.h
@@ -30,9 +30,9 @@ extern PGDLLIMPORT int extra_float_digits;
/*
* Utility functions in float.c
*/
-pg_noreturn extern void float_overflow_error(void);
-pg_noreturn extern void float_underflow_error(void);
-pg_noreturn extern void float_zero_divide_error(void);
+extern void float_overflow_error(struct Node *escontext);
+extern void float_underflow_error(struct Node *escontext);
+extern void float_zero_divide_error(struct Node *escontext);
extern int is_infinite(float8 val);
extern float8 float8in_internal(char *num, char **endptr_p,
const char *type_name, const char *orig_string,
@@ -104,7 +104,7 @@ float4_pl(const float4 val1, const float4 val2)
result = val1 + val2;
if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error();
+ float_overflow_error(NULL);
return result;
}
@@ -116,7 +116,7 @@ float8_pl(const float8 val1, const float8 val2)
result = val1 + val2;
if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error();
+ float_overflow_error(NULL);
return result;
}
@@ -128,7 +128,7 @@ float4_mi(const float4 val1, const float4 val2)
result = val1 - val2;
if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error();
+ float_overflow_error(NULL);
return result;
}
@@ -140,7 +140,7 @@ float8_mi(const float8 val1, const float8 val2)
result = val1 - val2;
if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error();
+ float_overflow_error(NULL);
return result;
}
@@ -152,9 +152,9 @@ float4_mul(const float4 val1, const float4 val2)
result = val1 * val2;
if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0f) && val1 != 0.0f && val2 != 0.0f)
- float_underflow_error();
+ float_underflow_error(NULL);
return result;
}
@@ -166,9 +166,9 @@ float8_mul(const float8 val1, const float8 val2)
result = val1 * val2;
if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0) && val1 != 0.0 && val2 != 0.0)
- float_underflow_error();
+ float_underflow_error(NULL);
return result;
}
@@ -179,12 +179,12 @@ float4_div(const float4 val1, const float4 val2)
float4 result;
if (unlikely(val2 == 0.0f) && !isnan(val1))
- float_zero_divide_error();
+ float_zero_divide_error(NULL);
result = val1 / val2;
if (unlikely(isinf(result)) && !isinf(val1))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0f) && val1 != 0.0f && !isinf(val2))
- float_underflow_error();
+ float_underflow_error(NULL);
return result;
}
@@ -195,12 +195,12 @@ float8_div(const float8 val1, const float8 val2)
float8 result;
if (unlikely(val2 == 0.0) && !isnan(val1))
- float_zero_divide_error();
+ float_zero_divide_error(NULL);
result = val1 / val2;
if (unlikely(isinf(result)) && !isinf(val1))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0) && val1 != 0.0 && !isinf(val2))
- float_underflow_error();
+ float_underflow_error(NULL);
return result;
}
--
2.34.1
v17-0015-error-safe-for-casting-timestamptz-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v17-0015-error-safe-for-casting-timestamptz-to-other-types-per-pg_cast.patchDownload
From 02f751fcaa918177922987b59463d9c2f1d469ba Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Tue, 2 Dec 2025 12:04:40 +0800
Subject: [PATCH v17 15/23] error safe for casting timestamptz to other types
per pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
and pc.castfunc > 0 and (castsource::regtype ='timestamptz'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
--------------------------+-----------------------------+----------+-------------+------------+-----------------------+-------------
timestamp with time zone | date | 1178 | a | f | timestamptz_date | date
timestamp with time zone | time without time zone | 2019 | a | f | timestamptz_time | time
timestamp with time zone | timestamp without time zone | 2027 | a | f | timestamptz_timestamp | timestamp
timestamp with time zone | time with time zone | 1388 | a | f | timestamptz_timetz | timetz
timestamp with time zone | timestamp with time zone | 1967 | i | f | timestamptz_scale | timestamptz
(5 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 9 ++++++---
src/backend/utils/adt/timestamp.c | 10 ++++++++--
2 files changed, 14 insertions(+), 5 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index bc4c67775dd..be52777d88d 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -1406,7 +1406,10 @@ timestamptz_date(PG_FUNCTION_ARGS)
TimestampTz timestamp = PG_GETARG_TIMESTAMP(0);
DateADT result;
- result = timestamptz2date_safe(timestamp, NULL);
+ result = timestamptz2date_safe(timestamp, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->args))
+ PG_RETURN_NULL();
+
PG_RETURN_DATEADT(result);
}
@@ -2036,7 +2039,7 @@ timestamptz_time(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
@@ -2955,7 +2958,7 @@ timestamptz_timetz(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 62a7d2230d1..3623dcab3bf 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -875,7 +875,8 @@ timestamptz_scale(PG_FUNCTION_ARGS)
result = timestamp;
- AdjustTimestampForTypmod(&result, typmod, NULL);
+ if (!AdjustTimestampForTypmod(&result, typmod, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_TIMESTAMPTZ(result);
}
@@ -6494,8 +6495,13 @@ Datum
timestamptz_timestamp(PG_FUNCTION_ARGS)
{
TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(0);
+ Timestamp result;
- PG_RETURN_TIMESTAMP(timestamptz2timestamp(timestamp));
+ result = timestamptz2timestamp_safe(timestamp, fcinfo->context);
+ if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
+
+ PG_RETURN_TIMESTAMP(result);
}
/*
--
2.34.1
v17-0014-error-safe-for-casting-interval-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v17-0014-error-safe-for-casting-interval-to-other-types-per-pg_cast.patchDownload
From 943637ebc06b8204224bb4797e2b92dbb434235b Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Fri, 12 Dec 2025 15:05:26 +0800
Subject: [PATCH v17 14/23] error safe for casting interval to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'interval'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------------+----------+-------------+------------+----------------+----------
interval | time without time zone | 1419 | a | f | interval_time | time
interval | interval | 1200 | i | f | interval_scale | interval
(2 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 2 +-
src/backend/utils/adt/timestamp.c | 3 ++-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index e8dc8d276bf..bc4c67775dd 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2106,7 +2106,7 @@ interval_time(PG_FUNCTION_ARGS)
TimeADT result;
if (INTERVAL_NOT_FINITE(span))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("cannot convert infinite interval to time")));
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 8deb2369471..62a7d2230d1 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1334,7 +1334,8 @@ interval_scale(PG_FUNCTION_ARGS)
result = palloc_object(Interval);
*result = *interval;
- AdjustIntervalForTypmod(result, typmod, NULL);
+ if (!AdjustIntervalForTypmod(result, typmod, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_INTERVAL_P(result);
}
--
2.34.1
v17-0016-error-safe-for-casting-timestamp-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v17-0016-error-safe-for-casting-timestamp-to-other-types-per-pg_cast.patchDownload
From d37e7074f06d08a0b79ef36fb50d36ebefaf10ec Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Tue, 2 Dec 2025 12:14:36 +0800
Subject: [PATCH v17 16/23] error safe for casting timestamp to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc
and pc.castfunc > 0 and (castsource::regtype ='timestamp'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
-----------------------------+-----------------------------+----------+-------------+------------+-----------------------+-------------
timestamp without time zone | date | 2029 | a | f | timestamp_date | date
timestamp without time zone | time without time zone | 1316 | a | f | timestamp_time | time
timestamp without time zone | timestamp with time zone | 2028 | i | f | timestamp_timestamptz | timestamptz
timestamp without time zone | timestamp without time zone | 1961 | i | f | timestamp_scale | timestamp
(4 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 7 +++++--
src/backend/utils/adt/timestamp.c | 10 ++++++++--
2 files changed, 13 insertions(+), 4 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index be52777d88d..a057471099d 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -1330,7 +1330,10 @@ timestamp_date(PG_FUNCTION_ARGS)
Timestamp timestamp = PG_GETARG_TIMESTAMP(0);
DateADT result;
- result = timestamp2date_safe(timestamp, NULL);
+ result = timestamp2date_safe(timestamp, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
+
PG_RETURN_DATEADT(result);
}
@@ -2008,7 +2011,7 @@ timestamp_time(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 3623dcab3bf..7ed88260b5d 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -352,7 +352,8 @@ timestamp_scale(PG_FUNCTION_ARGS)
result = timestamp;
- AdjustTimestampForTypmod(&result, typmod, NULL);
+ if (!AdjustTimestampForTypmod(&result, typmod, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_TIMESTAMP(result);
}
@@ -6432,8 +6433,13 @@ Datum
timestamp_timestamptz(PG_FUNCTION_ARGS)
{
Timestamp timestamp = PG_GETARG_TIMESTAMP(0);
+ TimestampTz result;
- PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(timestamp));
+ result = timestamp2timestamptz_safe(timestamp, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
+
+ PG_RETURN_TIMESTAMPTZ(result);
}
/*
--
2.34.1
v17-0012-error-safe-for-casting-float8-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v17-0012-error-safe-for-casting-float8-to-other-types-per-pg_cast.patchDownload
From fed9a88b387b7106bbdda7cff618224a5ae117e4 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:31:53 +0800
Subject: [PATCH v17 12/23] error safe for casting float8 to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'float8'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------------+------------+----------+-------------+------------+----------------+---------
double precision | bigint | 483 | a | f | dtoi8 | int8
double precision | smallint | 237 | a | f | dtoi2 | int2
double precision | integer | 317 | a | f | dtoi4 | int4
double precision | real | 312 | a | f | dtof | float4
double precision | numeric | 1743 | a | f | float8_numeric | numeric
(5 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/float.c | 13 +++++++++----
src/backend/utils/adt/int8.c | 2 +-
src/backend/utils/adt/numeric.c | 3 ++-
3 files changed, 12 insertions(+), 6 deletions(-)
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index 58580b5f3cc..502398d29ec 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -1199,9 +1199,14 @@ 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 +1229,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 +1254,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")));
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 6dc363670c7..2e82bec9a2c 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")));
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 8eb9346bde4..8f97ee078e6 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -4559,7 +4559,8 @@ float8_numeric(PG_FUNCTION_ARGS)
init_var(&result);
/* Assume we need not worry about leading/trailing spaces */
- (void) set_var_from_str(buf, buf, &result, &endptr, NULL);
+ if (!set_var_from_str(buf, buf, &result, &endptr, fcinfo->context))
+ PG_RETURN_NULL();
res = make_result(&result);
--
2.34.1
v17-0013-error-safe-for-casting-date-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v17-0013-error-safe-for-casting-date-to-other-types-per-pg_cast.patchDownload
From bbc7e9298ebaa643bdbe1934eb708f7401853f97 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Tue, 2 Dec 2025 13:15:32 +0800
Subject: [PATCH v17 13/23] error safe for casting date to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'date'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+-----------------------------+----------+-------------+------------+------------------+-------------
date | timestamp without time zone | 2024 | i | f | date_timestamp | timestamp
date | timestamp with time zone | 1174 | i | f | date_timestamptz | timestamptz
(2 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 17 ++++++-----------
1 file changed, 6 insertions(+), 11 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 621b9175c12..e8dc8d276bf 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -730,15 +730,6 @@ date2timestamptz_safe(DateADT dateVal, Node *escontext)
return result;
}
-/*
- * Promote date to timestamptz, throwing error for overflow.
- */
-static TimestampTz
-date2timestamptz(DateADT dateVal)
-{
- return date2timestamptz_safe(dateVal, NULL);
-}
-
/*
* date2timestamp_no_overflow
*
@@ -1323,7 +1314,9 @@ date_timestamp(PG_FUNCTION_ARGS)
DateADT dateVal = PG_GETARG_DATEADT(0);
Timestamp result;
- result = date2timestamp(dateVal);
+ result = date2timestamp_safe(dateVal, fcinfo->context);
+ if(SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_TIMESTAMP(result);
}
@@ -1396,7 +1389,9 @@ date_timestamptz(PG_FUNCTION_ARGS)
DateADT dateVal = PG_GETARG_DATEADT(0);
TimestampTz result;
- result = date2timestamptz(dateVal);
+ result = date2timestamptz_safe(dateVal, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_TIMESTAMP(result);
}
--
2.34.1
v17-0011-error-safe-for-casting-float4-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v17-0011-error-safe-for-casting-float4-to-other-types-per-pg_cast.patchDownload
From ac30a9319abc5935f58b0285e184a2c212007a73 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:28:20 +0800
Subject: [PATCH v17 11/23] error safe for casting float4 to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'float4'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+----------------+---------
real | bigint | 653 | a | f | ftoi8 | int8
real | smallint | 238 | a | f | ftoi2 | int2
real | integer | 319 | a | f | ftoi4 | int4
real | double precision | 311 | i | f | ftod | float8
real | numeric | 1742 | a | f | float4_numeric | numeric
(5 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/float.c | 4 ++--
src/backend/utils/adt/int8.c | 2 +-
src/backend/utils/adt/numeric.c | 3 ++-
3 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index b5a7c57e53a..58580b5f3cc 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -1298,7 +1298,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 +1323,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 4542d239c5f..6dc363670c7 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -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 65af268473c..8eb9346bde4 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -4657,7 +4657,8 @@ float4_numeric(PG_FUNCTION_ARGS)
init_var(&result);
/* Assume we need not worry about leading/trailing spaces */
- (void) set_var_from_str(buf, buf, &result, &endptr, NULL);
+ if (!set_var_from_str(buf, buf, &result, &endptr, fcinfo->context))
+ PG_RETURN_NULL();
res = make_result(&result);
--
2.34.1
v17-0010-error-safe-for-casting-numeric-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v17-0010-error-safe-for-casting-numeric-to-other-types-per-pg_cast.patchDownload
From f7a6f3640cc61aefd2cbf0e81842b17408815952 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:25:37 +0800
Subject: [PATCH v17 10/23] error safe for casting numeric to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'numeric'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+----------------+---------
numeric | bigint | 1779 | a | f | numeric_int8 | int8
numeric | smallint | 1783 | a | f | numeric_int2 | int2
numeric | integer | 1744 | a | f | numeric_int4 | int4
numeric | real | 1745 | i | f | numeric_float4 | float4
numeric | double precision | 1746 | i | f | numeric_float8 | float8
numeric | money | 3824 | a | f | numeric_cash | money
numeric | numeric | 1703 | i | f | numeric | numeric
(7 rows)
discussion: https://postgr.es/m/CACJufxHCMzrHOW=wRe8L30rMhB3sjwAv1LE928Fa7sxMu1Tx-g@mail.gmail.com
---
src/backend/utils/adt/numeric.c | 58 ++++++++++++++++++++++++---------
1 file changed, 43 insertions(+), 15 deletions(-)
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 891ae6ba7fe..65af268473c 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -1244,7 +1244,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));
}
@@ -1295,8 +1296,9 @@ numeric (PG_FUNCTION_ARGS)
init_var(&var);
set_var_from_num(num, &var);
- (void) apply_typmod(&var, typmod, NULL);
- new = make_result(&var);
+ if (!apply_typmod(&var, typmod, fcinfo->context))
+ PG_RETURN_NULL();
+ new = make_result_safe(&var, fcinfo->context);
free_var(&var);
@@ -3018,7 +3020,10 @@ numeric_mul(PG_FUNCTION_ARGS)
Numeric num2 = PG_GETARG_NUMERIC(1);
Numeric res;
- res = numeric_mul_safe(num1, num2, NULL);
+ res = numeric_mul_safe(num1, num2, fcinfo->context);
+
+ if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
PG_RETURN_NUMERIC(res);
}
@@ -4392,9 +4397,15 @@ numeric_int4_safe(Numeric num, Node *escontext)
Datum
numeric_int4(PG_FUNCTION_ARGS)
{
+ int32 result;
Numeric num = PG_GETARG_NUMERIC(0);
- PG_RETURN_INT32(numeric_int4_safe(num, NULL));
+ result = numeric_int4_safe(num, fcinfo->context);
+
+ if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
+
+ PG_RETURN_INT32(result);
}
/*
@@ -4462,9 +4473,15 @@ numeric_int8_safe(Numeric num, Node *escontext)
Datum
numeric_int8(PG_FUNCTION_ARGS)
{
+ int64 result;
Numeric num = PG_GETARG_NUMERIC(0);
- PG_RETURN_INT64(numeric_int8_safe(num, NULL));
+ result = numeric_int8_safe(num, fcinfo->context);
+
+ if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
+
+ PG_RETURN_INT64(result);
}
@@ -4488,11 +4505,11 @@ numeric_int2(PG_FUNCTION_ARGS)
if (NUMERIC_IS_SPECIAL(num))
{
if (NUMERIC_IS_NAN(num))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert NaN to %s", "smallint")));
else
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert infinity to %s", "smallint")));
}
@@ -4501,12 +4518,12 @@ numeric_int2(PG_FUNCTION_ARGS)
init_var_from_num(num, &x);
if (!numericvar_to_int64(&x, &val))
- ereport(ERROR,
+ ereturn(fcinfo->context, (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(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
@@ -4571,10 +4588,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);
}
@@ -4666,7 +4687,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
v17-0009-error-safe-for-casting-bigint-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v17-0009-error-safe-for-casting-bigint-to-other-types-per-pg_cast.patchDownload
From 4236648b119a919b81a0837d93049f5e812de749 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:22:00 +0800
Subject: [PATCH v17 09/23] error safe for casting bigint to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'bigint'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+--------------+---------
bigint | smallint | 714 | a | f | int82 | int2
bigint | integer | 480 | a | f | int84 | int4
bigint | real | 652 | i | f | i8tof | float4
bigint | double precision | 482 | i | f | i8tod | float8
bigint | numeric | 1781 | i | f | int8_numeric | numeric
bigint | money | 3812 | a | f | int8_cash | money
bigint | oid | 1287 | i | f | i8tooid | oid
bigint | regproc | 1287 | i | f | i8tooid | oid
bigint | regprocedure | 1287 | i | f | i8tooid | oid
bigint | regoper | 1287 | i | f | i8tooid | oid
bigint | regoperator | 1287 | i | f | i8tooid | oid
bigint | regclass | 1287 | i | f | i8tooid | oid
bigint | regcollation | 1287 | i | f | i8tooid | oid
bigint | regtype | 1287 | i | f | i8tooid | oid
bigint | regconfig | 1287 | i | f | i8tooid | oid
bigint | regdictionary | 1287 | i | f | i8tooid | oid
bigint | regrole | 1287 | i | f | i8tooid | oid
bigint | regnamespace | 1287 | i | f | i8tooid | oid
bigint | regdatabase | 1287 | i | f | i8tooid | oid
bigint | bytea | 6369 | e | f | int8_bytea | bytea
bigint | bit | 2075 | e | f | bitfromint8 | bit
(21 rows)
already error safe: i8tof, i8tod, int8_numeric, int8_bytea, bitfromint8
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/int8.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index d4509206217..4542d239c5f 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -1251,7 +1251,7 @@ int84(PG_FUNCTION_ARGS)
int64 arg = PG_GETARG_INT64(0);
if (unlikely(arg < PG_INT32_MIN) || unlikely(arg > PG_INT32_MAX))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
@@ -1272,7 +1272,7 @@ int82(PG_FUNCTION_ARGS)
int64 arg = PG_GETARG_INT64(0);
if (unlikely(arg < PG_INT16_MIN) || unlikely(arg > PG_INT16_MAX))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
@@ -1355,7 +1355,7 @@ i8tooid(PG_FUNCTION_ARGS)
int64 arg = PG_GETARG_INT64(0);
if (unlikely(arg < 0) || unlikely(arg > PG_UINT32_MAX))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("OID out of range")));
--
2.34.1
v17-0008-error-safe-for-casting-integer-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v17-0008-error-safe-for-casting-integer-to-other-types-per-pg_cast.patchDownload
From d5e96c22f258d2ff042fd45971a6411045e933e7 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:20:10 +0800
Subject: [PATCH v17 08/23] error safe for casting integer to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'integer'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+--------------+---------
integer | bigint | 481 | i | f | int48 | int8
integer | smallint | 314 | a | f | i4toi2 | int2
integer | real | 318 | i | f | i4tof | float4
integer | double precision | 316 | i | f | i4tod | float8
integer | numeric | 1740 | i | f | int4_numeric | numeric
integer | money | 3811 | a | f | int4_cash | money
integer | boolean | 2557 | e | f | int4_bool | bool
integer | bytea | 6368 | e | f | int4_bytea | bytea
integer | "char" | 78 | e | f | i4tochar | char
integer | bit | 1683 | e | f | bitfromint4 | bit
(10 rows)
only int4_cash, i4toi2, i4tochar need take care of error handling. but support
for cash data type is not easy, so only i4toi2, i4tochar function refactoring.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/char.c | 2 +-
src/backend/utils/adt/int.c | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/backend/utils/adt/char.c b/src/backend/utils/adt/char.c
index 3e4def68fe4..698863924ee 100644
--- a/src/backend/utils/adt/char.c
+++ b/src/backend/utils/adt/char.c
@@ -192,7 +192,7 @@ i4tochar(PG_FUNCTION_ARGS)
int32 arg1 = PG_GETARG_INT32(0);
if (arg1 < SCHAR_MIN || arg1 > SCHAR_MAX)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("\"char\" out of range")));
diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c
index d2302626585..2d124172c6f 100644
--- a/src/backend/utils/adt/int.c
+++ b/src/backend/utils/adt/int.c
@@ -350,7 +350,7 @@ i4toi2(PG_FUNCTION_ARGS)
int32 arg1 = PG_GETARG_INT32(0);
if (unlikely(arg1 < SHRT_MIN) || unlikely(arg1 > SHRT_MAX))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
--
2.34.1
v17-0007-error-safe-for-casting-macaddr8-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v17-0007-error-safe-for-casting-macaddr8-to-other-types-per-pg_cast.patchDownload
From 8d0a16aca978b43d24e46691e1dc26f0dc2e4bbc Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Fri, 12 Dec 2025 15:03:06 +0800
Subject: [PATCH v17 07/23] error safe for casting macaddr8 to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
and pc.castfunc > 0 and castsource::regtype ='macaddr8'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------+----------+-------------+------------+-------------------+---------
macaddr8 | macaddr | 4124 | i | f | macaddr8tomacaddr | macaddr
(1 row)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/mac8.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/backend/utils/adt/mac8.c b/src/backend/utils/adt/mac8.c
index c1bf9fd5783..0425ea473a5 100644
--- a/src/backend/utils/adt/mac8.c
+++ b/src/backend/utils/adt/mac8.c
@@ -550,7 +550,7 @@ macaddr8tomacaddr(PG_FUNCTION_ARGS)
result = palloc0_object(macaddr);
if ((addr->d != 0xFF) || (addr->e != 0xFE))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("macaddr8 data out of range to convert to macaddr"),
errhint("Only addresses that have FF and FE as values in the "
--
2.34.1
v17-0006-error-safe-for-casting-inet-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v17-0006-error-safe-for-casting-inet-to-other-types-per-pg_cast.patchDownload
From 88f1f363b886068103b465669c7cba0e4766d5e4 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 10:28:54 +0800
Subject: [PATCH v17 06/23] error safe for casting inet to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'inet'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+-------------------+----------+-------------+------------+--------------+---------
inet | cidr | 1715 | a | f | inet_to_cidr | cidr
inet | text | 730 | a | f | network_show | text
inet | character varying | 730 | a | f | network_show | text
inet | character | 730 | a | f | network_show | text
(4 rows)
inet_to_cidr is already error safe.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/network.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/backend/utils/adt/network.c b/src/backend/utils/adt/network.c
index 3a2002097dd..c7e0828764e 100644
--- a/src/backend/utils/adt/network.c
+++ b/src/backend/utils/adt/network.c
@@ -1137,7 +1137,7 @@ network_show(PG_FUNCTION_ARGS)
if (pg_inet_net_ntop(ip_family(ip), ip_addr(ip), ip_maxbits(ip),
tmp, sizeof(tmp)) == NULL)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
errmsg("could not format inet value: %m")));
--
2.34.1
v17-0005-error-safe-for-casting-character-varying-to-other-types-per-pg_c.patchtext/x-patch; charset=US-ASCII; name=v17-0005-error-safe-for-casting-character-varying-to-other-types-per-pg_c.patchDownload
From 2601d3b739d5cbe7a478dd9ac53b1e753880644e Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:13:45 +0800
Subject: [PATCH v17 05/23] error safe for casting character varying to other
types per pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc and pc.castfunc > 0 and
(castsource::regtype = 'character varying'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
-------------------+-------------------+----------+-------------+------------+---------------+----------
character varying | regclass | 1079 | i | f | text_regclass | regclass
character varying | "char" | 944 | a | f | text_char | char
character varying | name | 1400 | i | f | text_name | name
character varying | xml | 2896 | e | f | texttoxml | xml
character varying | character varying | 669 | i | f | varchar | varchar
(5 rows)
texttoxml, text_regclass was refactored as error safe in prior patch.
text_char, text_name is already error safe.
so here we only need handle function "varchar".
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/varchar.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c
index 6f083973fe7..a62e55eec19 100644
--- a/src/backend/utils/adt/varchar.c
+++ b/src/backend/utils/adt/varchar.c
@@ -634,7 +634,7 @@ varchar(PG_FUNCTION_ARGS)
{
for (i = maxmblen; i < len; i++)
if (s_data[i] != ' ')
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
errmsg("value too long for type character varying(%d)",
maxlen)));
--
2.34.1
v17-0004-error-safe-for-casting-text-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v17-0004-error-safe-for-casting-text-to-other-types-per-pg_cast.patchDownload
From 19326d269596a62c23aac9237ba3c75bad9b463e Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Fri, 12 Dec 2025 15:31:17 +0800
Subject: [PATCH v17 04/23] error safe for casting text to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'text'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------+----------+-------------+------------+---------------+----------
text | regclass | 1079 | i | f | text_regclass | regclass
text | "char" | 944 | a | f | text_char | char
text | name | 407 | i | f | text_name | name
text | xml | 2896 | e | f | texttoxml | xml
(4 rows)
already error safe: text_name, text_char.
texttoxml is refactored in character type error safe patch.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/catalog/namespace.c | 58 ++++++++++++++++++++++++++-------
src/backend/utils/adt/regproc.c | 13 ++++++--
src/backend/utils/adt/varlena.c | 10 ++++--
src/backend/utils/adt/xml.c | 2 +-
src/include/catalog/namespace.h | 6 ++++
src/include/utils/varlena.h | 1 +
6 files changed, 73 insertions(+), 17 deletions(-)
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index c3b79a2ba48..ea996121b05 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -440,6 +440,16 @@ Oid
RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
uint32 flags,
RangeVarGetRelidCallback callback, void *callback_arg)
+{
+ return RangeVarGetRelidExtendedSafe(relation, lockmode, flags,
+ callback, callback_arg,
+ NULL);
+}
+
+Oid
+RangeVarGetRelidExtendedSafe(const RangeVar *relation, LOCKMODE lockmode, uint32 flags,
+ RangeVarGetRelidCallback callback, void *callback_arg,
+ Node *escontext)
{
uint64 inval_count;
Oid relId;
@@ -456,7 +466,7 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
if (relation->catalogname)
{
if (strcmp(relation->catalogname, get_database_name(MyDatabaseId)) != 0)
- ereport(ERROR,
+ ereturn(escontext, InvalidOid,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cross-database references are not implemented: \"%s.%s.%s\"",
relation->catalogname, relation->schemaname,
@@ -513,7 +523,7 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
* return InvalidOid.
*/
if (namespaceId != myTempNamespace)
- ereport(ERROR,
+ ereturn(escontext, InvalidOid,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("temporary tables cannot specify a schema name")));
}
@@ -593,13 +603,23 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
{
int elevel = (flags & RVR_SKIP_LOCKED) ? DEBUG1 : ERROR;
- if (relation->schemaname)
- ereport(elevel,
+ if (relation->schemaname && elevel == DEBUG1)
+ ereport(DEBUG1,
(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
errmsg("could not obtain lock on relation \"%s.%s\"",
relation->schemaname, relation->relname)));
- else
- ereport(elevel,
+ else if (relation->schemaname && elevel == ERROR)
+ ereturn(escontext, InvalidOid,
+ errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s.%s\"",
+ relation->schemaname, relation->relname));
+ else if (elevel == DEBUG1)
+ ereport(DEBUG1,
+ errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s\"",
+ relation->relname));
+ else if (elevel == ERROR)
+ ereturn(escontext, InvalidOid,
(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
errmsg("could not obtain lock on relation \"%s\"",
relation->relname)));
@@ -626,13 +646,23 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
{
int elevel = missing_ok ? DEBUG1 : ERROR;
- if (relation->schemaname)
- ereport(elevel,
+ if (relation->schemaname && elevel == DEBUG1)
+ ereport(DEBUG1,
(errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("relation \"%s.%s\" does not exist",
relation->schemaname, relation->relname)));
- else
- ereport(elevel,
+ else if (relation->schemaname && elevel == ERROR)
+ ereturn(escontext, InvalidOid,
+ errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s.%s\" does not exist",
+ relation->schemaname, relation->relname));
+ else if (elevel == DEBUG1)
+ ereport(DEBUG1,
+ errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" does not exist",
+ relation->relname));
+ else if (elevel == ERROR)
+ ereturn(escontext, InvalidOid,
(errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("relation \"%s\" does not exist",
relation->relname)));
@@ -3622,6 +3652,12 @@ get_namespace_oid(const char *nspname, bool missing_ok)
*/
RangeVar *
makeRangeVarFromNameList(const List *names)
+{
+ return makeRangeVarFromNameListSafe(names, NULL);
+}
+
+RangeVar *
+makeRangeVarFromNameListSafe(const List *names, Node *escontext)
{
RangeVar *rel = makeRangeVar(NULL, NULL, -1);
@@ -3640,7 +3676,7 @@ makeRangeVarFromNameList(const List *names)
rel->relname = strVal(lthird(names));
break;
default:
- ereport(ERROR,
+ ereturn(escontext, NULL,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("improper relation name (too many dotted names): %s",
NameListToString(names))));
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index ee34d1d85f8..604e19a1cb9 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -1901,12 +1901,19 @@ text_regclass(PG_FUNCTION_ARGS)
text *relname = PG_GETARG_TEXT_PP(0);
Oid result;
RangeVar *rv;
+ List *namelist;
- rv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
+ namelist = textToQualifiedNameListSafe(relname, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
+
+ rv = makeRangeVarFromNameListSafe(namelist, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
/* We might not even have permissions on this relation; don't lock it. */
- result = RangeVarGetRelid(rv, NoLock, false);
-
+ result = RangeVarGetRelidExtendedSafe(rv, NoLock, 0, NULL, NULL,
+ fcinfo->context);
PG_RETURN_OID(result);
}
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index cfcc35592e3..fa38e8a5bb6 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -2668,6 +2668,12 @@ name_text(PG_FUNCTION_ARGS)
*/
List *
textToQualifiedNameList(text *textval)
+{
+ return textToQualifiedNameListSafe(textval, NULL);
+}
+
+List *
+textToQualifiedNameListSafe(text *textval, Node *escontext)
{
char *rawname;
List *result = NIL;
@@ -2679,12 +2685,12 @@ textToQualifiedNameList(text *textval)
rawname = text_to_cstring(textval);
if (!SplitIdentifierString(rawname, '.', &namelist))
- ereport(ERROR,
+ ereturn(escontext, NIL,
(errcode(ERRCODE_INVALID_NAME),
errmsg("invalid name syntax")));
if (namelist == NIL)
- ereport(ERROR,
+ ereturn(escontext, NIL,
(errcode(ERRCODE_INVALID_NAME),
errmsg("invalid name syntax")));
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 431c0bcea44..890fd674bdb 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -1043,7 +1043,7 @@ xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, Node
return (xmltype *) data;
#else
- ereturn(escontext, NULL
+ ereturn(escontext, NULL,
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("unsupported XML feature"),
errdetail("This functionality requires the server to be built with libxml support."));
diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h
index 1a25973685c..153a439e374 100644
--- a/src/include/catalog/namespace.h
+++ b/src/include/catalog/namespace.h
@@ -103,6 +103,11 @@ extern Oid RangeVarGetRelidExtended(const RangeVar *relation,
LOCKMODE lockmode, uint32 flags,
RangeVarGetRelidCallback callback,
void *callback_arg);
+extern Oid RangeVarGetRelidExtendedSafe(const RangeVar *relation,
+ LOCKMODE lockmode, uint32 flags,
+ RangeVarGetRelidCallback callback,
+ void *callback_arg,
+ Node *escontext);
extern Oid RangeVarGetCreationNamespace(const RangeVar *newRelation);
extern Oid RangeVarGetAndCheckCreationNamespace(RangeVar *relation,
LOCKMODE lockmode,
@@ -168,6 +173,7 @@ extern Oid LookupCreationNamespace(const char *nspname);
extern void CheckSetNamespace(Oid oldNspOid, Oid nspOid);
extern Oid QualifiedNameGetCreationNamespace(const List *names, char **objname_p);
extern RangeVar *makeRangeVarFromNameList(const List *names);
+extern RangeVar *makeRangeVarFromNameListSafe(const List *names, Node *escontext);
extern char *NameListToString(const List *names);
extern char *NameListToQuotedString(const List *names);
diff --git a/src/include/utils/varlena.h b/src/include/utils/varlena.h
index 4b32574a075..5bc78aa02c0 100644
--- a/src/include/utils/varlena.h
+++ b/src/include/utils/varlena.h
@@ -27,6 +27,7 @@ extern int varstr_levenshtein_less_equal(const char *source, int slen,
int ins_c, int del_c, int sub_c,
int max_d, bool trusted);
extern List *textToQualifiedNameList(text *textval);
+extern List *textToQualifiedNameListSafe(text *textval, Node *escontext);
extern bool SplitIdentifierString(char *rawstring, char separator,
List **namelist);
extern bool SplitDirectoriesString(char *rawstring, char separator,
--
2.34.1
v17-0003-error-safe-for-casting-character-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v17-0003-error-safe-for-casting-character-to-other-types-per-pg_cast.patchDownload
From 71093e4f9659a975118f8e1f3daf7bbf13148971 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Mon, 24 Nov 2025 12:52:16 +0800
Subject: [PATCH v17 03/23] error safe for casting character to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype ='character'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+-------------------+----------+-------------+------------+-------------+---------
character | text | 401 | i | f | rtrim1 | text
character | character varying | 401 | i | f | rtrim1 | text
character | "char" | 944 | a | f | text_char | char
character | name | 409 | i | f | bpchar_name | name
character | xml | 2896 | e | f | texttoxml | xml
character | character | 668 | i | f | bpchar | bpchar
(6 rows)
only texttoxml, bpchar(PG_FUNCTION_ARGS) need take care of error handling.
other functions already error safe.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/executor/execExprInterp.c | 2 +-
src/backend/utils/adt/varchar.c | 2 +-
src/backend/utils/adt/xml.c | 18 ++++++++++++------
src/include/utils/xml.h | 2 +-
4 files changed, 15 insertions(+), 9 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 86ab3704b66..0a2d25c1b62 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -4542,7 +4542,7 @@ ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op)
*op->resvalue = PointerGetDatum(xmlparse(data,
xexpr->xmloption,
- preserve_whitespace));
+ preserve_whitespace, NULL));
*op->resnull = false;
}
break;
diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c
index df305098130..6f083973fe7 100644
--- a/src/backend/utils/adt/varchar.c
+++ b/src/backend/utils/adt/varchar.c
@@ -307,7 +307,7 @@ bpchar(PG_FUNCTION_ARGS)
{
for (i = maxmblen; i < len; i++)
if (s[i] != ' ')
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
errmsg("value too long for type character(%d)",
maxlen)));
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index f69dc68286c..431c0bcea44 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -659,7 +659,7 @@ texttoxml(PG_FUNCTION_ARGS)
{
text *data = PG_GETARG_TEXT_PP(0);
- PG_RETURN_XML_P(xmlparse(data, xmloption, true));
+ PG_RETURN_XML_P(xmlparse(data, xmloption, true, fcinfo->context));
}
@@ -1028,19 +1028,25 @@ xmlelement(XmlExpr *xexpr,
xmltype *
-xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace)
+xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, Node *escontext)
{
#ifdef USE_LIBXML
xmlDocPtr doc;
doc = xml_parse(data, xmloption_arg, preserve_whitespace,
- GetDatabaseEncoding(), NULL, NULL, NULL);
- xmlFreeDoc(doc);
+ GetDatabaseEncoding(), NULL, NULL, escontext);
+ if (doc)
+ xmlFreeDoc(doc);
+
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return NULL;
return (xmltype *) data;
#else
- NO_XML_SUPPORT();
- return NULL;
+ ereturn(escontext, NULL
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unsupported XML feature"),
+ errdetail("This functionality requires the server to be built with libxml support."));
#endif
}
diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h
index 03acb255449..553bdc96c3f 100644
--- a/src/include/utils/xml.h
+++ b/src/include/utils/xml.h
@@ -73,7 +73,7 @@ extern xmltype *xmlconcat(List *args);
extern xmltype *xmlelement(XmlExpr *xexpr,
const Datum *named_argvalue, const bool *named_argnull,
const Datum *argvalue, const bool *argnull);
-extern xmltype *xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace);
+extern xmltype *xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, Node *escontext);
extern xmltype *xmlpi(const char *target, text *arg, bool arg_is_null, bool *result_is_null);
extern xmltype *xmlroot(xmltype *data, text *version, int standalone);
extern bool xml_is_document(xmltype *arg);
--
2.34.1
v17-0002-error-safe-for-casting-bit-varbit-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v17-0002-error-safe-for-casting-bit-varbit-to-other-types-per-pg_cast.patchDownload
From af78e24d35223b2d84cbf4b41261b6c760097892 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 10:33:08 +0800
Subject: [PATCH v17 02/23] error safe for casting bit/varbit to other types
per pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
where pc.castfunc > 0 and (castsource::regtype ='bit'::regtype or
castsource::regtype ='varbit'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
-------------+-------------+----------+-------------+------------+-----------+---------
bit | bigint | 2076 | e | f | bittoint8 | int8
bit | integer | 1684 | e | f | bittoint4 | int4
bit | bit | 1685 | i | f | bit | bit
bit varying | bit varying | 1687 | i | f | varbit | varbit
(4 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/varbit.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/backend/utils/adt/varbit.c b/src/backend/utils/adt/varbit.c
index 50ffee679b9..223b01f9308 100644
--- a/src/backend/utils/adt/varbit.c
+++ b/src/backend/utils/adt/varbit.c
@@ -401,7 +401,7 @@ bit(PG_FUNCTION_ARGS)
PG_RETURN_VARBIT_P(arg);
if (!isExplicit)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_STRING_DATA_LENGTH_MISMATCH),
errmsg("bit string length %d does not match type bit(%d)",
VARBITLEN(arg), len)));
@@ -752,7 +752,7 @@ varbit(PG_FUNCTION_ARGS)
PG_RETURN_VARBIT_P(arg);
if (!isExplicit)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
errmsg("bit string too long for type bit varying(%d)",
len)));
@@ -1591,7 +1591,7 @@ bittoint4(PG_FUNCTION_ARGS)
/* Check that the bit string is not too long */
if (VARBITLEN(arg) > sizeof(result) * BITS_PER_BYTE)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
@@ -1671,7 +1671,7 @@ bittoint8(PG_FUNCTION_ARGS)
/* Check that the bit string is not too long */
if (VARBITLEN(arg) > sizeof(result) * BITS_PER_BYTE)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("bigint out of range")));
--
2.34.1
v17-0001-error-safe-for-casting-bytea-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v17-0001-error-safe-for-casting-bytea-to-other-types-per-pg_cast.patchDownload
From a652d58b33f16359988ed0e168924f93bf5408ae Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:08:00 +0800
Subject: [PATCH v17 01/23] error safe for casting bytea to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc and pc.castfunc > 0 and castsource::regtype ='bytea'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------+----------+-------------+------------+------------+---------
bytea | smallint | 6370 | e | f | bytea_int2 | int2
bytea | integer | 6371 | e | f | bytea_int4 | int4
bytea | bigint | 6372 | e | f | bytea_int8 | int8
(3 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/bytea.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/backend/utils/adt/bytea.c b/src/backend/utils/adt/bytea.c
index fd7662d41ee..a02b054b873 100644
--- a/src/backend/utils/adt/bytea.c
+++ b/src/backend/utils/adt/bytea.c
@@ -1255,7 +1255,7 @@ bytea_int2(PG_FUNCTION_ARGS)
/* Check that the byte array is not too long */
if (len > sizeof(result))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range"));
@@ -1280,7 +1280,7 @@ bytea_int4(PG_FUNCTION_ARGS)
/* Check that the byte array is not too long */
if (len > sizeof(result))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range"));
@@ -1305,7 +1305,7 @@ bytea_int8(PG_FUNCTION_ARGS)
/* Check that the byte array is not too long */
if (len > sizeof(result))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("bigint out of range"));
--
2.34.1
v17-0019-introduce-float8-safe-function.patchtext/x-patch; charset=US-ASCII; name=v17-0019-introduce-float8-safe-function.patchDownload
From 885bbfdbcba651dd9bea7e76c8f69f904ddf9565 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Fri, 2 Jan 2026 16:14:28 +0800
Subject: [PATCH v17 19/23] introduce float8 safe function
this patch introduce the following function:
float8_pl_safe
float8_mi_safe
float8_mul_safe
float8_div_safe
refactoring existing function is be too invasive. thus add these new functions.
It's close to existing non-safe version functions.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/include/utils/float.h | 79 ++++++++++++++++++++++++++++-----------
1 file changed, 58 insertions(+), 21 deletions(-)
diff --git a/src/include/utils/float.h b/src/include/utils/float.h
index d2e989960a5..8723b4c00b0 100644
--- a/src/include/utils/float.h
+++ b/src/include/utils/float.h
@@ -109,16 +109,24 @@ float4_pl(const float4 val1, const float4 val2)
return result;
}
+static inline float8
+float8_pl_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+ float8 result;
+
+ result = val1 + val2;
+ if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
+ {
+ float_overflow_error(escontext);
+ return 0.0;
+ }
+ return result;
+}
+
static inline float8
float8_pl(const float8 val1, const float8 val2)
{
- float8 result;
-
- result = val1 + val2;
- if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error(NULL);
-
- return result;
+ return float8_pl_safe(val1, val2, NULL);;
}
static inline float4
@@ -133,16 +141,24 @@ float4_mi(const float4 val1, const float4 val2)
return result;
}
+static inline float8
+float8_mi_safe(const float8 val1, const float8 val2, struct Node *escontext)
+{
+ float8 result;
+
+ result = val1 - val2;
+ if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
+ {
+ float_overflow_error(escontext);
+ return 0.0;
+ }
+ return result;
+}
+
static inline float8
float8_mi(const float8 val1, const float8 val2)
{
- float8 result;
-
- result = val1 - val2;
- if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error(NULL);
-
- return result;
+ return float8_mi_safe(val1, val2, NULL);
}
static inline float4
@@ -160,19 +176,33 @@ float4_mul(const float4 val1, const float4 val2)
}
static inline float8
-float8_mul(const float8 val1, const float8 val2)
+float8_mul_safe(const float8 val1, const float8 val2, struct Node *escontext)
{
float8 result;
result = val1 * val2;
if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error(NULL);
+ {
+ float_overflow_error(escontext);
+ return 0.0;
+ }
+
if (unlikely(result == 0.0) && val1 != 0.0 && val2 != 0.0)
- float_underflow_error(NULL);
+ {
+ float_underflow_error(escontext);
+ return 0.0;
+ }
return result;
}
+static inline float8
+float8_mul(const float8 val1, const float8 val2)
+{
+ return float8_mul_safe(val1, val2, NULL);
+}
+
+
static inline float4
float4_div(const float4 val1, const float4 val2)
{
@@ -190,21 +220,28 @@ float4_div(const float4 val1, const float4 val2)
}
static inline float8
-float8_div(const float8 val1, const float8 val2)
+float8_div_safe(const float8 val1, const float8 val2, struct Node *escontext)
{
float8 result;
if (unlikely(val2 == 0.0) && !isnan(val1))
- float_zero_divide_error(NULL);
+ float_zero_divide_error(escontext);
result = val1 / val2;
if (unlikely(isinf(result)) && !isinf(val1))
- float_overflow_error(NULL);
+ float_overflow_error(escontext);
if (unlikely(result == 0.0) && val1 != 0.0 && !isinf(val2))
- float_underflow_error(NULL);
+ float_underflow_error(escontext);
return result;
}
+static inline float8
+float8_div(const float8 val1, const float8 val2)
+{
+ return float8_div_safe(val1, val2, NULL);
+}
+
+
/*
* Routines for NaN-aware comparisons
*
--
2.34.1
v17-0022-CAST-expr-AS-newtype-DEFAULT-expr-ON-CONVERSION-ERROR.patchtext/x-patch; charset=UTF-8; name=v17-0022-CAST-expr-AS-newtype-DEFAULT-expr-ON-CONVERSION-ERROR.patchDownload
From 8f273fa2788d0f812e37cfad9e2ea5286b27dada Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Mon, 5 Jan 2026 13:57:17 +0800
Subject: [PATCH v17 22/23] CAST(expr AS newtype DEFAULT expr ON CONVERSION
ERROR)
# Bumps catversion required
* With this patchset, most functions in pg_cast.castfunc are now error-safe.
* CoerceViaIO and CoerceToDomain were already error-safe in the HEAD.
* this patch extends error-safe behavior to ArrayCoerceExpr.
* We also ensure that when a coercion fails, execution falls back to evaluating
the specified default node.
* The doc has been refined, though it may still need more review.
demo:
SELECT CAST('1' AS date DEFAULT '2011-01-01' ON CONVERSION ERROR),
CAST('{234,def,567}'::text[] AS integer[] DEFAULT '{-1011}' ON CONVERSION ERROR);
date | int4
------------+---------
2011-01-01 | {-1011}
[0]: https://git.postgresql.org/cgit/postgresql.git/commit/?id=aaaf9449ec6be62cb0d30ed3588dc384f56274b
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
contrib/citext/expected/citext.out | 5 +
contrib/citext/expected/citext_1.out | 5 +
contrib/citext/sql/citext.sql | 2 +
.../pg_stat_statements/expected/select.out | 23 +-
contrib/pg_stat_statements/sql/select.sql | 5 +
doc/src/sgml/syntax.sgml | 33 +
src/backend/executor/execExpr.c | 86 +-
src/backend/executor/execExprInterp.c | 29 +
src/backend/jit/llvm/llvmjit_expr.c | 27 +
src/backend/nodes/nodeFuncs.c | 53 ++
src/backend/optimizer/util/clauses.c | 49 +-
src/backend/parser/gram.y | 23 +
src/backend/parser/parse_agg.c | 9 +
src/backend/parser/parse_coerce.c | 77 +-
src/backend/parser/parse_expr.c | 379 +++++++-
src/backend/parser/parse_func.c | 3 +
src/backend/parser/parse_type.c | 14 +
src/backend/parser/parse_utilcmd.c | 2 +-
src/backend/utils/adt/arrayfuncs.c | 8 +
src/backend/utils/adt/ruleutils.c | 21 +
src/backend/utils/fmgr/fmgr.c | 14 +
src/include/executor/execExpr.h | 7 +
src/include/executor/executor.h | 1 +
src/include/fmgr.h | 3 +
src/include/nodes/execnodes.h | 21 +
src/include/nodes/parsenodes.h | 1 +
src/include/nodes/primnodes.h | 36 +
src/include/optimizer/optimizer.h | 2 +-
src/include/parser/parse_coerce.h | 13 +
src/include/parser/parse_node.h | 2 +
src/include/parser/parse_type.h | 2 +
src/test/regress/expected/cast.out | 810 ++++++++++++++++++
src/test/regress/expected/create_cast.out | 5 +
src/test/regress/expected/equivclass.out | 7 +
src/test/regress/parallel_schedule | 2 +-
src/test/regress/sql/cast.sql | 350 ++++++++
src/test/regress/sql/create_cast.sql | 1 +
src/test/regress/sql/equivclass.sql | 3 +
src/tools/pgindent/typedefs.list | 2 +
39 files changed, 2075 insertions(+), 60 deletions(-)
create mode 100644 src/test/regress/expected/cast.out
create mode 100644 src/test/regress/sql/cast.sql
diff --git a/contrib/citext/expected/citext.out b/contrib/citext/expected/citext.out
index 8c0bf54f0f3..33da19d8df4 100644
--- a/contrib/citext/expected/citext.out
+++ b/contrib/citext/expected/citext.out
@@ -10,6 +10,11 @@ WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid);
--------+---------
(0 rows)
+SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR: cannot cast type character to citext when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSI...
+ ^
+HINT: Safe type cast for user-defined types are not yet supported
-- Test the operators and indexing functions
-- Test = and <>.
SELECT 'a'::citext = 'a'::citext AS t;
diff --git a/contrib/citext/expected/citext_1.out b/contrib/citext/expected/citext_1.out
index c5e5f180f2b..647eea19142 100644
--- a/contrib/citext/expected/citext_1.out
+++ b/contrib/citext/expected/citext_1.out
@@ -10,6 +10,11 @@ WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid);
--------+---------
(0 rows)
+SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR: cannot cast type character to citext when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSI...
+ ^
+HINT: Safe type cast for user-defined types are not yet supported
-- Test the operators and indexing functions
-- Test = and <>.
SELECT 'a'::citext = 'a'::citext AS t;
diff --git a/contrib/citext/sql/citext.sql b/contrib/citext/sql/citext.sql
index aa1cf9abd5c..99794497d47 100644
--- a/contrib/citext/sql/citext.sql
+++ b/contrib/citext/sql/citext.sql
@@ -9,6 +9,8 @@ SELECT amname, opcname
FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod
WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid);
+SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR); --error
+
-- Test the operators and indexing functions
-- Test = and <>.
diff --git a/contrib/pg_stat_statements/expected/select.out b/contrib/pg_stat_statements/expected/select.out
index 75c896f3885..6e67997f0a2 100644
--- a/contrib/pg_stat_statements/expected/select.out
+++ b/contrib/pg_stat_statements/expected/select.out
@@ -73,6 +73,25 @@ SELECT 1 AS "int" OFFSET 2 FETCH FIRST 3 ROW ONLY;
-----
(0 rows)
+--error safe type cast
+SELECT CAST('a' AS int DEFAULT 2 ON CONVERSION ERROR);
+ int4
+------
+ 2
+(1 row)
+
+SELECT CAST('11' AS int DEFAULT 2 ON CONVERSION ERROR);
+ int4
+------
+ 11
+(1 row)
+
+SELECT CAST('12' AS numeric DEFAULT 2 ON CONVERSION ERROR);
+ numeric
+---------
+ 12
+(1 row)
+
-- DISTINCT and ORDER BY patterns
-- Try some query permutations which once produced identical query IDs
SELECT DISTINCT 1 AS "int";
@@ -222,6 +241,8 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
2 | 2 | SELECT $1 AS "int" ORDER BY 1
1 | 2 | SELECT $1 AS i UNION SELECT $2 ORDER BY i
1 | 1 | SELECT $1 || $2
+ 2 | 2 | SELECT CAST($1 AS int DEFAULT $2 ON CONVERSION ERROR)
+ 1 | 1 | SELECT CAST($1 AS numeric DEFAULT $2 ON CONVERSION ERROR)
2 | 2 | SELECT DISTINCT $1 AS "int"
0 | 0 | SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"
1 | 1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
@@ -230,7 +251,7 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
| | ) +
| | SELECT f FROM t ORDER BY f
1 | 1 | select $1::jsonb ? $2
-(17 rows)
+(19 rows)
SELECT pg_stat_statements_reset() IS NOT NULL AS t;
t
diff --git a/contrib/pg_stat_statements/sql/select.sql b/contrib/pg_stat_statements/sql/select.sql
index 11662cde08c..7ee8160fd84 100644
--- a/contrib/pg_stat_statements/sql/select.sql
+++ b/contrib/pg_stat_statements/sql/select.sql
@@ -25,6 +25,11 @@ SELECT 1 AS "int" LIMIT 3 OFFSET 3;
SELECT 1 AS "int" OFFSET 1 FETCH FIRST 2 ROW ONLY;
SELECT 1 AS "int" OFFSET 2 FETCH FIRST 3 ROW ONLY;
+--error safe type cast
+SELECT CAST('a' AS int DEFAULT 2 ON CONVERSION ERROR);
+SELECT CAST('11' AS int DEFAULT 2 ON CONVERSION ERROR);
+SELECT CAST('12' AS numeric DEFAULT 2 ON CONVERSION ERROR);
+
-- DISTINCT and ORDER BY patterns
-- Try some query permutations which once produced identical query IDs
SELECT DISTINCT 1 AS "int";
diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml
index 34c83880a66..d1cc932f7b1 100644
--- a/doc/src/sgml/syntax.sgml
+++ b/doc/src/sgml/syntax.sgml
@@ -2106,6 +2106,10 @@ CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable>
The <literal>CAST</literal> syntax conforms to SQL; the syntax with
<literal>::</literal> is historical <productname>PostgreSQL</productname>
usage.
+ The equivalent ON CONVERSION ERROR behavior is:
+<synopsis>
+CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable> ERROR ON CONVERSION ERROR )
+</synopsis>
</para>
<para>
@@ -2160,6 +2164,35 @@ CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable>
<xref linkend="sql-createcast"/>.
</para>
</note>
+
+ <sect3 id="sql-syntax-type-casts-safe">
+ <title>Safe Type Cast</title>
+ <para>
+ A type cast may occasionally fail. To guard against such failures, you can
+ provide an <literal>ON CONVERSION ERROR</literal> clause to handle potential errors.
+ The syntax for safe type cast is:
+<synopsis>
+CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable> DEFAULT <replaceable>expression</replaceable> ON CONVERSION ERROR )
+</synopsis>
+ If the type cast fails, instead of error out, evaluation falls back to the
+ default <replaceable>expression</replaceable>
+ specified in the <literal>ON CONVERSION ERROR</literal> clause.
+
+ At present, this only support built-in type casts, see <xref linkend="catalog-pg-cast"/>.
+ User-defined type casts created with <link linkend="sql-createcast">CREATE CAST</link>
+ are not supported.
+ </para>
+
+ <para>
+ Some examples:
+<screen>
+SELECT CAST(TEXT 'error' AS integer DEFAULT 3 ON CONVERSION ERROR);
+<lineannotation>Result: </lineannotation><computeroutput>3</computeroutput>
+SELECT CAST(TEXT 'error' AS numeric DEFAULT 1.1 ON CONVERSION ERROR);
+<lineannotation>Result: </lineannotation><computeroutput>1.1</computeroutput>
+</screen>
+ </para>
+ </sect3>
</sect2>
<sect2 id="sql-syntax-collate-exprs">
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index e0a1fb76aa8..d5cbcfbbef6 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,
@@ -141,6 +144,19 @@ static void ExecInitJsonCoercion(ExprState *state, JsonReturning *returning,
*/
ExprState *
ExecInitExpr(Expr *node, PlanState *parent)
+{
+ return ExecInitExprExtended(node, NULL, parent);
+}
+
+/*
+ * ExecInitExprExtended: soft error variant of ExecInitExpr.
+ *
+ * escontext is expected to be non-NULL only for expression nodes that support
+ * soft errors.
+ * Not all expression nodes support this; if in doubt, pass NULL.
+ */
+ExprState *
+ExecInitExprExtended(Expr *node, Node *escontext, PlanState *parent)
{
ExprState *state;
ExprEvalStep scratch = {0};
@@ -154,6 +170,7 @@ ExecInitExpr(Expr *node, PlanState *parent)
state->expr = node;
state->parent = parent;
state->ext_params = NULL;
+ state->escontext = (ErrorSaveContext *) escontext;
/* Insert setup steps as needed */
ExecCreateExprSetupSteps(state, (Node *) node);
@@ -1701,6 +1718,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
elemstate->innermost_caseval = palloc_object(Datum);
elemstate->innermost_casenull = palloc_object(bool);
+ elemstate->escontext = state->escontext;
ExecInitExprRec(acoerce->elemexpr, elemstate,
&elemstate->resvalue, &elemstate->resnull);
@@ -2176,6 +2194,15 @@ 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;
@@ -2736,7 +2763,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;
@@ -4733,6 +4760,63 @@ ExecBuildParamSetEqual(TupleDesc desc,
return state;
}
+/*
+ * Push steps to evaluate a SafeTypeCastExpr and its various subsidiary
+ * expressions.
+ */
+static void
+ExecInitSafeTypeCastExpr(SafeTypeCastExpr *stcexpr, ExprState *state,
+ Datum *resv, bool *resnull,
+ ExprEvalStep *scratch)
+{
+ /*
+ * If we cannot coerce to the target type, fallback to the DEFAULT
+ * expression specified in ON CONVERSION ERROR clause, and we are done.
+ */
+ if (stcexpr->cast_expr == NULL)
+ {
+ ExecInitExprRec((Expr *) stcexpr->default_expr,
+ state, resv, resnull);
+ return;
+ }
+ else
+ {
+ SafeTypeCastState *stcstate;
+
+ stcstate = palloc0(sizeof(SafeTypeCastState));
+ stcstate->stcexpr = stcexpr;
+ stcstate->escontext.type = T_ErrorSaveContext;
+ state->escontext = &stcstate->escontext;
+
+ /* evaluate argument expression into step's result area */
+ ExecInitExprRec((Expr *) stcexpr->cast_expr,
+ state, resv, resnull);
+ scratch->opcode = EEOP_SAFETYPE_CAST;
+ scratch->d.stcexpr.stcstate = stcstate;
+ ExprEvalPushStep(state, scratch);
+
+ /*
+ * Steps to evaluate the DEFAULT expression. Skip it if this is a
+ * binary coercion cast.
+ */
+ if (!IsA(stcexpr->cast_expr, RelabelType))
+ {
+ ErrorSaveContext *saved_escontext;
+
+ saved_escontext = state->escontext;
+
+ state->escontext = NULL;
+
+ ExecInitExprRec((Expr *) stcstate->stcexpr->default_expr,
+ state, resv, resnull);
+
+ state->escontext = saved_escontext;
+ }
+
+ 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 0a2d25c1b62..40623b98491 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,28 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
EEO_NEXT();
}
+ EEO_CASE(EEOP_SAFETYPE_CAST)
+ {
+ SafeTypeCastState *stcstate = op->d.stcexpr.stcstate;
+
+ if (SOFT_ERROR_OCCURRED(&stcstate->escontext))
+ {
+ *op->resvalue = (Datum) 0;
+ *op->resnull = true;
+
+ /*
+ * Reset for next use such as for catching errors when
+ * coercing a expression.
+ */
+ stcstate->escontext.error_occurred = false;
+ stcstate->escontext.details_wanted = false;
+
+ EEO_NEXT();
+ }
+ else
+ EEO_JUMP(stcstate->jump_end);
+ }
+
EEO_CASE(EEOP_JSONEXPR_PATH)
{
/* too complex for an inline implementation */
@@ -3644,6 +3667,12 @@ 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/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index 650f1d42a93..475c03c5488 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -2256,6 +2256,33 @@ llvm_compile_expr(ExprState *state)
LLVMBuildBr(b, opblocks[opno + 1]);
break;
+ case EEOP_SAFETYPE_CAST:
+ {
+ SafeTypeCastState *stcstate = op->d.stcexpr.stcstate;
+
+ if (SOFT_ERROR_OCCURRED(&stcstate->escontext))
+ {
+ /*
+ * Reset for next use such as for catching errors when
+ * coercing a expression.
+ */
+ stcstate->escontext.error_occurred = false;
+ stcstate->escontext.details_wanted = false;
+
+ /* set resnull to true */
+ LLVMBuildStore(b, l_sbool_const(1), v_resnullp);
+
+ /* reset resvalue */
+ LLVMBuildStore(b, l_datum_const(0), v_resvaluep);
+
+ LLVMBuildBr(b, opblocks[opno + 1]);
+ }
+ else
+ 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 c7660df92f4..89fb1b357d5 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;
@@ -1550,6 +1561,9 @@ exprLocation(const Node *expr)
/* just use leftmost argument's location */
loc = exprLocation((Node *) ((const RowCompareExpr *) expr)->largs);
break;
+ case T_SafeTypeCastExpr:
+ loc = ((const SafeTypeCastExpr *) expr)->location;
+ break;
case T_CoalesceExpr:
/* COALESCE keyword should always be the first thing */
loc = ((const CoalesceExpr *) expr)->location;
@@ -2321,6 +2335,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:
@@ -3330,6 +3356,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;
@@ -4462,6 +4501,20 @@ raw_expression_tree_walker_impl(Node *node,
return true;
if (WALK(tc->typeName))
return true;
+ if (WALK(tc->raw_default))
+ 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:
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 39d35827c35..9c118c7abcb 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -2447,7 +2447,8 @@ estimate_expression_value(PlannerInfo *root, Node *node)
((Node *) evaluate_expr((Expr *) (node), \
exprType((Node *) (node)), \
exprTypmod((Node *) (node)), \
- exprCollation((Node *) (node))))
+ exprCollation((Node *) (node)), \
+ NULL))
/*
* Recursive guts of eval_const_expressions/estimate_expression_value
@@ -2958,6 +2959,32 @@ 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);
+
+ /*
+ * We must not reduce any recognizably constant subexpressions
+ * in cast_expr here, since we don’t want it to fail
+ * prematurely.
+ */
+ 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:
@@ -3388,7 +3415,8 @@ eval_const_expressions_mutator(Node *node,
return (Node *) evaluate_expr((Expr *) svf,
svf->type,
svf->typmod,
- InvalidOid);
+ InvalidOid,
+ NULL);
else
return copyObject((Node *) svf);
}
@@ -4828,7 +4856,7 @@ evaluate_function(Oid funcid, Oid result_type, int32 result_typmod,
newexpr->location = -1;
return evaluate_expr((Expr *) newexpr, result_type, result_typmod,
- result_collid);
+ result_collid, NULL);
}
/*
@@ -5282,10 +5310,14 @@ sql_inline_error_callback(void *arg)
*
* We use the executor's routine ExecEvalExpr() to avoid duplication of
* code and ensure we get the same result as the executor would get.
+ *
+ * When escontext is not NULL, we will evaluate the constant expression in a
+ * error safe way. If the evaluation fails, return NULL instead of throwing an
+ * error.
*/
Expr *
evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
- Oid result_collation)
+ Oid result_collation, Node *escontext)
{
EState *estate;
ExprState *exprstate;
@@ -5310,7 +5342,7 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
* Prepare expr for execution. (Note: we can't use ExecPrepareExpr
* because it'd result in recursively invoking eval_const_expressions.)
*/
- exprstate = ExecInitExpr(expr, NULL);
+ exprstate = ExecInitExprExtended(expr, escontext, NULL);
/*
* And evaluate it.
@@ -5330,6 +5362,13 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
/* Get back to outer memory context */
MemoryContextSwitchTo(oldcontext);
+ if (SOFT_ERROR_OCCURRED(exprstate->escontext))
+ {
+ FreeExecutorState(estate);
+
+ return NULL;
+ }
+
/*
* Must copy result out of sub-context used by expression eval.
*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9ea81250ce8..15e4f064782 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -158,6 +158,8 @@ static void updateRawStmtEnd(RawStmt *rs, int end_location);
static Node *makeColumnRef(char *colname, List *indirection,
int location, core_yyscan_t yyscanner);
static Node *makeTypeCast(Node *arg, TypeName *typename, int location);
+static Node *makeTypeCastWithDefault(Node *arg, TypeName *typename,
+ Node *raw_default, int location);
static Node *makeStringConstCast(char *str, int location, TypeName *typename);
static Node *makeIntConst(int val, int location);
static Node *makeFloatConst(char *str, int location);
@@ -16065,6 +16067,12 @@ func_expr_common_subexpr:
}
| CAST '(' a_expr AS Typename ')'
{ $$ = makeTypeCast($3, $5, @1); }
+ | CAST '(' a_expr AS Typename ERROR_P ON CONVERSION_P ERROR_P ')'
+ { $$ = makeTypeCast($3, $5, @1); }
+ | CAST '(' a_expr AS Typename NULL_P ON CONVERSION_P ERROR_P ')'
+ { $$ = makeTypeCastWithDefault($3, $5, makeNullAConst(-1), @1); }
+ | CAST '(' a_expr AS Typename DEFAULT a_expr ON CONVERSION_P ERROR_P ')'
+ { $$ = makeTypeCastWithDefault($3, $5, $7, @1); }
| EXTRACT '(' extract_list ')'
{
$$ = (Node *) makeFuncCall(SystemFuncName("extract"),
@@ -18996,10 +19004,25 @@ makeTypeCast(Node *arg, TypeName *typename, int location)
n->arg = arg;
n->typeName = typename;
+ n->raw_default = NULL;
n->location = location;
return (Node *) n;
}
+static Node *
+makeTypeCastWithDefault(Node *arg, TypeName *typename, Node *raw_default,
+ int location)
+{
+ TypeCast *n = makeNode(TypeCast);
+
+ n->arg = arg;
+ n->typeName = typename;
+ n->raw_default = raw_default;
+ n->location = location;
+
+ return (Node *) n;
+}
+
static Node *
makeStringConstCast(char *str, int location, TypeName *typename)
{
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 25ee0f87d93..0e106fdfced 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -490,6 +490,12 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
err = _("grouping operations are not allowed in check constraints");
break;
+ case EXPR_KIND_CAST_DEFAULT:
+ if (isAgg)
+ err = _("aggregate functions are not allowed in CAST DEFAULT expressions");
+ else
+ err = _("grouping operations are not allowed in CAST DEFAULT expressions");
+ break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
@@ -983,6 +989,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_DOMAIN_CHECK:
err = _("window functions are not allowed in check constraints");
break;
+ case EXPR_KIND_CAST_DEFAULT:
+ err = _("window functions are not allowed in CAST DEFAULT expressions");
+ break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
err = _("window functions are not allowed in DEFAULT expressions");
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 913ca53666f..2ab49a5743d 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -81,6 +81,31 @@ coerce_to_target_type(ParseState *pstate, Node *expr, Oid exprtype,
CoercionContext ccontext,
CoercionForm cformat,
int location)
+{
+ return coerce_to_target_type_extended(pstate,
+ expr,
+ exprtype,
+ targettype,
+ targettypmod,
+ ccontext,
+ cformat,
+ location,
+ NULL);
+}
+
+/*
+ * escontext: If not NULL, expr (Unknown Const node type) will be coerced to the
+ * target type in an error-safe way. If it fails, NULL is returned.
+ *
+ * For other parameters, see above coerce_to_target_type.
+ */
+Node *
+coerce_to_target_type_extended(ParseState *pstate, Node *expr, Oid exprtype,
+ Oid targettype, int32 targettypmod,
+ CoercionContext ccontext,
+ CoercionForm cformat,
+ int location,
+ Node *escontext)
{
Node *result;
Node *origexpr;
@@ -102,9 +127,12 @@ 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_extended(pstate, expr, exprtype,
+ targettype, targettypmod,
+ ccontext, cformat, location,
+ escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return NULL;
/*
* If the target is a fixed-length type, it may need a length coercion as
@@ -158,6 +186,17 @@ Node *
coerce_type(ParseState *pstate, Node *node,
Oid inputTypeId, Oid targetTypeId, int32 targetTypeMod,
CoercionContext ccontext, CoercionForm cformat, int location)
+{
+ return coerce_type_extended(pstate, node,
+ inputTypeId, targetTypeId, targetTypeMod,
+ ccontext, cformat, location, NULL);
+}
+
+Node *
+coerce_type_extended(ParseState *pstate, Node *node,
+ Oid inputTypeId, Oid targetTypeId, int32 targetTypeMod,
+ CoercionContext ccontext, CoercionForm cformat,
+ int location, Node *escontext)
{
Node *result;
CoercionPathType pathtype;
@@ -256,6 +295,8 @@ coerce_type(ParseState *pstate, Node *node,
int32 inputTypeMod;
Type baseType;
ParseCallbackState pcbstate;
+ char *string;
+ bool coercion_failed = false;
/*
* If the target type is a domain, we want to call its base type's
@@ -308,21 +349,27 @@ 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 (con->constisnull)
+ string = NULL;
else
- newcon->constvalue = stringTypeDatum(baseType,
- NULL,
- inputTypeMod);
+ string = DatumGetCString(con->constvalue);
+
+ if (!stringTypeDatumSafe(baseType,
+ string,
+ inputTypeMod,
+ escontext,
+ &newcon->constvalue))
+ {
+ coercion_failed = true;
+ newcon->constisnull = true;
+ }
/*
* 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 (!coercion_failed && !con->constisnull && newcon->constlen == -1)
newcon->constvalue =
PointerGetDatum(PG_DETOAST_DATUM(newcon->constvalue));
@@ -339,7 +386,7 @@ 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 (!coercion_failed && !con->constisnull && !newcon->constbyval)
{
Datum val2;
@@ -358,8 +405,10 @@ coerce_type(ParseState *pstate, Node *node,
result = (Node *) newcon;
- /* If target is a domain, apply constraints. */
- if (baseTypeId != targetTypeId)
+ if (coercion_failed)
+ result = NULL;
+ else if (baseTypeId != targetTypeId)
+ /* If target is a domain, apply constraints. */
result = coerce_to_domain(result,
baseTypeId, baseTypeMod,
targetTypeId,
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 9cba1272253..6fef908804c 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -17,6 +17,7 @@
#include "access/htup_details.h"
#include "catalog/pg_aggregate.h"
+#include "catalog/pg_cast.h"
#include "catalog/pg_type.h"
#include "miscadmin.h"
#include "nodes/makefuncs.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,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 *can_coerce);
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 +79,8 @@ 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 void CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr,
+ Node *sourceexpr, Oid inputType, Oid targetType);
static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
static Node *transformJsonObjectConstructor(ParseState *pstate,
JsonObjectConstructor *ctor);
@@ -164,7 +169,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:
@@ -564,6 +569,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
case EXPR_KIND_VALUES_SINGLE:
case EXPR_KIND_CHECK_CONSTRAINT:
case EXPR_KIND_DOMAIN_CHECK:
+ case EXPR_KIND_CAST_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
case EXPR_KIND_INDEX_EXPRESSION:
case EXPR_KIND_INDEX_PREDICATE:
@@ -1824,6 +1830,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_DOMAIN_CHECK:
err = _("cannot use subquery in check constraint");
break;
+ case EXPR_KIND_CAST_DEFAULT:
+ err = _("cannot use subquery in CAST DEFAULT expression");
+ break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
err = _("cannot use subquery in DEFAULT expression");
@@ -2011,17 +2020,24 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
* 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().
+ *
+ * Most of the time, can_coerce will be NULL. It is not NULL only when
+ * performing parse analysis for CAST(DEFAULT ... ON CONVERSION ERROR)
+ * expression. It's default to true. If coercing array elements fails, it will
+ * be set to 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;
List *newcoercedelems = NIL;
ListCell *element;
Oid coerce_type;
+ Oid coerce_type_coll;
bool coerce_hard;
+ ErrorSaveContext escontext = {T_ErrorSaveContext};
/*
* Transform the element expressions
@@ -2045,9 +2061,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
@@ -2088,6 +2105,9 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
}
else
{
+ /* Target type must valid for CAST DEFAULT */
+ Assert(can_coerce == NULL);
+
/* Can't handle an empty array without a target type */
if (newelems == NIL)
ereport(ERROR,
@@ -2125,6 +2145,8 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
coerce_hard = false;
}
+ coerce_type_coll = get_typcollation(coerce_type);
+
/*
* Coerce elements to target type
*
@@ -2134,28 +2156,82 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
* If the array's type was merely derived from the common type of its
* elements, then the elements are implicitly coerced to the common type.
* This is consistent with other uses of select_common_type().
+ *
+ * If can_coerce is not NULL, we need to safely test whether each element
+ * can be coerced to the target type, similar to what is done in
+ * transformTypeSafeCast.
*/
foreach(element, newelems)
{
Node *e = (Node *) lfirst(element);
- Node *newe;
+ Node *newe = NULL;
if (coerce_hard)
{
- newe = coerce_to_target_type(pstate, e,
- exprType(e),
- coerce_type,
- typmod,
- COERCION_EXPLICIT,
- COERCE_EXPLICIT_CAST,
- -1);
- if (newe == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_CANNOT_COERCE),
- errmsg("cannot cast type %s to %s",
- format_type_be(exprType(e)),
- format_type_be(coerce_type)),
- parser_errposition(pstate, exprLocation(e))));
+ /*
+ * Can not coerce, just append the transformed element expression
+ * to the list.
+ */
+ if (can_coerce && (!*can_coerce))
+ newe = e;
+ else
+ {
+ Node *ecopy = NULL;
+
+ if (can_coerce)
+ ecopy = copyObject(e);
+
+ newe = coerce_to_target_type_extended(pstate, e,
+ exprType(e),
+ coerce_type,
+ typmod,
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ -1,
+ (Node *) &escontext);
+ if (newe == NULL)
+ {
+ if (!can_coerce)
+ ereport(ERROR,
+ (errcode(ERRCODE_CANNOT_COERCE),
+ errmsg("cannot cast type %s to %s",
+ format_type_be(exprType(e)),
+ format_type_be(coerce_type)),
+ parser_errposition(pstate, exprLocation(e))));
+ else
+ {
+ /*
+ * Can not coerce, just append the transformed element
+ * expression to the list.
+ */
+ newe = ecopy;
+ *can_coerce = false;
+ }
+ }
+ else if (can_coerce && IsA(ecopy, Const) && IsA(newe, FuncExpr))
+ {
+ Node *result;
+
+ /*
+ * pre-evaluate simple constant cast expressions in a way
+ * that tolerate errors.
+ */
+ FuncExpr *newexpr = (FuncExpr *) copyObject(newe);
+
+ assign_expr_collations(pstate, (Node *) newexpr);
+
+ result = (Node *) evaluate_expr((Expr *) newexpr,
+ coerce_type,
+ typmod,
+ coerce_type_coll,
+ (Node *) &escontext);
+ if (result == NULL)
+ {
+ newe = ecopy;
+ *can_coerce = false;
+ }
+ }
+ }
}
else
newe = coerce_to_common_type(pstate, e,
@@ -2696,7 +2772,8 @@ transformWholeRowRef(ParseState *pstate, ParseNamespaceItem *nsitem,
}
/*
- * Handle an explicit CAST construct.
+ * Handle an explicit CAST construct or
+ * CAST(... DEFAULT ... ON CONVERSION ERROR) construct.
*
* Transform the argument, look up the type name, and apply any necessary
* coercion function(s).
@@ -2704,17 +2781,64 @@ transformWholeRowRef(ParseState *pstate, ParseNamespaceItem *nsitem,
static Node *
transformTypeCast(ParseState *pstate, TypeCast *tc)
{
- Node *result;
+ SafeTypeCastExpr *stc;
+ Node *cast_expr = NULL;
+ Node *defexpr = NULL;
Node *arg = tc->arg;
+ Node *srcexpr;
Node *expr;
Oid inputType;
Oid targetType;
+ Oid targetTypecoll;
int32 targetTypmod;
int location;
+ bool can_coerce = true;
+ ErrorSaveContext escontext = {T_ErrorSaveContext};
/* Look up the type name first */
typenameTypeIdAndMod(pstate, tc->typeName, &targetType, &targetTypmod);
+ targetTypecoll = get_typcollation(targetType);
+
+ /* now looking at DEFAULT expression */
+ if (tc->raw_default)
+ {
+ Oid defColl;
+
+ defexpr = transformExpr(pstate, tc->raw_default, EXPR_KIND_CAST_DEFAULT);
+
+ defexpr = coerce_to_target_type(pstate, defexpr, exprType(defexpr),
+ targetType, targetTypmod,
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ exprLocation(defexpr));
+ if (defexpr == NULL)
+ ereport(ERROR,
+ errcode(ERRCODE_CANNOT_COERCE),
+ errmsg("cannot coerce %s expression to type %s",
+ "CAST DEFAULT",
+ format_type_be(targetType)),
+ parser_coercion_errposition(pstate, exprLocation(tc->raw_default), defexpr));
+
+ assign_expr_collations(pstate, defexpr);
+
+ /*
+ * The collation of DEFAULT expression must match the collation of the
+ * target type.
+ */
+ defColl = exprCollation(defexpr);
+
+ if (OidIsValid(targetTypecoll) && OidIsValid(defColl) &&
+ targetTypecoll != defColl)
+ ereport(ERROR,
+ errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("the collation of CAST DEFAULT expression conflicts with target type collation"),
+ errdetail("\"%s\" versus \"%s\"",
+ get_collation_name(targetTypecoll),
+ get_collation_name(defColl)),
+ parser_errposition(pstate, exprLocation(defexpr)));
+ }
+
/*
* If the subject of the typecast is an ARRAY[] construct and the target
* type is an array type, we invoke transformArrayExpr() directly so that
@@ -2743,7 +2867,8 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
(A_ArrayExpr *) arg,
targetBaseType,
elementType,
- targetBaseTypmod);
+ targetBaseTypmod,
+ defexpr ? &can_coerce : NULL);
}
else
expr = transformExprRecurse(pstate, arg);
@@ -2764,20 +2889,200 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
if (location < 0)
location = tc->typeName->location;
- result = coerce_to_target_type(pstate, expr, inputType,
- targetType, targetTypmod,
- COERCION_EXPLICIT,
- COERCE_EXPLICIT_CAST,
- location);
- if (result == NULL)
+ /*
+ * coerce_to_target_type_extended is unlikely to modify the source
+ * expression, but we still create a copy beforehand. This allows
+ * SafeTypeCastExpr to receive the transformed source expression
+ * unchanged.
+ */
+ srcexpr = copyObject(expr);
+
+ if (can_coerce)
+ {
+ cast_expr = coerce_to_target_type_extended(pstate, expr, inputType,
+ targetType, targetTypmod,
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ location,
+ defexpr ? (Node *) &escontext : NULL);
+
+ /*
+ * If this is a simple CAST construct, return the cast expression now,
+ * or throw an error.
+ */
+ if (defexpr == NULL)
+ {
+ if (cast_expr == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_CANNOT_COERCE),
+ errmsg("cannot cast type %s to %s",
+ format_type_be(inputType),
+ format_type_be(targetType)),
+ parser_coercion_errposition(pstate, location, expr)));
+
+ return cast_expr;
+ }
+
+ if (cast_expr == NULL)
+ can_coerce = false;
+ }
+
+ /* Further check CAST(... DEFAULT ... ON CONVERSION ERROR) construct */
+ CoercionErrorSafeCheck(pstate, cast_expr, srcexpr, inputType,
+ targetType);
+
+ if (IsA(srcexpr, Const) && cast_expr && IsA(cast_expr, FuncExpr))
+ {
+ Node *result;
+
+ /*
+ * pre-evaluate simple constant cast expression in a error safe way
+ *
+ * Rationale:
+ *
+ * 1. When deparsing safe cast expression (or in other cases),
+ * eval_const_expressions might be invoked, but it cannot handle
+ * errors gracefully.
+ *
+ * 2. Even if FuncExpr (for castfunc) node has three arguments, the
+ * second and third arguments will also be constants per
+ * coerce_to_target_type. Evaluating a FuncExpr whose arguments are
+ * all Const is safe.
+ */
+ FuncExpr *newexpr = (FuncExpr *) copyObject(cast_expr);
+
+ assign_expr_collations(pstate, (Node *) newexpr);
+
+ result = (Node *) evaluate_expr((Expr *) newexpr,
+ targetType,
+ targetTypmod,
+ targetTypecoll,
+ (Node *) &escontext);
+ if (result == NULL)
+ {
+ /* can not coerce, set can_coerce to false */
+ can_coerce = false;
+ cast_expr = NULL;
+ }
+ }
+
+ Assert(can_coerce || cast_expr == NULL);
+
+ stc = makeNode(SafeTypeCastExpr);
+ stc->source_expr = srcexpr;
+ stc->cast_expr = cast_expr;
+ stc->default_expr = defexpr;
+ stc->resulttype = targetType;
+ stc->resulttypmod = targetTypmod;
+ stc->resultcollid = targetTypecoll;
+ stc->location = location;
+
+ return (Node *) stc;
+}
+
+/*
+ * Check type coercion is error safe or not. If not then report error
+ */
+static void
+CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr, Node *sourceexpr,
+ Oid inputType, Oid targetType)
+{
+ HeapTuple tuple;
+ bool errorsafe = true;
+ bool userdefined = false;
+ Oid inputBaseType;
+ Oid targetBaseType;
+ Oid inputElementType;
+ Oid inputElementBaseType;
+ Oid targetElementType;
+ Oid targetElementBaseType;
+
+ /*
+ * Binary coercion cast is equivalent to no cast at all. CoerceViaIO can
+ * also be evaluated in an error-safe manner. So skip these two cases.
+ */
+ if (!castexpr || IsA(castexpr, RelabelType) ||
+ IsA(castexpr, CoerceViaIO))
+ return;
+
+ /*
+ * Type cast involving with some types is not error safe, do the check
+ * now. We need consider domain over array and array over domain scarenio.
+ * We already checked user-defined type cast above.
+ */
+ inputElementType = get_element_type(inputType);
+
+ if (OidIsValid(inputElementType))
+ inputElementBaseType = getBaseType(inputElementType);
+ else
+ {
+ inputBaseType = getBaseType(inputType);
+
+ inputElementBaseType = get_element_type(inputBaseType);
+ if (!OidIsValid(inputElementBaseType))
+ inputElementBaseType = inputBaseType;
+ }
+
+ targetElementType = get_element_type(targetType);
+
+ if (OidIsValid(targetElementType))
+ targetElementBaseType = getBaseType(targetElementType);
+ else
+ {
+ targetBaseType = getBaseType(targetType);
+
+ targetElementBaseType = get_element_type(targetBaseType);
+ if (!OidIsValid(targetElementBaseType))
+ targetElementBaseType = targetBaseType;
+ }
+
+ if (inputElementBaseType == MONEYOID ||
+ targetElementBaseType == MONEYOID ||
+ (inputElementBaseType == CIRCLEOID &&
+ targetElementBaseType == POLYGONOID))
+ {
+ /*
+ * Casts involving MONEY type are not error safe. The CIRCLE to
+ * POLYGON cast is also unsafe because it relies on a SQL-language
+ * function; only C-language functions are currently supported for
+ * error safe casts.
+ */
+ errorsafe = false;
+ }
+
+ if (errorsafe)
+ {
+ /* Look in pg_cast */
+ tuple = SearchSysCache2(CASTSOURCETARGET,
+ ObjectIdGetDatum(inputElementBaseType),
+ ObjectIdGetDatum(targetElementBaseType));
+
+ if (HeapTupleIsValid(tuple))
+ {
+ Form_pg_cast castForm = (Form_pg_cast) GETSTRUCT(tuple);
+
+ if (castForm->oid > FirstUnpinnedObjectId)
+ {
+ errorsafe = false;
+ userdefined = true;
+ }
+
+ ReleaseSysCache(tuple);
+ }
+ }
+
+ if (!errorsafe)
ereport(ERROR,
- (errcode(ERRCODE_CANNOT_COERCE),
- errmsg("cannot cast type %s to %s",
- format_type_be(inputType),
- format_type_be(targetType)),
- parser_coercion_errposition(pstate, location, expr)));
-
- return result;
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot cast type %s to %s when %s expression is specified in %s",
+ format_type_be(inputType),
+ format_type_be(targetType),
+ "DEFAULT",
+ "CAST ... ON CONVERSION ERROR"),
+ userdefined
+ ? errhint("Safe type cast for user-defined types are not yet supported")
+ : errhint("Explicit cast is defined but definition is not error safe"),
+ parser_errposition(pstate, exprLocation(sourceexpr)));
}
/*
@@ -3193,6 +3498,8 @@ ParseExprKindName(ParseExprKind exprKind)
case EXPR_KIND_CHECK_CONSTRAINT:
case EXPR_KIND_DOMAIN_CHECK:
return "CHECK";
+ case EXPR_KIND_CAST_DEFAULT:
+ return "CAST DEFAULT";
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
return "DEFAULT";
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 24f6745923b..590a8a785c9 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2743,6 +2743,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
case EXPR_KIND_DOMAIN_CHECK:
err = _("set-returning functions are not allowed in check constraints");
break;
+ case EXPR_KIND_CAST_DEFAULT:
+ err = _("set-returning functions are not allowed in CAST DEFAULT expressions");
+ break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
err = _("set-returning functions are not allowed in DEFAULT expressions");
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index bb7eccde9fd..eba17cbf241 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,19 @@ stringTypeDatum(Type tp, char *string, int32 atttypmod)
return OidInputFunctionCall(typinput, string, typioparam, atttypmod);
}
+/* error-safe version of stringTypeDatum */
+bool
+stringTypeDatumSafe(Type tp, char *string, int32 atttypmod,
+ Node *escontext, Datum *result)
+{
+ Form_pg_type typform = (Form_pg_type) GETSTRUCT(tp);
+ Oid typinput = typform->typinput;
+ Oid typioparam = getTypeIOParam(tp);
+
+ return OidInputFunctionCallSafe(typinput, string, typioparam, atttypmod,
+ escontext, result);
+}
+
/*
* Given a typeid, return the type's typrelid (associated relation), if any.
* Returns InvalidOid if type is not a composite type.
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 96f442c4145..7ac9486f781 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -4943,7 +4943,7 @@ transformPartitionBoundValue(ParseState *pstate, Node *val,
assign_expr_collations(pstate, value);
value = (Node *) expression_planner((Expr *) value);
value = (Node *) evaluate_expr((Expr *) value, colType, colTypmod,
- partCollation);
+ partCollation, NULL);
if (!IsA(value, Const))
elog(ERROR, "could not evaluate partition bound expression");
}
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index e71d32773b5..c2effe10620 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -3289,6 +3289,14 @@ array_map(Datum arrayd,
/* Apply the given expression to source element */
values[i] = ExecEvalExpr(exprstate, econtext, &nulls[i]);
+ /* Exit early if the evaluation fails */
+ 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 416f1a21ae4..ae1a113b9a5 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -10562,6 +10562,27 @@ get_rule_expr(Node *node, deparse_context *context,
}
break;
+ case T_SafeTypeCastExpr:
+ {
+ SafeTypeCastExpr *stcexpr = castNode(SafeTypeCastExpr, node);
+
+ /*
+ * Here, we cannot deparsing cast_expr directly, since
+ * transformTypeSafeCast may have folded it into a simple
+ * constant or NULL. Instead, we use source_expr and
+ * default_expr to reconstruct the CAST DEFAULT clause.
+ */
+ 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 05984e7ef26..28fed125af7 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -1759,6 +1759,20 @@ OidInputFunctionCall(Oid functionId, char *str, Oid typioparam, int32 typmod)
return InputFunctionCall(&flinfo, str, typioparam, typmod);
}
+/* error-safe version of OidInputFunctionCall */
+bool
+OidInputFunctionCallSafe(Oid functionId, char *str, Oid typioparam,
+ int32 typmod, Node *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 aa9b361fa31..fa9db8fd687 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_SAFETYPE_CAST */
+ struct
+ {
+ struct SafeTypeCastState *stcstate;
+ } stcexpr;
+
/* for EEOP_JSONEXPR_PATH */
struct
{
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 5929aabc353..8ffba99d352 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -324,6 +324,7 @@ ExecProcNode(PlanState *node)
* prototypes from functions in execExpr.c
*/
extern ExprState *ExecInitExpr(Expr *node, PlanState *parent);
+extern ExprState *ExecInitExprExtended(Expr *node, Node *escontext, PlanState *parent);
extern ExprState *ExecInitExprWithParams(Expr *node, ParamListInfo ext_params);
extern ExprState *ExecInitQual(List *qual, PlanState *parent);
extern ExprState *ExecInitCheck(List *qual, PlanState *parent);
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index 22dd6526169..44d9ea07c47 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, Node *escontext,
+ Datum *result);
extern char *OutputFunctionCall(FmgrInfo *flinfo, Datum val);
extern char *OidOutputFunctionCall(Oid functionId, Datum val);
extern Datum ReceiveFunctionCall(FmgrInfo *flinfo, StringInfo buf,
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 02265456978..1842fad8f45 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1059,6 +1059,27 @@ typedef struct DomainConstraintState
ExprState *check_exprstate; /* check_expr's eval state, or NULL */
} DomainConstraintState;
+typedef struct SafeTypeCastState
+{
+ SafeTypeCastExpr *stcexpr;
+
+ /*
+ * Address to jump to when skipping all the steps to evaulate the default
+ * expression.
+ */
+ int jump_end;
+
+ /*
+ * For error-safe evaluation of coercions. 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 896c4f34cc0..62f27d301d0 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -397,6 +397,7 @@ typedef struct TypeCast
NodeTag type;
Node *arg; /* the expression being casted */
TypeName *typeName; /* the target type */
+ Node *raw_default; /* untransformed DEFAULT expression */
ParseLoc location; /* token location, or -1 if unknown */
} TypeCast;
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 5211cadc258..63de960fdd7 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -769,6 +769,42 @@ typedef enum CoercionForm
COERCE_SQL_SYNTAX, /* display with SQL-mandated special syntax */
} CoercionForm;
+/*
+ * SafeTypeCastExpr -
+ * Transformed representation of
+ * CAST(expr AS typename DEFAULT expr ON CONVERSION ERROR)
+ * CAST(expr AS typename NULL ON CONVERSION ERROR)
+ */
+typedef struct SafeTypeCastExpr
+{
+ Expr xpr;
+
+ /* transformed source expression */
+ Node *source_expr;
+
+ /*
+ * The transformed cast expression; NULL if we can not cocerce source
+ * expression to the target type
+ */
+ Node *cast_expr pg_node_attr(query_jumble_ignore);
+
+ /* Fall back to the default expression if cast evaluation fails */
+ Node *default_expr;
+
+ /* cast result data type */
+ Oid resulttype;
+
+ /* cast result data type typmod (usually -1) */
+ int32 resulttypmod;
+
+ /* cast result data type collation */
+ Oid resultcollid;
+
+ /* Original SafeTypeCastExpr's location */
+ ParseLoc location;
+
+} SafeTypeCastExpr;
+
/*
* FuncExpr - expression node for a function call
*
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index 485f641b3b4..bed86459430 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -143,7 +143,7 @@ extern void convert_saop_to_hashed_saop(Node *node);
extern Node *estimate_expression_value(PlannerInfo *root, Node *node);
extern Expr *evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
- Oid result_collation);
+ Oid result_collation, Node *escontext);
extern bool var_is_nonnullable(PlannerInfo *root, Var *var, bool use_rel_info);
diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h
index aabacd49b65..2c921617762 100644
--- a/src/include/parser/parse_coerce.h
+++ b/src/include/parser/parse_coerce.h
@@ -43,11 +43,24 @@ extern Node *coerce_to_target_type(ParseState *pstate,
CoercionContext ccontext,
CoercionForm cformat,
int location);
+extern Node *coerce_to_target_type_extended(ParseState *pstate,
+ Node *expr,
+ Oid exprtype,
+ Oid targettype,
+ int32 targettypmod,
+ CoercionContext ccontext,
+ CoercionForm cformat,
+ int location,
+ Node *escontext);
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_extended(ParseState *pstate, Node *node,
+ Oid inputTypeId, Oid targetTypeId, int32 targetTypeMod,
+ CoercionContext ccontext, CoercionForm cformat,
+ int location, Node *escontext);
extern Node *coerce_to_domain(Node *arg, Oid baseTypeId, int32 baseTypeMod,
Oid typeId,
CoercionContext ccontext, CoercionForm cformat, int location,
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index a9bffb8a78f..dd72944a5a1 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -67,6 +67,8 @@ typedef enum ParseExprKind
EXPR_KIND_VALUES_SINGLE, /* single-row VALUES (in INSERT only) */
EXPR_KIND_CHECK_CONSTRAINT, /* CHECK constraint for a table */
EXPR_KIND_DOMAIN_CHECK, /* CHECK constraint for a domain */
+ EXPR_KIND_CAST_DEFAULT, /* default expression in
+ CAST DEFAULT ON CONVERSION ERROR */
EXPR_KIND_COLUMN_DEFAULT, /* default value for a table column */
EXPR_KIND_FUNCTION_DEFAULT, /* default parameter value for function */
EXPR_KIND_INDEX_EXPRESSION, /* index expression */
diff --git a/src/include/parser/parse_type.h b/src/include/parser/parse_type.h
index a335807b0b0..e303a1073c1 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,
+ Node *escontext, Datum *result);
extern Oid typeidTypeRelid(Oid type_id);
extern Oid typeOrDomainTypeRelid(Oid type_id);
diff --git a/src/test/regress/expected/cast.out b/src/test/regress/expected/cast.out
new file mode 100644
index 00000000000..b0dc7b8faea
--- /dev/null
+++ b/src/test/regress/expected/cast.out
@@ -0,0 +1,810 @@
+SET extra_float_digits = 0;
+-- CAST DEFAULT ON CONVERSION ERROR
+SELECT CAST(B'01' AS date DEFAULT NULL ON CONVERSION ERROR);
+ date
+------
+
+(1 row)
+
+SELECT CAST(BIT'01' AS date DEFAULT NULL ON CONVERSION ERROR);
+ date
+------
+
+(1 row)
+
+SELECT CAST(TRUE AS date DEFAULT NULL ON CONVERSION ERROR);
+ date
+------
+
+(1 row)
+
+SELECT CAST(1.1 AS date DEFAULT NULL ON CONVERSION ERROR);
+ date
+------
+
+(1 row)
+
+SELECT CAST(1 AS date DEFAULT NULL ON CONVERSION ERROR);
+ date
+------
+
+(1 row)
+
+SELECT CAST(1111 AS "char" DEFAULT 'A' ON CONVERSION ERROR);
+ char
+------
+ A
+(1 row)
+
+--test source expression is a unknown const
+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('a' as int DEFAULT 18 ON CONVERSION ERROR);
+ int4
+------
+ 18
+(1 row)
+
+SELECT CAST('a' as int DEFAULT sum(1) ON CONVERSION ERROR); --error
+ERROR: aggregate functions are not allowed in CAST DEFAULT expressions
+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: window functions are not allowed in CAST DEFAULT expressions
+LINE 1: SELECT CAST('a' as int DEFAULT sum(1) over() ON CONVERSION E...
+ ^
+SELECT CAST('a' as int DEFAULT (SELECT NULL) ON CONVERSION ERROR); --error
+ERROR: cannot use subquery in CAST DEFAULT expression
+LINE 1: SELECT CAST('a' as int DEFAULT (SELECT NULL) 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('a'::int as int DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR: invalid input syntax for type integer: "a"
+LINE 1: SELECT CAST('a'::int as int DEFAULT NULL ON CONVERSION ERROR...
+ ^
+--the default expression’s collation should match the collation of the cast
+--target type
+VALUES (CAST('error' AS text DEFAULT '1' COLLATE "C" ON CONVERSION ERROR));
+ERROR: the collation of CAST DEFAULT expression conflicts with target type collation
+LINE 1: VALUES (CAST('error' AS text DEFAULT '1' COLLATE "C" ON CONV...
+ ^
+DETAIL: "default" versus "C"
+VALUES (CAST('error' AS int2vector DEFAULT '1 3' ON CONVERSION ERROR));
+ column1
+---------
+ 1 3
+(1 row)
+
+VALUES (CAST('error' AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR));
+ column1
+---------
+ {"1 3"}
+(1 row)
+
+-- test source expression contain subquery
+SELECT CAST((SELECT b FROM generate_series(1, 1), (VALUES('H')) s(b))
+ AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR);
+ b
+---------
+ {"1 3"}
+(1 row)
+
+SELECT CAST((SELECT ARRAY((SELECT b FROM generate_series(1, 2) g, (VALUES('H')) s(b))))
+ AS INT[]
+ DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+CREATE FUNCTION ret_int8() RETURNS BIGINT AS
+$$
+BEGIN RETURN 2147483648; END;
+$$
+LANGUAGE plpgsql IMMUTABLE;
+SELECT CAST('a' as int DEFAULT ret_int8() ON CONVERSION ERROR); --error
+ERROR: integer out of range
+SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERROR); --error
+ERROR: cannot coerce CAST DEFAULT expression to type date
+LINE 1: SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERR...
+ ^
+-- DEFAULT expression can not be set-returning
+CREATE 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);
+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: set-returning functions are not allowed in CAST DEFAULT expressions
+LINE 1: SELECT CAST('a' as int DEFAULT ret_setint() ON CONVERSION ER...
+ ^
+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 user-defined type, domain, array over domain, domain over array
+CREATE DOMAIN d_int42 as int check (value = 42) NOT NULL;
+CREATE DOMAIN d_char3_not_null as char(3) NOT NULL;
+CREATE DOMAIN d_varchar as varchar(3) NOT NULL;
+CREATE DOMAIN d_int_arr as int[] check (value = '{41, 43}') NOT NULL;
+CREATE TYPE comp_domain_with_typmod AS (a d_char3_not_null, b int);
+CREATE TYPE comp2 AS (a d_varchar);
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION ERROR); --error
+ERROR: value too long for type character varying(3)
+LINE 1: SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION...
+ ^
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(123)' ON CONVERSION ERROR);
+ comp2
+-------
+ (123)
+(1 row)
+
+SELECT CAST(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);
+ 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);
+ 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)'...
+ ^
+SELECT CAST(ARRAY[42,41] AS d_int42[] DEFAULT '{42, 42}' ON CONVERSION ERROR);
+ array
+---------
+ {42,42}
+(1 row)
+
+SELECT CAST(ARRAY[42,41] AS d_int_arr DEFAULT '{41, 43}' ON CONVERSION ERROR);
+ array
+---------
+ {41,43}
+(1 row)
+
+-- test array coerce
+SELECT CAST(array['a'::text] AS int[] DEFAULT NULL ON CONVERSION ERROR); --error
+ERROR: invalid input syntax for type integer: "a"
+SELECT CAST(array['a'] AS int[] DEFAULT ARRAY[1] ON CONVERSION ERROR);
+ array
+-------
+ {1}
+(1 row)
+
+SELECT CAST(array[11.12] AS date[] DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+SELECT CAST(array[11111111111111111] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+SELECT CAST(array[['abc'],[456]] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+SELECT CAST('{123,abc,456}' AS int[] DEFAULT '{-789}' ON CONVERSION ERROR);
+ int4
+--------
+ {-789}
+(1 row)
+
+SELECT CAST('{234,def,567}'::text[] AS integer[] DEFAULT '{-1011}' ON CONVERSION ERROR);
+ int4
+---------
+ {-1011}
+(1 row)
+
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS int[] DEFAULT '{1,2}' ON CONVERSION ERROR);
+ array
+-------
+ {1,2}
+(1 row)
+
+SELECT CAST(ARRAY[['1', '2'], ['three', 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --ok
+ array
+---------
+ {21,22}
+(1 row)
+
+SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --error
+ERROR: invalid input syntax for type integer: "three"
+LINE 1: SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] ...
+ ^
+SELECT CAST(ARRAY[1, 'three'::int] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --error
+ERROR: invalid input syntax for type integer: "three"
+LINE 1: SELECT CAST(ARRAY[1, 'three'::int] AS int[] DEFAULT '{21,22}...
+ ^
+-----safe cast with geometry data type
+SELECT CAST('(1,2)'::point AS box DEFAULT NULL ON CONVERSION ERROR);
+ box
+-------------
+ (1,2),(1,2)
+(1 row)
+
+SELECT CAST('[(NaN,1),(NaN,infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+----------------
+ (NaN,Infinity)
+(1 row)
+
+SELECT CAST('[(1e+300,Infinity),(1e+300,Infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+-------------------
+ (1e+300,Infinity)
+(1 row)
+
+SELECT CAST('[(1,2),(3,4)]'::path as polygon DEFAULT NULL ON CONVERSION ERROR);
+ polygon
+---------
+
+(1 row)
+
+SELECT CAST('(NaN,1.0,NaN,infinity)'::box AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+----------------
+ (NaN,Infinity)
+(1 row)
+
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS lseg DEFAULT NULL ON CONVERSION ERROR);
+ lseg
+---------------
+ [(2,2),(0,0)]
+(1 row)
+
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS polygon DEFAULT NULL ON CONVERSION ERROR);
+ polygon
+---------------------------
+ ((0,0),(0,2),(2,2),(2,0))
+(1 row)
+
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS path DEFAULT NULL ON CONVERSION ERROR);
+ path
+------
+
+(1 row)
+
+SELECT CAST('(2.0,infinity,NaN,infinity)'::box AS circle DEFAULT NULL ON CONVERSION ERROR);
+ circle
+----------------------
+ <(NaN,Infinity),NaN>
+(1 row)
+
+SELECT CAST('(NaN,0.0),(2.0,4.0),(0.0,infinity)'::polygon AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+----------------
+ (NaN,Infinity)
+(1 row)
+
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS path DEFAULT NULL ON CONVERSION ERROR);
+ path
+---------------------
+ ((2,0),(2,4),(0,0))
+(1 row)
+
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS box DEFAULT NULL ON CONVERSION ERROR);
+ box
+-------------
+ (2,4),(0,0)
+(1 row)
+
+SELECT CAST('(NaN,infinity),(2.0,4.0),(0.0,infinity)'::polygon AS circle DEFAULT NULL ON CONVERSION ERROR);
+ circle
+----------------------
+ <(NaN,Infinity),NaN>
+(1 row)
+
+SELECT CAST('<(5,1),3>'::circle AS point DEFAULT NULL ON CONVERSION ERROR);
+ point
+-------
+ (5,1)
+(1 row)
+
+SELECT CAST('<(3,5),0>'::circle as box DEFAULT NULL ON CONVERSION ERROR);
+ box
+-------------
+ (3,5),(3,5)
+(1 row)
+
+-- not supported because the cast from circle to polygon is implemented as a SQL
+-- function, which cannot be error-safe.
+SELECT CAST('<(3,5),0>'::circle as polygon DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type circle to polygon when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST('<(3,5),0>'::circle as polygon DEFAULT NULL ON C...
+ ^
+HINT: Explicit cast is defined but definition is not error safe
+-----safe cast from/to money type is not supported, all the following would result error
+SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type bigint to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION E...
+ ^
+HINT: Explicit cast is defined but definition is not error safe
+SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type integer to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION E...
+ ^
+HINT: Explicit cast is defined but definition is not error safe
+SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type numeric to money when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSIO...
+ ^
+HINT: Explicit cast is defined but definition is not error safe
+SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type money to numeric when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSIO...
+ ^
+HINT: Explicit cast is defined but definition is not error safe
+SELECT CAST(NULL::money[] AS numeric[] DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type money[] to numeric[] when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(NULL::money[] AS numeric[] DEFAULT NULL ON CONVE...
+ ^
+HINT: Explicit cast is defined but definition is not error safe
+-----safe cast from bytea type to other data types
+SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT 19 ON CONVERSION ERROR);
+ int8
+------
+ 19
+(1 row)
+
+SELECT CAST('\x123456789A'::bytea AS int4 DEFAULT 20 ON CONVERSION ERROR);
+ int4
+------
+ 20
+(1 row)
+
+SELECT CAST('\x123456'::bytea AS int2 DEFAULT 21 ON CONVERSION ERROR);
+ int2
+------
+ 21
+(1 row)
+
+-----safe cast from bit type to other data types
+SELECT CAST('111111111100001'::bit(100) AS INT DEFAULT 22 ON CONVERSION ERROR);
+ int4
+------
+ 22
+(1 row)
+
+SELECT CAST ('111111111100001'::bit(100) AS INT8 DEFAULT 23 ON CONVERSION ERROR);
+ int8
+------
+ 23
+(1 row)
+
+-----safe cast from text type to other data types
+select CAST('a.b.c.d'::text as regclass default NULL on conversion error);
+ regclass
+----------
+
+(1 row)
+
+CREATE TABLE test_safecast(col0 text);
+INSERT INTO test_safecast(col0) VALUES ('<value>one</value');
+SELECT col0 as text,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) as to_regclass,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) IS NULL as expect_true,
+ CAST(col0 AS "char" DEFAULT NULL ON CONVERSION ERROR) as to_char,
+ CAST(col0 AS name DEFAULT NULL ON CONVERSION ERROR) as to_name,
+ CAST(col0 AS xml DEFAULT NULL ON CONVERSION ERROR) as to_xml
+FROM test_safecast;
+ text | to_regclass | expect_true | to_char | to_name | to_xml
+-------------------+-------------+-------------+---------+-------------------+--------
+ <value>one</value | | t | < | <value>one</value |
+(1 row)
+
+SELECT CAST('192.168.1.x' as inet DEFAULT NULL ON CONVERSION ERROR);
+ inet
+------
+
+(1 row)
+
+SELECT CAST('192.168.1.2/30' as cidr DEFAULT NULL ON CONVERSION ERROR);
+ cidr
+------
+
+(1 row)
+
+SELECT CAST('22:00:5c:08:55:08:01:02'::macaddr8 as macaddr DEFAULT NULL ON CONVERSION ERROR);
+ macaddr
+---------
+
+(1 row)
+
+-----safe cast betweeen range data types
+SELECT CAST('[1,2]'::int4range AS int4multirange DEFAULT NULL ON CONVERSION ERROR);
+ int4multirange
+----------------
+ {[1,3)}
+(1 row)
+
+SELECT CAST('[1,2]'::int8range AS int8multirange DEFAULT NULL ON CONVERSION ERROR);
+ int8multirange
+----------------
+ {[1,3)}
+(1 row)
+
+SELECT CAST('[1,2]'::numrange AS nummultirange DEFAULT NULL ON CONVERSION ERROR);
+ nummultirange
+---------------
+ {[1,2]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::daterange AS datemultirange DEFAULT NULL ON CONVERSION ERROR);
+ datemultirange
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::tsrange AS tsmultirange DEFAULT NULL ON CONVERSION ERROR);
+ tsmultirange
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::tstzrange AS tstzmultirange DEFAULT NULL ON CONVERSION ERROR);
+ tstzmultirange
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+-----safe cast betweeen numeric data types
+CREATE TABLE test_safecast1(
+ col1 float4, col2 float8, col3 numeric, col4 numeric[],
+ col5 int2 default 32767,
+ col6 int4 default 32768,
+ col7 int8 default 4294967296);
+INSERT INTO test_safecast1 VALUES('11.1234', '11.1234', '9223372036854775808', '{11.1234}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('inf', 'inf', 'inf', '{11.1234, 12, inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('-inf', '-inf', '-inf', '{11.1234, 12, -inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('NaN', 'NaN', 'NaN', '{11.1234, 12, -inf, NaN}'::numeric[]);
+SELECT col5 as int2,
+ CAST(col5 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col5 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col5 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col5 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col5 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col5 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col5 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col5 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ int2 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+(4 rows)
+
+SELECT col6 as int4,
+ CAST(col6 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col6 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col6 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col6 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col6 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col6 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col6 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col6 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ int4 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+(4 rows)
+
+SELECT col7 as int8,
+ CAST(col7 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col7 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col7 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col7 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col7 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col7 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col7 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col7 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ int8 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+------------+---------+---------+--------+------------+-------------+------------+------------+------------------
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+(4 rows)
+
+SELECT col3 as numeric,
+ CAST(col3 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col3 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col3 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col3 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col3 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col3 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col3 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col3 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ numeric | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+---------------------+---------+---------+--------+---------+-------------+----------------------+---------------------+------------------
+ 9223372036854775808 | | | | | 9.22337e+18 | 9.22337203685478e+18 | 9223372036854775808 |
+ Infinity | | | | | Infinity | Infinity | Infinity |
+ -Infinity | | | | | -Infinity | -Infinity | -Infinity |
+ NaN | | | | | NaN | NaN | NaN | NaN
+(4 rows)
+
+SELECT col4 as num_arr,
+ CAST(col4 AS int2[] DEFAULT NULL ON CONVERSION ERROR) as int2arr,
+ CAST(col4 AS int4[] DEFAULT NULL ON CONVERSION ERROR) as int4arr,
+ CAST(col4 as int8[] DEFAULT NULL ON CONVERSION ERROR) as int8arr,
+ CAST(col4 as float4[] DEFAULT NULL ON CONVERSION ERROR) as f4arr,
+ CAST(col4 as float8[] DEFAULT NULL ON CONVERSION ERROR) as f8arr,
+ CAST(col4 as numeric(10,1)[] DEFAULT NULL ON CONVERSION ERROR) as numarr
+FROM test_safecast1;
+ num_arr | int2arr | int4arr | int8arr | f4arr | f8arr | numarr
+----------------------------+---------+---------+---------+----------------------------+----------------------------+--------
+ {11.1234} | {11} | {11} | {11} | {11.1234} | {11.1234} | {11.1}
+ {11.1234,12,Infinity,NaN} | | | | {11.1234,12,Infinity,NaN} | {11.1234,12,Infinity,NaN} |
+ {11.1234,12,-Infinity,NaN} | | | | {11.1234,12,-Infinity,NaN} | {11.1234,12,-Infinity,NaN} |
+ {11.1234,12,-Infinity,NaN} | | | | {11.1234,12,-Infinity,NaN} | {11.1234,12,-Infinity,NaN} |
+(4 rows)
+
+SELECT col1 as float4,
+ CAST(col1 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col1 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col1 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col1 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col1 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col1 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col1 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col1 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ float4 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-----------+---------+---------+--------+---------+-----------+------------------+------------+------------------
+ 11.1234 | 11 | 11 | | 11 | 11.1234 | 11.1233997344971 | 11.1234 | 11.1
+ Infinity | | | | | Infinity | Infinity | Infinity |
+ -Infinity | | | | | -Infinity | -Infinity | -Infinity |
+ NaN | | | | | NaN | NaN | NaN | NaN
+(4 rows)
+
+SELECT col2 as float8,
+ CAST(col2 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col2 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col2 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col2 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col2 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col2 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col2 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col2 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ float8 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-----------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 11.1234 | 11 | 11 | | 11 | 11.1234 | 11.1234 | 11.1234 | 11.1
+ Infinity | | | | | Infinity | Infinity | Infinity |
+ -Infinity | | | | | -Infinity | -Infinity | -Infinity |
+ NaN | | | | | NaN | NaN | NaN | NaN
+(4 rows)
+
+-- date/timestamp/interval related safe type cast
+CREATE TABLE test_safecast2(
+ col0 date, col1 timestamp, col2 timestamptz,
+ col3 interval, col4 time, col5 timetz);
+INSERT INTO test_safecast2 VALUES
+('-infinity', '-infinity', '-infinity', '-infinity',
+ '2003-03-07 15:36:39 America/New_York', '2003-07-07 15:36:39 America/New_York'),
+('-infinity', 'infinity', 'infinity', 'infinity',
+ '11:59:59.99 PM', '11:59:59.99 PM PDT');
+SELECT col0 as date,
+ CAST(col0 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col0 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col0 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col0 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col0 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+ date | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale
+-----------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity | -infinity | | | -infinity
+ -infinity | -infinity | -infinity | | | -infinity
+(2 rows)
+
+SELECT col1 as timestamp,
+ CAST(col1 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col1 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col1 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col1 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col1 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+ timestamp | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale
+-----------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity | -infinity | | | -infinity
+ infinity | infinity | infinity | | | infinity
+(2 rows)
+
+SELECT col2 as timestamptz,
+ CAST(col2 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col2 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col2 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col2 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col2 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+ timestamptz | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale
+-------------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity | -infinity | | | -infinity
+ infinity | infinity | infinity | | | infinity
+(2 rows)
+
+SELECT col3 as interval,
+ CAST(col3 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col3 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col3 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col3 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col3 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale,
+ CAST(col3 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+ interval | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale | to_interval_scale
+-----------+----------------+---------+----------+-----------+--------------------+-------------------
+ -infinity | | | | | | -infinity
+ infinity | | | | | | infinity
+(2 rows)
+
+SELECT col4 as time,
+ CAST(col4 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col4 AS timetz DEFAULT NULL ON CONVERSION ERROR) IS NOT NULL as to_timetz,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+ time | to_times | to_timetz | to_interval | to_interval_scale
+-------------+-------------+-----------+-------------------------------+-------------------------------
+ 15:36:39 | 15:36:39 | t | @ 15 hours 36 mins 39 secs | @ 15 hours 36 mins 39 secs
+ 23:59:59.99 | 23:59:59.99 | t | @ 23 hours 59 mins 59.99 secs | @ 23 hours 59 mins 59.99 secs
+(2 rows)
+
+SELECT col5 as timetz,
+ CAST(col5 AS time DEFAULT NULL ON CONVERSION ERROR) as to_time,
+ CAST(col5 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_time_scale,
+ CAST(col5 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col5 AS timetz(6) DEFAULT NULL ON CONVERSION ERROR) as to_timetz_scale,
+ CAST(col5 AS interval DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col5 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+ timetz | to_time | to_time_scale | to_timetz | to_timetz_scale | to_interval | to_interval_scale
+----------------+-------------+---------------+----------------+-----------------+-------------+-------------------
+ 15:36:39-04 | 15:36:39 | 15:36:39 | 15:36:39-04 | 15:36:39-04 | |
+ 23:59:59.99-07 | 23:59:59.99 | 23:59:59.99 | 23:59:59.99-07 | 23:59:59.99-07 | |
+(2 rows)
+
+CREATE TABLE test_safecast3(col0 jsonb);
+INSERT INTO test_safecast3(col0) VALUES ('"test"');
+SELECT col0 as jsonb,
+ CAST(col0 AS integer DEFAULT NULL ON CONVERSION ERROR) as to_integer,
+ CAST(col0 AS numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col0 AS bigint DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col0 AS float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col0 AS float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col0 AS boolean DEFAULT NULL ON CONVERSION ERROR) as to_bool,
+ CAST(col0 AS smallint DEFAULT NULL ON CONVERSION ERROR) as to_smallint
+FROM test_safecast3;
+ jsonb | to_integer | to_numeric | to_int8 | to_float4 | to_float8 | to_bool | to_smallint
+--------+------------+------------+---------+-----------+-----------+---------+-------------
+ "test" | | | | | | |
+(1 row)
+
+-- test deparse
+SET datestyle TO ISO, YMD;
+CREATE VIEW safecastview AS
+SELECT CAST('1234' as char(3) DEFAULT -1111 ON CONVERSION ERROR),
+ CAST(1 as date DEFAULT (('2025-Dec-06'::date + random(min=>1, max=>1::int))) ON CONVERSION ERROR) as cast0,
+ CAST(ARRAY[['1'], ['three'],['a']] AS int[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1,
+ CAST(ARRAY[['1', '2'], ['three', 'a']] AS date[] DEFAULT NULL ON CONVERSION ERROR) as cast2,
+ CAST(ARRAY['three'] AS INT[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast3;
+\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 '2025-12-06'::date + random(min => 1, max => 1) ON CONVERSION ERROR) AS cast0,
+ CAST(ARRAY[ARRAY[1], ARRAY['three'], ARRAY['a']] AS integer[] DEFAULT '{1,2}'::integer[] ON CONVERSION ERROR) AS cast1,
+ CAST(ARRAY[ARRAY['1', '2'], ARRAY['three', 'a']] AS date[] DEFAULT NULL::date[] ON CONVERSION ERROR) AS cast2,
+ CAST(ARRAY['three'] AS integer[] DEFAULT '{1,2}'::integer[] ON CONVERSION ERROR) AS cast3
+SELECT * FROM safecastview;
+ bpchar | cast0 | cast1 | cast2 | cast3
+--------+------------+-------+-------+-------
+ 123 | 2025-12-07 | {1,2} | | {1,2}
+(1 row)
+
+CREATE VIEW safecastview1 AS
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS d_int_arr
+ DEFAULT '{41,43}' ON CONVERSION ERROR) as cast1,
+ CAST(ARRAY[['1', '2', 1.1], ['three', true, B'01']] AS d_int_arr
+ DEFAULT '{41,43}' ON CONVERSION ERROR) as cast2;
+\sv safecastview1
+CREATE OR REPLACE VIEW public.safecastview1 AS
+ SELECT CAST(ARRAY[ARRAY[1], ARRAY['three'], ARRAY['a']] AS d_int_arr DEFAULT '{41,43}'::integer[]::d_int_arr ON CONVERSION ERROR) AS cast1,
+ CAST(ARRAY[ARRAY[1, 2, 1.1::integer], ARRAY['three', true, '01'::"bit"]] AS d_int_arr DEFAULT '{41,43}'::integer[]::d_int_arr ON CONVERSION ERROR) AS cast2
+SELECT * FROM safecastview1;
+ cast1 | cast2
+---------+---------
+ {41,43} | {41,43}
+(1 row)
+
+RESET datestyle;
+--test CAST DEFAULT expression mutability
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as int DEFAULT random(min=>1, max=>1) ON CONVERSION ERROR)));
+ERROR: functions in index expression must be marked IMMUTABLE
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as xid DEFAULT NULL ON CONVERSION ERROR)));
+ERROR: data type xid has no default operator class for access method "btree"
+HINT: You must specify an operator class for the index or define a default operator class for the data type.
+CREATE INDEX test_safecast3_idx ON test_safecast3((CAST(col0 as int DEFAULT NULL ON CONVERSION ERROR))); --ok
+SELECT pg_get_indexdef('test_safecast3_idx'::regclass);
+ pg_get_indexdef
+------------------------------------------------------------------------------------------------------------------------------------------
+ CREATE INDEX test_safecast3_idx ON public.test_safecast3 USING btree ((CAST(col0 AS integer DEFAULT NULL::integer ON CONVERSION ERROR)))
+(1 row)
+
+DROP TABLE test_safecast;
+DROP TABLE test_safecast1;
+DROP TABLE test_safecast2;
+DROP TABLE test_safecast3;
+DROP TABLE tcast;
+DROP FUNCTION ret_int8;
+DROP FUNCTION ret_setint;
+DROP TYPE comp_domain_with_typmod;
+DROP TYPE comp2;
+DROP DOMAIN d_varchar;
+DROP DOMAIN d_int42;
+DROP DOMAIN d_char3_not_null;
+RESET extra_float_digits;
diff --git a/src/test/regress/expected/create_cast.out b/src/test/regress/expected/create_cast.out
index 0e69644bca2..0054ed0ef67 100644
--- a/src/test/regress/expected/create_cast.out
+++ b/src/test/regress/expected/create_cast.out
@@ -88,6 +88,11 @@ SELECT 1234::int4::casttesttype; -- Should work now
bar1234
(1 row)
+SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- error
+ERROR: cannot cast type integer to casttesttype when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVE...
+ ^
+HINT: Safe type cast for user-defined types are not yet supported
-- check dependencies generated for that
SELECT pg_describe_object(classid, objid, objsubid) as obj,
pg_describe_object(refclassid, refobjid, refobjsubid) as objref,
diff --git a/src/test/regress/expected/equivclass.out b/src/test/regress/expected/equivclass.out
index ad8ab294ff6..5aee2c7a9fb 100644
--- a/src/test/regress/expected/equivclass.out
+++ b/src/test/regress/expected/equivclass.out
@@ -95,6 +95,13 @@ create function int8alias1cmp(int8, int8alias1) returns int
strict immutable language internal as 'btint8cmp';
alter operator family integer_ops using btree add
function 1 int8alias1cmp (int8, int8alias1);
+-- int8alias2 binary-coercible to int8. so this is ok
+select cast('1'::int8 as int8alias2 default null on conversion error);
+ int8alias2
+------------
+ 1
+(1 row)
+
create table ec0 (ff int8 primary key, f1 int8, f2 int8);
create table ec1 (ff int8 primary key, f1 int8alias1, f2 int8alias2);
create table ec2 (xf int8 primary key, x1 int8alias1, x2 int8alias2);
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 905f9bca959..e41b368dd94 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -81,7 +81,7 @@ test: create_table_like alter_generic alter_operator misc async dbsize merge mis
# collate.linux.utf8 and collate.icu.utf8 tests cannot be run in parallel with each other
# psql depends on create_am
# amutils depends on geometry, create_index_spgist, hash_index, brin
-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 00000000000..9b6f3cc0649
--- /dev/null
+++ b/src/test/regress/sql/cast.sql
@@ -0,0 +1,350 @@
+SET extra_float_digits = 0;
+
+-- CAST DEFAULT ON CONVERSION ERROR
+SELECT CAST(B'01' AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(BIT'01' AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(TRUE AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(1.1 AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(1 AS date DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(1111 AS "char" DEFAULT 'A' ON CONVERSION ERROR);
+
+--test source expression is a unknown const
+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('a' as int DEFAULT 18 ON CONVERSION 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 (SELECT NULL) ON CONVERSION ERROR); --error
+SELECT CAST('a' as int DEFAULT 'b' ON CONVERSION ERROR); --error
+SELECT CAST('a'::int as int DEFAULT NULL ON CONVERSION ERROR); --error
+
+--the default expression’s collation should match the collation of the cast
+--target type
+VALUES (CAST('error' AS text DEFAULT '1' COLLATE "C" ON CONVERSION ERROR));
+
+VALUES (CAST('error' AS int2vector DEFAULT '1 3' ON CONVERSION ERROR));
+VALUES (CAST('error' AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR));
+
+-- test source expression contain subquery
+SELECT CAST((SELECT b FROM generate_series(1, 1), (VALUES('H')) s(b))
+ AS int2vector[] DEFAULT '{1 3}' ON CONVERSION ERROR);
+SELECT CAST((SELECT ARRAY((SELECT b FROM generate_series(1, 2) g, (VALUES('H')) s(b))))
+ AS INT[]
+ DEFAULT NULL ON CONVERSION ERROR);
+
+CREATE FUNCTION ret_int8() RETURNS BIGINT AS
+$$
+BEGIN RETURN 2147483648; END;
+$$
+LANGUAGE plpgsql IMMUTABLE;
+
+SELECT CAST('a' as int DEFAULT ret_int8() ON CONVERSION ERROR); --error
+SELECT CAST('a' as date DEFAULT ret_int8() ON CONVERSION ERROR); --error
+
+-- DEFAULT expression can not be set-returning
+CREATE 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);
+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(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 user-defined type, domain, array over domain, domain over array
+CREATE DOMAIN d_int42 as int check (value = 42) NOT NULL;
+CREATE DOMAIN d_char3_not_null as char(3) NOT NULL;
+CREATE DOMAIN d_varchar as varchar(3) NOT NULL;
+CREATE DOMAIN d_int_arr as int[] check (value = '{41, 43}') NOT NULL;
+CREATE TYPE comp_domain_with_typmod AS (a d_char3_not_null, b int);
+CREATE TYPE comp2 AS (a d_varchar);
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION ERROR); --error
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(123)' ON CONVERSION ERROR);
+SELECT CAST(11 AS d_int42 DEFAULT 41 ON CONVERSION ERROR); --error
+SELECT CAST(11 AS d_int42 DEFAULT 42 ON CONVERSION ERROR);
+SELECT CAST(NULL AS d_int42 DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(NULL AS d_int42 DEFAULT 42 ON CONVERSION ERROR);
+SELECT CAST('(,42)' AS comp_domain_with_typmod DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1,2)' ON CONVERSION ERROR);
+SELECT CAST('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1234,2)' ON CONVERSION ERROR); --error
+
+SELECT CAST(ARRAY[42,41] AS d_int42[] DEFAULT '{42, 42}' ON CONVERSION ERROR);
+SELECT CAST(ARRAY[42,41] AS d_int_arr DEFAULT '{41, 43}' ON CONVERSION ERROR);
+
+-- test array coerce
+SELECT CAST(array['a'::text] AS int[] DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST(array['a'] AS int[] DEFAULT ARRAY[1] ON CONVERSION ERROR);
+SELECT CAST(array[11.12] AS date[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(array[11111111111111111] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(array[['abc'],[456]] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('{123,abc,456}' AS int[] 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 int[] DEFAULT '{1,2}' ON CONVERSION ERROR);
+SELECT CAST(ARRAY[['1', '2'], ['three', 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --ok
+SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --error
+SELECT CAST(ARRAY[1, 'three'::int] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR); --error
+
+-----safe cast with geometry data type
+SELECT CAST('(1,2)'::point AS box DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[(NaN,1),(NaN,infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[(1e+300,Infinity),(1e+300,Infinity)]'::lseg AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[(1,2),(3,4)]'::path as polygon DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NaN,1.0,NaN,infinity)'::box AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS lseg DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS polygon DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,2.0,0.0,0.0)'::box AS path DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,infinity,NaN,infinity)'::box AS circle DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NaN,0.0),(2.0,4.0),(0.0,infinity)'::polygon AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS path DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(2.0,0.0),(2.0,4.0),(0.0,0.0)'::polygon AS box DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NaN,infinity),(2.0,4.0),(0.0,infinity)'::polygon AS circle DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('<(5,1),3>'::circle AS point DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('<(3,5),0>'::circle as box DEFAULT NULL ON CONVERSION ERROR);
+
+-- not supported because the cast from circle to polygon is implemented as a SQL
+-- function, which cannot be error-safe.
+SELECT CAST('<(3,5),0>'::circle as polygon DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast from/to money type is not supported, all the following would result error
+SELECT CAST(NULL::int8 AS money DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::int4 AS money DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::numeric AS money DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::money[] AS numeric[] DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast from bytea type to other data types
+SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT 19 ON CONVERSION ERROR);
+SELECT CAST('\x123456789A'::bytea AS int4 DEFAULT 20 ON CONVERSION ERROR);
+SELECT CAST('\x123456'::bytea AS int2 DEFAULT 21 ON CONVERSION ERROR);
+
+-----safe cast from bit type to other data types
+SELECT CAST('111111111100001'::bit(100) AS INT DEFAULT 22 ON CONVERSION ERROR);
+SELECT CAST ('111111111100001'::bit(100) AS INT8 DEFAULT 23 ON CONVERSION ERROR);
+
+-----safe cast from text type to other data types
+select CAST('a.b.c.d'::text as regclass default NULL on conversion error);
+
+CREATE TABLE test_safecast(col0 text);
+INSERT INTO test_safecast(col0) VALUES ('<value>one</value');
+
+SELECT col0 as text,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) as to_regclass,
+ CAST(col0 AS regclass DEFAULT NULL ON CONVERSION ERROR) IS NULL as expect_true,
+ CAST(col0 AS "char" DEFAULT NULL ON CONVERSION ERROR) as to_char,
+ CAST(col0 AS name DEFAULT NULL ON CONVERSION ERROR) as to_name,
+ CAST(col0 AS xml DEFAULT NULL ON CONVERSION ERROR) as to_xml
+FROM test_safecast;
+
+SELECT CAST('192.168.1.x' as inet DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('192.168.1.2/30' as cidr DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('22:00:5c:08:55:08:01:02'::macaddr8 as macaddr DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast betweeen range data types
+SELECT CAST('[1,2]'::int4range AS int4multirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[1,2]'::int8range AS int8multirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[1,2]'::numrange AS nummultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::daterange AS datemultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::tsrange AS tsmultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::tstzrange AS tstzmultirange DEFAULT NULL ON CONVERSION ERROR);
+
+-----safe cast betweeen numeric data types
+CREATE TABLE test_safecast1(
+ col1 float4, col2 float8, col3 numeric, col4 numeric[],
+ col5 int2 default 32767,
+ col6 int4 default 32768,
+ col7 int8 default 4294967296);
+INSERT INTO test_safecast1 VALUES('11.1234', '11.1234', '9223372036854775808', '{11.1234}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('inf', 'inf', 'inf', '{11.1234, 12, inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('-inf', '-inf', '-inf', '{11.1234, 12, -inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('NaN', 'NaN', 'NaN', '{11.1234, 12, -inf, NaN}'::numeric[]);
+
+SELECT col5 as int2,
+ CAST(col5 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col5 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col5 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col5 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col5 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col5 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col5 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col5 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col6 as int4,
+ CAST(col6 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col6 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col6 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col6 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col6 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col6 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col6 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col6 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col7 as int8,
+ CAST(col7 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col7 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col7 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col7 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col7 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col7 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col7 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col7 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col3 as numeric,
+ CAST(col3 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col3 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col3 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col3 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col3 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col3 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col3 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col3 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col4 as num_arr,
+ CAST(col4 AS int2[] DEFAULT NULL ON CONVERSION ERROR) as int2arr,
+ CAST(col4 AS int4[] DEFAULT NULL ON CONVERSION ERROR) as int4arr,
+ CAST(col4 as int8[] DEFAULT NULL ON CONVERSION ERROR) as int8arr,
+ CAST(col4 as float4[] DEFAULT NULL ON CONVERSION ERROR) as f4arr,
+ CAST(col4 as float8[] DEFAULT NULL ON CONVERSION ERROR) as f8arr,
+ CAST(col4 as numeric(10,1)[] DEFAULT NULL ON CONVERSION ERROR) as numarr
+FROM test_safecast1;
+
+SELECT col1 as float4,
+ CAST(col1 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col1 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col1 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col1 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col1 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col1 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col1 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col1 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col2 as float8,
+ CAST(col2 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col2 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col2 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col2 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col2 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col2 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col2 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col2 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+-- date/timestamp/interval related safe type cast
+CREATE TABLE test_safecast2(
+ col0 date, col1 timestamp, col2 timestamptz,
+ col3 interval, col4 time, col5 timetz);
+INSERT INTO test_safecast2 VALUES
+('-infinity', '-infinity', '-infinity', '-infinity',
+ '2003-03-07 15:36:39 America/New_York', '2003-07-07 15:36:39 America/New_York'),
+('-infinity', 'infinity', 'infinity', 'infinity',
+ '11:59:59.99 PM', '11:59:59.99 PM PDT');
+
+SELECT col0 as date,
+ CAST(col0 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col0 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col0 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col0 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col0 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+
+SELECT col1 as timestamp,
+ CAST(col1 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col1 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col1 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col1 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col1 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+
+SELECT col2 as timestamptz,
+ CAST(col2 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col2 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col2 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col2 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col2 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+
+SELECT col3 as interval,
+ CAST(col3 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col3 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col3 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col3 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col3 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale,
+ CAST(col3 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+
+SELECT col4 as time,
+ CAST(col4 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col4 AS timetz DEFAULT NULL ON CONVERSION ERROR) IS NOT NULL as to_timetz,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+
+SELECT col5 as timetz,
+ CAST(col5 AS time DEFAULT NULL ON CONVERSION ERROR) as to_time,
+ CAST(col5 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_time_scale,
+ CAST(col5 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col5 AS timetz(6) DEFAULT NULL ON CONVERSION ERROR) as to_timetz_scale,
+ CAST(col5 AS interval DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col5 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+
+CREATE TABLE test_safecast3(col0 jsonb);
+INSERT INTO test_safecast3(col0) VALUES ('"test"');
+SELECT col0 as jsonb,
+ CAST(col0 AS integer DEFAULT NULL ON CONVERSION ERROR) as to_integer,
+ CAST(col0 AS numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col0 AS bigint DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col0 AS float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col0 AS float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col0 AS boolean DEFAULT NULL ON CONVERSION ERROR) as to_bool,
+ CAST(col0 AS smallint DEFAULT NULL ON CONVERSION ERROR) as to_smallint
+FROM test_safecast3;
+
+-- test deparse
+SET datestyle TO ISO, YMD;
+CREATE VIEW safecastview AS
+SELECT CAST('1234' as char(3) DEFAULT -1111 ON CONVERSION ERROR),
+ CAST(1 as date DEFAULT (('2025-Dec-06'::date + random(min=>1, max=>1::int))) ON CONVERSION ERROR) as cast0,
+ CAST(ARRAY[['1'], ['three'],['a']] AS int[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast1,
+ CAST(ARRAY[['1', '2'], ['three', 'a']] AS date[] DEFAULT NULL ON CONVERSION ERROR) as cast2,
+ CAST(ARRAY['three'] AS INT[] DEFAULT '{1,2}' ON CONVERSION ERROR) as cast3;
+\sv safecastview
+SELECT * FROM safecastview;
+
+CREATE VIEW safecastview1 AS
+SELECT CAST(ARRAY[['1'], ['three'],['a']] AS d_int_arr
+ DEFAULT '{41,43}' ON CONVERSION ERROR) as cast1,
+ CAST(ARRAY[['1', '2', 1.1], ['three', true, B'01']] AS d_int_arr
+ DEFAULT '{41,43}' ON CONVERSION ERROR) as cast2;
+\sv safecastview1
+SELECT * FROM safecastview1;
+
+RESET datestyle;
+
+--test CAST DEFAULT expression mutability
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as int DEFAULT random(min=>1, max=>1) ON CONVERSION ERROR)));
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as xid DEFAULT NULL ON CONVERSION ERROR)));
+CREATE INDEX test_safecast3_idx ON test_safecast3((CAST(col0 as int DEFAULT NULL ON CONVERSION ERROR))); --ok
+SELECT pg_get_indexdef('test_safecast3_idx'::regclass);
+
+DROP TABLE test_safecast;
+DROP TABLE test_safecast1;
+DROP TABLE test_safecast2;
+DROP TABLE test_safecast3;
+DROP TABLE tcast;
+DROP FUNCTION ret_int8;
+DROP FUNCTION ret_setint;
+DROP TYPE comp_domain_with_typmod;
+DROP TYPE comp2;
+DROP DOMAIN d_varchar;
+DROP DOMAIN d_int42;
+DROP DOMAIN d_char3_not_null;
+RESET extra_float_digits;
diff --git a/src/test/regress/sql/create_cast.sql b/src/test/regress/sql/create_cast.sql
index 32187853cc7..0a15a795d87 100644
--- a/src/test/regress/sql/create_cast.sql
+++ b/src/test/regress/sql/create_cast.sql
@@ -62,6 +62,7 @@ $$ SELECT ('bar'::text || $1::text); $$;
CREATE CAST (int4 AS casttesttype) WITH FUNCTION bar_int4_text(int4) AS IMPLICIT;
SELECT 1234::int4::casttesttype; -- Should work now
+SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- error
-- check dependencies generated for that
SELECT pg_describe_object(classid, objid, objsubid) as obj,
diff --git a/src/test/regress/sql/equivclass.sql b/src/test/regress/sql/equivclass.sql
index 7fc2159349b..5ad1d26311d 100644
--- a/src/test/regress/sql/equivclass.sql
+++ b/src/test/regress/sql/equivclass.sql
@@ -98,6 +98,9 @@ create function int8alias1cmp(int8, int8alias1) returns int
alter operator family integer_ops using btree add
function 1 int8alias1cmp (int8, int8alias1);
+-- int8alias2 binary-coercible to int8. so this is ok
+select cast('1'::int8 as int8alias2 default null on conversion error);
+
create table ec0 (ff int8 primary key, f1 int8, f2 int8);
create table ec1 (ff int8 primary key, f1 int8alias1, f2 int8alias2);
create table ec2 (xf int8 primary key, x1 int8alias1, x2 int8alias2);
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b9e671fcda8..731a9e52416 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2688,6 +2688,8 @@ STRLEN
SV
SYNCHRONIZATION_BARRIER
SYSTEM_INFO
+SafeTypeCastExpr
+SafeTypeCastState
SampleScan
SampleScanGetSampleSize_function
SampleScanState
--
2.34.1
v17-0023-error-safe-for-user-defined-CREATE-CAST.patchtext/x-patch; charset=US-ASCII; name=v17-0023-error-safe-for-user-defined-CREATE-CAST.patchDownload
From 8eabe4bfcbe7a6c10d4825ffe254e8750cc14125 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sun, 4 Jan 2026 01:13:57 +0800
Subject: [PATCH v17 23/23] error safe for user defined CREATE CAST
pg_cast.casterrorsafe column to indicate castfunc is error safe or not.
change src/include/catalog/pg_cast.dat to indicate that most of the system cast
function support soft error evaluation.
The SAFE keyword is introduced for allow user-defined CREATE CAST can also be
evaluated in soft-error.
The new synopsis of CREATE CAST is:
CREATE CAST (source_type AS target_type)
WITH [SAFE] FUNCTION function_name [ (argument_type [, ...]) ]
[ AS ASSIGNMENT | AS IMPLICIT ]
The following cast in citext, hstore contrib module refactored to error safe:
CAST (bpchar AS citext)
CAST (boolean AS citext)
CAST (inet AS citext)
CAST (text[] AS hstore)
CAST (hstore AS json)
CAST (hstore AS jsonb)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
contrib/citext/citext--1.4.sql | 6 +-
contrib/citext/expected/citext.out | 24 +-
contrib/citext/expected/citext_1.out | 24 +-
contrib/citext/sql/citext.sql | 5 +-
contrib/hstore/expected/hstore.out | 37 +++
contrib/hstore/hstore--1.2--1.3.sql | 2 +-
contrib/hstore/hstore--1.4.sql | 6 +-
contrib/hstore/hstore_io.c | 8 +-
contrib/hstore/sql/hstore.sql | 11 +
doc/src/sgml/catalogs.sgml | 15 +
doc/src/sgml/ref/create_cast.sgml | 14 +-
doc/src/sgml/syntax.sgml | 3 +-
src/backend/catalog/pg_cast.c | 4 +-
src/backend/commands/functioncmds.c | 9 +-
src/backend/commands/typecmds.c | 1 +
src/backend/parser/gram.y | 18 +-
src/backend/parser/parse_expr.c | 10 +-
src/include/catalog/pg_cast.dat | 330 +++++++++++-----------
src/include/catalog/pg_cast.h | 5 +
src/include/nodes/parsenodes.h | 1 +
src/include/parser/kwlist.h | 1 +
src/test/regress/expected/create_cast.out | 8 +-
src/test/regress/expected/opr_sanity.out | 24 +-
src/test/regress/sql/create_cast.sql | 5 +
24 files changed, 353 insertions(+), 218 deletions(-)
diff --git a/contrib/citext/citext--1.4.sql b/contrib/citext/citext--1.4.sql
index 7b061989352..5c87820388f 100644
--- a/contrib/citext/citext--1.4.sql
+++ b/contrib/citext/citext--1.4.sql
@@ -85,9 +85,9 @@ CREATE CAST (citext AS varchar) WITHOUT FUNCTION AS IMPLICIT;
CREATE CAST (citext AS bpchar) WITHOUT FUNCTION AS ASSIGNMENT;
CREATE CAST (text AS citext) WITHOUT FUNCTION AS ASSIGNMENT;
CREATE CAST (varchar AS citext) WITHOUT FUNCTION AS ASSIGNMENT;
-CREATE CAST (bpchar AS citext) WITH FUNCTION citext(bpchar) AS ASSIGNMENT;
-CREATE CAST (boolean AS citext) WITH FUNCTION citext(boolean) AS ASSIGNMENT;
-CREATE CAST (inet AS citext) WITH FUNCTION citext(inet) AS ASSIGNMENT;
+CREATE CAST (bpchar AS citext) WITH SAFE FUNCTION citext(bpchar) AS ASSIGNMENT;
+CREATE CAST (boolean AS citext) WITH SAFE FUNCTION citext(boolean) AS ASSIGNMENT;
+CREATE CAST (inet AS citext) WITH SAFE FUNCTION citext(inet) AS ASSIGNMENT;
--
-- Operator Functions.
diff --git a/contrib/citext/expected/citext.out b/contrib/citext/expected/citext.out
index 33da19d8df4..be328715492 100644
--- a/contrib/citext/expected/citext.out
+++ b/contrib/citext/expected/citext.out
@@ -10,11 +10,12 @@ WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid);
--------+---------
(0 rows)
-SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR); --error
-ERROR: cannot cast type character to citext when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
-LINE 1: SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSI...
- ^
-HINT: Safe type cast for user-defined types are not yet supported
+SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR);
+ citext
+--------
+ abc
+(1 row)
+
-- Test the operators and indexing functions
-- Test = and <>.
SELECT 'a'::citext = 'a'::citext AS t;
@@ -523,6 +524,12 @@ SELECT true::citext = 'true' AS t;
t
(1 row)
+SELECT CAST(true AS citext DEFAULT NULL ON CONVERSION ERROR);
+ citext
+--------
+ true
+(1 row)
+
SELECT 'true'::citext::boolean = true AS t;
t
---
@@ -787,6 +794,13 @@ SELECT '192.168.100.128'::citext::inet = '192.168.100.128'::inet AS t;
t
(1 row)
+SELECT CAST(inet '192.168.100.128' AS citext
+ DEFAULT NULL ON CONVERSION ERROR) = '192.168.100.128/32' AS t;
+ t
+---
+ t
+(1 row)
+
SELECT '08:00:2b:01:02:03'::macaddr::citext = '08:00:2b:01:02:03' AS t;
t
---
diff --git a/contrib/citext/expected/citext_1.out b/contrib/citext/expected/citext_1.out
index 647eea19142..e9f8454c662 100644
--- a/contrib/citext/expected/citext_1.out
+++ b/contrib/citext/expected/citext_1.out
@@ -10,11 +10,12 @@ WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid);
--------+---------
(0 rows)
-SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR); --error
-ERROR: cannot cast type character to citext when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
-LINE 1: SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSI...
- ^
-HINT: Safe type cast for user-defined types are not yet supported
+SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR);
+ citext
+--------
+ abc
+(1 row)
+
-- Test the operators and indexing functions
-- Test = and <>.
SELECT 'a'::citext = 'a'::citext AS t;
@@ -523,6 +524,12 @@ SELECT true::citext = 'true' AS t;
t
(1 row)
+SELECT CAST(true AS citext DEFAULT NULL ON CONVERSION ERROR);
+ citext
+--------
+ true
+(1 row)
+
SELECT 'true'::citext::boolean = true AS t;
t
---
@@ -787,6 +794,13 @@ SELECT '192.168.100.128'::citext::inet = '192.168.100.128'::inet AS t;
t
(1 row)
+SELECT CAST(inet '192.168.100.128' AS citext
+ DEFAULT NULL ON CONVERSION ERROR) = '192.168.100.128/32' AS t;
+ t
+---
+ t
+(1 row)
+
SELECT '08:00:2b:01:02:03'::macaddr::citext = '08:00:2b:01:02:03' AS t;
t
---
diff --git a/contrib/citext/sql/citext.sql b/contrib/citext/sql/citext.sql
index 99794497d47..62f83d749f9 100644
--- a/contrib/citext/sql/citext.sql
+++ b/contrib/citext/sql/citext.sql
@@ -9,7 +9,7 @@ SELECT amname, opcname
FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod
WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid);
-SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR); --error
+SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR);
-- Test the operators and indexing functions
@@ -165,6 +165,7 @@ SELECT name FROM srt WHERE name SIMILAR TO '%A.*';
-- Explicit casts.
SELECT true::citext = 'true' AS t;
+SELECT CAST(true AS citext DEFAULT NULL ON CONVERSION ERROR);
SELECT 'true'::citext::boolean = true AS t;
SELECT 4::citext = '4' AS t;
@@ -224,6 +225,8 @@ SELECT '192.168.100.128/25'::citext::cidr = '192.168.100.128/25'::cidr AS t;
SELECT '192.168.100.128'::inet::citext = '192.168.100.128/32' AS t;
SELECT '192.168.100.128'::citext::inet = '192.168.100.128'::inet AS t;
+SELECT CAST(inet '192.168.100.128' AS citext
+ DEFAULT NULL ON CONVERSION ERROR) = '192.168.100.128/32' AS t;
SELECT '08:00:2b:01:02:03'::macaddr::citext = '08:00:2b:01:02:03' AS t;
SELECT '08:00:2b:01:02:03'::citext::macaddr = '08:00:2b:01:02:03'::macaddr AS t;
diff --git a/contrib/hstore/expected/hstore.out b/contrib/hstore/expected/hstore.out
index 1836c9acf39..2622137cbf9 100644
--- a/contrib/hstore/expected/hstore.out
+++ b/contrib/hstore/expected/hstore.out
@@ -841,12 +841,28 @@ select '{}'::text[]::hstore;
select ARRAY['a','g','b','h','asd']::hstore;
ERROR: array must have even number of elements
+select CAST(ARRAY['a','g','b','h','asd'] AS hstore
+ DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
select ARRAY['a','g','b','h','asd','i']::hstore;
array
--------------------------------
"a"=>"g", "b"=>"h", "asd"=>"i"
(1 row)
+select ARRAY['a','g','b','h',null,'i']::hstore;
+ERROR: null value not allowed for hstore key
+select CAST(ARRAY['a','g','b','h',null,'i'] AS hstore
+ DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
select ARRAY[['a','g'],['b','h'],['asd','i']]::hstore;
array
--------------------------------
@@ -855,6 +871,13 @@ select ARRAY[['a','g'],['b','h'],['asd','i']]::hstore;
select ARRAY[['a','g','b'],['h','asd','i']]::hstore;
ERROR: array must have two columns
+select CAST(ARRAY[['a','g','b'],['h','asd','i']] AS hstore
+ DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
select ARRAY[[['a','g'],['b','h'],['asd','i']]]::hstore;
ERROR: wrong number of array subscripts
select hstore('{}'::text[]);
@@ -1553,6 +1576,13 @@ select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=
{"b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4", "a key": "1"}
(1 row)
+select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as json
+ default null on conversion error);
+ json
+-------------------------------------------------------------------------------------------------
+ {"b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4", "a key": "1"}
+(1 row)
+
select hstore_to_json_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4, h=> "2016-01-01"');
hstore_to_json_loose
-------------------------------------------------------------------------------------------------------------
@@ -1571,6 +1601,13 @@ select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=
{"b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4", "a key": "1"}
(1 row)
+select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as jsonb
+ default null on conversion error);
+ jsonb
+-------------------------------------------------------------------------------------------------
+ {"b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4", "a key": "1"}
+(1 row)
+
select hstore_to_jsonb_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4, h=> "2016-01-01"');
hstore_to_jsonb_loose
----------------------------------------------------------------------------------------------------------
diff --git a/contrib/hstore/hstore--1.2--1.3.sql b/contrib/hstore/hstore--1.2--1.3.sql
index 0a7056015b7..cbb1a139e69 100644
--- a/contrib/hstore/hstore--1.2--1.3.sql
+++ b/contrib/hstore/hstore--1.2--1.3.sql
@@ -9,7 +9,7 @@ AS 'MODULE_PATHNAME', 'hstore_to_jsonb'
LANGUAGE C IMMUTABLE STRICT;
CREATE CAST (hstore AS jsonb)
- WITH FUNCTION hstore_to_jsonb(hstore);
+ WITH SAFE FUNCTION hstore_to_jsonb(hstore);
CREATE FUNCTION hstore_to_jsonb_loose(hstore)
RETURNS jsonb
diff --git a/contrib/hstore/hstore--1.4.sql b/contrib/hstore/hstore--1.4.sql
index 4294d14ceb5..451c2ed8187 100644
--- a/contrib/hstore/hstore--1.4.sql
+++ b/contrib/hstore/hstore--1.4.sql
@@ -232,7 +232,7 @@ AS 'MODULE_PATHNAME', 'hstore_from_array'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE CAST (text[] AS hstore)
- WITH FUNCTION hstore(text[]);
+ WITH SAFE FUNCTION hstore(text[]);
CREATE FUNCTION hstore_to_json(hstore)
RETURNS json
@@ -240,7 +240,7 @@ AS 'MODULE_PATHNAME', 'hstore_to_json'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE CAST (hstore AS json)
- WITH FUNCTION hstore_to_json(hstore);
+ WITH SAFE FUNCTION hstore_to_json(hstore);
CREATE FUNCTION hstore_to_json_loose(hstore)
RETURNS json
@@ -253,7 +253,7 @@ AS 'MODULE_PATHNAME', 'hstore_to_jsonb'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE CAST (hstore AS jsonb)
- WITH FUNCTION hstore_to_jsonb(hstore);
+ WITH SAFE FUNCTION hstore_to_jsonb(hstore);
CREATE FUNCTION hstore_to_jsonb_loose(hstore)
RETURNS jsonb
diff --git a/contrib/hstore/hstore_io.c b/contrib/hstore/hstore_io.c
index 34e3918811c..f5166679783 100644
--- a/contrib/hstore/hstore_io.c
+++ b/contrib/hstore/hstore_io.c
@@ -738,20 +738,20 @@ hstore_from_array(PG_FUNCTION_ARGS)
case 1:
if ((ARR_DIMS(in_array)[0]) % 2)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
errmsg("array must have even number of elements")));
break;
case 2:
if ((ARR_DIMS(in_array)[1]) != 2)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
errmsg("array must have two columns")));
break;
default:
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
errmsg("wrong number of array subscripts")));
}
@@ -772,7 +772,7 @@ hstore_from_array(PG_FUNCTION_ARGS)
for (i = 0; i < count; ++i)
{
if (in_nulls[i * 2])
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("null value not allowed for hstore key")));
diff --git a/contrib/hstore/sql/hstore.sql b/contrib/hstore/sql/hstore.sql
index efef91292a3..8fa46630d6d 100644
--- a/contrib/hstore/sql/hstore.sql
+++ b/contrib/hstore/sql/hstore.sql
@@ -192,9 +192,16 @@ select pg_column_size(slice(hstore 'aa=>1, b=>2, c=>3', ARRAY['c','b','aa']))
-- array input
select '{}'::text[]::hstore;
select ARRAY['a','g','b','h','asd']::hstore;
+select CAST(ARRAY['a','g','b','h','asd'] AS hstore
+ DEFAULT NULL ON CONVERSION ERROR);
select ARRAY['a','g','b','h','asd','i']::hstore;
+select ARRAY['a','g','b','h',null,'i']::hstore;
+select CAST(ARRAY['a','g','b','h',null,'i'] AS hstore
+ DEFAULT NULL ON CONVERSION ERROR);
select ARRAY[['a','g'],['b','h'],['asd','i']]::hstore;
select ARRAY[['a','g','b'],['h','asd','i']]::hstore;
+select CAST(ARRAY[['a','g','b'],['h','asd','i']] AS hstore
+ DEFAULT NULL ON CONVERSION ERROR);
select ARRAY[[['a','g'],['b','h'],['asd','i']]]::hstore;
select hstore('{}'::text[]);
select hstore(ARRAY['a','g','b','h','asd']);
@@ -363,10 +370,14 @@ select count(*) from testhstore where h = 'pos=>98, line=>371, node=>CBA, indexe
-- json and jsonb
select hstore_to_json('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4');
select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as json);
+select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as json
+ default null on conversion error);
select hstore_to_json_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4, h=> "2016-01-01"');
select hstore_to_jsonb('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4');
select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as jsonb);
+select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as jsonb
+ default null on conversion error);
select hstore_to_jsonb_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4, h=> "2016-01-01"');
create table test_json_agg (f1 text, f2 hstore);
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2fc63442980..8fca3534f32 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1849,6 +1849,21 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<literal>b</literal> means that the types are binary-coercible, thus no conversion is required.
</para></entry>
</row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>casterrorsafe</structfield> <type>bool</type>
+ </para>
+ <para>
+ This flag indicates whether the <structfield>castfunc</structfield> function
+ is error-safe. It is meaningful only when <structfield>castfunc</structfield>
+ is not zero. User-defined casts can set it
+ to <literal>true</literal> via <link linkend="sql-createcast">CREATE CAST</link>.
+ For error-safe type cast, see <xref linkend="sql-syntax-type-casts-safe"/>.
+ </para>
+ </entry>
+ </row>
+
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/ref/create_cast.sgml b/doc/src/sgml/ref/create_cast.sgml
index bad75bc1dce..888d7142e42 100644
--- a/doc/src/sgml/ref/create_cast.sgml
+++ b/doc/src/sgml/ref/create_cast.sgml
@@ -22,7 +22,7 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE CAST (<replaceable>source_type</replaceable> AS <replaceable>target_type</replaceable>)
- WITH FUNCTION <replaceable>function_name</replaceable> [ (<replaceable>argument_type</replaceable> [, ...]) ]
+ WITH [SAFE] FUNCTION <replaceable>function_name</replaceable> [ (<replaceable>argument_type</replaceable> [, ...]) ]
[ AS ASSIGNMENT | AS IMPLICIT ]
CREATE CAST (<replaceable>source_type</replaceable> AS <replaceable>target_type</replaceable>)
@@ -194,6 +194,18 @@ SELECT CAST ( 2 AS numeric ) + 4.0;
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>SAFE</literal></term>
+ <listitem>
+ <para>
+ The function used to perform the cast support soft-error evaluation,
+ Currently, only functions written in C or the internal language are supported.
+ An alternate expression can be specified to be evaluated if the cast
+ error occurs. See <link linkend="sql-syntax-type-casts-safe">safe type cast</link>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal><replaceable>function_name</replaceable>[(<replaceable>argument_type</replaceable> [, ...])]</literal></term>
diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml
index d1cc932f7b1..6af1d21d465 100644
--- a/doc/src/sgml/syntax.sgml
+++ b/doc/src/sgml/syntax.sgml
@@ -2178,9 +2178,8 @@ CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable>
default <replaceable>expression</replaceable>
specified in the <literal>ON CONVERSION ERROR</literal> clause.
- At present, this only support built-in type casts, see <xref linkend="catalog-pg-cast"/>.
User-defined type casts created with <link linkend="sql-createcast">CREATE CAST</link>
- are not supported.
+ are supported too.
</para>
<para>
diff --git a/src/backend/catalog/pg_cast.c b/src/backend/catalog/pg_cast.c
index 5119c2acda2..9eff941eabb 100644
--- a/src/backend/catalog/pg_cast.c
+++ b/src/backend/catalog/pg_cast.c
@@ -48,7 +48,8 @@
ObjectAddress
CastCreate(Oid sourcetypeid, Oid targettypeid,
Oid funcid, Oid incastid, Oid outcastid,
- char castcontext, char castmethod, DependencyType behavior)
+ char castcontext, char castmethod, bool casterrorsafe,
+ DependencyType behavior)
{
Relation relation;
HeapTuple tuple;
@@ -84,6 +85,7 @@ CastCreate(Oid sourcetypeid, Oid targettypeid,
values[Anum_pg_cast_castfunc - 1] = ObjectIdGetDatum(funcid);
values[Anum_pg_cast_castcontext - 1] = CharGetDatum(castcontext);
values[Anum_pg_cast_castmethod - 1] = CharGetDatum(castmethod);
+ values[Anum_pg_cast_casterrorsafe - 1] = BoolGetDatum(casterrorsafe);
tuple = heap_form_tuple(RelationGetDescr(relation), values, nulls);
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index a516b037dea..b818065af27 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -1667,6 +1667,13 @@ CreateCast(CreateCastStmt *stmt)
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("cast function must not return a set")));
+ if (stmt->safe &&
+ procstruct->prolang != INTERNALlanguageId &&
+ procstruct->prolang != ClanguageId)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Safe type cast functions are only supported for C and internal languages"));
+
ReleaseSysCache(tuple);
}
else
@@ -1795,7 +1802,7 @@ CreateCast(CreateCastStmt *stmt)
}
myself = CastCreate(sourcetypeid, targettypeid, funcid, incastid, outcastid,
- castcontext, castmethod, DEPENDENCY_NORMAL);
+ castcontext, castmethod, stmt->safe, DEPENDENCY_NORMAL);
return myself;
}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index e5fa0578889..078551e45a9 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -1754,6 +1754,7 @@ DefineRange(ParseState *pstate, CreateRangeStmt *stmt)
/* Create cast from the range type to its multirange type */
CastCreate(typoid, multirangeOid, castFuncOid, InvalidOid, InvalidOid,
COERCION_CODE_EXPLICIT, COERCION_METHOD_FUNCTION,
+ false,
DEPENDENCY_INTERNAL);
pfree(multirangeArrayName);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 15e4f064782..89bb718d0f6 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -356,7 +356,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <defelt> drop_option
%type <boolean> opt_or_replace opt_no
opt_grant_grant_option
- opt_nowait opt_if_exists opt_with_data
+ opt_nowait opt_safe opt_if_exists opt_with_data
opt_transaction_chain
%type <list> grant_role_opt_list
%type <defelt> grant_role_opt
@@ -779,7 +779,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
RESET RESPECT_P RESTART RESTRICT RETURN RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
ROUTINE ROUTINES ROW ROWS RULE
- SAVEPOINT SCALAR SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT
+ SAFE SAVEPOINT SCALAR SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT
SEQUENCE SEQUENCES
SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SPLIT SOURCE SQL_P STABLE STANDALONE_P
@@ -9345,14 +9345,15 @@ dostmt_opt_item:
*****************************************************************************/
CreateCastStmt: CREATE CAST '(' Typename AS Typename ')'
- WITH FUNCTION function_with_argtypes cast_context
+ WITH opt_safe FUNCTION function_with_argtypes cast_context
{
CreateCastStmt *n = makeNode(CreateCastStmt);
n->sourcetype = $4;
n->targettype = $6;
- n->func = $10;
- n->context = (CoercionContext) $11;
+ n->safe = $9;
+ n->func = $11;
+ n->context = (CoercionContext) $12;
n->inout = false;
$$ = (Node *) n;
}
@@ -9363,6 +9364,7 @@ CreateCastStmt: CREATE CAST '(' Typename AS Typename ')'
n->sourcetype = $4;
n->targettype = $6;
+ n->safe = false;
n->func = NULL;
n->context = (CoercionContext) $10;
n->inout = false;
@@ -9375,6 +9377,7 @@ CreateCastStmt: CREATE CAST '(' Typename AS Typename ')'
n->sourcetype = $4;
n->targettype = $6;
+ n->safe = false;
n->func = NULL;
n->context = (CoercionContext) $10;
n->inout = true;
@@ -9387,6 +9390,9 @@ cast_context: AS IMPLICIT_P { $$ = COERCION_IMPLICIT; }
| /*EMPTY*/ { $$ = COERCION_EXPLICIT; }
;
+opt_safe: SAFE { $$ = true; }
+ | /*EMPTY*/ { $$ = false; }
+ ;
DropCastStmt: DROP CAST opt_if_exists '(' Typename AS Typename ')' opt_drop_behavior
{
@@ -18147,6 +18153,7 @@ unreserved_keyword:
| ROUTINES
| ROWS
| RULE
+ | SAFE
| SAVEPOINT
| SCALAR
| SCHEMA
@@ -18785,6 +18792,7 @@ bare_label_keyword:
| ROW
| ROWS
| RULE
+ | SAFE
| SAVEPOINT
| SCALAR
| SCHEMA
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 6fef908804c..dab76a0bb66 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -2989,7 +2989,6 @@ CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr, Node *sourceexpr,
{
HeapTuple tuple;
bool errorsafe = true;
- bool userdefined = false;
Oid inputBaseType;
Oid targetBaseType;
Oid inputElementType;
@@ -3061,11 +3060,8 @@ CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr, Node *sourceexpr,
{
Form_pg_cast castForm = (Form_pg_cast) GETSTRUCT(tuple);
- if (castForm->oid > FirstUnpinnedObjectId)
- {
+ if (OidIsValid(castForm->castfunc) && !castForm->casterrorsafe)
errorsafe = false;
- userdefined = true;
- }
ReleaseSysCache(tuple);
}
@@ -3079,9 +3075,7 @@ CoercionErrorSafeCheck(ParseState *pstate, Node *castexpr, Node *sourceexpr,
format_type_be(targetType),
"DEFAULT",
"CAST ... ON CONVERSION ERROR"),
- userdefined
- ? errhint("Safe type cast for user-defined types are not yet supported")
- : errhint("Explicit cast is defined but definition is not error safe"),
+ errhint("Explicit cast is defined but definition is not error safe"),
parser_errposition(pstate, exprLocation(sourceexpr)));
}
diff --git a/src/include/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat
index 1b718a15044..30855e2c864 100644
--- a/src/include/catalog/pg_cast.dat
+++ b/src/include/catalog/pg_cast.dat
@@ -19,65 +19,65 @@
# int2->int4->int8->numeric->float4->float8, while casts in the
# reverse direction are assignment-only.
{ castsource => 'int8', casttarget => 'int2', castfunc => 'int2(int8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'int4', castfunc => 'int4(int8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'float4', castfunc => 'float4(int8)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'float8', castfunc => 'float8(int8)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'numeric', castfunc => 'numeric(int8)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'int8', castfunc => 'int8(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'int4', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'float4', castfunc => 'float4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'float8', castfunc => 'float8(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'numeric', castfunc => 'numeric(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'int8', castfunc => 'int8(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'int2', castfunc => 'int2(int4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'float4', castfunc => 'float4(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'float8', castfunc => 'float8(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'numeric', castfunc => 'numeric(int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'int8', castfunc => 'int8(float4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'int2', castfunc => 'int2(float4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'int4', castfunc => 'int4(float4)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'float8', castfunc => 'float8(float4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float4', casttarget => 'numeric',
- castfunc => 'numeric(float4)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'numeric(float4)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'int8', castfunc => 'int8(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'int2', castfunc => 'int2(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'int4', castfunc => 'int4(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'float4', castfunc => 'float4(float8)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'float8', casttarget => 'numeric',
- castfunc => 'numeric(float8)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'numeric(float8)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'int8', castfunc => 'int8(numeric)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'int2', castfunc => 'int2(numeric)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'int4', castfunc => 'int4(numeric)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'float4',
- castfunc => 'float4(numeric)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'float4(numeric)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'float8',
- castfunc => 'float8(numeric)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'float8(numeric)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'money', casttarget => 'numeric', castfunc => 'numeric(money)',
castcontext => 'a', castmethod => 'f' },
{ castsource => 'numeric', casttarget => 'money', castfunc => 'money(numeric)',
@@ -89,13 +89,13 @@
# Allow explicit coercions between int4 and bool
{ castsource => 'int4', casttarget => 'bool', castfunc => 'bool(int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'int4', castfunc => 'int4(bool)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Allow explicit coercions between xid8 and xid
{ castsource => 'xid8', casttarget => 'xid', castfunc => 'xid(xid8)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# OID category: allow implicit conversion from any integral type (including
# int8, to support OID literals > 2G) to OID, as well as assignment coercion
@@ -106,13 +106,13 @@
# casts from text and varchar to regclass, which exist mainly to support
# legacy forms of nextval() and related functions.
{ castsource => 'int8', casttarget => 'oid', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'oid', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'oid', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regproc', castfunc => '0',
@@ -120,13 +120,13 @@
{ castsource => 'regproc', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regproc', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regproc', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regproc', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regproc', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regproc', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'regproc', casttarget => 'regprocedure', castfunc => '0',
@@ -138,13 +138,13 @@
{ castsource => 'regprocedure', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regprocedure', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regprocedure', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regprocedure', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regprocedure', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regprocedure', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regoper', castfunc => '0',
@@ -152,13 +152,13 @@
{ castsource => 'regoper', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regoper', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regoper', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regoper', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regoper', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regoper', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'regoper', casttarget => 'regoperator', castfunc => '0',
@@ -170,13 +170,13 @@
{ castsource => 'regoperator', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regoperator', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regoperator', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regoperator', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regoperator', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regoperator', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regclass', castfunc => '0',
@@ -184,13 +184,13 @@
{ castsource => 'regclass', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regclass', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regclass', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regclass', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regclass', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regclass', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regcollation', castfunc => '0',
@@ -198,13 +198,13 @@
{ castsource => 'regcollation', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regcollation', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regcollation', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regcollation', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regcollation', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regcollation', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regtype', castfunc => '0',
@@ -212,13 +212,13 @@
{ castsource => 'regtype', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regtype', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regtype', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regtype', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regtype', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regtype', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regconfig', castfunc => '0',
@@ -226,13 +226,13 @@
{ castsource => 'regconfig', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regconfig', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regconfig', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regconfig', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regconfig', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regconfig', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regdictionary', castfunc => '0',
@@ -240,31 +240,31 @@
{ castsource => 'regdictionary', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regdictionary', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regdictionary', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regdictionary', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regdictionary', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regdictionary', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'text', casttarget => 'regclass', castfunc => 'regclass',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'regclass', castfunc => 'regclass',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'oid', casttarget => 'regrole', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regrole', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regrole', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regrole', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regrole', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regrole', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regrole', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regnamespace', castfunc => '0',
@@ -272,13 +272,13 @@
{ castsource => 'regnamespace', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regnamespace', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regnamespace', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regnamespace', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regnamespace', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regnamespace', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'oid', casttarget => 'regdatabase', castfunc => '0',
@@ -286,13 +286,13 @@
{ castsource => 'regdatabase', casttarget => 'oid', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'int8', casttarget => 'regdatabase', castfunc => 'oid',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int2', casttarget => 'regdatabase', castfunc => 'int4(int2)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'regdatabase', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'regdatabase', casttarget => 'int8', castfunc => 'int8(oid)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'regdatabase', casttarget => 'int4', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
@@ -302,57 +302,57 @@
{ castsource => 'text', casttarget => 'varchar', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'bpchar', casttarget => 'text', castfunc => 'text(bpchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bpchar', casttarget => 'varchar', castfunc => 'text(bpchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'text', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'varchar', casttarget => 'bpchar', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'char', casttarget => 'text', castfunc => 'text(char)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'char', casttarget => 'bpchar', castfunc => 'bpchar(char)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'char', casttarget => 'varchar', castfunc => 'text(char)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'name', casttarget => 'text', castfunc => 'text(name)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'name', casttarget => 'bpchar', castfunc => 'bpchar(name)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'name', casttarget => 'varchar', castfunc => 'varchar(name)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'text', casttarget => 'char', castfunc => 'char(text)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bpchar', casttarget => 'char', castfunc => 'char(text)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'char', castfunc => 'char(text)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'text', casttarget => 'name', castfunc => 'name(text)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bpchar', casttarget => 'name', castfunc => 'name(bpchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'name', castfunc => 'name(varchar)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
# Allow explicit coercions between bytea and integer types
{ castsource => 'int2', casttarget => 'bytea', castfunc => 'bytea(int2)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'bytea', castfunc => 'bytea(int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8', casttarget => 'bytea', castfunc => 'bytea(int8)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bytea', casttarget => 'int2', castfunc => 'int2(bytea)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bytea', casttarget => 'int4', castfunc => 'int4(bytea)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bytea', casttarget => 'int8', castfunc => 'int8(bytea)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Allow explicit coercions between int4 and "char"
{ castsource => 'char', casttarget => 'int4', castfunc => 'int4(char)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'char', castfunc => 'char(int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# pg_node_tree can be coerced to, but not from, text
{ castsource => 'pg_node_tree', casttarget => 'text', castfunc => '0',
@@ -378,73 +378,73 @@
# Datetime category
{ castsource => 'date', casttarget => 'timestamp',
- castfunc => 'timestamp(date)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timestamp(date)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'date', casttarget => 'timestamptz',
- castfunc => 'timestamptz(date)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timestamptz(date)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'time', casttarget => 'interval', castfunc => 'interval(time)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'time', casttarget => 'timetz', castfunc => 'timetz(time)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'date',
- castfunc => 'date(timestamp)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'date(timestamp)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'time',
- castfunc => 'time(timestamp)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'time(timestamp)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'timestamptz',
- castfunc => 'timestamptz(timestamp)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timestamptz(timestamp)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'date',
- castfunc => 'date(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'date(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'time',
- castfunc => 'time(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'time(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'timestamp',
- castfunc => 'timestamp(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'timestamp(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'timetz',
- castfunc => 'timetz(timestamptz)', castcontext => 'a', castmethod => 'f' },
+ castfunc => 'timetz(timestamptz)', castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'interval', casttarget => 'time', castfunc => 'time(interval)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timetz', casttarget => 'time', castfunc => 'time(timetz)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
# Geometric category
{ castsource => 'point', casttarget => 'box', castfunc => 'box(point)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'lseg', casttarget => 'point', castfunc => 'point(lseg)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'path', casttarget => 'polygon', castfunc => 'polygon(path)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'box', casttarget => 'point', castfunc => 'point(box)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'box', casttarget => 'lseg', castfunc => 'lseg(box)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'box', casttarget => 'polygon', castfunc => 'polygon(box)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'box', casttarget => 'circle', castfunc => 'circle(box)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'polygon', casttarget => 'point', castfunc => 'point(polygon)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'polygon', casttarget => 'path', castfunc => 'path',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'polygon', casttarget => 'box', castfunc => 'box(polygon)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'polygon', casttarget => 'circle',
- castfunc => 'circle(polygon)', castcontext => 'e', castmethod => 'f' },
+ castfunc => 'circle(polygon)', castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'circle', casttarget => 'point', castfunc => 'point(circle)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'circle', casttarget => 'box', castfunc => 'box(circle)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'circle', casttarget => 'polygon',
- castfunc => 'polygon(circle)', castcontext => 'e', castmethod => 'f' },
+ castfunc => 'polygon(circle)', castcontext => 'e', castmethod => 'f'},
# MAC address category
{ castsource => 'macaddr', casttarget => 'macaddr8', castfunc => 'macaddr8',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'macaddr8', casttarget => 'macaddr', castfunc => 'macaddr',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
# INET category
{ castsource => 'cidr', casttarget => 'inet', castfunc => '0',
castcontext => 'i', castmethod => 'b' },
{ castsource => 'inet', casttarget => 'cidr', castfunc => 'cidr',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
# BitString category
{ castsource => 'bit', casttarget => 'varbit', castfunc => '0',
@@ -454,13 +454,13 @@
# Cross-category casts between bit and int4, int8
{ castsource => 'int8', casttarget => 'bit', castfunc => 'bit(int8,int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int4', casttarget => 'bit', castfunc => 'bit(int4,int4)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bit', casttarget => 'int8', castfunc => 'int8(bit)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bit', casttarget => 'int4', castfunc => 'int4(bit)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Cross-category casts to and from TEXT
# We need entries here only for a few specialized cases where the behavior
@@ -471,68 +471,68 @@
# behavior will ensue when the automatic cast is applied instead of the
# pg_cast entry!
{ castsource => 'cidr', casttarget => 'text', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'inet', casttarget => 'text', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'text', castfunc => 'text(bool)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'xml', casttarget => 'text', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'text', casttarget => 'xml', castfunc => 'xml',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Cross-category casts to and from VARCHAR
# We support all the same casts as for TEXT.
{ castsource => 'cidr', casttarget => 'varchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'inet', casttarget => 'varchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'varchar', castfunc => 'text(bool)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'xml', casttarget => 'varchar', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'varchar', casttarget => 'xml', castfunc => 'xml',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Cross-category casts to and from BPCHAR
# We support all the same casts as for TEXT.
{ castsource => 'cidr', casttarget => 'bpchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'inet', casttarget => 'bpchar', castfunc => 'text(inet)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bool', casttarget => 'bpchar', castfunc => 'text(bool)',
- castcontext => 'a', castmethod => 'f' },
+ castcontext => 'a', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'xml', casttarget => 'bpchar', castfunc => '0',
castcontext => 'a', castmethod => 'b' },
{ castsource => 'bpchar', casttarget => 'xml', castfunc => 'xml',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# Length-coercion functions
{ castsource => 'bpchar', casttarget => 'bpchar',
castfunc => 'bpchar(bpchar,int4,bool)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varchar', casttarget => 'varchar',
castfunc => 'varchar(varchar,int4,bool)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'time', casttarget => 'time', castfunc => 'time(time,int4)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamp', casttarget => 'timestamp',
castfunc => 'timestamp(timestamp,int4)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timestamptz', casttarget => 'timestamptz',
castfunc => 'timestamptz(timestamptz,int4)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'interval', casttarget => 'interval',
castfunc => 'interval(interval,int4)', castcontext => 'i',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'timetz', casttarget => 'timetz',
- castfunc => 'timetz(timetz,int4)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'timetz(timetz,int4)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'bit', casttarget => 'bit', castfunc => 'bit(bit,int4,bool)',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'varbit', casttarget => 'varbit', castfunc => 'varbit',
- castcontext => 'i', castmethod => 'f' },
+ castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numeric', casttarget => 'numeric',
- castfunc => 'numeric(numeric,int4)', castcontext => 'i', castmethod => 'f' },
+ castfunc => 'numeric(numeric,int4)', castcontext => 'i', castmethod => 'f', casterrorsafe => 't' },
# json to/from jsonb
{ castsource => 'json', casttarget => 'jsonb', castfunc => '0',
@@ -542,36 +542,36 @@
# jsonb to numeric and bool types
{ castsource => 'jsonb', casttarget => 'bool', castfunc => 'bool(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'numeric', castfunc => 'numeric(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'int2', castfunc => 'int2(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'int4', castfunc => 'int4(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'int8', castfunc => 'int8(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'float4', castfunc => 'float4(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'jsonb', casttarget => 'float8', castfunc => 'float8(jsonb)',
- castcontext => 'e', castmethod => 'f' },
+ castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
# range to multirange
{ castsource => 'int4range', casttarget => 'int4multirange',
castfunc => 'int4multirange(int4range)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'int8range', casttarget => 'int8multirange',
castfunc => 'int8multirange(int8range)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'numrange', casttarget => 'nummultirange',
castfunc => 'nummultirange(numrange)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'daterange', casttarget => 'datemultirange',
castfunc => 'datemultirange(daterange)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'tsrange', casttarget => 'tsmultirange',
- castfunc => 'tsmultirange(tsrange)', castcontext => 'e', castmethod => 'f' },
+ castfunc => 'tsmultirange(tsrange)', castcontext => 'e', castmethod => 'f', casterrorsafe => 't' },
{ castsource => 'tstzrange', casttarget => 'tstzmultirange',
castfunc => 'tstzmultirange(tstzrange)', castcontext => 'e',
- castmethod => 'f' },
+ castmethod => 'f', casterrorsafe => 't' },
]
diff --git a/src/include/catalog/pg_cast.h b/src/include/catalog/pg_cast.h
index 2c9633a5ecb..068f344e200 100644
--- a/src/include/catalog/pg_cast.h
+++ b/src/include/catalog/pg_cast.h
@@ -47,6 +47,10 @@ CATALOG(pg_cast,2605,CastRelationId)
/* cast method */
char castmethod;
+
+ /* cast function error safe */
+ bool casterrorsafe BKI_DEFAULT(f);
+
} FormData_pg_cast;
/* ----------------
@@ -101,6 +105,7 @@ extern ObjectAddress CastCreate(Oid sourcetypeid,
Oid outcastid,
char castcontext,
char castmethod,
+ bool casterrorsafe,
DependencyType behavior);
#endif /* PG_CAST_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 62f27d301d0..1ef19cbc5e0 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4173,6 +4173,7 @@ typedef struct CreateCastStmt
ObjectWithArgs *func;
CoercionContext context;
bool inout;
+ bool safe;
} CreateCastStmt;
/* ----------------------
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f7753c5c8a8..61b808281df 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -397,6 +397,7 @@ PG_KEYWORD("routines", ROUTINES, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("row", ROW, COL_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("rows", ROWS, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("rule", RULE, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("safe", SAFE, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("savepoint", SAVEPOINT, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("scalar", SCALAR, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("schema", SCHEMA, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/test/regress/expected/create_cast.out b/src/test/regress/expected/create_cast.out
index 0054ed0ef67..1e32c041b9f 100644
--- a/src/test/regress/expected/create_cast.out
+++ b/src/test/regress/expected/create_cast.out
@@ -81,6 +81,12 @@ NOTICE: drop cascades to cast from integer to casttesttype
-- Try it with a function that requires an implicit cast
CREATE FUNCTION bar_int4_text(int4) RETURNS text LANGUAGE SQL AS
$$ SELECT ('bar'::text || $1::text); $$;
+CREATE FUNCTION bar_int4_text_plpg(int4) RETURNS text LANGUAGE plpgsql AS
+$$ BEGIN RETURN ('bar'::text || $1::text); END $$;
+CREATE CAST (int4 AS casttesttype) WITH SAFE FUNCTION bar_int4_text(int4) AS IMPLICIT; -- error
+ERROR: Safe type cast functions are only supported for C and internal languages
+CREATE CAST (int4 AS casttesttype) WITH SAFE FUNCTION bar_int4_text_plpg(int4) AS IMPLICIT; -- error
+ERROR: Safe type cast functions are only supported for C and internal languages
CREATE CAST (int4 AS casttesttype) WITH FUNCTION bar_int4_text(int4) AS IMPLICIT;
SELECT 1234::int4::casttesttype; -- Should work now
casttesttype
@@ -92,7 +98,7 @@ SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- err
ERROR: cannot cast type integer to casttesttype when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
LINE 1: SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVE...
^
-HINT: Safe type cast for user-defined types are not yet supported
+HINT: Explicit cast is defined but definition is not error safe
-- check dependencies generated for that
SELECT pg_describe_object(classid, objid, objsubid) as obj,
pg_describe_object(refclassid, refobjid, refobjsubid) as objref,
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index a357e1d0c0e..81ea244859f 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -943,8 +943,8 @@ SELECT *
FROM pg_cast c
WHERE castsource = 0 OR casttarget = 0 OR castcontext NOT IN ('e', 'a', 'i')
OR castmethod NOT IN ('f', 'b' ,'i');
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Check that castfunc is nonzero only for cast methods that need a function,
@@ -953,8 +953,8 @@ SELECT *
FROM pg_cast c
WHERE (castmethod = 'f' AND castfunc = 0)
OR (castmethod IN ('b', 'i') AND castfunc <> 0);
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Look for casts to/from the same type that aren't length coercion functions.
@@ -963,15 +963,15 @@ WHERE (castmethod = 'f' AND castfunc = 0)
SELECT *
FROM pg_cast c
WHERE castsource = casttarget AND castfunc = 0;
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
SELECT c.*
FROM pg_cast c, pg_proc p
WHERE c.castfunc = p.oid AND p.pronargs < 2 AND castsource = casttarget;
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Look for cast functions that don't have the right signature. The
@@ -989,8 +989,8 @@ WHERE c.castfunc = p.oid AND
OR (c.castsource = 'character'::regtype AND
p.proargtypes[0] = 'text'::regtype))
OR NOT binary_coercible(p.prorettype, c.casttarget));
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
SELECT c.*
@@ -998,8 +998,8 @@ FROM pg_cast c, pg_proc p
WHERE c.castfunc = p.oid AND
((p.pronargs > 1 AND p.proargtypes[1] != 'int4'::regtype) OR
(p.pronargs > 2 AND p.proargtypes[2] != 'bool'::regtype));
- oid | castsource | casttarget | castfunc | castcontext | castmethod
------+------------+------------+----------+-------------+------------
+ oid | castsource | casttarget | castfunc | castcontext | castmethod | casterrorsafe
+-----+------------+------------+----------+-------------+------------+---------------
(0 rows)
-- Look for binary compatible casts that do not have the reverse
diff --git a/src/test/regress/sql/create_cast.sql b/src/test/regress/sql/create_cast.sql
index 0a15a795d87..30a0ff077c9 100644
--- a/src/test/regress/sql/create_cast.sql
+++ b/src/test/regress/sql/create_cast.sql
@@ -60,6 +60,11 @@ DROP FUNCTION int4_casttesttype(int4) CASCADE;
CREATE FUNCTION bar_int4_text(int4) RETURNS text LANGUAGE SQL AS
$$ SELECT ('bar'::text || $1::text); $$;
+CREATE FUNCTION bar_int4_text_plpg(int4) RETURNS text LANGUAGE plpgsql AS
+$$ BEGIN RETURN ('bar'::text || $1::text); END $$;
+
+CREATE CAST (int4 AS casttesttype) WITH SAFE FUNCTION bar_int4_text(int4) AS IMPLICIT; -- error
+CREATE CAST (int4 AS casttesttype) WITH SAFE FUNCTION bar_int4_text_plpg(int4) AS IMPLICIT; -- error
CREATE CAST (int4 AS casttesttype) WITH FUNCTION bar_int4_text(int4) AS IMPLICIT;
SELECT 1234::int4::casttesttype; -- Should work now
SELECT CAST(1234::int4 AS casttesttype DEFAULT NULL ON CONVERSION ERROR); -- error
--
2.34.1
v17-0021-error-safe-for-casting-geometry-data-type.patchtext/x-patch; charset=US-ASCII; name=v17-0021-error-safe-for-casting-geometry-data-type.patchDownload
From 6106bd6bc6fc89751bd35f80ca79e4f8768c67a3 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 3 Jan 2026 14:44:38 +0800
Subject: [PATCH v17 21/23] error safe for casting geometry data type
select castsource::regtype, casttarget::regtype, pp.prosrc
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
join pg_type pt on pt.oid = castsource
join pg_type pt1 on pt1.oid = casttarget
and pc.castfunc > 0 and pt.typarray <> 0
and pt.typnamespace = 'pg_catalog'::regnamespace
and pt1.typnamespace = 'pg_catalog'::regnamespace
and (pt.typcategory = 'G' or pt1.typcategory = 'G')
order by castsource::regtype, casttarget::regtype;
castsource | casttarget | prosrc
------------+------------+---------------
point | box | point_box
lseg | point | lseg_center
path | polygon | path_poly
box | point | box_center
box | lseg | box_diagonal
box | polygon | box_poly
box | circle | box_circle
polygon | point | poly_center
polygon | path | poly_path
polygon | box | poly_box
polygon | circle | poly_circle
circle | point | circle_center
circle | box | circle_box
circle | polygon |
(14 rows)
already error safe: point_box, box_diagonal, box_poly, poly_path, poly_box,
circle_center
This patch make these functions error safe: path_poly, lseg_center, box_center,
box_circle, poly_center, poly_circle, circle_box
can not error safe: cast circle to polygon, because it's a SQL function.
discussion: https://postgr.es/m/CACJufxHCMzrHOW=wRe8L30rMhB3sjwAv1LE928Fa7sxMu1Tx-g@mail.gmail.com
---
src/backend/utils/adt/geo_ops.c | 192 +++++++++++++++++++++++++-------
1 file changed, 152 insertions(+), 40 deletions(-)
diff --git a/src/backend/utils/adt/geo_ops.c b/src/backend/utils/adt/geo_ops.c
index c655b015c14..90228ad2175 100644
--- a/src/backend/utils/adt/geo_ops.c
+++ b/src/backend/utils/adt/geo_ops.c
@@ -77,7 +77,8 @@ enum path_delim
/* Routines for points */
static inline void point_construct(Point *result, float8 x, float8 y);
-static inline void point_add_point(Point *result, Point *pt1, Point *pt2);
+static inline void point_add_point(Point *result, Point *pt1, Point *pt2,
+ Node *escontext);
static inline void point_sub_point(Point *result, Point *pt1, Point *pt2);
static inline void point_mul_point(Point *result, Point *pt1, Point *pt2);
static inline void point_div_point(Point *result, Point *pt1, Point *pt2);
@@ -108,7 +109,7 @@ static float8 lseg_closept_lseg(Point *result, LSEG *on_lseg, LSEG *to_lseg);
/* Routines for boxes */
static inline void box_construct(BOX *result, Point *pt1, Point *pt2);
-static void box_cn(Point *center, BOX *box);
+static void box_cn(Point *center, BOX *box, Node *escontext);
static bool box_ov(BOX *box1, BOX *box2);
static float8 box_ar(BOX *box);
static float8 box_ht(BOX *box);
@@ -125,7 +126,7 @@ static float8 circle_ar(CIRCLE *circle);
/* Routines for polygons */
static void make_bound_box(POLYGON *poly);
-static void poly_to_circle(CIRCLE *result, POLYGON *poly);
+static bool poly_to_circle(CIRCLE *result, POLYGON *poly, Node *escontext);
static bool lseg_inside_poly(Point *a, Point *b, POLYGON *poly, int start);
static bool poly_contain_poly(POLYGON *contains_poly, POLYGON *contained_poly);
static bool plist_same(int npts, Point *p1, Point *p2);
@@ -836,8 +837,8 @@ box_distance(PG_FUNCTION_ARGS)
Point a,
b;
- box_cn(&a, box1);
- box_cn(&b, box2);
+ box_cn(&a, box1, NULL);
+ box_cn(&b, box2, NULL);
PG_RETURN_FLOAT8(point_dt(&a, &b, fcinfo->context));
}
@@ -851,7 +852,9 @@ box_center(PG_FUNCTION_ARGS)
BOX *box = PG_GETARG_BOX_P(0);
Point *result = palloc_object(Point);
- box_cn(result, box);
+ box_cn(result, box, fcinfo->context);
+ if ((SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
PG_RETURN_POINT_P(result);
}
@@ -869,13 +872,26 @@ box_ar(BOX *box)
/* box_cn - stores the centerpoint of the box into *center.
*/
static void
-box_cn(Point *center, BOX *box)
+box_cn(Point *center, BOX *box, Node *escontext)
{
- center->x = float8_div(float8_pl(box->high.x, box->low.x), 2.0);
- center->y = float8_div(float8_pl(box->high.y, box->low.y), 2.0);
+ float8 x;
+ float8 y;
+
+ x = float8_pl_safe(box->high.x, box->low.x, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return;
+
+ center->x = float8_div_safe(x, 2.0, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return;
+
+ y = float8_pl_safe(box->high.y, box->low.y, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return;
+
+ center->y = float8_div_safe(y, 2.0, escontext);
}
-
/* box_wd - returns the width (length) of the box
* (horizontal magnitude).
*/
@@ -2329,13 +2345,31 @@ lseg_center(PG_FUNCTION_ARGS)
{
LSEG *lseg = PG_GETARG_LSEG_P(0);
Point *result;
+ float8 x;
+ float8 y;
result = palloc_object(Point);
- result->x = float8_div(float8_pl(lseg->p[0].x, lseg->p[1].x), 2.0);
- result->y = float8_div(float8_pl(lseg->p[0].y, lseg->p[1].y), 2.0);
+ x = float8_pl_safe(lseg->p[0].x, lseg->p[1].x, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ result->x = float8_div_safe(x, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ y = float8_pl_safe(lseg->p[0].y, lseg->p[1].y, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ result->y = float8_div_safe(y, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
PG_RETURN_POINT_P(result);
+
+fail:
+ PG_RETURN_NULL();
}
@@ -3290,7 +3324,7 @@ box_interpt_lseg(Point *result, BOX *box, LSEG *lseg)
if (result != NULL)
{
- box_cn(&point, box);
+ box_cn(&point, box, NULL);
lseg_closept_point(result, lseg, &point);
}
@@ -4121,11 +4155,20 @@ construct_point(PG_FUNCTION_ARGS)
static inline void
-point_add_point(Point *result, Point *pt1, Point *pt2)
+point_add_point(Point *result, Point *pt1, Point *pt2, Node *escontext)
{
- point_construct(result,
- float8_pl(pt1->x, pt2->x),
- float8_pl(pt1->y, pt2->y));
+ float8 x;
+ float8 y;
+
+ x = float8_pl_safe(pt1->x, pt2->x, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return;
+
+ y = float8_pl_safe(pt1->y, pt2->y, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return;
+
+ point_construct(result, x, y);
}
Datum
@@ -4137,7 +4180,7 @@ point_add(PG_FUNCTION_ARGS)
result = palloc_object(Point);
- point_add_point(result, p1, p2);
+ point_add_point(result, p1, p2, NULL);
PG_RETURN_POINT_P(result);
}
@@ -4249,8 +4292,8 @@ box_add(PG_FUNCTION_ARGS)
result = palloc_object(BOX);
- point_add_point(&result->high, &box->high, p);
- point_add_point(&result->low, &box->low, p);
+ point_add_point(&result->high, &box->high, p, NULL);
+ point_add_point(&result->low, &box->low, p, NULL);
PG_RETURN_BOX_P(result);
}
@@ -4413,7 +4456,7 @@ path_add_pt(PG_FUNCTION_ARGS)
int i;
for (i = 0; i < path->npts; i++)
- point_add_point(&path->p[i], &path->p[i], point);
+ point_add_point(&path->p[i], &path->p[i], point, NULL);
PG_RETURN_PATH_P(path);
}
@@ -4471,7 +4514,7 @@ path_poly(PG_FUNCTION_ARGS)
/* This is not very consistent --- other similar cases return NULL ... */
if (!path->closed)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("open path cannot be converted to polygon")));
@@ -4521,7 +4564,9 @@ poly_center(PG_FUNCTION_ARGS)
result = palloc_object(Point);
- poly_to_circle(&circle, poly);
+ if (!poly_to_circle(&circle, poly, fcinfo->context))
+ PG_RETURN_NULL();
+
*result = circle.center;
PG_RETURN_POINT_P(result);
@@ -4983,7 +5028,7 @@ circle_add_pt(PG_FUNCTION_ARGS)
result = palloc_object(CIRCLE);
- point_add_point(&result->center, &circle->center, point);
+ point_add_point(&result->center, &circle->center, point, NULL);
result->radius = circle->radius;
PG_RETURN_CIRCLE_P(result);
@@ -5204,14 +5249,30 @@ circle_box(PG_FUNCTION_ARGS)
box = palloc_object(BOX);
- delta = float8_div(circle->radius, sqrt(2.0));
+ delta = float8_div_safe(circle->radius, sqrt(2.0), fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
- box->high.x = float8_pl(circle->center.x, delta);
- box->low.x = float8_mi(circle->center.x, delta);
- box->high.y = float8_pl(circle->center.y, delta);
- box->low.y = float8_mi(circle->center.y, delta);
+ box->high.x = float8_pl_safe(circle->center.x, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ box->low.x = float8_mi_safe(circle->center.x, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ box->high.y = float8_pl_safe(circle->center.y, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ box->low.y = float8_mi_safe(circle->center.y, delta, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
PG_RETURN_BOX_P(box);
+
+fail:
+ PG_RETURN_NULL();
}
/* box_circle()
@@ -5222,15 +5283,35 @@ box_circle(PG_FUNCTION_ARGS)
{
BOX *box = PG_GETARG_BOX_P(0);
CIRCLE *circle;
+ float8 x;
+ float8 y;
circle = palloc_object(CIRCLE);
- circle->center.x = float8_div(float8_pl(box->high.x, box->low.x), 2.0);
- circle->center.y = float8_div(float8_pl(box->high.y, box->low.y), 2.0);
+ x = float8_pl_safe(box->high.x, box->low.x, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ circle->center.x = float8_div_safe(x, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ y = float8_pl_safe(box->high.y, box->low.y, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ circle->center.y = float8_div_safe(y, 2.0, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
circle->radius = point_dt(&circle->center, &box->high, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
PG_RETURN_CIRCLE_P(circle);
+
+fail:
+ PG_RETURN_NULL();
}
@@ -5294,10 +5375,11 @@ circle_poly(PG_FUNCTION_ARGS)
* XXX This algorithm should use weighted means of line segments
* rather than straight average values of points - tgl 97/01/21.
*/
-static void
-poly_to_circle(CIRCLE *result, POLYGON *poly)
+static bool
+poly_to_circle(CIRCLE *result, POLYGON *poly, Node *escontext)
{
int i;
+ float8 x;
Assert(poly->npts > 0);
@@ -5306,14 +5388,43 @@ poly_to_circle(CIRCLE *result, POLYGON *poly)
result->radius = 0;
for (i = 0; i < poly->npts; i++)
- point_add_point(&result->center, &result->center, &poly->p[i]);
- result->center.x = float8_div(result->center.x, poly->npts);
- result->center.y = float8_div(result->center.y, poly->npts);
+ {
+ point_add_point(&result->center,
+ &result->center,
+ &poly->p[i],
+ escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+ }
+
+ result->center.x = float8_div_safe(result->center.x,
+ poly->npts,
+ escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ result->center.y = float8_div_safe(result->center.y,
+ poly->npts,
+ escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
for (i = 0; i < poly->npts; i++)
- result->radius = float8_pl(result->radius,
- point_dt(&poly->p[i], &result->center, NULL));
- result->radius = float8_div(result->radius, poly->npts);
+ {
+ x = point_dt(&poly->p[i], &result->center, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ result->radius = float8_pl_safe(result->radius, x, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+ }
+
+ result->radius = float8_div_safe(result->radius, poly->npts, escontext);
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return false;
+
+ return true;
}
Datum
@@ -5324,7 +5435,8 @@ poly_circle(PG_FUNCTION_ARGS)
result = palloc_object(CIRCLE);
- poly_to_circle(result, poly);
+ if (!poly_to_circle(result, poly, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_CIRCLE_P(result);
}
--
2.34.1
v17-0020-refactor-point_dt.patchtext/x-patch; charset=US-ASCII; name=v17-0020-refactor-point_dt.patchDownload
From f966c2b4571325caa55200fcbef03271d09a1696 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Mon, 5 Jan 2026 13:33:51 +0800
Subject: [PATCH v17 20/23] refactor point_dt
point_dt is used in multiple locations and will be needed by a later patch, thus
refactoring make it error safe.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/geo_ops.c | 79 +++++++++++++++++++--------------
1 file changed, 46 insertions(+), 33 deletions(-)
diff --git a/src/backend/utils/adt/geo_ops.c b/src/backend/utils/adt/geo_ops.c
index bfb4859b4cb..c655b015c14 100644
--- a/src/backend/utils/adt/geo_ops.c
+++ b/src/backend/utils/adt/geo_ops.c
@@ -82,7 +82,7 @@ static inline void point_sub_point(Point *result, Point *pt1, Point *pt2);
static inline void point_mul_point(Point *result, Point *pt1, Point *pt2);
static inline void point_div_point(Point *result, Point *pt1, Point *pt2);
static inline bool point_eq_point(Point *pt1, Point *pt2);
-static inline float8 point_dt(Point *pt1, Point *pt2);
+static inline float8 point_dt(Point *pt1, Point *pt2, Node *escontext);
static inline float8 point_sl(Point *pt1, Point *pt2);
static int point_inside(Point *p, int npts, Point *plist);
@@ -839,7 +839,7 @@ box_distance(PG_FUNCTION_ARGS)
box_cn(&a, box1);
box_cn(&b, box2);
- PG_RETURN_FLOAT8(point_dt(&a, &b));
+ PG_RETURN_FLOAT8(point_dt(&a, &b, fcinfo->context));
}
@@ -1808,7 +1808,8 @@ path_length(PG_FUNCTION_ARGS)
iprev = path->npts - 1; /* include the closure segment */
}
- result = float8_pl(result, point_dt(&path->p[iprev], &path->p[i]));
+ result = float8_pl(result,
+ point_dt(&path->p[iprev], &path->p[i], fcinfo->context));
}
PG_RETURN_FLOAT8(result);
@@ -1995,13 +1996,24 @@ point_distance(PG_FUNCTION_ARGS)
Point *pt1 = PG_GETARG_POINT_P(0);
Point *pt2 = PG_GETARG_POINT_P(1);
- PG_RETURN_FLOAT8(point_dt(pt1, pt2));
+ PG_RETURN_FLOAT8(point_dt(pt1, pt2, fcinfo->context));
}
static inline float8
-point_dt(Point *pt1, Point *pt2)
+point_dt(Point *pt1, Point *pt2, Node *escontext)
{
- return hypot(float8_mi(pt1->x, pt2->x), float8_mi(pt1->y, pt2->y));
+ float8 x;
+ float8 y;
+
+ x = float8_mi_safe(pt1->x, pt2->x, escontext);
+ if (unlikely(SOFT_ERROR_OCCURRED(escontext)))
+ return 0.0;
+
+ y = float8_mi_safe(pt1->y, pt2->y, escontext);
+ if (unlikely(SOFT_ERROR_OCCURRED(escontext)))
+ return 0.0;
+
+ return hypot(x, y);
}
Datum
@@ -2173,7 +2185,7 @@ lseg_length(PG_FUNCTION_ARGS)
{
LSEG *lseg = PG_GETARG_LSEG_P(0);
- PG_RETURN_FLOAT8(point_dt(&lseg->p[0], &lseg->p[1]));
+ PG_RETURN_FLOAT8(point_dt(&lseg->p[0], &lseg->p[1], fcinfo->context));
}
/*----------------------------------------------------------
@@ -2258,8 +2270,8 @@ lseg_lt(PG_FUNCTION_ARGS)
LSEG *l1 = PG_GETARG_LSEG_P(0);
LSEG *l2 = PG_GETARG_LSEG_P(1);
- PG_RETURN_BOOL(FPlt(point_dt(&l1->p[0], &l1->p[1]),
- point_dt(&l2->p[0], &l2->p[1])));
+ PG_RETURN_BOOL(FPlt(point_dt(&l1->p[0], &l1->p[1], fcinfo->context),
+ point_dt(&l2->p[0], &l2->p[1], fcinfo->context)));
}
Datum
@@ -2268,8 +2280,8 @@ lseg_le(PG_FUNCTION_ARGS)
LSEG *l1 = PG_GETARG_LSEG_P(0);
LSEG *l2 = PG_GETARG_LSEG_P(1);
- PG_RETURN_BOOL(FPle(point_dt(&l1->p[0], &l1->p[1]),
- point_dt(&l2->p[0], &l2->p[1])));
+ PG_RETURN_BOOL(FPle(point_dt(&l1->p[0], &l1->p[1], fcinfo->context),
+ point_dt(&l2->p[0], &l2->p[1], fcinfo->context)));
}
Datum
@@ -2278,8 +2290,8 @@ lseg_gt(PG_FUNCTION_ARGS)
LSEG *l1 = PG_GETARG_LSEG_P(0);
LSEG *l2 = PG_GETARG_LSEG_P(1);
- PG_RETURN_BOOL(FPgt(point_dt(&l1->p[0], &l1->p[1]),
- point_dt(&l2->p[0], &l2->p[1])));
+ PG_RETURN_BOOL(FPgt(point_dt(&l1->p[0], &l1->p[1], fcinfo->context),
+ point_dt(&l2->p[0], &l2->p[1], fcinfo->context)));
}
Datum
@@ -2288,8 +2300,8 @@ lseg_ge(PG_FUNCTION_ARGS)
LSEG *l1 = PG_GETARG_LSEG_P(0);
LSEG *l2 = PG_GETARG_LSEG_P(1);
- PG_RETURN_BOOL(FPge(point_dt(&l1->p[0], &l1->p[1]),
- point_dt(&l2->p[0], &l2->p[1])));
+ PG_RETURN_BOOL(FPge(point_dt(&l1->p[0], &l1->p[1], fcinfo->context),
+ point_dt(&l2->p[0], &l2->p[1], fcinfo->context)));
}
@@ -2743,7 +2755,7 @@ line_closept_point(Point *result, LINE *line, Point *point)
if (result != NULL)
*result = closept;
- return point_dt(&closept, point);
+ return point_dt(&closept, point, NULL);
}
Datum
@@ -2784,7 +2796,7 @@ lseg_closept_point(Point *result, LSEG *lseg, Point *pt)
if (result != NULL)
*result = closept;
- return point_dt(&closept, pt);
+ return point_dt(&closept, pt, NULL);
}
Datum
@@ -3108,9 +3120,9 @@ on_pl(PG_FUNCTION_ARGS)
static bool
lseg_contain_point(LSEG *lseg, Point *pt)
{
- return FPeq(point_dt(pt, &lseg->p[0]) +
- point_dt(pt, &lseg->p[1]),
- point_dt(&lseg->p[0], &lseg->p[1]));
+ return FPeq(point_dt(pt, &lseg->p[0], NULL) +
+ point_dt(pt, &lseg->p[1], NULL),
+ point_dt(&lseg->p[0], &lseg->p[1], NULL));
}
Datum
@@ -3176,11 +3188,12 @@ on_ppath(PG_FUNCTION_ARGS)
if (!path->closed)
{
n = path->npts - 1;
- a = point_dt(pt, &path->p[0]);
+ a = point_dt(pt, &path->p[0], fcinfo->context);
for (i = 0; i < n; i++)
{
- b = point_dt(pt, &path->p[i + 1]);
- if (FPeq(float8_pl(a, b), point_dt(&path->p[i], &path->p[i + 1])))
+ b = point_dt(pt, &path->p[i + 1], fcinfo->context);
+ if (FPeq(float8_pl(a, b),
+ point_dt(&path->p[i], &path->p[i + 1], fcinfo->context)))
PG_RETURN_BOOL(true);
a = b;
}
@@ -4766,7 +4779,7 @@ circle_overlap(PG_FUNCTION_ARGS)
CIRCLE *circle1 = PG_GETARG_CIRCLE_P(0);
CIRCLE *circle2 = PG_GETARG_CIRCLE_P(1);
- PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center),
+ PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center, fcinfo->context),
float8_pl(circle1->radius, circle2->radius)));
}
@@ -4828,7 +4841,7 @@ circle_contained(PG_FUNCTION_ARGS)
CIRCLE *circle1 = PG_GETARG_CIRCLE_P(0);
CIRCLE *circle2 = PG_GETARG_CIRCLE_P(1);
- PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center),
+ PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center, fcinfo->context),
float8_mi(circle2->radius, circle1->radius)));
}
@@ -4840,7 +4853,7 @@ circle_contain(PG_FUNCTION_ARGS)
CIRCLE *circle1 = PG_GETARG_CIRCLE_P(0);
CIRCLE *circle2 = PG_GETARG_CIRCLE_P(1);
- PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center),
+ PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center, fcinfo->context),
float8_mi(circle1->radius, circle2->radius)));
}
@@ -5069,7 +5082,7 @@ circle_distance(PG_FUNCTION_ARGS)
CIRCLE *circle2 = PG_GETARG_CIRCLE_P(1);
float8 result;
- result = float8_mi(point_dt(&circle1->center, &circle2->center),
+ result = float8_mi(point_dt(&circle1->center, &circle2->center, fcinfo->context),
float8_pl(circle1->radius, circle2->radius));
if (result < 0.0)
result = 0.0;
@@ -5085,7 +5098,7 @@ circle_contain_pt(PG_FUNCTION_ARGS)
Point *point = PG_GETARG_POINT_P(1);
float8 d;
- d = point_dt(&circle->center, point);
+ d = point_dt(&circle->center, point, fcinfo->context);
PG_RETURN_BOOL(d <= circle->radius);
}
@@ -5097,7 +5110,7 @@ pt_contained_circle(PG_FUNCTION_ARGS)
CIRCLE *circle = PG_GETARG_CIRCLE_P(1);
float8 d;
- d = point_dt(&circle->center, point);
+ d = point_dt(&circle->center, point, fcinfo->context);
PG_RETURN_BOOL(d <= circle->radius);
}
@@ -5112,7 +5125,7 @@ dist_pc(PG_FUNCTION_ARGS)
CIRCLE *circle = PG_GETARG_CIRCLE_P(1);
float8 result;
- result = float8_mi(point_dt(point, &circle->center),
+ result = float8_mi(point_dt(point, &circle->center, fcinfo->context),
circle->radius);
if (result < 0.0)
result = 0.0;
@@ -5130,7 +5143,7 @@ dist_cpoint(PG_FUNCTION_ARGS)
Point *point = PG_GETARG_POINT_P(1);
float8 result;
- result = float8_mi(point_dt(point, &circle->center), circle->radius);
+ result = float8_mi(point_dt(point, &circle->center, fcinfo->context), circle->radius);
if (result < 0.0)
result = 0.0;
@@ -5215,7 +5228,7 @@ box_circle(PG_FUNCTION_ARGS)
circle->center.x = float8_div(float8_pl(box->high.x, box->low.x), 2.0);
circle->center.y = float8_div(float8_pl(box->high.y, box->low.y), 2.0);
- circle->radius = point_dt(&circle->center, &box->high);
+ circle->radius = point_dt(&circle->center, &box->high, fcinfo->context);
PG_RETURN_CIRCLE_P(circle);
}
@@ -5299,7 +5312,7 @@ poly_to_circle(CIRCLE *result, POLYGON *poly)
for (i = 0; i < poly->npts; i++)
result->radius = float8_pl(result->radius,
- point_dt(&poly->p[i], &result->center));
+ point_dt(&poly->p[i], &result->center, NULL));
result->radius = float8_div(result->radius, poly->npts);
}
--
2.34.1
v17-0018-refactor-float_overflow_error-float_underflow_error-float_zero_d.patchtext/x-patch; charset=US-ASCII; name=v17-0018-refactor-float_overflow_error-float_underflow_error-float_zero_d.patchDownload
From 320b55f406a015f527e6ebc02aa139c8684774f8 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Tue, 9 Dec 2025 14:36:39 +0800
Subject: [PATCH v17 18/23] refactor
float_overflow_error,float_underflow_error,float_zero_divide_error
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
contrib/btree_gist/btree_float4.c | 2 +-
contrib/btree_gist/btree_float8.c | 4 +-
src/backend/utils/adt/float.c | 104 +++++++++++++++---------------
src/include/utils/float.h | 34 +++++-----
4 files changed, 72 insertions(+), 72 deletions(-)
diff --git a/contrib/btree_gist/btree_float4.c b/contrib/btree_gist/btree_float4.c
index d9c859835da..a7325a7bb29 100644
--- a/contrib/btree_gist/btree_float4.c
+++ b/contrib/btree_gist/btree_float4.c
@@ -101,7 +101,7 @@ float4_dist(PG_FUNCTION_ARGS)
r = a - b;
if (unlikely(isinf(r)) && !isinf(a) && !isinf(b))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT4(fabsf(r));
}
diff --git a/contrib/btree_gist/btree_float8.c b/contrib/btree_gist/btree_float8.c
index 567beede178..7c99b84de35 100644
--- a/contrib/btree_gist/btree_float8.c
+++ b/contrib/btree_gist/btree_float8.c
@@ -79,7 +79,7 @@ gbt_float8_dist(const void *a, const void *b, FmgrInfo *flinfo)
r = arg1 - arg2;
if (unlikely(isinf(r)) && !isinf(arg1) && !isinf(arg2))
- float_overflow_error();
+ float_overflow_error(NULL);
return fabs(r);
}
@@ -109,7 +109,7 @@ float8_dist(PG_FUNCTION_ARGS)
r = a - b;
if (unlikely(isinf(r)) && !isinf(a) && !isinf(b))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(fabs(r));
}
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index 502398d29ec..5b71c40b00c 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -83,25 +83,25 @@ static void init_degree_constants(void);
* This does mean that you don't get a useful error location indicator.
*/
pg_noinline void
-float_overflow_error(void)
+float_overflow_error(struct Node *escontext)
{
- ereport(ERROR,
+ errsave(escontext,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("value out of range: overflow")));
}
pg_noinline void
-float_underflow_error(void)
+float_underflow_error(struct Node *escontext)
{
- ereport(ERROR,
+ errsave(escontext,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("value out of range: underflow")));
}
pg_noinline void
-float_zero_divide_error(void)
+float_zero_divide_error(struct Node *escontext)
{
- ereport(ERROR,
+ errsave(escontext,
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
}
@@ -1460,9 +1460,9 @@ dsqrt(PG_FUNCTION_ARGS)
result = sqrt(arg1);
if (unlikely(isinf(result)) && !isinf(arg1))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0) && arg1 != 0.0)
- float_underflow_error();
+ float_underflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1479,9 +1479,9 @@ dcbrt(PG_FUNCTION_ARGS)
result = cbrt(arg1);
if (unlikely(isinf(result)) && !isinf(arg1))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0) && arg1 != 0.0)
- float_underflow_error();
+ float_underflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1617,24 +1617,24 @@ dpow(PG_FUNCTION_ARGS)
if (absx == 1.0)
result = 1.0;
else if (arg2 >= 0.0 ? (absx > 1.0) : (absx < 1.0))
- float_overflow_error();
+ float_overflow_error(NULL);
else
- float_underflow_error();
+ float_underflow_error(NULL);
}
}
else if (errno == ERANGE)
{
if (result != 0.0)
- float_overflow_error();
+ float_overflow_error(NULL);
else
- float_underflow_error();
+ float_underflow_error(NULL);
}
else
{
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0) && arg1 != 0.0)
- float_underflow_error();
+ float_underflow_error(NULL);
}
}
@@ -1674,14 +1674,14 @@ dexp(PG_FUNCTION_ARGS)
if (unlikely(errno == ERANGE))
{
if (result != 0.0)
- float_overflow_error();
+ float_overflow_error(NULL);
else
- float_underflow_error();
+ float_underflow_error(NULL);
}
else if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
else if (unlikely(result == 0.0))
- float_underflow_error();
+ float_underflow_error(NULL);
}
PG_RETURN_FLOAT8(result);
@@ -1712,9 +1712,9 @@ dlog1(PG_FUNCTION_ARGS)
result = log(arg1);
if (unlikely(isinf(result)) && !isinf(arg1))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0) && arg1 != 1.0)
- float_underflow_error();
+ float_underflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1745,9 +1745,9 @@ dlog10(PG_FUNCTION_ARGS)
result = log10(arg1);
if (unlikely(isinf(result)) && !isinf(arg1))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0) && arg1 != 1.0)
- float_underflow_error();
+ float_underflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1778,7 +1778,7 @@ dacos(PG_FUNCTION_ARGS)
result = acos(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1809,7 +1809,7 @@ dasin(PG_FUNCTION_ARGS)
result = asin(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1835,7 +1835,7 @@ datan(PG_FUNCTION_ARGS)
*/
result = atan(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1861,7 +1861,7 @@ datan2(PG_FUNCTION_ARGS)
*/
result = atan2(arg1, arg2);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1902,7 +1902,7 @@ dcos(PG_FUNCTION_ARGS)
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("input is out of range")));
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -1957,7 +1957,7 @@ dsin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("input is out of range")));
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2137,7 +2137,7 @@ dacosd(PG_FUNCTION_ARGS)
result = 90.0 + asind_q1(-arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2174,7 +2174,7 @@ dasind(PG_FUNCTION_ARGS)
result = -asind_q1(-arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2206,7 +2206,7 @@ datand(PG_FUNCTION_ARGS)
result = (atan_arg1 / atan_1_0) * 45.0;
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2242,7 +2242,7 @@ datan2d(PG_FUNCTION_ARGS)
result = (atan2_arg1_arg2 / atan_1_0) * 45.0;
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2365,7 +2365,7 @@ dcosd(PG_FUNCTION_ARGS)
result = sign * cosd_q1(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2487,7 +2487,7 @@ dsind(PG_FUNCTION_ARGS)
result = sign * sind_q1(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2645,7 +2645,7 @@ dcosh(PG_FUNCTION_ARGS)
result = get_float8_infinity();
if (unlikely(result == 0.0))
- float_underflow_error();
+ float_underflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2665,7 +2665,7 @@ dtanh(PG_FUNCTION_ARGS)
result = tanh(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2765,7 +2765,7 @@ derf(PG_FUNCTION_ARGS)
result = erf(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2785,7 +2785,7 @@ derfc(PG_FUNCTION_ARGS)
result = erfc(arg1);
if (unlikely(isinf(result)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -2814,7 +2814,7 @@ dgamma(PG_FUNCTION_ARGS)
/* Per POSIX, an input of -Inf causes a domain error */
if (arg1 < 0)
{
- float_overflow_error();
+ float_overflow_error(NULL);
result = get_float8_nan(); /* keep compiler quiet */
}
else
@@ -2836,12 +2836,12 @@ dgamma(PG_FUNCTION_ARGS)
if (errno != 0 || isinf(result) || isnan(result))
{
if (result != 0.0)
- float_overflow_error();
+ float_overflow_error(NULL);
else
- float_underflow_error();
+ float_underflow_error(NULL);
}
else if (result == 0.0)
- float_underflow_error();
+ float_underflow_error(NULL);
}
PG_RETURN_FLOAT8(result);
@@ -2873,7 +2873,7 @@ dlgamma(PG_FUNCTION_ARGS)
* to report overflow, but it should never underflow.
*/
if (errno == ERANGE || (isinf(result) && !isinf(arg1)))
- float_overflow_error();
+ float_overflow_error(NULL);
PG_RETURN_FLOAT8(result);
}
@@ -3013,7 +3013,7 @@ float8_combine(PG_FUNCTION_ARGS)
tmp = Sx1 / N1 - Sx2 / N2;
Sxx = Sxx1 + Sxx2 + N1 * N2 * tmp * tmp / N;
if (unlikely(isinf(Sxx)) && !isinf(Sxx1) && !isinf(Sxx2))
- float_overflow_error();
+ float_overflow_error(NULL);
}
/*
@@ -3080,7 +3080,7 @@ float8_accum(PG_FUNCTION_ARGS)
if (isinf(Sx) || isinf(Sxx))
{
if (!isinf(transvalues[1]) && !isinf(newval))
- float_overflow_error();
+ float_overflow_error(NULL);
Sxx = get_float8_nan();
}
@@ -3163,7 +3163,7 @@ float4_accum(PG_FUNCTION_ARGS)
if (isinf(Sx) || isinf(Sxx))
{
if (!isinf(transvalues[1]) && !isinf(newval))
- float_overflow_error();
+ float_overflow_error(NULL);
Sxx = get_float8_nan();
}
@@ -3430,7 +3430,7 @@ float8_regr_accum(PG_FUNCTION_ARGS)
(isinf(Sxy) &&
!isinf(transvalues[1]) && !isinf(newvalX) &&
!isinf(transvalues[3]) && !isinf(newvalY)))
- float_overflow_error();
+ float_overflow_error(NULL);
if (isinf(Sxx))
Sxx = get_float8_nan();
@@ -3603,15 +3603,15 @@ float8_regr_combine(PG_FUNCTION_ARGS)
tmp1 = Sx1 / N1 - Sx2 / N2;
Sxx = Sxx1 + Sxx2 + N1 * N2 * tmp1 * tmp1 / N;
if (unlikely(isinf(Sxx)) && !isinf(Sxx1) && !isinf(Sxx2))
- float_overflow_error();
+ float_overflow_error(NULL);
Sy = float8_pl(Sy1, Sy2);
tmp2 = Sy1 / N1 - Sy2 / N2;
Syy = Syy1 + Syy2 + N1 * N2 * tmp2 * tmp2 / N;
if (unlikely(isinf(Syy)) && !isinf(Syy1) && !isinf(Syy2))
- float_overflow_error();
+ float_overflow_error(NULL);
Sxy = Sxy1 + Sxy2 + N1 * N2 * tmp1 * tmp2 / N;
if (unlikely(isinf(Sxy)) && !isinf(Sxy1) && !isinf(Sxy2))
- float_overflow_error();
+ float_overflow_error(NULL);
if (float8_eq(Cx1, Cx2))
Cx = Cx1;
else
diff --git a/src/include/utils/float.h b/src/include/utils/float.h
index b340678ca92..d2e989960a5 100644
--- a/src/include/utils/float.h
+++ b/src/include/utils/float.h
@@ -30,9 +30,9 @@ extern PGDLLIMPORT int extra_float_digits;
/*
* Utility functions in float.c
*/
-pg_noreturn extern void float_overflow_error(void);
-pg_noreturn extern void float_underflow_error(void);
-pg_noreturn extern void float_zero_divide_error(void);
+extern void float_overflow_error(struct Node *escontext);
+extern void float_underflow_error(struct Node *escontext);
+extern void float_zero_divide_error(struct Node *escontext);
extern int is_infinite(float8 val);
extern float8 float8in_internal(char *num, char **endptr_p,
const char *type_name, const char *orig_string,
@@ -104,7 +104,7 @@ float4_pl(const float4 val1, const float4 val2)
result = val1 + val2;
if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error();
+ float_overflow_error(NULL);
return result;
}
@@ -116,7 +116,7 @@ float8_pl(const float8 val1, const float8 val2)
result = val1 + val2;
if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error();
+ float_overflow_error(NULL);
return result;
}
@@ -128,7 +128,7 @@ float4_mi(const float4 val1, const float4 val2)
result = val1 - val2;
if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error();
+ float_overflow_error(NULL);
return result;
}
@@ -140,7 +140,7 @@ float8_mi(const float8 val1, const float8 val2)
result = val1 - val2;
if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error();
+ float_overflow_error(NULL);
return result;
}
@@ -152,9 +152,9 @@ float4_mul(const float4 val1, const float4 val2)
result = val1 * val2;
if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0f) && val1 != 0.0f && val2 != 0.0f)
- float_underflow_error();
+ float_underflow_error(NULL);
return result;
}
@@ -166,9 +166,9 @@ float8_mul(const float8 val1, const float8 val2)
result = val1 * val2;
if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0) && val1 != 0.0 && val2 != 0.0)
- float_underflow_error();
+ float_underflow_error(NULL);
return result;
}
@@ -179,12 +179,12 @@ float4_div(const float4 val1, const float4 val2)
float4 result;
if (unlikely(val2 == 0.0f) && !isnan(val1))
- float_zero_divide_error();
+ float_zero_divide_error(NULL);
result = val1 / val2;
if (unlikely(isinf(result)) && !isinf(val1))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0f) && val1 != 0.0f && !isinf(val2))
- float_underflow_error();
+ float_underflow_error(NULL);
return result;
}
@@ -195,12 +195,12 @@ float8_div(const float8 val1, const float8 val2)
float8 result;
if (unlikely(val2 == 0.0) && !isnan(val1))
- float_zero_divide_error();
+ float_zero_divide_error(NULL);
result = val1 / val2;
if (unlikely(isinf(result)) && !isinf(val1))
- float_overflow_error();
+ float_overflow_error(NULL);
if (unlikely(result == 0.0) && val1 != 0.0 && !isinf(val2))
- float_underflow_error();
+ float_underflow_error(NULL);
return result;
}
--
2.34.1
v17-0017-error-safe-for-casting-jsonb-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v17-0017-error-safe-for-casting-jsonb-to-other-types-per-pg_cast.patchDownload
From ef6e617bca19901053a1bec6e473313a49833a93 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Mon, 24 Nov 2025 14:26:53 +0800
Subject: [PATCH v17 17/23] error safe for casting jsonb to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'jsonb'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+---------------+---------
jsonb | boolean | 3556 | e | f | jsonb_bool | bool
jsonb | numeric | 3449 | e | f | jsonb_numeric | numeric
jsonb | smallint | 3450 | e | f | jsonb_int2 | int2
jsonb | integer | 3451 | e | f | jsonb_int4 | int4
jsonb | bigint | 3452 | e | f | jsonb_int8 | int8
jsonb | real | 3453 | e | f | jsonb_float4 | float4
jsonb | double precision | 2580 | e | f | jsonb_float8 | float8
(7 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/jsonb.c | 74 +++++++++++++++++++++++++++--------
1 file changed, 58 insertions(+), 16 deletions(-)
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index 28e7f80d77f..abcd5baa7c0 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -1818,7 +1818,7 @@ JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res)
* Emit correct, translatable cast error message
*/
static void
-cannotCastJsonbValue(enum jbvType type, const char *sqltype)
+cannotCastJsonbValue(enum jbvType type, const char *sqltype, Node *escontext)
{
static const struct
{
@@ -1839,7 +1839,7 @@ cannotCastJsonbValue(enum jbvType type, const char *sqltype)
for (i = 0; i < lengthof(messages); i++)
if (messages[i].type == type)
- ereport(ERROR,
+ ereturn(escontext,,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(messages[i].msg, sqltype)));
@@ -1854,7 +1854,10 @@ jsonb_bool(PG_FUNCTION_ARGS)
JsonbValue v;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "boolean");
+ {
+ cannotCastJsonbValue(v.type, "boolean", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -1863,7 +1866,10 @@ jsonb_bool(PG_FUNCTION_ARGS)
}
if (v.type != jbvBool)
- cannotCastJsonbValue(v.type, "boolean");
+ {
+ cannotCastJsonbValue(v.type, "boolean", fcinfo->context);
+ PG_RETURN_NULL();
+ }
PG_FREE_IF_COPY(in, 0);
@@ -1878,7 +1884,10 @@ jsonb_numeric(PG_FUNCTION_ARGS)
Numeric retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "numeric");
+ {
+ cannotCastJsonbValue(v.type, "numeric", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -1887,7 +1896,10 @@ jsonb_numeric(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "numeric");
+ {
+ cannotCastJsonbValue(v.type, "numeric", fcinfo->context);
+ PG_RETURN_NULL();
+ }
/*
* v.val.numeric points into jsonb body, so we need to make a copy to
@@ -1908,7 +1920,10 @@ jsonb_int2(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "smallint");
+ {
+ cannotCastJsonbValue(v.type, "smallint", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -1917,7 +1932,10 @@ jsonb_int2(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "smallint");
+ {
+ cannotCastJsonbValue(v.type, "smallint", fcinfo->context);
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_int2,
NumericGetDatum(v.val.numeric));
@@ -1935,7 +1953,10 @@ jsonb_int4(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "integer");
+ {
+ cannotCastJsonbValue(v.type, "integer", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -1944,7 +1965,10 @@ jsonb_int4(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "integer");
+ {
+ cannotCastJsonbValue(v.type, "integer", fcinfo->context);
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_int4,
NumericGetDatum(v.val.numeric));
@@ -1962,7 +1986,10 @@ jsonb_int8(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "bigint");
+ {
+ cannotCastJsonbValue(v.type, "bigint", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -1971,7 +1998,10 @@ jsonb_int8(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "bigint");
+ {
+ cannotCastJsonbValue(v.type, "bigint", fcinfo->context);
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_int8,
NumericGetDatum(v.val.numeric));
@@ -1989,7 +2019,10 @@ jsonb_float4(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "real");
+ {
+ cannotCastJsonbValue(v.type, "real", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -1998,7 +2031,10 @@ jsonb_float4(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "real");
+ {
+ cannotCastJsonbValue(v.type, "real", fcinfo->context);
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_float4,
NumericGetDatum(v.val.numeric));
@@ -2016,7 +2052,10 @@ jsonb_float8(PG_FUNCTION_ARGS)
Datum retValue;
if (!JsonbExtractScalar(&in->root, &v))
- cannotCastJsonbValue(v.type, "double precision");
+ {
+ cannotCastJsonbValue(v.type, "double precision", fcinfo->context);
+ PG_RETURN_NULL();
+ }
if (v.type == jbvNull)
{
@@ -2025,7 +2064,10 @@ jsonb_float8(PG_FUNCTION_ARGS)
}
if (v.type != jbvNumeric)
- cannotCastJsonbValue(v.type, "double precision");
+ {
+ cannotCastJsonbValue(v.type, "double precision", fcinfo->context);
+ PG_RETURN_NULL();
+ }
retValue = DirectFunctionCall1(numeric_float8,
NumericGetDatum(v.val.numeric));
--
2.34.1
v17-0016-error-safe-for-casting-timestamp-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v17-0016-error-safe-for-casting-timestamp-to-other-types-per-pg_cast.patchDownload
From d37e7074f06d08a0b79ef36fb50d36ebefaf10ec Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Tue, 2 Dec 2025 12:14:36 +0800
Subject: [PATCH v17 16/23] error safe for casting timestamp to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc
and pc.castfunc > 0 and (castsource::regtype ='timestamp'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
-----------------------------+-----------------------------+----------+-------------+------------+-----------------------+-------------
timestamp without time zone | date | 2029 | a | f | timestamp_date | date
timestamp without time zone | time without time zone | 1316 | a | f | timestamp_time | time
timestamp without time zone | timestamp with time zone | 2028 | i | f | timestamp_timestamptz | timestamptz
timestamp without time zone | timestamp without time zone | 1961 | i | f | timestamp_scale | timestamp
(4 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 7 +++++--
src/backend/utils/adt/timestamp.c | 10 ++++++++--
2 files changed, 13 insertions(+), 4 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index be52777d88d..a057471099d 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -1330,7 +1330,10 @@ timestamp_date(PG_FUNCTION_ARGS)
Timestamp timestamp = PG_GETARG_TIMESTAMP(0);
DateADT result;
- result = timestamp2date_safe(timestamp, NULL);
+ result = timestamp2date_safe(timestamp, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
+
PG_RETURN_DATEADT(result);
}
@@ -2008,7 +2011,7 @@ timestamp_time(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 3623dcab3bf..7ed88260b5d 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -352,7 +352,8 @@ timestamp_scale(PG_FUNCTION_ARGS)
result = timestamp;
- AdjustTimestampForTypmod(&result, typmod, NULL);
+ if (!AdjustTimestampForTypmod(&result, typmod, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_TIMESTAMP(result);
}
@@ -6432,8 +6433,13 @@ Datum
timestamp_timestamptz(PG_FUNCTION_ARGS)
{
Timestamp timestamp = PG_GETARG_TIMESTAMP(0);
+ TimestampTz result;
- PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(timestamp));
+ result = timestamp2timestamptz_safe(timestamp, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
+
+ PG_RETURN_TIMESTAMPTZ(result);
}
/*
--
2.34.1
v17-0015-error-safe-for-casting-timestamptz-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v17-0015-error-safe-for-casting-timestamptz-to-other-types-per-pg_cast.patchDownload
From 02f751fcaa918177922987b59463d9c2f1d469ba Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Tue, 2 Dec 2025 12:04:40 +0800
Subject: [PATCH v17 15/23] error safe for casting timestamptz to other types
per pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
and pc.castfunc > 0 and (castsource::regtype ='timestamptz'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
--------------------------+-----------------------------+----------+-------------+------------+-----------------------+-------------
timestamp with time zone | date | 1178 | a | f | timestamptz_date | date
timestamp with time zone | time without time zone | 2019 | a | f | timestamptz_time | time
timestamp with time zone | timestamp without time zone | 2027 | a | f | timestamptz_timestamp | timestamp
timestamp with time zone | time with time zone | 1388 | a | f | timestamptz_timetz | timetz
timestamp with time zone | timestamp with time zone | 1967 | i | f | timestamptz_scale | timestamptz
(5 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 9 ++++++---
src/backend/utils/adt/timestamp.c | 10 ++++++++--
2 files changed, 14 insertions(+), 5 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index bc4c67775dd..be52777d88d 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -1406,7 +1406,10 @@ timestamptz_date(PG_FUNCTION_ARGS)
TimestampTz timestamp = PG_GETARG_TIMESTAMP(0);
DateADT result;
- result = timestamptz2date_safe(timestamp, NULL);
+ result = timestamptz2date_safe(timestamp, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->args))
+ PG_RETURN_NULL();
+
PG_RETURN_DATEADT(result);
}
@@ -2036,7 +2039,7 @@ timestamptz_time(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
@@ -2955,7 +2958,7 @@ timestamptz_timetz(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 62a7d2230d1..3623dcab3bf 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -875,7 +875,8 @@ timestamptz_scale(PG_FUNCTION_ARGS)
result = timestamp;
- AdjustTimestampForTypmod(&result, typmod, NULL);
+ if (!AdjustTimestampForTypmod(&result, typmod, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_TIMESTAMPTZ(result);
}
@@ -6494,8 +6495,13 @@ Datum
timestamptz_timestamp(PG_FUNCTION_ARGS)
{
TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(0);
+ Timestamp result;
- PG_RETURN_TIMESTAMP(timestamptz2timestamp(timestamp));
+ result = timestamptz2timestamp_safe(timestamp, fcinfo->context);
+ if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
+
+ PG_RETURN_TIMESTAMP(result);
}
/*
--
2.34.1
v17-0014-error-safe-for-casting-interval-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v17-0014-error-safe-for-casting-interval-to-other-types-per-pg_cast.patchDownload
From 943637ebc06b8204224bb4797e2b92dbb434235b Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Fri, 12 Dec 2025 15:05:26 +0800
Subject: [PATCH v17 14/23] error safe for casting interval to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'interval'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------------+----------+-------------+------------+----------------+----------
interval | time without time zone | 1419 | a | f | interval_time | time
interval | interval | 1200 | i | f | interval_scale | interval
(2 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 2 +-
src/backend/utils/adt/timestamp.c | 3 ++-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index e8dc8d276bf..bc4c67775dd 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2106,7 +2106,7 @@ interval_time(PG_FUNCTION_ARGS)
TimeADT result;
if (INTERVAL_NOT_FINITE(span))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("cannot convert infinite interval to time")));
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 8deb2369471..62a7d2230d1 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1334,7 +1334,8 @@ interval_scale(PG_FUNCTION_ARGS)
result = palloc_object(Interval);
*result = *interval;
- AdjustIntervalForTypmod(result, typmod, NULL);
+ if (!AdjustIntervalForTypmod(result, typmod, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_INTERVAL_P(result);
}
--
2.34.1
v17-0013-error-safe-for-casting-date-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v17-0013-error-safe-for-casting-date-to-other-types-per-pg_cast.patchDownload
From bbc7e9298ebaa643bdbe1934eb708f7401853f97 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Tue, 2 Dec 2025 13:15:32 +0800
Subject: [PATCH v17 13/23] error safe for casting date to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'date'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+-----------------------------+----------+-------------+------------+------------------+-------------
date | timestamp without time zone | 2024 | i | f | date_timestamp | timestamp
date | timestamp with time zone | 1174 | i | f | date_timestamptz | timestamptz
(2 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/date.c | 17 ++++++-----------
1 file changed, 6 insertions(+), 11 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 621b9175c12..e8dc8d276bf 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -730,15 +730,6 @@ date2timestamptz_safe(DateADT dateVal, Node *escontext)
return result;
}
-/*
- * Promote date to timestamptz, throwing error for overflow.
- */
-static TimestampTz
-date2timestamptz(DateADT dateVal)
-{
- return date2timestamptz_safe(dateVal, NULL);
-}
-
/*
* date2timestamp_no_overflow
*
@@ -1323,7 +1314,9 @@ date_timestamp(PG_FUNCTION_ARGS)
DateADT dateVal = PG_GETARG_DATEADT(0);
Timestamp result;
- result = date2timestamp(dateVal);
+ result = date2timestamp_safe(dateVal, fcinfo->context);
+ if(SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_TIMESTAMP(result);
}
@@ -1396,7 +1389,9 @@ date_timestamptz(PG_FUNCTION_ARGS)
DateADT dateVal = PG_GETARG_DATEADT(0);
TimestampTz result;
- result = date2timestamptz(dateVal);
+ result = date2timestamptz_safe(dateVal, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_TIMESTAMP(result);
}
--
2.34.1
v17-0011-error-safe-for-casting-float4-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v17-0011-error-safe-for-casting-float4-to-other-types-per-pg_cast.patchDownload
From ac30a9319abc5935f58b0285e184a2c212007a73 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:28:20 +0800
Subject: [PATCH v17 11/23] error safe for casting float4 to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'float4'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+----------------+---------
real | bigint | 653 | a | f | ftoi8 | int8
real | smallint | 238 | a | f | ftoi2 | int2
real | integer | 319 | a | f | ftoi4 | int4
real | double precision | 311 | i | f | ftod | float8
real | numeric | 1742 | a | f | float4_numeric | numeric
(5 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/float.c | 4 ++--
src/backend/utils/adt/int8.c | 2 +-
src/backend/utils/adt/numeric.c | 3 ++-
3 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index b5a7c57e53a..58580b5f3cc 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -1298,7 +1298,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 +1323,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 4542d239c5f..6dc363670c7 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -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 65af268473c..8eb9346bde4 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -4657,7 +4657,8 @@ float4_numeric(PG_FUNCTION_ARGS)
init_var(&result);
/* Assume we need not worry about leading/trailing spaces */
- (void) set_var_from_str(buf, buf, &result, &endptr, NULL);
+ if (!set_var_from_str(buf, buf, &result, &endptr, fcinfo->context))
+ PG_RETURN_NULL();
res = make_result(&result);
--
2.34.1
v17-0012-error-safe-for-casting-float8-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v17-0012-error-safe-for-casting-float8-to-other-types-per-pg_cast.patchDownload
From fed9a88b387b7106bbdda7cff618224a5ae117e4 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:31:53 +0800
Subject: [PATCH v17 12/23] error safe for casting float8 to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'float8'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------------+------------+----------+-------------+------------+----------------+---------
double precision | bigint | 483 | a | f | dtoi8 | int8
double precision | smallint | 237 | a | f | dtoi2 | int2
double precision | integer | 317 | a | f | dtoi4 | int4
double precision | real | 312 | a | f | dtof | float4
double precision | numeric | 1743 | a | f | float8_numeric | numeric
(5 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/float.c | 13 +++++++++----
src/backend/utils/adt/int8.c | 2 +-
src/backend/utils/adt/numeric.c | 3 ++-
3 files changed, 12 insertions(+), 6 deletions(-)
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index 58580b5f3cc..502398d29ec 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -1199,9 +1199,14 @@ 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 +1229,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 +1254,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")));
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 6dc363670c7..2e82bec9a2c 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")));
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 8eb9346bde4..8f97ee078e6 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -4559,7 +4559,8 @@ float8_numeric(PG_FUNCTION_ARGS)
init_var(&result);
/* Assume we need not worry about leading/trailing spaces */
- (void) set_var_from_str(buf, buf, &result, &endptr, NULL);
+ if (!set_var_from_str(buf, buf, &result, &endptr, fcinfo->context))
+ PG_RETURN_NULL();
res = make_result(&result);
--
2.34.1
v17-0010-error-safe-for-casting-numeric-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v17-0010-error-safe-for-casting-numeric-to-other-types-per-pg_cast.patchDownload
From f7a6f3640cc61aefd2cbf0e81842b17408815952 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:25:37 +0800
Subject: [PATCH v17 10/23] error safe for casting numeric to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'numeric'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+----------------+---------
numeric | bigint | 1779 | a | f | numeric_int8 | int8
numeric | smallint | 1783 | a | f | numeric_int2 | int2
numeric | integer | 1744 | a | f | numeric_int4 | int4
numeric | real | 1745 | i | f | numeric_float4 | float4
numeric | double precision | 1746 | i | f | numeric_float8 | float8
numeric | money | 3824 | a | f | numeric_cash | money
numeric | numeric | 1703 | i | f | numeric | numeric
(7 rows)
discussion: https://postgr.es/m/CACJufxHCMzrHOW=wRe8L30rMhB3sjwAv1LE928Fa7sxMu1Tx-g@mail.gmail.com
---
src/backend/utils/adt/numeric.c | 58 ++++++++++++++++++++++++---------
1 file changed, 43 insertions(+), 15 deletions(-)
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 891ae6ba7fe..65af268473c 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -1244,7 +1244,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));
}
@@ -1295,8 +1296,9 @@ numeric (PG_FUNCTION_ARGS)
init_var(&var);
set_var_from_num(num, &var);
- (void) apply_typmod(&var, typmod, NULL);
- new = make_result(&var);
+ if (!apply_typmod(&var, typmod, fcinfo->context))
+ PG_RETURN_NULL();
+ new = make_result_safe(&var, fcinfo->context);
free_var(&var);
@@ -3018,7 +3020,10 @@ numeric_mul(PG_FUNCTION_ARGS)
Numeric num2 = PG_GETARG_NUMERIC(1);
Numeric res;
- res = numeric_mul_safe(num1, num2, NULL);
+ res = numeric_mul_safe(num1, num2, fcinfo->context);
+
+ if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
PG_RETURN_NUMERIC(res);
}
@@ -4392,9 +4397,15 @@ numeric_int4_safe(Numeric num, Node *escontext)
Datum
numeric_int4(PG_FUNCTION_ARGS)
{
+ int32 result;
Numeric num = PG_GETARG_NUMERIC(0);
- PG_RETURN_INT32(numeric_int4_safe(num, NULL));
+ result = numeric_int4_safe(num, fcinfo->context);
+
+ if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
+
+ PG_RETURN_INT32(result);
}
/*
@@ -4462,9 +4473,15 @@ numeric_int8_safe(Numeric num, Node *escontext)
Datum
numeric_int8(PG_FUNCTION_ARGS)
{
+ int64 result;
Numeric num = PG_GETARG_NUMERIC(0);
- PG_RETURN_INT64(numeric_int8_safe(num, NULL));
+ result = numeric_int8_safe(num, fcinfo->context);
+
+ if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
+
+ PG_RETURN_INT64(result);
}
@@ -4488,11 +4505,11 @@ numeric_int2(PG_FUNCTION_ARGS)
if (NUMERIC_IS_SPECIAL(num))
{
if (NUMERIC_IS_NAN(num))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert NaN to %s", "smallint")));
else
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert infinity to %s", "smallint")));
}
@@ -4501,12 +4518,12 @@ numeric_int2(PG_FUNCTION_ARGS)
init_var_from_num(num, &x);
if (!numericvar_to_int64(&x, &val))
- ereport(ERROR,
+ ereturn(fcinfo->context, (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(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
@@ -4571,10 +4588,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);
}
@@ -4666,7 +4687,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
v17-0009-error-safe-for-casting-bigint-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v17-0009-error-safe-for-casting-bigint-to-other-types-per-pg_cast.patchDownload
From 4236648b119a919b81a0837d93049f5e812de749 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:22:00 +0800
Subject: [PATCH v17 09/23] error safe for casting bigint to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'bigint'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+--------------+---------
bigint | smallint | 714 | a | f | int82 | int2
bigint | integer | 480 | a | f | int84 | int4
bigint | real | 652 | i | f | i8tof | float4
bigint | double precision | 482 | i | f | i8tod | float8
bigint | numeric | 1781 | i | f | int8_numeric | numeric
bigint | money | 3812 | a | f | int8_cash | money
bigint | oid | 1287 | i | f | i8tooid | oid
bigint | regproc | 1287 | i | f | i8tooid | oid
bigint | regprocedure | 1287 | i | f | i8tooid | oid
bigint | regoper | 1287 | i | f | i8tooid | oid
bigint | regoperator | 1287 | i | f | i8tooid | oid
bigint | regclass | 1287 | i | f | i8tooid | oid
bigint | regcollation | 1287 | i | f | i8tooid | oid
bigint | regtype | 1287 | i | f | i8tooid | oid
bigint | regconfig | 1287 | i | f | i8tooid | oid
bigint | regdictionary | 1287 | i | f | i8tooid | oid
bigint | regrole | 1287 | i | f | i8tooid | oid
bigint | regnamespace | 1287 | i | f | i8tooid | oid
bigint | regdatabase | 1287 | i | f | i8tooid | oid
bigint | bytea | 6369 | e | f | int8_bytea | bytea
bigint | bit | 2075 | e | f | bitfromint8 | bit
(21 rows)
already error safe: i8tof, i8tod, int8_numeric, int8_bytea, bitfromint8
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/int8.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index d4509206217..4542d239c5f 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -1251,7 +1251,7 @@ int84(PG_FUNCTION_ARGS)
int64 arg = PG_GETARG_INT64(0);
if (unlikely(arg < PG_INT32_MIN) || unlikely(arg > PG_INT32_MAX))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
@@ -1272,7 +1272,7 @@ int82(PG_FUNCTION_ARGS)
int64 arg = PG_GETARG_INT64(0);
if (unlikely(arg < PG_INT16_MIN) || unlikely(arg > PG_INT16_MAX))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
@@ -1355,7 +1355,7 @@ i8tooid(PG_FUNCTION_ARGS)
int64 arg = PG_GETARG_INT64(0);
if (unlikely(arg < 0) || unlikely(arg > PG_UINT32_MAX))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("OID out of range")));
--
2.34.1
v17-0008-error-safe-for-casting-integer-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v17-0008-error-safe-for-casting-integer-to-other-types-per-pg_cast.patchDownload
From d5e96c22f258d2ff042fd45971a6411045e933e7 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:20:10 +0800
Subject: [PATCH v17 08/23] error safe for casting integer to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'integer'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------------+----------+-------------+------------+--------------+---------
integer | bigint | 481 | i | f | int48 | int8
integer | smallint | 314 | a | f | i4toi2 | int2
integer | real | 318 | i | f | i4tof | float4
integer | double precision | 316 | i | f | i4tod | float8
integer | numeric | 1740 | i | f | int4_numeric | numeric
integer | money | 3811 | a | f | int4_cash | money
integer | boolean | 2557 | e | f | int4_bool | bool
integer | bytea | 6368 | e | f | int4_bytea | bytea
integer | "char" | 78 | e | f | i4tochar | char
integer | bit | 1683 | e | f | bitfromint4 | bit
(10 rows)
only int4_cash, i4toi2, i4tochar need take care of error handling. but support
for cash data type is not easy, so only i4toi2, i4tochar function refactoring.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/char.c | 2 +-
src/backend/utils/adt/int.c | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/backend/utils/adt/char.c b/src/backend/utils/adt/char.c
index 3e4def68fe4..698863924ee 100644
--- a/src/backend/utils/adt/char.c
+++ b/src/backend/utils/adt/char.c
@@ -192,7 +192,7 @@ i4tochar(PG_FUNCTION_ARGS)
int32 arg1 = PG_GETARG_INT32(0);
if (arg1 < SCHAR_MIN || arg1 > SCHAR_MAX)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("\"char\" out of range")));
diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c
index d2302626585..2d124172c6f 100644
--- a/src/backend/utils/adt/int.c
+++ b/src/backend/utils/adt/int.c
@@ -350,7 +350,7 @@ i4toi2(PG_FUNCTION_ARGS)
int32 arg1 = PG_GETARG_INT32(0);
if (unlikely(arg1 < SHRT_MIN) || unlikely(arg1 > SHRT_MAX))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
--
2.34.1
v17-0006-error-safe-for-casting-inet-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v17-0006-error-safe-for-casting-inet-to-other-types-per-pg_cast.patchDownload
From 88f1f363b886068103b465669c7cba0e4766d5e4 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 10:28:54 +0800
Subject: [PATCH v17 06/23] error safe for casting inet to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'inet'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+-------------------+----------+-------------+------------+--------------+---------
inet | cidr | 1715 | a | f | inet_to_cidr | cidr
inet | text | 730 | a | f | network_show | text
inet | character varying | 730 | a | f | network_show | text
inet | character | 730 | a | f | network_show | text
(4 rows)
inet_to_cidr is already error safe.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/network.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/backend/utils/adt/network.c b/src/backend/utils/adt/network.c
index 3a2002097dd..c7e0828764e 100644
--- a/src/backend/utils/adt/network.c
+++ b/src/backend/utils/adt/network.c
@@ -1137,7 +1137,7 @@ network_show(PG_FUNCTION_ARGS)
if (pg_inet_net_ntop(ip_family(ip), ip_addr(ip), ip_maxbits(ip),
tmp, sizeof(tmp)) == NULL)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
errmsg("could not format inet value: %m")));
--
2.34.1
v17-0007-error-safe-for-casting-macaddr8-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v17-0007-error-safe-for-casting-macaddr8-to-other-types-per-pg_cast.patchDownload
From 8d0a16aca978b43d24e46691e1dc26f0dc2e4bbc Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Fri, 12 Dec 2025 15:03:06 +0800
Subject: [PATCH v17 07/23] error safe for casting macaddr8 to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
and pc.castfunc > 0 and castsource::regtype ='macaddr8'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------+----------+-------------+------------+-------------------+---------
macaddr8 | macaddr | 4124 | i | f | macaddr8tomacaddr | macaddr
(1 row)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/mac8.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/backend/utils/adt/mac8.c b/src/backend/utils/adt/mac8.c
index c1bf9fd5783..0425ea473a5 100644
--- a/src/backend/utils/adt/mac8.c
+++ b/src/backend/utils/adt/mac8.c
@@ -550,7 +550,7 @@ macaddr8tomacaddr(PG_FUNCTION_ARGS)
result = palloc0_object(macaddr);
if ((addr->d != 0xFF) || (addr->e != 0xFE))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("macaddr8 data out of range to convert to macaddr"),
errhint("Only addresses that have FF and FE as values in the "
--
2.34.1
v17-0005-error-safe-for-casting-character-varying-to-other-types-per-pg_c.patchtext/x-patch; charset=US-ASCII; name=v17-0005-error-safe-for-casting-character-varying-to-other-types-per-pg_c.patchDownload
From 2601d3b739d5cbe7a478dd9ac53b1e753880644e Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:13:45 +0800
Subject: [PATCH v17 05/23] error safe for casting character varying to other
types per pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc and pc.castfunc > 0 and
(castsource::regtype = 'character varying'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
-------------------+-------------------+----------+-------------+------------+---------------+----------
character varying | regclass | 1079 | i | f | text_regclass | regclass
character varying | "char" | 944 | a | f | text_char | char
character varying | name | 1400 | i | f | text_name | name
character varying | xml | 2896 | e | f | texttoxml | xml
character varying | character varying | 669 | i | f | varchar | varchar
(5 rows)
texttoxml, text_regclass was refactored as error safe in prior patch.
text_char, text_name is already error safe.
so here we only need handle function "varchar".
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/varchar.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c
index 6f083973fe7..a62e55eec19 100644
--- a/src/backend/utils/adt/varchar.c
+++ b/src/backend/utils/adt/varchar.c
@@ -634,7 +634,7 @@ varchar(PG_FUNCTION_ARGS)
{
for (i = maxmblen; i < len; i++)
if (s_data[i] != ' ')
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
errmsg("value too long for type character varying(%d)",
maxlen)));
--
2.34.1
v17-0004-error-safe-for-casting-text-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v17-0004-error-safe-for-casting-text-to-other-types-per-pg_cast.patchDownload
From 19326d269596a62c23aac9237ba3c75bad9b463e Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Fri, 12 Dec 2025 15:31:17 +0800
Subject: [PATCH v17 04/23] error safe for casting text to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype = 'text'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------+----------+-------------+------------+---------------+----------
text | regclass | 1079 | i | f | text_regclass | regclass
text | "char" | 944 | a | f | text_char | char
text | name | 407 | i | f | text_name | name
text | xml | 2896 | e | f | texttoxml | xml
(4 rows)
already error safe: text_name, text_char.
texttoxml is refactored in character type error safe patch.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/catalog/namespace.c | 58 ++++++++++++++++++++++++++-------
src/backend/utils/adt/regproc.c | 13 ++++++--
src/backend/utils/adt/varlena.c | 10 ++++--
src/backend/utils/adt/xml.c | 2 +-
src/include/catalog/namespace.h | 6 ++++
src/include/utils/varlena.h | 1 +
6 files changed, 73 insertions(+), 17 deletions(-)
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index c3b79a2ba48..ea996121b05 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -440,6 +440,16 @@ Oid
RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
uint32 flags,
RangeVarGetRelidCallback callback, void *callback_arg)
+{
+ return RangeVarGetRelidExtendedSafe(relation, lockmode, flags,
+ callback, callback_arg,
+ NULL);
+}
+
+Oid
+RangeVarGetRelidExtendedSafe(const RangeVar *relation, LOCKMODE lockmode, uint32 flags,
+ RangeVarGetRelidCallback callback, void *callback_arg,
+ Node *escontext)
{
uint64 inval_count;
Oid relId;
@@ -456,7 +466,7 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
if (relation->catalogname)
{
if (strcmp(relation->catalogname, get_database_name(MyDatabaseId)) != 0)
- ereport(ERROR,
+ ereturn(escontext, InvalidOid,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cross-database references are not implemented: \"%s.%s.%s\"",
relation->catalogname, relation->schemaname,
@@ -513,7 +523,7 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
* return InvalidOid.
*/
if (namespaceId != myTempNamespace)
- ereport(ERROR,
+ ereturn(escontext, InvalidOid,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("temporary tables cannot specify a schema name")));
}
@@ -593,13 +603,23 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
{
int elevel = (flags & RVR_SKIP_LOCKED) ? DEBUG1 : ERROR;
- if (relation->schemaname)
- ereport(elevel,
+ if (relation->schemaname && elevel == DEBUG1)
+ ereport(DEBUG1,
(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
errmsg("could not obtain lock on relation \"%s.%s\"",
relation->schemaname, relation->relname)));
- else
- ereport(elevel,
+ else if (relation->schemaname && elevel == ERROR)
+ ereturn(escontext, InvalidOid,
+ errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s.%s\"",
+ relation->schemaname, relation->relname));
+ else if (elevel == DEBUG1)
+ ereport(DEBUG1,
+ errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("could not obtain lock on relation \"%s\"",
+ relation->relname));
+ else if (elevel == ERROR)
+ ereturn(escontext, InvalidOid,
(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
errmsg("could not obtain lock on relation \"%s\"",
relation->relname)));
@@ -626,13 +646,23 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
{
int elevel = missing_ok ? DEBUG1 : ERROR;
- if (relation->schemaname)
- ereport(elevel,
+ if (relation->schemaname && elevel == DEBUG1)
+ ereport(DEBUG1,
(errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("relation \"%s.%s\" does not exist",
relation->schemaname, relation->relname)));
- else
- ereport(elevel,
+ else if (relation->schemaname && elevel == ERROR)
+ ereturn(escontext, InvalidOid,
+ errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s.%s\" does not exist",
+ relation->schemaname, relation->relname));
+ else if (elevel == DEBUG1)
+ ereport(DEBUG1,
+ errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" does not exist",
+ relation->relname));
+ else if (elevel == ERROR)
+ ereturn(escontext, InvalidOid,
(errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("relation \"%s\" does not exist",
relation->relname)));
@@ -3622,6 +3652,12 @@ get_namespace_oid(const char *nspname, bool missing_ok)
*/
RangeVar *
makeRangeVarFromNameList(const List *names)
+{
+ return makeRangeVarFromNameListSafe(names, NULL);
+}
+
+RangeVar *
+makeRangeVarFromNameListSafe(const List *names, Node *escontext)
{
RangeVar *rel = makeRangeVar(NULL, NULL, -1);
@@ -3640,7 +3676,7 @@ makeRangeVarFromNameList(const List *names)
rel->relname = strVal(lthird(names));
break;
default:
- ereport(ERROR,
+ ereturn(escontext, NULL,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("improper relation name (too many dotted names): %s",
NameListToString(names))));
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index ee34d1d85f8..604e19a1cb9 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -1901,12 +1901,19 @@ text_regclass(PG_FUNCTION_ARGS)
text *relname = PG_GETARG_TEXT_PP(0);
Oid result;
RangeVar *rv;
+ List *namelist;
- rv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
+ namelist = textToQualifiedNameListSafe(relname, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
+
+ rv = makeRangeVarFromNameListSafe(namelist, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ PG_RETURN_NULL();
/* We might not even have permissions on this relation; don't lock it. */
- result = RangeVarGetRelid(rv, NoLock, false);
-
+ result = RangeVarGetRelidExtendedSafe(rv, NoLock, 0, NULL, NULL,
+ fcinfo->context);
PG_RETURN_OID(result);
}
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index cfcc35592e3..fa38e8a5bb6 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -2668,6 +2668,12 @@ name_text(PG_FUNCTION_ARGS)
*/
List *
textToQualifiedNameList(text *textval)
+{
+ return textToQualifiedNameListSafe(textval, NULL);
+}
+
+List *
+textToQualifiedNameListSafe(text *textval, Node *escontext)
{
char *rawname;
List *result = NIL;
@@ -2679,12 +2685,12 @@ textToQualifiedNameList(text *textval)
rawname = text_to_cstring(textval);
if (!SplitIdentifierString(rawname, '.', &namelist))
- ereport(ERROR,
+ ereturn(escontext, NIL,
(errcode(ERRCODE_INVALID_NAME),
errmsg("invalid name syntax")));
if (namelist == NIL)
- ereport(ERROR,
+ ereturn(escontext, NIL,
(errcode(ERRCODE_INVALID_NAME),
errmsg("invalid name syntax")));
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 431c0bcea44..890fd674bdb 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -1043,7 +1043,7 @@ xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, Node
return (xmltype *) data;
#else
- ereturn(escontext, NULL
+ ereturn(escontext, NULL,
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("unsupported XML feature"),
errdetail("This functionality requires the server to be built with libxml support."));
diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h
index 1a25973685c..153a439e374 100644
--- a/src/include/catalog/namespace.h
+++ b/src/include/catalog/namespace.h
@@ -103,6 +103,11 @@ extern Oid RangeVarGetRelidExtended(const RangeVar *relation,
LOCKMODE lockmode, uint32 flags,
RangeVarGetRelidCallback callback,
void *callback_arg);
+extern Oid RangeVarGetRelidExtendedSafe(const RangeVar *relation,
+ LOCKMODE lockmode, uint32 flags,
+ RangeVarGetRelidCallback callback,
+ void *callback_arg,
+ Node *escontext);
extern Oid RangeVarGetCreationNamespace(const RangeVar *newRelation);
extern Oid RangeVarGetAndCheckCreationNamespace(RangeVar *relation,
LOCKMODE lockmode,
@@ -168,6 +173,7 @@ extern Oid LookupCreationNamespace(const char *nspname);
extern void CheckSetNamespace(Oid oldNspOid, Oid nspOid);
extern Oid QualifiedNameGetCreationNamespace(const List *names, char **objname_p);
extern RangeVar *makeRangeVarFromNameList(const List *names);
+extern RangeVar *makeRangeVarFromNameListSafe(const List *names, Node *escontext);
extern char *NameListToString(const List *names);
extern char *NameListToQuotedString(const List *names);
diff --git a/src/include/utils/varlena.h b/src/include/utils/varlena.h
index 4b32574a075..5bc78aa02c0 100644
--- a/src/include/utils/varlena.h
+++ b/src/include/utils/varlena.h
@@ -27,6 +27,7 @@ extern int varstr_levenshtein_less_equal(const char *source, int slen,
int ins_c, int del_c, int sub_c,
int max_d, bool trusted);
extern List *textToQualifiedNameList(text *textval);
+extern List *textToQualifiedNameListSafe(text *textval, Node *escontext);
extern bool SplitIdentifierString(char *rawstring, char separator,
List **namelist);
extern bool SplitDirectoriesString(char *rawstring, char separator,
--
2.34.1
v17-0003-error-safe-for-casting-character-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v17-0003-error-safe-for-casting-character-to-other-types-per-pg_cast.patchDownload
From 71093e4f9659a975118f8e1f3daf7bbf13148971 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Mon, 24 Nov 2025 12:52:16 +0800
Subject: [PATCH v17 03/23] error safe for casting character to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype ='character'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+-------------------+----------+-------------+------------+-------------+---------
character | text | 401 | i | f | rtrim1 | text
character | character varying | 401 | i | f | rtrim1 | text
character | "char" | 944 | a | f | text_char | char
character | name | 409 | i | f | bpchar_name | name
character | xml | 2896 | e | f | texttoxml | xml
character | character | 668 | i | f | bpchar | bpchar
(6 rows)
only texttoxml, bpchar(PG_FUNCTION_ARGS) need take care of error handling.
other functions already error safe.
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/executor/execExprInterp.c | 2 +-
src/backend/utils/adt/varchar.c | 2 +-
src/backend/utils/adt/xml.c | 18 ++++++++++++------
src/include/utils/xml.h | 2 +-
4 files changed, 15 insertions(+), 9 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 86ab3704b66..0a2d25c1b62 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -4542,7 +4542,7 @@ ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op)
*op->resvalue = PointerGetDatum(xmlparse(data,
xexpr->xmloption,
- preserve_whitespace));
+ preserve_whitespace, NULL));
*op->resnull = false;
}
break;
diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c
index df305098130..6f083973fe7 100644
--- a/src/backend/utils/adt/varchar.c
+++ b/src/backend/utils/adt/varchar.c
@@ -307,7 +307,7 @@ bpchar(PG_FUNCTION_ARGS)
{
for (i = maxmblen; i < len; i++)
if (s[i] != ' ')
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
errmsg("value too long for type character(%d)",
maxlen)));
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index f69dc68286c..431c0bcea44 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -659,7 +659,7 @@ texttoxml(PG_FUNCTION_ARGS)
{
text *data = PG_GETARG_TEXT_PP(0);
- PG_RETURN_XML_P(xmlparse(data, xmloption, true));
+ PG_RETURN_XML_P(xmlparse(data, xmloption, true, fcinfo->context));
}
@@ -1028,19 +1028,25 @@ xmlelement(XmlExpr *xexpr,
xmltype *
-xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace)
+xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, Node *escontext)
{
#ifdef USE_LIBXML
xmlDocPtr doc;
doc = xml_parse(data, xmloption_arg, preserve_whitespace,
- GetDatabaseEncoding(), NULL, NULL, NULL);
- xmlFreeDoc(doc);
+ GetDatabaseEncoding(), NULL, NULL, escontext);
+ if (doc)
+ xmlFreeDoc(doc);
+
+ if (SOFT_ERROR_OCCURRED(escontext))
+ return NULL;
return (xmltype *) data;
#else
- NO_XML_SUPPORT();
- return NULL;
+ ereturn(escontext, NULL
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unsupported XML feature"),
+ errdetail("This functionality requires the server to be built with libxml support."));
#endif
}
diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h
index 03acb255449..553bdc96c3f 100644
--- a/src/include/utils/xml.h
+++ b/src/include/utils/xml.h
@@ -73,7 +73,7 @@ extern xmltype *xmlconcat(List *args);
extern xmltype *xmlelement(XmlExpr *xexpr,
const Datum *named_argvalue, const bool *named_argnull,
const Datum *argvalue, const bool *argnull);
-extern xmltype *xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace);
+extern xmltype *xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, Node *escontext);
extern xmltype *xmlpi(const char *target, text *arg, bool arg_is_null, bool *result_is_null);
extern xmltype *xmlroot(xmltype *data, text *version, int standalone);
extern bool xml_is_document(xmltype *arg);
--
2.34.1
v17-0001-error-safe-for-casting-bytea-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v17-0001-error-safe-for-casting-bytea-to-other-types-per-pg_cast.patchDownload
From a652d58b33f16359988ed0e168924f93bf5408ae Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 00:08:00 +0800
Subject: [PATCH v17 01/23] error safe for casting bytea to other types per
pg_cast
select castsource::regtype, casttarget::regtype, castfunc, castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc and pc.castfunc > 0 and castsource::regtype ='bytea'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------+----------+-------------+------------+------------+---------
bytea | smallint | 6370 | e | f | bytea_int2 | int2
bytea | integer | 6371 | e | f | bytea_int4 | int4
bytea | bigint | 6372 | e | f | bytea_int8 | int8
(3 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/bytea.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/backend/utils/adt/bytea.c b/src/backend/utils/adt/bytea.c
index fd7662d41ee..a02b054b873 100644
--- a/src/backend/utils/adt/bytea.c
+++ b/src/backend/utils/adt/bytea.c
@@ -1255,7 +1255,7 @@ bytea_int2(PG_FUNCTION_ARGS)
/* Check that the byte array is not too long */
if (len > sizeof(result))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range"));
@@ -1280,7 +1280,7 @@ bytea_int4(PG_FUNCTION_ARGS)
/* Check that the byte array is not too long */
if (len > sizeof(result))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range"));
@@ -1305,7 +1305,7 @@ bytea_int8(PG_FUNCTION_ARGS)
/* Check that the byte array is not too long */
if (len > sizeof(result))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("bigint out of range"));
--
2.34.1
v17-0002-error-safe-for-casting-bit-varbit-to-other-types-per-pg_cast.patchtext/x-patch; charset=US-ASCII; name=v17-0002-error-safe-for-casting-bit-varbit-to-other-types-per-pg_cast.patchDownload
From af78e24d35223b2d84cbf4b41261b6c760097892 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 22 Nov 2025 10:33:08 +0800
Subject: [PATCH v17 02/23] error safe for casting bit/varbit to other types
per pg_cast
select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname
from pg_cast pc join pg_proc pp on pp.oid = pc.castfunc
where pc.castfunc > 0 and (castsource::regtype ='bit'::regtype or
castsource::regtype ='varbit'::regtype)
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
-------------+-------------+----------+-------------+------------+-----------+---------
bit | bigint | 2076 | e | f | bittoint8 | int8
bit | integer | 1684 | e | f | bittoint4 | int4
bit | bit | 1685 | i | f | bit | bit
bit varying | bit varying | 1687 | i | f | varbit | varbit
(4 rows)
discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
src/backend/utils/adt/varbit.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/backend/utils/adt/varbit.c b/src/backend/utils/adt/varbit.c
index 50ffee679b9..223b01f9308 100644
--- a/src/backend/utils/adt/varbit.c
+++ b/src/backend/utils/adt/varbit.c
@@ -401,7 +401,7 @@ bit(PG_FUNCTION_ARGS)
PG_RETURN_VARBIT_P(arg);
if (!isExplicit)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_STRING_DATA_LENGTH_MISMATCH),
errmsg("bit string length %d does not match type bit(%d)",
VARBITLEN(arg), len)));
@@ -752,7 +752,7 @@ varbit(PG_FUNCTION_ARGS)
PG_RETURN_VARBIT_P(arg);
if (!isExplicit)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
errmsg("bit string too long for type bit varying(%d)",
len)));
@@ -1591,7 +1591,7 @@ bittoint4(PG_FUNCTION_ARGS)
/* Check that the bit string is not too long */
if (VARBITLEN(arg) > sizeof(result) * BITS_PER_BYTE)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
@@ -1671,7 +1671,7 @@ bittoint8(PG_FUNCTION_ARGS)
/* Check that the bit string is not too long */
if (VARBITLEN(arg) > sizeof(result) * BITS_PER_BYTE)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("bigint out of range")));
--
2.34.1
On Mon, Jan 5, 2026 at 1:01 AM jian he <jian.universality@gmail.com> wrote:
On Fri, Jan 2, 2026 at 2:08 PM Amul Sul <sulamul@gmail.com> wrote:
Hi,
I am still thinking through a design that avoids having two different
code paths for type casting. Can't we avoid adding a new SafeTypeCast
structure by simply adding a raw_default variable (name could be
simply default) to the existing TypeCast structure? If we do that, we
would need to update transformTypeCast() and other places (like
ExecInterpExpr()) to handle the raw_default. This approach would allow
us to avoid the extra code required for a new node structure (e.g.,
T_SafeTypeCastExpr) and a separate EEOP_SAFETYPE_CAST step.
If that code path requires all casting operations to have the overhead of
safety, then that may be a significant performance regression and a
non-starter. I think we should continue with the design as-is, and if it
turns out later than we can merge the safe/unsafe code paths without
impacting performance, then we'll do it.
Jian, can you rebase the patch-set? I tried applying it today, without luck.