public inbox for [email protected]
help / color / mirror / Atom feedFrom: jian he <[email protected]>
To: Peter Eisentraut <[email protected]>
Cc: Corey Huinker <[email protected]>
Cc: Amul Sul <[email protected]>
Cc: Kirill Reshke <[email protected]>
Cc: Vik Fearing <[email protected]>
Cc: Isaac Morland <[email protected]>
Cc: [email protected]
Subject: Re: CAST(... ON DEFAULT) - WIP build on top of Error-Safe User Functions
Date: Sat, 28 Mar 2026 08:37:07 +0800
Message-ID: <CACJufxHGox47X4zNtVeNw7H=SZ7ATAAD5SEYpvxa2o5mPV0XCA@mail.gmail.com> (raw)
In-Reply-To: <CACJufxH_1EtEBMb0JvxaM3Gmnt33HYrS37m5eYjJ_OfBkMVFJg@mail.gmail.com>
References: <CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com>
<CACJufxHGxB7KPAStUHZcWhFEDXsnU06qOugh60X4hHG6J-xoUA@mail.gmail.com>
<CADkLM=f0xruKRH+6XvCsM1EVic9y4BL4AFo3VUfvRLdu-Qp5Gg@mail.gmail.com>
<CADkLM=e4y2bLv-6_JY5c6vC9QtweW9VfxVmVJFpcriy8Rs23TA@mail.gmail.com>
<CACJufxGO_qmyGjBvkwhCZzm6bWiqj8iyd1dGbL+_guok6yPMCQ@mail.gmail.com>
<CADkLM=cZ7N+f4Bj-8MiccFcTASjBB2r1_s3BD9OFbbQOwK01cg@mail.gmail.com>
<CACJufxE7yoH42juViFvYuCQMPwXMWxDVwiugs-BW+N4oUXB=1w@mail.gmail.com>
<CADkLM=dFZ8-mBJDkiZZ4JadnJy8zLExUoo_b1FOs2Ozrtge=gQ@mail.gmail.com>
<CACJufxHynx40aHHUCR4wZh1qnL=X0yw6baW4Z-+vkUgT0OhjOg@mail.gmail.com>
<CADkLM=eCPiVhGKcbNW5bUEYpozUaKNXjd_UmBVR256p2zWtYQQ@mail.gmail.com>
<CACJufxH4LrCpL63SRYO3zVk46YdD4--VYQoBL7GmHmCm=NCAJQ@mail.gmail.com>
<CADkLM=cmv_bmxBe8KmZd6rEgiqSdoDfHnJa63u7rdRuAsqOwDA@mail.gmail.com>
<CACJufxGbw9iNT8QVm4QD9cPFKnDnvDBQp7AGxkoqDa-JjzVXmg@mail.gmail.com>
<CACJufxFkLLuX1VJ-J3fppCr37PHtxkvwyd_e4zNd+VYK0v0gnQ@mail.gmail.com>
<[email protected]>
<CACJufxHx-UfprE6P4_ZB_cOYktHd4pLMNx=jWJFOGGGFj2YZWQ@mail.gmail.com>
<CACJufxH_1EtEBMb0JvxaM3Gmnt33HYrS37m5eYjJ_OfBkMVFJg@mail.gmail.com>
On Thu, Mar 26, 2026 at 4:59 PM jian he <[email protected]> wrote:
>
> Currently, when a valid ErrorSaveContext is passed to state->escontext
> (ExprState->escontext),
> ExecInitExprRec is designed to compile the entire expression tree
> using soft errors.
> Consider the following example:
> create table t1(a text, b text, c int, d int8);
> create domain d2 as text check (value <> 'a');
> insert into t1(a, d) values('a', 2147483648);
> select cast (cast(d as int) as text default null on conversion
> error) from t1; -- queryA
> select cast (cast(a as d2) as text default null on conversion
> error) from t1; -- queryB
> select cast (cast(a as int) as text default null on conversion
> error) from t1; -- queryC
>
In ATExecAddColumn, we have:
exprState = ExecPrepareExprWithContext(defval, estate, (Node *) &escontext);
The entire defval expression tree (including all the potential FuncExpr node
subexpression) must be marked as error-safe (all the
FuncExpr->errorsafe must be set to true).
If they aren't, ExecPrepareExprWithContext won't work.
In that scarenio, we need a walker function to set FuncExpr->errorsafe
to true for the above defval, overall feels very ugly.
Therefore, the previous approach (v24) of simply adding an errorsafe boolean to
FuncExpr will not work.
IMHO, it is not feasible to mark only part of an expression tree as error-safe.
Because of this, we should try to make queryA, queryB, queryC return NULL.
However, some inconsistency remains, for example:
SELECT CAST (CAST('a' AS int) AS text DEFAULT NULL ON CONVERSION
ERROR) FROM tcast1; -- error
SELECT CAST (CAST('a'::text AS int) AS text DEFAULT NULL ON CONVERSION
ERROR) FROM tcast1; -- ok
V24: There is a segfault because of eval_const_expressions_mutator,
T_ArrayCoerceExpr handling,
You can invoke it via:
select cast (cast('{a}'::text[] as int[]) as text[] default null on
conversion error) from t2;
In V24, there were no nested type cast regress tests, now I have added some.
I removed the errorsafe boolean from FuncExpr. As mentioned previously, it is
difficult to make ExecInitExprRec compile expression partically eror safe.
refactoring on transformArrayExpr, transformTypeCast, ExecInitSafeTypeCastExpr.
--
jian
https://www.enterprisedb.com/
Attachments:
[text/x-patch] v25-0008-error-safe-for-casting-money-data-type.patch (3.7K, 2-v25-0008-error-safe-for-casting-money-data-type.patch)
download | inline diff:
From 4f1bac86869486a2756b55550a5fe5184745cd60 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Wed, 25 Mar 2026 19:12:10 +0800
Subject: [PATCH v25 8/9] error safe for casting money data type
NOTE: casting from money data type is not error safe, since cash_numeric is not
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
and casttarget::regtype = 'money'::regtype
order by castsource::regtype;
castsource | casttarget | castfunc | castcontext | castmethod | prosrc | proname
------------+------------+----------+-------------+------------+--------------+---------
bigint | money | 3812 | a | f | int8_cash | money
integer | money | 3811 | a | f | int4_cash | money
numeric | money | 3824 | a | f | numeric_cash | money
(3 rows)
Author: jian he <[email protected]>
Discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
Commitfest: https://commitfest.postgresql.org/patch/5941
---
src/backend/utils/adt/cash.c | 28 +++++++++++++++++++---------
1 file changed, 19 insertions(+), 9 deletions(-)
diff --git a/src/backend/utils/adt/cash.c b/src/backend/utils/adt/cash.c
index 623f6eec056..f0487a60f00 100644
--- a/src/backend/utils/adt/cash.c
+++ b/src/backend/utils/adt/cash.c
@@ -24,6 +24,7 @@
#include "common/int.h"
#include "libpq/pqformat.h"
+#include "nodes/miscnodes.h"
#include "utils/builtins.h"
#include "utils/cash.h"
#include "utils/float.h"
@@ -1106,12 +1107,12 @@ cash_numeric(PG_FUNCTION_ARGS)
Datum
numeric_cash(PG_FUNCTION_ARGS)
{
- Datum amount = PG_GETARG_DATUM(0);
+ Numeric amount = PG_GETARG_NUMERIC(0);
Cash result;
int fpoint;
int64 scale;
int i;
- Datum numeric_scale;
+ Numeric numeric_scale;
struct lconv *lconvert = PGLC_localeconv();
/* see comments about frac_digits in cash_in() */
@@ -1125,11 +1126,16 @@ numeric_cash(PG_FUNCTION_ARGS)
scale *= 10;
/* multiply the input amount by scale factor */
- numeric_scale = NumericGetDatum(int64_to_numeric(scale));
- amount = DirectFunctionCall2(numeric_mul, amount, numeric_scale);
+ numeric_scale = int64_to_numeric(scale);
+
+ amount = numeric_mul_safe(amount, numeric_scale, fcinfo->context);
+ if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
/* note that numeric_int8 will round to nearest integer for us */
- result = DatumGetInt64(DirectFunctionCall1(numeric_int8, amount));
+ result = numeric_int8_safe(amount, fcinfo->context);
+ if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
+ PG_RETURN_NULL();
PG_RETURN_CASH(result);
}
@@ -1158,8 +1164,10 @@ int4_cash(PG_FUNCTION_ARGS)
scale *= 10;
/* compute amount * scale, checking for overflow */
- result = DatumGetInt64(DirectFunctionCall2(int8mul, Int64GetDatum(amount),
- Int64GetDatum(scale)));
+ if (unlikely(pg_mul_s64_overflow(amount, scale, &result)))
+ ereturn(fcinfo->context, (Datum) 0,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("bigint out of range"));
PG_RETURN_CASH(result);
}
@@ -1188,8 +1196,10 @@ int8_cash(PG_FUNCTION_ARGS)
scale *= 10;
/* compute amount * scale, checking for overflow */
- result = DatumGetInt64(DirectFunctionCall2(int8mul, Int64GetDatum(amount),
- Int64GetDatum(scale)));
+ if (unlikely(pg_mul_s64_overflow(amount, scale, &result)))
+ ereturn(fcinfo->context, (Datum) 0,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("bigint out of range"));
PG_RETURN_CASH(result);
}
--
2.34.1
[text/x-patch] v25-0009-CAST-expr-AS-newtype-DEFAULT-expr-ON-CONVERSION-ERROR.patch (139.4K, 3-v25-0009-CAST-expr-AS-newtype-DEFAULT-expr-ON-CONVERSION-ERROR.patch)
download | inline diff:
From ff2ce117225ac46970ef251698c44f51e1839e9c Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Sat, 28 Mar 2026 08:36:24 +0800
Subject: [PATCH v25 9/9] CAST(expr AS newtype DEFAULT expr ON CONVERSION
ERROR)
Introduce SQL-standard safe type cast syntax:
CAST(expr AS newtype DEFAULT expr ON CONVERSION ERROR)
This allow users to specify a default fallback expression if a type conversion
fails, rather than raising an error.
With this patchset, almost all of the cast functions in pg_cast.castfunc are now
error-safe. CoerceViaIO and CoerceToDomain were already error-safe in the HEAD,
see [0], this patch extends error-safe behavior to ArrayCoerceExpr.
Example:
-- Returns '2011-01-01' instead of throwing an invalid input syntax error.
SELECT CAST('1' AS date DEFAULT '2011-01-01' ON CONVERSION ERROR);
# Bumps catversion required
[0]: https://git.postgresql.org/cgit/postgresql.git/commit/?id=aaaf9449ec6be62cb0d30ed3588dc384f56274b
Author: jian he <[email protected]>
Reviewed-by: Amul Sul <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Corey Huinker <[email protected]>
Reviewed-by: Kirill Reshke <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
Commitfest: https://commitfest.postgresql.org/patch/5941
---
contrib/citext/expected/citext.out | 5 +
contrib/citext/expected/citext_1.out | 5 +
contrib/citext/sql/citext.sql | 2 +
.../pg_stat_statements/expected/select.out | 24 +-
contrib/pg_stat_statements/sql/select.sql | 5 +
doc/src/sgml/syntax.sgml | 32 +
src/backend/executor/execExpr.c | 61 +-
src/backend/executor/execExprInterp.c | 29 +
src/backend/jit/llvm/llvmjit_expr.c | 56 +
src/backend/nodes/nodeFuncs.c | 53 +
src/backend/optimizer/util/clauses.c | 90 +-
src/backend/parser/gram.y | 23 +
src/backend/parser/parse_agg.c | 9 +
src/backend/parser/parse_coerce.c | 142 ++-
src/backend/parser/parse_expr.c | 358 +++++-
src/backend/parser/parse_func.c | 3 +
src/backend/parser/parse_target.c | 3 +-
src/backend/parser/parse_type.c | 18 +
src/backend/parser/parse_utilcmd.c | 2 +-
src/backend/utils/adt/arrayfuncs.c | 9 +
src/backend/utils/adt/ruleutils.c | 25 +
src/backend/utils/fmgr/fmgr.c | 13 +
src/include/executor/execExpr.h | 7 +
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 | 16 +-
src/include/parser/parse_node.h | 2 +
src/include/parser/parse_type.h | 2 +
src/test/regress/expected/cast.out | 1071 +++++++++++++++++
src/test/regress/expected/create_cast.out | 5 +
src/test/regress/expected/equivclass.out | 7 +
src/test/regress/expected/xml.out | 6 +
src/test/regress/expected/xml_1.out | 3 +
src/test/regress/parallel_schedule | 2 +-
src/test/regress/sql/cast.sql | 424 +++++++
src/test/regress/sql/create_cast.sql | 1 +
src/test/regress/sql/equivclass.sql | 3 +
src/test/regress/sql/xml.sql | 2 +
src/tools/pgindent/typedefs.list | 2 +
42 files changed, 2489 insertions(+), 94 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..ecfaf2aa85a 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..3e12cd793ad 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..c820b0bd4d9 100644
--- a/contrib/citext/sql/citext.sql
+++ b/contrib/citext/sql/citext.sql
@@ -9,6 +9,8 @@ SELECT amname, opcname
FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod
WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid);
+SELECT CAST('abc'::bpchar AS citext DEFAULT NULL ON CONVERSION ERROR); -- error
+
-- Test the operators and indexing functions
-- Test = and <>.
diff --git a/contrib/pg_stat_statements/expected/select.out b/contrib/pg_stat_statements/expected/select.out
index a069119c790..84f10810f72 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('12' AS numeric DEFAULT 2 ON CONVERSION ERROR);
+ numeric
+---------
+ 12
+(1 row)
+
+SELECT CAST('12' AS numeric(10) 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,9 @@ 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
+ 1 | 1 | SELECT CAST($1 AS int DEFAULT $2 ON CONVERSION ERROR)
+ 1 | 1 | SELECT CAST($1 AS numeric DEFAULT $2 ON CONVERSION ERROR)
+ 1 | 1 | SELECT CAST($1 AS numeric(10) 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 +252,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)
+(20 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 a10d618c034..4be97977ba6 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('12' AS numeric DEFAULT 2 ON CONVERSION ERROR);
+SELECT CAST('12' AS numeric(10) 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 67482996861..aa0fc1ef81c 100644
--- a/doc/src/sgml/syntax.sgml
+++ b/doc/src/sgml/syntax.sgml
@@ -2071,6 +2071,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 default behavior when no <literal>ON CONVERSION ERROR</literal> clause is specified is equivalent to:
+<synopsis>
+CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable> ERROR ON CONVERSION ERROR )
+</synopsis>
</para>
<para>
@@ -2125,6 +2129,34 @@ 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, an error is not raised. Instead, 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 77229141b38..51cfd5472e5 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,
@@ -1734,6 +1737,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);
@@ -2209,6 +2213,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;
@@ -2769,7 +2782,8 @@ ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid,
/* Initialize function call parameter structure too */
InitFunctionCallInfoData(*fcinfo, flinfo,
- nargs, inputcollid, NULL, NULL);
+ nargs, inputcollid,
+ (Node *) state->escontext, NULL);
/* Keep extra copies of this info to save an indirection at runtime */
scratch->d.func.fn_addr = flinfo->fn_addr;
@@ -4770,6 +4784,51 @@ 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)
+{
+ /*
+ * Coercion to the target type failed. Falling back to the DEFAULT
+ * expression from the ON CONVERSION ERROR clause to finish.
+ */
+ if (stcexpr->castexpr == NULL)
+ {
+ ExecInitExprRec(stcexpr->defexpr, state, resv, resnull);
+
+ return;
+ }
+ else
+ {
+ SafeTypeCastState *stcstate = palloc0_object(SafeTypeCastState);
+ ErrorSaveContext *saved_escontext = state->escontext;
+
+ stcstate->stcexpr = stcexpr;
+ stcstate->escontext.type = T_ErrorSaveContext;
+ state->escontext = &stcstate->escontext;
+
+ /* evaluate argument expression into step's result area */
+ ExecInitExprRec(stcexpr->castexpr, state, resv, resnull);
+ scratch->opcode = EEOP_SAFETYPE_CAST;
+ scratch->d.stcexpr.stcstate = stcstate;
+ ExprEvalPushStep(state, scratch);
+
+ /*
+ * Evaluate the DEFAULT expression cast. state->escontext should be
+ * the previous used one
+ */
+ state->escontext = saved_escontext;
+ ExecInitExprRec(stcstate->stcexpr->defexpr, state, resv, resnull);
+
+ 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 43116431edf..79ebe0254fd 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -570,6 +570,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,
@@ -1928,6 +1929,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))
+ EEO_JUMP(stcstate->jump_end);
+ else
+ {
+ *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();
+ }
+ }
+
EEO_CASE(EEOP_JSONEXPR_PATH)
{
/* too complex for an inline implementation */
@@ -3646,6 +3669,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..233bd2ae27b 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -2256,6 +2256,62 @@ llvm_compile_expr(ExprState *state)
LLVMBuildBr(b, opblocks[opno + 1]);
break;
+ case EEOP_SAFETYPE_CAST:
+ {
+ SafeTypeCastState *stcstate = op->d.stcexpr.stcstate;
+ LLVMBasicBlockRef b_noerror;
+ LLVMBasicBlockRef b_error;
+ LLVMValueRef v_error_occurred_p;
+ LLVMValueRef v_details_wanted_p;
+ LLVMValueRef v_error_occurred;
+
+ b_noerror = l_bb_before_v(opblocks[opno + 1],
+ "op.%d.noerror", opno);
+ b_error = l_bb_before_v(opblocks[opno + 1],
+ "op.%d.error", opno);
+
+ /* Get pointer to error_occurred field */
+ v_error_occurred_p = l_ptr_const(&stcstate->escontext.error_occurred,
+ l_ptr(TypeStorageBool));
+
+ /* Load error_occurred at runtime */
+ v_error_occurred = l_load(b, TypeStorageBool, v_error_occurred_p, "");
+
+ /* Branch based on error_occurred: no error -> jump_end */
+ LLVMBuildCondBr(b,
+ LLVMBuildICmp(b, LLVMIntEQ, v_error_occurred,
+ l_sbool_const(0), ""),
+ b_noerror,
+ b_error);
+
+ /* No error: jump to end */
+ LLVMPositionBuilderAtEnd(b, b_noerror);
+ LLVMBuildBr(b, opblocks[stcstate->jump_end]);
+
+ /* Error occurred: set null, reset flags, evaluate default */
+ LLVMPositionBuilderAtEnd(b, b_error);
+
+ /* set resnull to true */
+ LLVMBuildStore(b, l_sbool_const(1), v_resnullp);
+
+ /* reset resvalue */
+ LLVMBuildStore(b, l_datum_const(0), v_resvaluep);
+
+ /*
+ * Reset for next use such as for catching errors when
+ * coercing a expression.
+ */
+ LLVMBuildStore(b, l_sbool_const(0), v_error_occurred_p);
+
+ v_details_wanted_p = l_ptr_const(&stcstate->escontext.details_wanted,
+ l_ptr(TypeStorageBool));
+ LLVMBuildStore(b, l_sbool_const(0), v_details_wanted_p);
+
+ LLVMBuildBr(b, opblocks[opno + 1]);
+
+ 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 6a850349cf7..b5e89b7f60c 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;
@@ -453,6 +456,8 @@ exprTypmod(const Node *expr)
return typmod;
}
break;
+ case T_SafeTypeCastExpr:
+ return ((const SafeTypeCastExpr *) expr)->resulttypmod;
case T_CoalesceExpr:
{
/*
@@ -970,6 +975,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;
@@ -1240,6 +1248,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;
@@ -1558,6 +1569,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;
@@ -2333,6 +2347,18 @@ expression_tree_walker_impl(Node *node,
return true;
}
break;
+ case T_SafeTypeCastExpr:
+ {
+ SafeTypeCastExpr *stcexpr = castNode(SafeTypeCastExpr, node);
+
+ if (WALK(stcexpr->source))
+ return true;
+ if (WALK(stcexpr->castexpr))
+ return true;
+ if (WALK(stcexpr->defexpr))
+ return true;
+ }
+ break;
case T_CoalesceExpr:
return WALK(((CoalesceExpr *) node)->args);
case T_MinMaxExpr:
@@ -3368,6 +3394,19 @@ expression_tree_mutator_impl(Node *node,
return (Node *) newnode;
}
break;
+ case T_SafeTypeCastExpr:
+ {
+ SafeTypeCastExpr *stcexpr = castNode(SafeTypeCastExpr, node);
+ SafeTypeCastExpr *newnode;
+
+ FLATCOPY(newnode, stcexpr, SafeTypeCastExpr);
+ MUTATE(newnode->source, stcexpr->source, Expr *);
+ MUTATE(newnode->castexpr, stcexpr->castexpr, Expr *);
+ MUTATE(newnode->defexpr, stcexpr->defexpr, Expr *);
+
+ return (Node *) newnode;
+ }
+ break;
case T_CoalesceExpr:
{
CoalesceExpr *coalesceexpr = (CoalesceExpr *) node;
@@ -4504,6 +4543,20 @@ raw_expression_tree_walker_impl(Node *node,
return true;
if (WALK(tc->typeName))
return true;
+ if (WALK(tc->defexpr))
+ return true;
+ }
+ break;
+ case T_SafeTypeCastExpr:
+ {
+ SafeTypeCastExpr *stcexpr = castNode(SafeTypeCastExpr, node);
+
+ if (WALK(stcexpr->source))
+ return true;
+ if (WALK(stcexpr->castexpr))
+ return true;
+ if (WALK(stcexpr->defexpr))
+ return true;
}
break;
case T_CollateClause:
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 9fb266d089d..ba77d4aecf8 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -69,6 +69,7 @@ typedef struct
List *active_fns;
Node *case_val;
bool estimate;
+ ErrorSaveContext *escontext;
} eval_const_expressions_context;
typedef struct
@@ -2507,6 +2508,7 @@ eval_const_expressions(PlannerInfo *root, Node *node)
context.active_fns = NIL; /* nothing being recursively simplified */
context.case_val = NULL; /* no CASE being examined */
context.estimate = false; /* safe transformations only */
+ context.escontext = NULL; /* for error-safe expression evaluation */
return eval_const_expressions_mutator(node, &context);
}
@@ -2646,6 +2648,7 @@ estimate_expression_value(PlannerInfo *root, Node *node)
context.active_fns = NIL; /* nothing being recursively simplified */
context.case_val = NULL; /* no CASE being examined */
context.estimate = true; /* unsafe transformations OK */
+ context.escontext = NULL; /* for error-safe expression evaluation */
return eval_const_expressions_mutator(node, &context);
}
@@ -2671,11 +2674,12 @@ estimate_expression_value(PlannerInfo *root, Node *node)
(!expression_tree_walker((Node *) (node), contain_non_const_walker, NULL))
/* Generic macro for applying evaluate_expr */
-#define ece_evaluate_expr(node) \
+#define ece_evaluate_expr(node, escontext) \
((Node *) evaluate_expr((Expr *) (node), \
exprType((Node *) (node)), \
exprTypmod((Node *) (node)), \
- exprCollation((Node *) (node))))
+ exprCollation((Node *) (node)), \
+ escontext))
/*
* Recursive guts of eval_const_expressions/estimate_expression_value
@@ -3136,7 +3140,7 @@ eval_const_expressions_mutator(Node *node,
if (!has_nonconst_input &&
ece_function_is_safe(expr->opfuncid, context))
- return ece_evaluate_expr(expr);
+ return ece_evaluate_expr(expr, (Node *) context->escontext);
return (Node *) expr;
}
@@ -3156,7 +3160,7 @@ eval_const_expressions_mutator(Node *node,
*/
if (ece_all_arguments_const(saop) &&
ece_function_is_safe(saop->opfuncid, context))
- return ece_evaluate_expr(saop);
+ return ece_evaluate_expr(saop, (Node *) context->escontext);
return (Node *) saop;
}
case T_BoolExpr:
@@ -3267,6 +3271,42 @@ eval_const_expressions_mutator(Node *node,
copyObject(jve->format));
}
+ case T_SafeTypeCastExpr:
+ {
+ SafeTypeCastExpr *stc = castNode(SafeTypeCastExpr, node);
+ SafeTypeCastExpr *newexpr = makeNode(SafeTypeCastExpr);
+
+ Node *castexpr = (Node *) stc->castexpr;
+ Node *defexpr = (Node *) stc->defexpr;
+
+ /*
+ * No need to fold "source" to a constant. The executor does
+ * not use it, see ExecInitSafeTypeCastExpr. Additionally,
+ * castexpr expression tree may already contain the "source"
+ * node.
+ */
+ context->escontext = makeNode(ErrorSaveContext);
+ context->escontext->type = T_ErrorSaveContext;
+ context->escontext->error_occurred = false;
+
+ castexpr = eval_const_expressions_mutator(castexpr,
+ context);
+ pfree(context->escontext);
+ context->escontext = NULL;
+
+ defexpr = eval_const_expressions_mutator(defexpr,
+ context);
+
+ newexpr->source = (Expr *) stc->source;
+ newexpr->castexpr = (Expr *) castexpr;
+ newexpr->defexpr = (Expr *) defexpr;
+ newexpr->resulttype = stc->resulttype;
+ newexpr->resulttypmod = stc->resulttypmod;
+ newexpr->resultcollid = stc->resultcollid;
+
+ return (Node *) newexpr;
+ }
+
case T_SubPlan:
case T_AlternativeSubPlan:
@@ -3384,6 +3424,7 @@ eval_const_expressions_mutator(Node *node,
{
ArrayCoerceExpr *ac = makeNode(ArrayCoerceExpr);
Node *save_case_val;
+ Expr *simple = NULL;
/*
* Copy the node and const-simplify its arguments. We can't
@@ -3391,9 +3432,10 @@ eval_const_expressions_mutator(Node *node,
* with case_val only while processing the elemexpr.
*/
memcpy(ac, node, sizeof(ArrayCoerceExpr));
- ac->arg = (Expr *)
- eval_const_expressions_mutator((Node *) ac->arg,
- context);
+ simple = (Expr *) eval_const_expressions_mutator((Node *) ac->arg,
+ context);
+ if (simple)
+ ac->arg = simple;
/*
* Set up for the CaseTestExpr node contained in the elemexpr.
@@ -3402,9 +3444,10 @@ eval_const_expressions_mutator(Node *node,
save_case_val = context->case_val;
context->case_val = NULL;
- ac->elemexpr = (Expr *)
- eval_const_expressions_mutator((Node *) ac->elemexpr,
- context);
+ simple = (Expr *) eval_const_expressions_mutator((Node *) ac->elemexpr,
+ context);
+ if (simple)
+ ac->elemexpr = simple;
context->case_val = save_case_val;
@@ -3419,7 +3462,7 @@ eval_const_expressions_mutator(Node *node,
if (ac->arg && IsA(ac->arg, Const) &&
ac->elemexpr && !IsA(ac->elemexpr, CoerceToDomain) &&
!contain_mutable_functions((Node *) ac->elemexpr))
- return ece_evaluate_expr(ac);
+ return ece_evaluate_expr(ac, (Node *) context->escontext);
return (Node *) ac;
}
@@ -3615,7 +3658,7 @@ eval_const_expressions_mutator(Node *node,
node = ece_generic_processing(node);
/* If all arguments are Consts, we can fold to a constant */
if (ece_all_arguments_const(node))
- return ece_evaluate_expr(node);
+ return ece_evaluate_expr(node, (Node *) context->escontext);
return node;
}
case T_CoalesceExpr:
@@ -3698,7 +3741,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);
}
@@ -3795,7 +3839,7 @@ eval_const_expressions_mutator(Node *node,
newfselect->resulttype,
newfselect->resulttypmod,
newfselect->resultcollid))
- return ece_evaluate_expr(newfselect);
+ return ece_evaluate_expr(newfselect, (Node *) context->escontext);
}
return (Node *) newfselect;
}
@@ -4125,7 +4169,7 @@ eval_const_expressions_mutator(Node *node,
newcre->arg = (Expr *) arg;
if (arg != NULL && IsA(arg, Const))
- return ece_evaluate_expr((Node *) newcre);
+ return ece_evaluate_expr((Node *) newcre, (Node *) context->escontext);
return (Node *) newcre;
}
default:
@@ -5229,7 +5273,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, (Node *) context->escontext);
}
/*
@@ -5683,10 +5727,13 @@ 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 non-NULL, safely evaluates the constant expression.
+ * Returns NULL on failure rather than 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;
@@ -5711,7 +5758,7 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
* Prepare expr for execution. (Note: we can't use ExecPrepareExpr
* because it'd result in recursively invoking eval_const_expressions.)
*/
- exprstate = ExecInitExpr(expr, NULL);
+ exprstate = ExecInitExprWithContext(expr, NULL, escontext);
/*
* And evaluate it.
@@ -5731,6 +5778,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 0fea726cdd5..2ac04565afc 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);
@@ -16687,6 +16689,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"),
@@ -19848,10 +19856,25 @@ makeTypeCast(Node *arg, TypeName *typename, int location)
n->arg = arg;
n->typeName = typename;
+ n->defexpr = NULL;
n->location = location;
return (Node *) n;
}
+static Node *
+makeTypeCastWithDefault(Node *arg, TypeName *typename, Node *defexpr,
+ int location)
+{
+ TypeCast *n = makeNode(TypeCast);
+
+ n->arg = arg;
+ n->typeName = typename;
+ n->defexpr = defexpr;
+ n->location = location;
+
+ return (Node *) n;
+}
+
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 6076e9373c1..07c73521df2 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -491,6 +491,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:
@@ -992,6 +998,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..8bd79fd5bfc 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -37,14 +37,16 @@ static Node *coerce_type_typmod(Node *node,
Oid targetTypeId, int32 targetTypMod,
CoercionContext ccontext, CoercionForm cformat,
int location,
- bool hideInputCoercion);
+ bool hideInputCoercion,
+ Node *escontext);
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,
- int location);
+ int location,
+ Node *escontext);
static Node *coerce_record_to_complex(ParseState *pstate, Node *node,
Oid targetTypeId,
CoercionContext ccontext,
@@ -81,6 +83,28 @@ 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 non-NULL, safely coerces 'expr' to the target type
+ * without raising an error. Returns NULL if the coercion fails.
+ *
+ * See 'coerce_to_target_type' above for details on other parameters.
+ */
+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 +126,15 @@ 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
@@ -114,7 +144,11 @@ coerce_to_target_type(ParseState *pstate, Node *expr, Oid exprtype,
result = coerce_type_typmod(result,
targettype, targettypmod,
ccontext, cformat, location,
- (result != expr && !IsA(result, Const)));
+ (result != expr && !IsA(result, Const)),
+ escontext);
+
+ if (result == NULL) /* shouldn't happen */
+ elog(ERROR, "failed to coerce type modifier as expected");
if (expr != origexpr && type_is_collatable(targettype))
{
@@ -158,6 +192,18 @@ 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 +302,7 @@ coerce_type(ParseState *pstate, Node *node,
int32 inputTypeMod;
Type baseType;
ParseCallbackState pcbstate;
+ char *string = NULL;
/*
* If the target type is a domain, we want to call its base type's
@@ -309,13 +356,21 @@ coerce_type(ParseState *pstate, Node *node,
* as CSTRING.
*/
if (!con->constisnull)
- newcon->constvalue = stringTypeDatum(baseType,
- DatumGetCString(con->constvalue),
- inputTypeMod);
- else
- newcon->constvalue = stringTypeDatum(baseType,
- NULL,
- inputTypeMod);
+ string = DatumGetCString(con->constvalue);
+
+ if (!stringTypeDatumSafe(baseType,
+ string,
+ inputTypeMod,
+ escontext,
+ &newcon->constvalue))
+ {
+ /* UNKNOWN Const cannot coerce to targetType, exit now */
+ cancel_parser_errposition_callback(&pcbstate);
+
+ ReleaseSysCache(baseType);
+
+ return NULL;
+ }
/*
* If it's a varlena value, force it to be in non-expanded
@@ -364,7 +419,8 @@ coerce_type(ParseState *pstate, Node *node,
baseTypeId, baseTypeMod,
targetTypeId,
ccontext, cformat, location,
- false);
+ false,
+ escontext);
ReleaseSysCache(baseType);
@@ -397,9 +453,11 @@ 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_extended(pstate, (Node *) coll->arg,
+ inputTypeId, targetTypeId, targetTypeMod,
+ ccontext, cformat, location,
+ escontext);
+
if (type_is_collatable(targetTypeId))
{
CollateExpr *newcoll = makeNode(CollateExpr);
@@ -432,7 +490,8 @@ coerce_type(ParseState *pstate, Node *node,
*/
result = build_coercion_expression(node, pathtype, funcId,
baseTypeId, baseTypeMod,
- ccontext, cformat, location);
+ ccontext, cformat, location,
+ escontext);
/*
* If domain, coerce to the domain type and relabel with domain
@@ -442,7 +501,8 @@ coerce_type(ParseState *pstate, Node *node,
result = coerce_to_domain(result, baseTypeId, baseTypeMod,
targetTypeId,
ccontext, cformat, location,
- true);
+ true,
+ escontext);
}
else
{
@@ -458,7 +518,9 @@ coerce_type(ParseState *pstate, Node *node,
result = coerce_to_domain(node, baseTypeId, baseTypeMod,
targetTypeId,
ccontext, cformat, location,
- false);
+ false,
+ escontext);
+
if (result == node)
{
/*
@@ -675,7 +737,8 @@ can_coerce_type(int nargs, const Oid *input_typeids, const Oid *target_typeids,
Node *
coerce_to_domain(Node *arg, Oid baseTypeId, int32 baseTypeMod, Oid typeId,
CoercionContext ccontext, CoercionForm cformat, int location,
- bool hideInputCoercion)
+ bool hideInputCoercion,
+ Node *escontext)
{
CoerceToDomain *result;
@@ -705,7 +768,10 @@ 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,
+ escontext);
+ if (arg == NULL) /* shouldn't happen */
+ elog(ERROR, "failed to coerce type modifier as expected");
/*
* Now build the domain coercion node. This represents run-time checking
@@ -752,7 +818,8 @@ static Node *
coerce_type_typmod(Node *node, Oid targetTypeId, int32 targetTypMod,
CoercionContext ccontext, CoercionForm cformat,
int location,
- bool hideInputCoercion)
+ bool hideInputCoercion,
+ Node *escontext)
{
CoercionPathType pathtype;
Oid funcId;
@@ -779,7 +846,8 @@ coerce_type_typmod(Node *node, Oid targetTypeId, int32 targetTypMod,
{
node = build_coercion_expression(node, pathtype, funcId,
targetTypeId, targetTypMod,
- ccontext, cformat, location);
+ ccontext, cformat, location,
+ escontext);
}
else
{
@@ -840,7 +908,8 @@ build_coercion_expression(Node *node,
Oid funcId,
Oid targetTypeId, int32 targetTypMod,
CoercionContext ccontext, CoercionForm cformat,
- int location)
+ int location,
+ Node *escontext)
{
int nargs = 0;
@@ -950,14 +1019,15 @@ build_coercion_expression(Node *node,
targetElementType = get_element_type(targetTypeId);
Assert(OidIsValid(targetElementType));
- elemexpr = coerce_to_target_type(NULL,
- (Node *) ctest,
- ctest->typeId,
- targetElementType,
- targetTypMod,
- ccontext,
- cformat,
- location);
+ elemexpr = coerce_to_target_type_extended(NULL,
+ (Node *) ctest,
+ ctest->typeId,
+ targetElementType,
+ targetTypMod,
+ ccontext,
+ cformat,
+ location,
+ escontext);
if (elemexpr == NULL) /* shouldn't happen */
elog(ERROR, "failed to coerce array element type as expected");
@@ -1140,7 +1210,8 @@ coerce_record_to_complex(ParseState *pstate, Node *node,
baseTypeId, baseTypeMod,
targetTypeId,
ccontext, cformat, location,
- false);
+ false,
+ NULL);
}
return (Node *) rowexpr;
@@ -1295,7 +1366,8 @@ coerce_null_to_domain(Oid typid, int32 typmod, Oid collation,
COERCION_IMPLICIT,
COERCE_IMPLICIT_CAST,
-1,
- false);
+ false,
+ NULL);
return result;
}
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 312dfdc182a..de60cfd6175 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"
@@ -38,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/typcache.h"
#include "utils/xml.h"
@@ -62,7 +64,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,
+ Node *escontext);
static Node *transformRowExpr(ParseState *pstate, RowExpr *r, bool allowDefault);
static Node *transformCoalesceExpr(ParseState *pstate, CoalesceExpr *c);
static Node *transformMinMaxExpr(ParseState *pstate, MinMaxExpr *m);
@@ -78,6 +81,11 @@ 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 *source, Oid inputType, Oid targetType);
+static void CoercionErrorSafe_Internal(Oid inputType, Oid targetType,
+ bool *errorsafe_coercion,
+ bool *userdefined);
static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
static Node *transformJsonObjectConstructor(ParseState *pstate,
JsonObjectConstructor *ctor);
@@ -166,7 +174,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:
@@ -566,6 +574,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:
@@ -1840,6 +1849,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");
@@ -2030,10 +2042,15 @@ 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().
+ *
+ * escontext (ErrorSaveContext *) is typically NULL, except during parse analysis for
+ * CAST(... DEFAULT ... ON CONVERSION ERROR). When provided, it should defaults
+ * ErrorSaveContext->error_occurred = false; error_occurred ise set
+ * to true if coercing array elements fails.
*/
static Node *
transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
- Oid array_type, Oid element_type, int32 typmod)
+ Oid array_type, Oid element_type, int32 typmod, Node *escontext)
{
ArrayExpr *newa = makeNode(ArrayExpr);
List *newelems = NIL;
@@ -2064,9 +2081,10 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
(A_ArrayExpr *) e,
array_type,
element_type,
- typmod);
+ typmod,
+ escontext);
/* we certainly have an array here */
- Assert(array_type == InvalidOid || array_type == exprType(newe));
+ Assert(escontext || array_type == InvalidOid || array_type == exprType(newe));
newa->multidims = true;
}
else
@@ -2107,6 +2125,8 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
}
else
{
+ Assert(escontext == NULL);
+
/* Can't handle an empty array without a target type */
if (newelems == NIL)
ereport(ERROR,
@@ -2153,28 +2173,62 @@ 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 "escontext" is not NULL, check whether each element can be coerced
+ * to the target type, similar to what is done in transformTypeCast.
*/
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))));
+ /*
+ * Cannot coerce, just append the transformed element expression
+ * to the list.
+ */
+ if (SOFT_ERROR_OCCURRED(escontext))
+ newe = e;
+ else
+ {
+ Node *ecopy = NULL;
+
+ if (escontext)
+ ecopy = copyObject(e);
+
+ newe = coerce_to_target_type_extended(pstate, e,
+ exprType(e),
+ coerce_type,
+ typmod,
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ -1,
+ escontext);
+ if (newe == NULL)
+ {
+ /*
+ * Cannot coerce. Raise an error or append the transformed
+ * element to the list. Also, set
+ * escontext->error_occurred to true so the caller can
+ * notice that array coercion failed.
+ */
+ if (!escontext)
+ 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
+ {
+ ErrorSaveContext *context = castNode(ErrorSaveContext, escontext);
+
+ newe = ecopy;
+ context->error_occurred = true;
+ }
+ }
+ }
}
else
newe = coerce_to_common_type(pstate, e,
@@ -2723,17 +2777,66 @@ transformWholeRowRef(ParseState *pstate, ParseNamespaceItem *nsitem,
static Node *
transformTypeCast(ParseState *pstate, TypeCast *tc)
{
- Node *result;
+ SafeTypeCastExpr *stc;
+ Node *castexpr = NULL;
+ Node *defexpr = NULL;
Node *arg = tc->arg;
+ Node *source;
Node *expr;
Oid inputType;
Oid targetType;
+ Oid targetTypecoll;
int32 targetTypmod;
int location;
+ ErrorSaveContext *escontext = makeNode(ErrorSaveContext);
+
+ escontext->type = T_ErrorSaveContext;
+ escontext->error_occurred = false;
+ escontext->details_wanted = false;
/* Look up the type name first */
typenameTypeIdAndMod(pstate, tc->typeName, &targetType, &targetTypmod);
+ targetTypecoll = get_typcollation(targetType);
+
+ /* looking at DEFAULT expression */
+ if (tc->defexpr)
+ {
+ Oid defColl;
+
+ defexpr = transformExpr(pstate, tc->defexpr, 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->defexpr), defexpr));
+
+ assign_expr_collations(pstate, defexpr);
+
+ /*
+ * The collation of DEFAULT expression must match the collation of the
+ * target type.
+ */
+ defColl = exprCollation(defexpr);
+
+ if (targetTypecoll != defColl)
+ ereport(ERROR,
+ errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("collation of CAST DEFAULT expression conflicts with target type collation"),
+ errdetail("\"%s\" versus \"%s\"",
+ get_collation_name(defColl),
+ get_collation_name(targetTypecoll)),
+ 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
@@ -2762,7 +2865,8 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
(A_ArrayExpr *) arg,
targetBaseType,
elementType,
- targetBaseTypmod);
+ targetBaseTypmod,
+ defexpr ? (Node *) escontext : NULL);
}
else
expr = transformExprRecurse(pstate, arg);
@@ -2783,20 +2887,204 @@ 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.
+ */
+ source = defexpr ? copyObject(expr) : expr;
+
+ if (!SOFT_ERROR_OCCURRED(escontext))
+ {
+ castexpr = coerce_to_target_type_extended(pstate, expr, inputType,
+ targetType, targetTypmod,
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ location,
+ defexpr ? (Node *) escontext : NULL);
+
+ /* Coercion failed. Error out if CAST DEFAULT is not available. */
+ if (!castexpr && !defexpr)
+ 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));
+
+
+ /* No DEFAULT ... ON CONVERSION ERROR, exit now */
+ if (!defexpr)
+ return castexpr;
+ }
+
+ /* Further check for CAST(... DEFAULT ... ON CONVERSION ERROR) */
+ CoercionErrorSafeCheck(pstate, castexpr, source, inputType,
+ targetType);
+
+ stc = makeNode(SafeTypeCastExpr);
+ stc->source = (Expr *) source;
+ stc->castexpr = (Expr *) castexpr;
+ stc->defexpr = (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 *source,
+ Oid inputType, Oid targetType)
+{
+ bool errorsafe_coercion = true;
+ bool userdefined = false;
+
+ /*
+ * Binary coercion cast is error-safe, CoerceViaIO can also be evaluated
+ * in an error-safe manner. Skip these cases.
+ */
+ if (castexpr == NULL ||
+ IsBinaryCoercible(inputType, targetType) ||
+ IsA(castexpr, CoerceViaIO))
+ return;
+
+ CoercionErrorSafe_Internal(inputType, targetType,
+ &errorsafe_coercion,
+ &userdefined);
+
+ if (!errorsafe_coercion)
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)));
+ 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(source)));
+}
- return result;
+static void
+CoercionErrorSafe_Internal(Oid inputType, Oid targetType,
+ bool *errorsafe_coercion, bool *userdefined)
+{
+ HeapTuple tuple;
+
+ char input_typtype = get_typtype(inputType);
+ char target_typtype = get_typtype(targetType);
+
+ /* since this function recurses, it could be driven to stack overflow */
+ check_stack_depth();
+
+ Assert(errorsafe_coercion != NULL);
+ Assert(userdefined != NULL);
+
+ if (!(*errorsafe_coercion))
+ return;
+
+ if (input_typtype == TYPTYPE_DOMAIN &&
+ target_typtype == TYPTYPE_DOMAIN)
+ {
+ CoercionErrorSafe_Internal(getBaseType(inputType),
+ getBaseType(targetType),
+ errorsafe_coercion,
+ userdefined);
+ return;
+ }
+ else if (input_typtype == TYPTYPE_DOMAIN)
+ {
+ CoercionErrorSafe_Internal(getBaseType(inputType),
+ targetType,
+ errorsafe_coercion,
+ userdefined);
+ return;
+ }
+ else if (target_typtype == TYPTYPE_DOMAIN)
+ {
+ CoercionErrorSafe_Internal(inputType,
+ getBaseType(targetType),
+ errorsafe_coercion,
+ userdefined);
+ return;
+ }
+ else if ((input_typtype != TYPTYPE_BASE && inputType > FirstUnpinnedObjectId)
+ || (target_typtype != TYPTYPE_BASE && targetType > FirstUnpinnedObjectId))
+ {
+ /*
+ * Input expression can be UNKNOWN Const, and UNKNOWN typtype is
+ * TYPTYPE_PSEUDO.
+ */
+
+ /*
+ * Composite-to-composite casting is not implemented, so error-safe
+ * cast between them should not be possible; we forbid it here.
+ *
+ * Our type system does not automatically cast a user-defined range
+ * type to an built-in range type, even if their base element types
+ * are the same. There may be complex edge cases to consider, safer to
+ * simply disallow error-safe casting for them now.
+ */
+ *errorsafe_coercion = false;
+
+ return;
+ }
+ else
+ {
+ Oid input_typelem = get_element_type(inputType);
+ Oid target_typelem = get_element_type(targetType);
+
+ if (OidIsValid(input_typelem) && OidIsValid(target_typelem))
+ {
+ /* recurse into array element type */
+ CoercionErrorSafe_Internal(input_typelem,
+ target_typelem,
+ errorsafe_coercion,
+ userdefined);
+ return;
+ }
+ }
+
+ /*
+ * Casts source data type as MONEY are not error safe.
+ */
+ if (inputType == MONEYOID)
+ {
+ *errorsafe_coercion = false;
+ return;
+ }
+
+ if (inputType != targetType)
+ {
+ tuple = SearchSysCache2(CASTSOURCETARGET,
+ ObjectIdGetDatum(inputType),
+ ObjectIdGetDatum(targetType));
+
+ /*
+ * A pg_cast entry might not exist for this specific cast; for
+ * example, when using CoerceViaIO.
+ */
+ if (HeapTupleIsValid(tuple))
+ {
+ Form_pg_cast castForm = (Form_pg_cast) GETSTRUCT(tuple);
+
+ if (castForm->castfunc > FirstUnpinnedObjectId)
+ {
+ *errorsafe_coercion = false;
+ *userdefined = true;
+ }
+ ReleaseSysCache(tuple);
+ }
+ else if (inputType > FirstUnpinnedObjectId || targetType > FirstUnpinnedObjectId)
+ *errorsafe_coercion = false;
+ }
}
/*
@@ -3212,6 +3500,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 8dbd41a3548..359a694fe09 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 541fef5f183..7cd1970bc99 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -845,7 +845,8 @@ transformAssignmentIndirection(ParseState *pstate,
COERCION_IMPLICIT,
COERCE_IMPLICIT_CAST,
location,
- false);
+ false,
+ NULL);
return (Node *) fstore;
}
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index bb7eccde9fd..283dfc2ee99 100644
--- a/src/backend/parser/parse_type.c
+++ b/src/backend/parser/parse_type.c
@@ -660,6 +660,24 @@ stringTypeDatum(Type tp, char *string, int32 atttypmod)
return OidInputFunctionCall(typinput, string, typioparam, atttypmod);
}
+/*
+ * Error-safe version of stringTypeDatum.
+ *
+ * Returns true if "string" is a valid representation of type "tp".
+ * On success, the internal Datum representation is written to "result".
+ */
+bool
+stringTypeDatumSafe(Type tp, 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 9a918e14aa7..10318fc4990 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -4951,7 +4951,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 734e5fea45e..dbfe5f4a79e 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -3294,6 +3294,15 @@ array_map(Datum arrayd,
/* Apply the given expression to source element */
values[i] = ExecEvalExpr(exprstate, econtext, &nulls[i]);
+ /* Exit early if evaluation failed */
+ if (SOFT_ERROR_OCCURRED(exprstate->escontext))
+ {
+ pfree(values);
+ pfree(nulls);
+
+ return (Datum) 0;
+ }
+
if (nulls[i])
hasnulls = true;
else
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 7bc12589e40..748586ac67c 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -11106,6 +11106,31 @@ get_rule_expr(Node *node, deparse_context *context,
}
break;
+ case T_SafeTypeCastExpr:
+ {
+ SafeTypeCastExpr *stcexpr = castNode(SafeTypeCastExpr, node);
+
+ /*
+ * We cannot deparse castexpr directly, since
+ * transformTypeCast may have already constant-folded it into
+ * a Const node. Instead, we can use "source" and "defexpr" to
+ * reconstruct the CAST DEFAULT clause.
+ */
+ appendStringInfoString(buf, "CAST(");
+ get_rule_expr((Node *) stcexpr->source, context, showimplicit);
+
+ appendStringInfo(buf, " AS %s ",
+ format_type_with_typemod(stcexpr->resulttype,
+ stcexpr->resulttypmod));
+
+ appendStringInfoString(buf, "DEFAULT ");
+
+ get_rule_expr((Node *) stcexpr->defexpr, context, showimplicit);
+
+ appendStringInfoString(buf, " ON CONVERSION ERROR)");
+ }
+ break;
+
case T_JsonExpr:
{
JsonExpr *jexpr = (JsonExpr *) node;
diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index bfeceb7a92f..90686ae20f9 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -1760,6 +1760,19 @@ 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/fmgr.h b/src/include/fmgr.h
index 10d02bdb79f..8e3e51dc228 100644
--- a/src/include/fmgr.h
+++ b/src/include/fmgr.h
@@ -752,6 +752,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 684e398f824..3ee039dacd4 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1071,6 +1071,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 evaluate 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 df431220ac5..ba27e0a714b 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 *defexpr; /* DEFAULT expression */
ParseLoc location; /* token location, or -1 if unknown */
} TypeCast;
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index f5b6b45664a..71e55ea1f8a 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -770,6 +770,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 from the parser. Constant folding
+ * (eval_const_expressions) is skipped because ExecInitSafeTypeCastExpr
+ * does not use this node. Therefore, it may contain a CollateExpr node,
+ * which ExecInitExprRec cannot handle. However, this is still required
+ * for deparsing purposes.
+ *
+ * For example, "CAST(1 AS date DEFAULT NULL ON CONVERSION ERROR)", the
+ * castexpr is now NULL. To deparse it, we need this transformed "source"
+ * expression.
+ */
+ Expr *source;
+ /* transformed cast expression, NULL means cannot coerce to target type */
+ Expr *castexpr pg_node_attr(query_jumble_ignore);
+ /* Fall-back to the default expression if cast evaluation fails */
+ Expr *defexpr;
+ /* target type Oid */
+ Oid resulttype;
+ /* target type modifier */
+ int32 resulttypmod;
+ /* OID of collation, or InvalidOid if none */
+ Oid resultcollid;
+ /* token location, or -1 if unknown */
+ ParseLoc location;
+} SafeTypeCastExpr;
+
/*
* FuncExpr - expression node for a function call
*
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index e8b409afb7f..b686be049b9 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -151,7 +151,7 @@ extern void convert_saop_to_hashed_saop(Node *node);
extern Node *estimate_expression_value(PlannerInfo *root, Node *node);
extern Expr *evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
- Oid result_collation);
+ Oid result_collation, Node *escontext);
extern bool var_is_nonnullable(PlannerInfo *root, Var *var,
NotNullSource source);
diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h
index aabacd49b65..12644f146bf 100644
--- a/src/include/parser/parse_coerce.h
+++ b/src/include/parser/parse_coerce.h
@@ -43,15 +43,29 @@ 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,
- bool hideInputCoercion);
+ bool hideInputCoercion,
+ Node *escontext);
extern Node *coerce_to_boolean(ParseState *pstate, Node *node,
const char *constructName);
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index fc2cbeb2083..726c1fcd0e2 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..348ed4ced03
--- /dev/null
+++ b/src/test/regress/expected/cast.out
@@ -0,0 +1,1071 @@
+SET extra_float_digits = 0;
+SET lc_monetary TO "C";
+-- 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)
+
+SELECT CAST('def'::text AS integer DEFAULT NULL ON CONVERSION ERROR);
+ int4
+------
+
+(1 row)
+
+SELECT CAST('2'::jsonb AS text DEFAULT 'fallback' ON CONVERSION ERROR);
+ text
+------
+ 2
+(1 row)
+
+SELECT CAST('1' COLLATE "C" || 'h' AS text DEFAULT 'fallback' ON CONVERSION ERROR);
+ text
+------
+ 1h
+(1 row)
+
+SELECT CAST(concat('1' collate "C", 'h') AS int DEFAULT NULL ON CONVERSION ERROR);
+ concat
+--------
+
+(1 row)
+
+SELECT CAST(65536 AS int2 DEFAULT NULL ON CONVERSION ERROR);
+ int2
+------
+
+(1 row)
+
+SELECT CAST(int8(65536) AS int2 DEFAULT NULL ON CONVERSION ERROR);
+ int8
+------
+
+(1 row)
+
+-- 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 target type collation
+VALUES (CAST('error' AS text DEFAULT '1' COLLATE "C" ON CONVERSION ERROR));
+ERROR: collation of CAST DEFAULT expression conflicts with target type collation
+LINE 1: VALUES (CAST('error' AS text DEFAULT '1' COLLATE "C" ON CONV...
+ ^
+DETAIL: "C" versus "default"
+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)
+
+-- 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 cannot 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 tcast0(a INT, b int);
+INSERT INTO tcast0 VALUES(1,2), (65536,3);
+SET jit_above_cost = 0;
+SELECT CAST(a AS int2 DEFAULT NULL ON CONVERSION ERROR) FROM tcast0;
+ a
+---
+ 1
+
+(2 rows)
+
+SET jit_above_cost TO DEFAULT;
+SELECT CAST(a AS int2 DEFAULT NULL ON CONVERSION ERROR) FROM tcast0;
+ a
+---
+ 1
+
+(2 rows)
+
+SELECT CAST(ROW(1,2) AS tcast0 DEFAULT NULL ON CONVERSION ERROR) FROM tcast0 as t;
+ERROR: cannot cast type record to tcast0 when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(ROW(1,2) AS tcast0 DEFAULT NULL ON CONVERSION ER...
+ ^
+HINT: Explicit cast is defined but definition is not error safe.
+DROP TABLE tcast0;
+SELECT CAST(12.111 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale;
+ to_numeric_scale
+------------------
+ 12.1
+(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('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 nested type casts; all expressions will be evaluated in an error-safe manner
+CREATE DOMAIN d_nota AS text CHECK (VALUE <> 'a');
+CREATE TABLE tcast1(a text, b text[], c int, d int8, e int8[]);
+INSERT INTO tcast1(a, b, d, e) values('a', '{a}', 2147483648, '{2147483648}');
+SELECT CAST (CAST(a AS int) AS text DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+ a
+---
+
+(1 row)
+
+SELECT CAST (CAST('a' AS int) AS text DEFAULT NULL ON CONVERSION ERROR) FROM tcast1; -- error
+ERROR: invalid input syntax for type integer: "a"
+LINE 1: SELECT CAST (CAST('a' AS int) AS text DEFAULT NULL ON CONVER...
+ ^
+SELECT CAST (CAST('a'::text AS int) AS text DEFAULT NULL ON CONVERSION ERROR) FROM tcast1; -- ok
+ text
+------
+
+(1 row)
+
+SELECT CAST (CAST(a AS d_nota) AS text DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+ a
+---
+
+(1 row)
+
+SELECT CAST (CAST('a' AS d_nota) AS text DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+ text
+------
+
+(1 row)
+
+SELECT CAST (CAST(d AS int) AS text DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+ d
+---
+
+(1 row)
+
+SELECT CAST (CAST('2147483648'::int8 AS int) AS text DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+ text
+------
+
+(1 row)
+
+SELECT CAST (CAST('{a}'::text[] AS int[]) AS text[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+ text
+------
+
+(1 row)
+
+SELECT CAST (CAST(b AS int[]) AS text[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+ b
+---
+
+(1 row)
+
+SELECT CAST (CAST(b AS d_nota[]) AS text[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+ b
+---
+
+(1 row)
+
+SELECT CAST (CAST(e AS int[]) AS text[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+ e
+---
+
+(1 row)
+
+-- test eval_const_expressions_mutator handle nested ArrayCoerceExpr
+SELECT CAST('{a}'::text[] AS int[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+ int4
+------
+
+(1 row)
+
+SELECT CAST(CAST('{a}'::text[] AS date[]) AS int[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+ int4
+------
+
+(1 row)
+
+-- test with user-defined type, domain, array over domain, domain over array
+CREATE DOMAIN d_int42 as int check (value = 42) NOT NULL;
+CREATE DOMAIN d_int42arr as d_int42[];
+CREATE DOMAIN d_int42arr1 as d_int42arr;
+CREATE DOMAIN d_int8 as int8 check (value > 0);
+CREATE DOMAIN d_int8arr as d_int8[];
+CREATE DOMAIN d_char3_not_null as char(3) NOT NULL;
+CREATE DOMAIN d_varchar as varchar(3) NOT NULL;
+CREATE DOMAIN d_int_arr as int[] check (value = '{41, 43}') NOT NULL;
+CREATE TYPE comp_domain_with_typmod AS (a d_char3_not_null, b int);
+CREATE TYPE comp2 AS (a d_varchar);
+CREATE DOMAIN d_numeric as numeric(4,4);
+SELECT CAST(1.0::float4 AS d_numeric DEFAULT NULL ON CONVERSION ERROR);
+ d_numeric
+-----------
+
+(1 row)
+
+SELECT CAST('{1.0}' AS numeric(4,1)[] DEFAULT NULL ON CONVERSION ERROR);
+ numeric
+---------
+ {1.0}
+(1 row)
+
+SELECT CAST('{0.1, 1.0}' AS d_numeric[] DEFAULT NULL ON CONVERSION ERROR);
+ d_numeric
+-----------
+
+(1 row)
+
+SELECT CAST(11 AS d_int42 DEFAULT 41 ON CONVERSION ERROR); -- error
+ERROR: value for domain d_int42 violates check constraint "d_int42_check"
+SELECT CAST(NULL AS d_int42 DEFAULT NULL ON CONVERSION ERROR); -- error
+ERROR: domain d_int42 does not allow null values
+SELECT CAST(11 AS d_int42 DEFAULT 42 ON CONVERSION ERROR);
+ d_int42
+---------
+ 42
+(1 row)
+
+SELECT CAST(NULL AS d_int42 DEFAULT 42 ON CONVERSION ERROR);
+ d_int42
+---------
+ 42
+(1 row)
+
+SELECT CAST(ARRAY[42]::d_int42arr1 AS d_int8 DEFAULT '1' ON CONVERSION ERROR);
+ array
+-------
+ 1
+(1 row)
+
+SELECT CAST(ARRAY[42]::d_int42arr1 AS d_int8[] DEFAULT '{1}' ON CONVERSION ERROR);
+ array
+-------
+ {42}
+(1 row)
+
+SELECT CAST(ARRAY[42]::d_int42arr1 AS d_int8arr DEFAULT '{1}' ON CONVERSION ERROR);
+ array
+-------
+ {42}
+(1 row)
+
+SELECT CAST(ARRAY[42]::d_int42arr1 AS d_int8arr[] DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+SELECT CAST(ARRAY[42,41] AS d_int42[] DEFAULT '{42, 42}' ON CONVERSION ERROR);
+ array
+---------
+ {42,42}
+(1 row)
+
+SELECT CAST(ARRAY[42,41] AS d_int_arr DEFAULT '{41, 43}' ON CONVERSION ERROR);
+ array
+---------
+ {41,43}
+(1 row)
+
+SELECT CAST(ARRAY[42, 41]::d_int42arr1 AS d_int8arr DEFAULT '{1,2,3}' ON CONVERSION ERROR);
+ array
+---------
+ {1,2,3}
+(1 row)
+
+SELECT CAST(ARRAY[42, 41] AS d_int42arr1 DEFAULT '{42, 42}' ON CONVERSION ERROR);
+ array
+---------
+ {42,42}
+(1 row)
+
+SELECT
+ CAST(CAST(ARRAY[CAST(ARRAY[42, 42] AS d_int42arr)] AS d_int42arr1[]) AS d_int8arr[] DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------------
+ {"{42,42}"}
+(1 row)
+
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION ERROR); -- error
+ERROR: value too long for type character varying(3)
+LINE 1: SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION...
+ ^
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(123)' ON CONVERSION ERROR);
+ comp2
+-------
+ (123)
+(1 row)
+
+SELECT CAST('(,42)' AS comp_domain_with_typmod DEFAULT NULL ON CONVERSION ERROR);
+ comp_domain_with_typmod
+-------------------------
+
+(1 row)
+
+SELECT CAST('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1,2)' ON CONVERSION ERROR);
+ comp_domain_with_typmod
+-------------------------
+ ("1 ",2)
+(1 row)
+
+SELECT CAST('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1234,2)' ON CONVERSION ERROR); -- error
+ERROR: value too long for type character(3)
+LINE 1: ...ST('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1234,2)'...
+ ^
+SELECT CAST(ROW(NULL,42) AS comp_domain_with_typmod DEFAULT NULL ON CONVERSION ERROR); -- error
+ERROR: cannot cast type record to comp_domain_with_typmod when DEFAULT expression is specified in CAST ... ON CONVERSION ERROR
+LINE 1: SELECT CAST(ROW(NULL,42) AS comp_domain_with_typmod DEFAULT ...
+ ^
+HINT: Explicit cast is defined but definition is not error safe.
+-- test array coerce
+SELECT CAST(array['a'::text] AS int[] DEFAULT NULL ON CONVERSION ERROR);
+ array
+-------
+
+(1 row)
+
+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);
+ array
+---------
+ {21,22}
+(1 row)
+
+-- error: `three'::int` will fail earlier
+SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR);
+ERROR: invalid input syntax for type integer: "three"
+LINE 1: SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] ...
+ ^
+SELECT CAST(ARRAY[1, 'three'::int] AS int[] DEFAULT '{21,22}' ON CONVERSION 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)
+
+-- cast from type circle to type polygon is implemented as an SQL function, which cannot be error-safe
+SELECT CAST('<(3,5),0>'::circle as polygon DEFAULT NULL ON CONVERSION ERROR);
+ polygon
+---------
+
+(1 row)
+
+-- Safe type cast supports money as a target type, but not as a source type.
+SELECT CAST('123456789012345678'::numeric AS MONEY DEFAULT NULL ON CONVERSION ERROR);
+ money
+-------
+
+(1 row)
+
+SELECT CAST('123456789012345678'::int8 AS MONEY DEFAULT NULL ON CONVERSION ERROR);
+ money
+-------
+
+(1 row)
+
+SELECT CAST('2147483647'::int4 AS MONEY DEFAULT NULL ON CONVERSION ERROR);
+ money
+-------------------
+ $2,147,483,647.00
+(1 row)
+
+SELECT CAST('-2147483648'::int4 AS MONEY DEFAULT NULL ON CONVERSION ERROR);
+ money
+--------------------
+ -$2,147,483,648.00
+(1 row)
+
+SELECT CAST('-0'::int4 AS MONEY DEFAULT NULL ON CONVERSION ERROR);
+ money
+-------
+ $0.00
+(1 row)
+
+SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSION ERROR);
+ERROR: cannot cast type money to numeric 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)
+
+SELECT CAST('\x1234567890abcdef'::bytea AS uuid DEFAULT NULL ON CONVERSION ERROR);
+ uuid
+------
+
+(1 row)
+
+-- safe cast from uuid type to other data types
+SELECT CAST('5b35380a-7143-4912-9b55-f322699c6770'::uuid AS bytea DEFAULT NULL ON CONVERSION ERROR);
+ bytea
+------------------------------------
+ \x5b35380a714349129b55f322699c6770
+(1 row)
+
+-- safe cast 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
+FROM test_safecast;
+ text | to_regclass | expect_true | to_char | to_name
+-------------------+-------------+-------------+---------+-------------------
+ <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)
+
+-- safe type cast for type date/timestamp/timestamptz/interval
+CREATE TABLE test_safecast2(
+ col0 date, col1 timestamp, col2 timestamptz,
+ col3 interval, col4 time, col5 timetz);
+INSERT INTO test_safecast2 VALUES
+('-infinity', '-infinity', '-infinity', '-infinity',
+ '2003-03-07 15:36:39 America/New_York', '2003-07-07 15:36:39 America/New_York'),
+('-infinity', 'infinity', 'infinity', 'infinity', '11:59:59.99 PM', '11:59:59.99 PM PDT');
+SELECT col0 as date,
+ CAST(col0 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col0 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col0 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col0 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col0 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+ date | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale
+-----------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity | -infinity | | | -infinity
+ -infinity | -infinity | -infinity | | | -infinity
+(2 rows)
+
+SELECT col1 as timestamp,
+ CAST(col1 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col1 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col1 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col1 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col1 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+ timestamp | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale
+-----------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity | -infinity | | | -infinity
+ infinity | infinity | infinity | | | infinity
+(2 rows)
+
+SELECT col2 as timestamptz,
+ CAST(col2 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col2 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col2 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col2 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col2 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+ timestamptz | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale
+-------------+----------------+-----------+----------+-----------+--------------------
+ -infinity | -infinity | -infinity | | | -infinity
+ infinity | infinity | infinity | | | infinity
+(2 rows)
+
+SELECT col3 as interval,
+ CAST(col3 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col3 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col3 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col3 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col3 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale,
+ CAST(col3 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+ interval | to_timestamptz | to_date | to_times | to_timetz | to_timestamp_scale | to_interval_scale
+-----------+----------------+---------+----------+-----------+--------------------+-------------------
+ -infinity | | | | | | -infinity
+ infinity | | | | | | infinity
+(2 rows)
+
+SELECT col4 as time,
+ CAST(col4 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col4 AS timetz DEFAULT NULL ON CONVERSION ERROR) IS NOT NULL as to_timetz,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+ time | to_times | to_timetz | to_interval | to_interval_scale
+-------------+-------------+-----------+-------------------------------+-------------------------------
+ 15:36:39 | 15:36:39 | t | @ 15 hours 36 mins 39 secs | @ 15 hours 36 mins 39 secs
+ 23:59:59.99 | 23:59:59.99 | t | @ 23 hours 59 mins 59.99 secs | @ 23 hours 59 mins 59.99 secs
+(2 rows)
+
+SELECT col5 as timetz,
+ CAST(col5 AS time DEFAULT NULL ON CONVERSION ERROR) as to_time,
+ CAST(col5 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_time_scale,
+ CAST(col5 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col5 AS timetz(6) DEFAULT NULL ON CONVERSION ERROR) as to_timetz_scale,
+ CAST(col5 AS interval DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col5 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+ timetz | to_time | to_time_scale | to_timetz | to_timetz_scale | to_interval | to_interval_scale
+----------------+-------------+---------------+----------------+-----------------+-------------+-------------------
+ 15:36:39-04 | 15:36:39 | 15:36:39 | 15:36:39-04 | 15:36:39-04 | |
+ 23:59:59.99-07 | 23:59:59.99 | 23:59:59.99 | 23:59:59.99-07 | 23:59:59.99-07 | |
+(2 rows)
+
+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
+ERROR: functions in index expression must be marked IMMUTABLE
+LINE 1: CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as ...
+ ^
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as xid DEFAULT NULL ON CONVERSION ERROR))); -- error
+ERROR: data type xid has no default operator class for access method "btree"
+HINT: You must specify an operator class for the index or define a default operator class for the data type.
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as int DEFAULT NULL ON CONVERSION ERROR))); --ok
+SELECT pg_get_indexdef('cast_error_idx'::regclass);
+ pg_get_indexdef
+--------------------------------------------------------------------------------------------------------------------------------------
+ CREATE INDEX cast_error_idx ON public.test_safecast3 USING btree ((CAST(col0 AS integer DEFAULT NULL::integer ON CONVERSION ERROR)))
+(1 row)
+
+DROP VIEW safecastview;
+DROP VIEW safecastview1;
+DROP TABLE test_safecast;
+DROP TABLE test_safecast1;
+DROP TABLE test_safecast2;
+DROP TABLE test_safecast3;
+DROP TABLE tcast;
+DROP TABLE tcast1;
+DROP FUNCTION ret_int8;
+DROP FUNCTION ret_setint;
+DROP TYPE comp_domain_with_typmod;
+DROP TYPE comp2;
+DROP DOMAIN d_nota;
+DROP DOMAIN d_varchar;
+DROP DOMAIN d_int8arr;
+DROP DOMAIN d_int8;
+DROP DOMAIN d_int42arr1;
+DROP DOMAIN d_int42arr;
+DROP DOMAIN d_int42;
+DROP DOMAIN d_char3_not_null;
+DROP DOMAIN d_numeric;
+RESET extra_float_digits;
+RESET lc_monetary;
diff --git a/src/test/regress/expected/create_cast.out b/src/test/regress/expected/create_cast.out
index 0e69644bca2..68e69d1c584 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..e3f8a2807f9 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, this should not fail
+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/expected/xml.out b/src/test/regress/expected/xml.out
index 103a22a3b1d..815a5e8899f 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -18,6 +18,12 @@ SELECT * FROM xmltest;
2 | <value>two</value>
(2 rows)
+SELECT CAST('<wrong'::text AS xml DEFAULT NULL ON CONVERSION ERROR) as to_xml;
+ to_xml
+--------
+
+(1 row)
+
-- test non-throwing API, too
SELECT pg_input_is_valid('<value>one</value>', 'xml');
pg_input_is_valid
diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out
index 73c411118a3..69aea833169 100644
--- a/src/test/regress/expected/xml_1.out
+++ b/src/test/regress/expected/xml_1.out
@@ -22,6 +22,9 @@ SELECT * FROM xmltest;
----+------
(0 rows)
+SELECT CAST('<wrong'::text AS xml DEFAULT NULL ON CONVERSION ERROR) as to_xml;
+ERROR: unsupported XML feature
+DETAIL: This functionality requires the server to be built with libxml support.
-- test non-throwing API, too
SELECT pg_input_is_valid('<value>one</value>', 'xml');
ERROR: unsupported XML feature
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 734da057c34..c82b4b31423 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..d4643f9e2f2
--- /dev/null
+++ b/src/test/regress/sql/cast.sql
@@ -0,0 +1,424 @@
+SET extra_float_digits = 0;
+SET lc_monetary TO "C";
+
+-- 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);
+SELECT CAST('def'::text AS integer DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('2'::jsonb AS text DEFAULT 'fallback' ON CONVERSION ERROR);
+SELECT CAST('1' COLLATE "C" || 'h' AS text DEFAULT 'fallback' ON CONVERSION ERROR);
+SELECT CAST(concat('1' collate "C", 'h') AS int DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(65536 AS int2 DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(int8(65536) AS int2 DEFAULT NULL ON CONVERSION ERROR);
+
+-- 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 target type collation
+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));
+
+-- 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 cannot 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 tcast0(a INT, b int);
+INSERT INTO tcast0 VALUES(1,2), (65536,3);
+SET jit_above_cost = 0;
+SELECT CAST(a AS int2 DEFAULT NULL ON CONVERSION ERROR) FROM tcast0;
+SET jit_above_cost TO DEFAULT;
+SELECT CAST(a AS int2 DEFAULT NULL ON CONVERSION ERROR) FROM tcast0;
+SELECT CAST(ROW(1,2) AS tcast0 DEFAULT NULL ON CONVERSION ERROR) FROM tcast0 as t;
+DROP TABLE tcast0;
+
+SELECT CAST(12.111 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale;
+
+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 nested type casts; all expressions will be evaluated in an error-safe manner
+CREATE DOMAIN d_nota AS text CHECK (VALUE <> 'a');
+CREATE TABLE tcast1(a text, b text[], c int, d int8, e int8[]);
+INSERT INTO tcast1(a, b, d, e) values('a', '{a}', 2147483648, '{2147483648}');
+SELECT CAST (CAST(a AS int) AS text DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+SELECT CAST (CAST('a' AS int) AS text DEFAULT NULL ON CONVERSION ERROR) FROM tcast1; -- error
+SELECT CAST (CAST('a'::text AS int) AS text DEFAULT NULL ON CONVERSION ERROR) FROM tcast1; -- ok
+SELECT CAST (CAST(a AS d_nota) AS text DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+SELECT CAST (CAST('a' AS d_nota) AS text DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+
+SELECT CAST (CAST(d AS int) AS text DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+SELECT CAST (CAST('2147483648'::int8 AS int) AS text DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+
+SELECT CAST (CAST('{a}'::text[] AS int[]) AS text[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+SELECT CAST (CAST(b AS int[]) AS text[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+SELECT CAST (CAST(b AS d_nota[]) AS text[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+SELECT CAST (CAST(e AS int[]) AS text[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+
+-- test eval_const_expressions_mutator handle nested ArrayCoerceExpr
+SELECT CAST('{a}'::text[] AS int[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+SELECT CAST(CAST('{a}'::text[] AS date[]) AS int[] DEFAULT NULL ON CONVERSION ERROR) FROM tcast1;
+
+-- test with user-defined type, domain, array over domain, domain over array
+CREATE DOMAIN d_int42 as int check (value = 42) NOT NULL;
+CREATE DOMAIN d_int42arr as d_int42[];
+CREATE DOMAIN d_int42arr1 as d_int42arr;
+CREATE DOMAIN d_int8 as int8 check (value > 0);
+CREATE DOMAIN d_int8arr as d_int8[];
+CREATE DOMAIN d_char3_not_null as char(3) NOT NULL;
+CREATE DOMAIN d_varchar as varchar(3) NOT NULL;
+CREATE DOMAIN d_int_arr as int[] check (value = '{41, 43}') NOT NULL;
+CREATE TYPE comp_domain_with_typmod AS (a d_char3_not_null, b int);
+CREATE TYPE comp2 AS (a d_varchar);
+CREATE DOMAIN d_numeric as numeric(4,4);
+
+SELECT CAST(1.0::float4 AS d_numeric DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('{1.0}' AS numeric(4,1)[] DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('{0.1, 1.0}' AS d_numeric[] DEFAULT NULL ON CONVERSION ERROR);
+
+SELECT CAST(11 AS d_int42 DEFAULT 41 ON CONVERSION ERROR); -- error
+SELECT CAST(NULL AS d_int42 DEFAULT NULL ON CONVERSION ERROR); -- error
+SELECT CAST(11 AS d_int42 DEFAULT 42 ON CONVERSION ERROR);
+SELECT CAST(NULL AS d_int42 DEFAULT 42 ON CONVERSION ERROR);
+
+SELECT CAST(ARRAY[42]::d_int42arr1 AS d_int8 DEFAULT '1' ON CONVERSION ERROR);
+SELECT CAST(ARRAY[42]::d_int42arr1 AS d_int8[] DEFAULT '{1}' ON CONVERSION ERROR);
+SELECT CAST(ARRAY[42]::d_int42arr1 AS d_int8arr DEFAULT '{1}' ON CONVERSION ERROR);
+SELECT CAST(ARRAY[42]::d_int42arr1 AS d_int8arr[] DEFAULT NULL ON CONVERSION ERROR);
+
+SELECT CAST(ARRAY[42,41] AS d_int42[] DEFAULT '{42, 42}' ON CONVERSION ERROR);
+SELECT CAST(ARRAY[42,41] AS d_int_arr DEFAULT '{41, 43}' ON CONVERSION ERROR);
+SELECT CAST(ARRAY[42, 41]::d_int42arr1 AS d_int8arr DEFAULT '{1,2,3}' ON CONVERSION ERROR);
+SELECT CAST(ARRAY[42, 41] AS d_int42arr1 DEFAULT '{42, 42}' ON CONVERSION ERROR);
+SELECT
+ CAST(CAST(ARRAY[CAST(ARRAY[42, 42] AS d_int42arr)] AS d_int42arr1[]) AS d_int8arr[] DEFAULT NULL ON CONVERSION ERROR);
+
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(1232)' ON CONVERSION ERROR); -- error
+SELECT CAST('(NULL)' AS comp2 DEFAULT '(123)' ON CONVERSION ERROR);
+SELECT CAST('(,42)' AS comp_domain_with_typmod DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1,2)' ON CONVERSION ERROR);
+SELECT CAST('(NULL,42)' AS comp_domain_with_typmod DEFAULT '(1234,2)' ON CONVERSION ERROR); -- error
+SELECT CAST(ROW(NULL,42) AS comp_domain_with_typmod DEFAULT NULL ON CONVERSION ERROR); -- error
+
+-- test array coerce
+SELECT CAST(array['a'::text] AS int[] DEFAULT NULL ON CONVERSION 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);
+-- error: `three'::int` will fail earlier
+SELECT CAST(ARRAY[['1', '2'], ['three'::int, 'a']] AS int[] DEFAULT '{21,22}' ON CONVERSION ERROR);
+SELECT CAST(ARRAY[1, 'three'::int] AS int[] DEFAULT '{21,22}' ON CONVERSION 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);
+
+-- cast from type circle to type polygon is implemented as an SQL function, which cannot be error-safe
+SELECT CAST('<(3,5),0>'::circle as polygon DEFAULT NULL ON CONVERSION ERROR);
+
+-- Safe type cast supports money as a target type, but not as a source type.
+SELECT CAST('123456789012345678'::numeric AS MONEY DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('123456789012345678'::int8 AS MONEY DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('2147483647'::int4 AS MONEY DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('-2147483648'::int4 AS MONEY DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('-0'::int4 AS MONEY DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::money AS numeric DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST(NULL::money[] AS numeric[] DEFAULT NULL ON CONVERSION ERROR);
+
+-- safe cast from bytea type to other data types
+SELECT CAST ('\x112233445566778899'::bytea AS int8 DEFAULT 19 ON CONVERSION ERROR);
+SELECT CAST('\x123456789A'::bytea AS int4 DEFAULT 20 ON CONVERSION ERROR);
+SELECT CAST('\x123456'::bytea AS int2 DEFAULT 21 ON CONVERSION ERROR);
+SELECT CAST('\x1234567890abcdef'::bytea AS uuid DEFAULT NULL ON CONVERSION ERROR);
+
+-- safe cast from uuid type to other data types
+SELECT CAST('5b35380a-7143-4912-9b55-f322699c6770'::uuid AS bytea DEFAULT NULL ON CONVERSION ERROR);
+
+-- safe cast 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
+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;
+
+-- safe type cast for type date/timestamp/timestamptz/interval
+CREATE TABLE test_safecast2(
+ col0 date, col1 timestamp, col2 timestamptz,
+ col3 interval, col4 time, col5 timetz);
+INSERT INTO test_safecast2 VALUES
+('-infinity', '-infinity', '-infinity', '-infinity',
+ '2003-03-07 15:36:39 America/New_York', '2003-07-07 15:36:39 America/New_York'),
+('-infinity', 'infinity', 'infinity', 'infinity', '11:59:59.99 PM', '11:59:59.99 PM PDT');
+
+SELECT col0 as date,
+ CAST(col0 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col0 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col0 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col0 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col0 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+
+SELECT col1 as timestamp,
+ CAST(col1 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col1 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col1 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col1 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col1 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+
+SELECT col2 as timestamptz,
+ CAST(col2 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col2 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col2 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col2 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col2 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale
+FROM test_safecast2;
+
+SELECT col3 as interval,
+ CAST(col3 AS timestamptz DEFAULT NULL ON CONVERSION ERROR) as to_timestamptz,
+ CAST(col3 AS date DEFAULT NULL ON CONVERSION ERROR) as to_date,
+ CAST(col3 AS time DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col3 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col3 AS timestamp(2) DEFAULT NULL ON CONVERSION ERROR) as to_timestamp_scale,
+ CAST(col3 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+
+SELECT col4 as time,
+ CAST(col4 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_times,
+ CAST(col4 AS timetz DEFAULT NULL ON CONVERSION ERROR) IS NOT NULL as to_timetz,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col4 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+
+SELECT col5 as timetz,
+ CAST(col5 AS time DEFAULT NULL ON CONVERSION ERROR) as to_time,
+ CAST(col5 AS time(2) DEFAULT NULL ON CONVERSION ERROR) as to_time_scale,
+ CAST(col5 AS timetz DEFAULT NULL ON CONVERSION ERROR) as to_timetz,
+ CAST(col5 AS timetz(6) DEFAULT NULL ON CONVERSION ERROR) as to_timetz_scale,
+ CAST(col5 AS interval DEFAULT NULL ON CONVERSION ERROR) as to_interval,
+ CAST(col5 AS interval(2) DEFAULT NULL ON CONVERSION ERROR) as to_interval_scale
+FROM test_safecast2;
+
+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))); -- error
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as xid DEFAULT NULL ON CONVERSION ERROR))); -- error
+CREATE INDEX cast_error_idx ON test_safecast3((CAST(col0 as int DEFAULT NULL ON CONVERSION ERROR))); --ok
+SELECT pg_get_indexdef('cast_error_idx'::regclass);
+
+DROP VIEW safecastview;
+DROP VIEW safecastview1;
+DROP TABLE test_safecast;
+DROP TABLE test_safecast1;
+DROP TABLE test_safecast2;
+DROP TABLE test_safecast3;
+DROP TABLE tcast;
+DROP TABLE tcast1;
+DROP FUNCTION ret_int8;
+DROP FUNCTION ret_setint;
+DROP TYPE comp_domain_with_typmod;
+DROP TYPE comp2;
+DROP DOMAIN d_nota;
+DROP DOMAIN d_varchar;
+DROP DOMAIN d_int8arr;
+DROP DOMAIN d_int8;
+DROP DOMAIN d_int42arr1;
+DROP DOMAIN d_int42arr;
+DROP DOMAIN d_int42;
+DROP DOMAIN d_char3_not_null;
+DROP DOMAIN d_numeric;
+RESET extra_float_digits;
+RESET lc_monetary;
diff --git a/src/test/regress/sql/create_cast.sql b/src/test/regress/sql/create_cast.sql
index 32187853cc7..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..7ada4bb5504 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, this should not fail
+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/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql
index 0ea4f508837..df3a06f4d82 100644
--- a/src/test/regress/sql/xml.sql
+++ b/src/test/regress/sql/xml.sql
@@ -9,6 +9,8 @@ INSERT INTO xmltest VALUES (3, '<wrong');
SELECT * FROM xmltest;
+SELECT CAST('<wrong'::text AS xml DEFAULT NULL ON CONVERSION ERROR) as to_xml;
+
-- test non-throwing API, too
SELECT pg_input_is_valid('<value>one</value>', 'xml');
SELECT pg_input_is_valid('<value>one</', 'xml');
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 712d84128ca..7c9ea1ae47d 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2739,6 +2739,8 @@ STRLEN
SV
SYNCHRONIZATION_BARRIER
SYSTEM_INFO
+SafeTypeCastExpr
+SafeTypeCastState
SampleScan
SampleScanGetSampleSize_function
SampleScanState
--
2.34.1
[text/x-patch] v25-0006-error-safe-for-casting-geometry-data-type.patch (12.5K, 4-v25-0006-error-safe-for-casting-geometry-data-type.patch)
download | inline diff:
From 7aca3c439cda733eb3f9abcd81336bfb3316e9f8 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Fri, 20 Mar 2026 10:19:10 +0800
Subject: [PATCH v25 6/9] 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: lseg_center, path_poly, box_center,
box_circle, poly_center, poly_circle, circle_box.
Function that casting type circle to type polygon cannot be error safe, because
it's a SQL language function.
Author: jian he <[email protected]>
Reviewed-by: Amul Sul <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Corey Huinker <[email protected]>
Discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
Commitfest: https://commitfest.postgresql.org/patch/5941
---
src/backend/utils/adt/geo_ops.c | 203 +++++++++++++++++++++++++-------
1 file changed, 160 insertions(+), 43 deletions(-)
diff --git a/src/backend/utils/adt/geo_ops.c b/src/backend/utils/adt/geo_ops.c
index 350e86f6377..a59f1cabc44 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 bool 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);
+ (void) box_cn(&a, box1, NULL);
+ (void) box_cn(&b, box2, NULL);
PG_RETURN_FLOAT8(point_dt(&a, &b, NULL));
}
@@ -851,7 +852,8 @@ box_center(PG_FUNCTION_ARGS)
BOX *box = PG_GETARG_BOX_P(0);
Point *result = palloc_object(Point);
- box_cn(result, box);
+ if (!box_cn(result, box, fcinfo->context))
+ PG_RETURN_NULL();
PG_RETURN_POINT_P(result);
}
@@ -868,14 +870,31 @@ box_ar(BOX *box)
/* box_cn - stores the centerpoint of the box into *center.
*/
-static void
-box_cn(Point *center, BOX *box)
+static bool
+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 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).
*/
@@ -2329,13 +2348,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 +3327,7 @@ box_interpt_lseg(Point *result, BOX *box, LSEG *lseg)
if (result != NULL)
{
- box_cn(&point, box);
+ (void) box_cn(&point, box, NULL);
lseg_closept_point(result, lseg, &point);
}
@@ -4121,11 +4158,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 +4183,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 +4295,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 +4459,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 +4517,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 +4567,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 +5031,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 +5252,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 +5286,37 @@ 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();
}
@@ -5289,15 +5375,16 @@ circle_poly(PG_FUNCTION_ARGS)
/*
* Convert polygon to circle
*
- * The result must be preallocated.
+ * The parameter "result" must be preallocated.
*
* 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 +5393,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 +5440,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
[text/x-patch] v25-0002-error-safe-for-casting-jsonb-to-other-types-per-pg_cast.patch (6.4K, 5-v25-0002-error-safe-for-casting-jsonb-to-other-types-per-pg_cast.patch)
download | inline diff:
From bc037fcd82966fd9e1e5dd69c41fe035df951144 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Wed, 25 Mar 2026 08:35:25 +0800
Subject: [PATCH v25 2/9] 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)
Author: jian he <[email protected]>
Reviewed-by: Amul Sul <[email protected]>
Reviewed-by: Corey Huinker <[email protected]>
Discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
Commitfest: https://commitfest.postgresql.org/patch/5941
---
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 1b1a8f301f2..864c5ac1c85 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -1785,8 +1785,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
{
@@ -1807,12 +1807,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
@@ -1822,7 +1823,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)
{
@@ -1831,7 +1832,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);
@@ -1846,7 +1847,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)
{
@@ -1855,7 +1856,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
@@ -1876,7 +1877,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)
{
@@ -1885,7 +1886,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));
@@ -1903,7 +1904,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)
{
@@ -1912,7 +1913,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));
@@ -1930,7 +1931,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)
{
@@ -1939,7 +1940,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));
@@ -1957,7 +1958,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)
{
@@ -1966,7 +1967,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));
@@ -1984,7 +1985,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)
{
@@ -1993,7 +1994,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
[text/x-patch] v25-0007-error-safe-for-casting-circle-to-polygon.patch (5.2K, 6-v25-0007-error-safe-for-casting-circle-to-polygon.patch)
download | inline diff:
From b99e27217a0aedcc4d45ee794bb1f276f0aebaad Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Wed, 25 Mar 2026 17:19:18 +0800
Subject: [PATCH v25 7/9] error safe for casting circle to polygon
Previously:
Function that casting type circle to type polygon cannot be error safe, because
it's a SQL language function.
Now refactor it as a C/internal function, so it can error-safe.
Author: jian he <[email protected]>
Reviewed-by: Amul Sul <[email protected]>
Reviewed-by: Corey Huinker <[email protected]>
Discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
Commitfest: https://commitfest.postgresql.org/patch/5941
---
src/backend/catalog/system_functions.sql | 6 ---
src/backend/utils/adt/geo_ops.c | 52 ++++++++++++++++++++----
src/include/catalog/pg_proc.dat | 4 +-
3 files changed, 45 insertions(+), 17 deletions(-)
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 1c5b6d6df05..c3c0a6e84ed 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -99,12 +99,6 @@ CREATE OR REPLACE FUNCTION path_contain_pt(path, point)
IMMUTABLE PARALLEL SAFE STRICT COST 1
RETURN on_ppath($2, $1);
-CREATE OR REPLACE FUNCTION polygon(circle)
- RETURNS polygon
- LANGUAGE sql
- IMMUTABLE PARALLEL SAFE STRICT COST 1
-RETURN polygon(12, $1);
-
CREATE OR REPLACE FUNCTION age(timestamptz)
RETURNS interval
LANGUAGE sql
diff --git a/src/backend/utils/adt/geo_ops.c b/src/backend/utils/adt/geo_ops.c
index a59f1cabc44..4846fe10f9c 100644
--- a/src/backend/utils/adt/geo_ops.c
+++ b/src/backend/utils/adt/geo_ops.c
@@ -147,6 +147,7 @@ static bool path_decode(char *str, bool opentype, int npts, Point *p,
const char *type_name, const char *orig_string,
Node *escontext);
static char *path_encode(enum path_delim path_delim, int npts, Point *pt);
+static Datum circle_poly_internal(int32 npts, CIRCLE *circle, FunctionCallInfo fcinfo);
/*
@@ -5319,26 +5320,33 @@ fail:
PG_RETURN_NULL();
}
-
Datum
circle_poly(PG_FUNCTION_ARGS)
{
int32 npts = PG_GETARG_INT32(0);
CIRCLE *circle = PG_GETARG_CIRCLE_P(1);
+
+ PG_RETURN_DATUM(circle_poly_internal(npts, circle, fcinfo));
+}
+
+Datum
+circle_poly_internal(int32 npts, CIRCLE *circle, FunctionCallInfo fcinfo)
+{
POLYGON *poly;
int base_size,
size;
int i;
float8 angle;
float8 anglestep;
+ float8 temp;
if (FPzero(circle->radius))
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert circle with radius zero to polygon")));
if (npts < 2)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("must request at least 2 points")));
@@ -5347,7 +5355,7 @@ circle_poly(PG_FUNCTION_ARGS)
/* Check for integer overflow */
if (base_size / npts != sizeof(poly->p[0]) || size <= base_size)
- ereport(ERROR,
+ ereturn(fcinfo->context, (Datum) 0,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("too many points requested")));
@@ -5359,17 +5367,43 @@ 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)));
+ temp = 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, temp, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
+
+ temp = 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, temp, fcinfo->context);
+ if (SOFT_ERROR_OCCURRED(fcinfo->context))
+ goto fail;
}
make_bound_box(poly);
PG_RETURN_POLYGON_P(poly);
+
+fail:
+ PG_RETURN_NULL();
+}
+
+/* convert circle to 12-vertex polygon */
+Datum
+circle_to_poly(PG_FUNCTION_ARGS)
+{
+ int32 npts = 12;
+ CIRCLE *circle = PG_GETARG_CIRCLE_P(0);
+
+ PG_RETURN_DATUM(circle_poly_internal(npts, circle, fcinfo));
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 0118e970dda..3579cec5744 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3390,8 +3390,8 @@
proname => 'center', prorettype => 'point', proargtypes => 'circle',
prosrc => 'circle_center' },
{ oid => '1544', descr => 'convert circle to 12-vertex polygon',
- proname => 'polygon', prolang => 'sql', prorettype => 'polygon',
- proargtypes => 'circle', prosrc => 'see system_functions.sql' },
+ proname => 'polygon', prorettype => 'polygon',
+ proargtypes => 'circle', prosrc => 'circle_to_poly' },
{ oid => '1545', descr => 'number of points',
proname => 'npoints', prorettype => 'int4', proargtypes => 'path',
prosrc => 'path_npoints' },
--
2.34.1
[text/x-patch] v25-0001-error-safe-for-casting-text-to-other-types-per-pg_cast.patch (9.7K, 7-v25-0001-error-safe-for-casting-text-to-other-types-per-pg_cast.patch)
download | inline diff:
From dd67559918c5929a6da589abc7d287e0bc0b5797 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Fri, 20 Mar 2026 09:55:58 +0800
Subject: [PATCH v25 1/9] 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.
Author: jian he <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Amul Sul <[email protected]>
Reviewed-by: Corey Huinker <[email protected]>
Discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
Commitfest: https://commitfest.postgresql.org/patch/5941
---
src/backend/catalog/namespace.c | 66 +++++++++++++++++++++++----------
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(+), 24 deletions(-)
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 56b87d878e8..061089f04c7 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -442,6 +442,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;
@@ -458,7 +468,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,
@@ -515,7 +525,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")));
}
@@ -595,16 +605,22 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
{
int elevel = (flags & RVR_SKIP_LOCKED) ? DEBUG1 : ERROR;
- if (relation->schemaname)
+ if (escontext == NULL)
ereport(elevel,
- (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
- errmsg("could not obtain lock on relation \"%s.%s\"",
- relation->schemaname, relation->relname)));
+ errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ relation->schemaname ?
+ errmsg("could not obtain lock on relation \"%s.%s\"",
+ relation->schemaname, relation->relname) :
+ errmsg("could not obtain lock on relation \"%s\"",
+ relation->relname));
else
- ereport(elevel,
- (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
- errmsg("could not obtain lock on relation \"%s\"",
- relation->relname)));
+ ereturn(escontext, InvalidOid,
+ errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ relation->schemaname ?
+ errmsg("could not obtain lock on relation \"%s.%s\"",
+ relation->schemaname, relation->relname) :
+ errmsg("could not obtain lock on relation \"%s\"",
+ relation->relname));
return InvalidOid;
}
@@ -628,16 +644,22 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
{
int elevel = missing_ok ? DEBUG1 : ERROR;
- if (relation->schemaname)
+ if (escontext == NULL)
ereport(elevel,
- (errcode(ERRCODE_UNDEFINED_TABLE),
- errmsg("relation \"%s.%s\" does not exist",
- relation->schemaname, relation->relname)));
+ errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ relation->schemaname ?
+ errmsg("relation \"%s.%s\" does not exist",
+ relation->schemaname, relation->relname) :
+ errmsg("relation \"%s\" does not exist",
+ relation->relname));
else
- ereport(elevel,
- (errcode(ERRCODE_UNDEFINED_TABLE),
- errmsg("relation \"%s\" does not exist",
- relation->relname)));
+ ereturn(escontext, InvalidOid,
+ errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ relation->schemaname ?
+ errmsg("relation \"%s.%s\" does not exist",
+ relation->schemaname, relation->relname) :
+ errmsg("relation \"%s\" does not exist",
+ relation->relname));
}
return relId;
}
@@ -3624,6 +3646,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);
@@ -3642,7 +3670,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 7b1ee61bde6..56da3d7b440 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -2717,6 +2717,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;
@@ -2728,12 +2734,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 9453a3e4932..7f3141ba27d 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
[text/x-patch] v25-0003-extended-function-for-float-overflow-underflow-zero_divide.patch (2.3K, 8-v25-0003-extended-function-for-float-overflow-underflow-zero_divide.patch)
download | inline diff:
From ae964e316ba565febb4dd3bc8d1c5e32499aee51 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Wed, 25 Mar 2026 16:19:39 +0800
Subject: [PATCH v25 3/9] extended function for float
overflow/underflow/zero_divide
float_overflow_error_ext, float_underflow_error_ext,
float_zero_divide_error_ext.
Author: jian he <[email protected]>
Discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
Commitfest: https://commitfest.postgresql.org/patch/5941
---
src/backend/utils/adt/float.c | 24 ++++++++++++++++++++++++
src/include/utils/float.h | 3 +++
2 files changed, 27 insertions(+)
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index a06fef42901..7b1bdf1e574 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -123,6 +123,30 @@ float_zero_divide_error(void)
errmsg("division by zero")));
}
+float8
+float_overflow_error_ext(struct Node *escontext)
+{
+ ereturn(escontext, 0.0,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: overflow"));
+}
+
+float8
+float_underflow_error_ext(struct Node *escontext)
+{
+ ereturn(escontext, 0.0,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value out of range: underflow"));
+}
+
+float8
+float_zero_divide_error_ext(struct Node *escontext)
+{
+ ereturn(escontext, 0.0,
+ (errcode(ERRCODE_DIVISION_BY_ZERO),
+ errmsg("division by zero")));
+}
+
/*
* Returns -1 if 'val' represents negative infinity, 1 if 'val'
diff --git a/src/include/utils/float.h b/src/include/utils/float.h
index b340678ca92..5840bbf7dc8 100644
--- a/src/include/utils/float.h
+++ b/src/include/utils/float.h
@@ -33,6 +33,9 @@ extern PGDLLIMPORT int extra_float_digits;
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 float8 float_overflow_error_ext(struct Node *escontext);
+extern float8 float_underflow_error_ext(struct Node *escontext);
+extern float8 float_zero_divide_error_ext(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,
--
2.34.1
[text/x-patch] v25-0005-refactor-point_dt.patch (8.7K, 9-v25-0005-refactor-point_dt.patch)
download | inline diff:
From d8bb3a3748c1f16878d8cd3a7134d509759c2984 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Fri, 20 Mar 2026 10:18:01 +0800
Subject: [PATCH v25 5/9] refactor point_dt
Refactor point_dt to be error-safe for an upcoming patch use. Note for future
work: lseg_center, path_poly, box_center, box_circle, poly_center, poly_circle,
and circle_box will need error-safe
Author: jian he <[email protected]>
Reviewed-by: Amul Sul <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Corey Huinker <[email protected]>
Discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
Commitfest: https://commitfest.postgresql.org/patch/5941
---
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..350e86f6377 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,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], NULL));
}
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, 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 +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], NULL));
}
/*----------------------------------------------------------
@@ -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], NULL),
+ point_dt(&l2->p[0], &l2->p[1], NULL)));
}
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], NULL),
+ point_dt(&l2->p[0], &l2->p[1], NULL)));
}
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], NULL),
+ point_dt(&l2->p[0], &l2->p[1], NULL)));
}
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], NULL),
+ point_dt(&l2->p[0], &l2->p[1], NULL)));
}
@@ -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], 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 +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, NULL),
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, NULL),
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, NULL),
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, NULL),
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, NULL);
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, NULL);
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, NULL),
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, NULL), 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, NULL);
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
[text/x-patch] v25-0004-introduce-float8-safe-function.patch (3.9K, 10-v25-0004-introduce-float8-safe-function.patch)
download | inline diff:
From 04808be3b435e48ddad6b98a3d86b4d79911e4bf Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Wed, 25 Mar 2026 16:26:30 +0800
Subject: [PATCH v25 4/9] 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 very similar to existing non-safe version functions.
Author: jian he <[email protected]>
Reviewed-by: Amul Sul <[email protected]>
Reviewed-by: Corey Huinker <[email protected]>
Discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
Commitfest: https://commitfest.postgresql.org/patch/5941
---
src/include/utils/float.h | 66 ++++++++++++++++++++++++++-------------
1 file changed, 45 insertions(+), 21 deletions(-)
diff --git a/src/include/utils/float.h b/src/include/utils/float.h
index 5840bbf7dc8..ffa743d6273 100644
--- a/src/include/utils/float.h
+++ b/src/include/utils/float.h
@@ -112,16 +112,22 @@ 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))
+ return float_overflow_error_ext(escontext);
+
+ 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();
-
- return result;
+ return float8_pl_safe(val1, val2, NULL);
}
static inline float4
@@ -136,16 +142,22 @@ 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))
+ return float_overflow_error_ext(escontext);
+
+ 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();
-
- return result;
+ return float8_mi_safe(val1, val2, NULL);
}
static inline float4
@@ -163,19 +175,25 @@ 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();
+ return float_overflow_error_ext(escontext);
if (unlikely(result == 0.0) && val1 != 0.0 && val2 != 0.0)
- float_underflow_error();
+ return float_underflow_error_ext(escontext);
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)
{
@@ -193,21 +211,27 @@ 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();
+ return float_zero_divide_error_ext(escontext);
result = val1 / val2;
if (unlikely(isinf(result)) && !isinf(val1))
- float_overflow_error();
+ return float_overflow_error_ext(escontext);
if (unlikely(result == 0.0) && val1 != 0.0 && !isinf(val2))
- float_underflow_error();
+ return float_underflow_error_ext(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
view thread (75+ messages) latest in thread
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: [email protected]
Cc: [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected]
Subject: Re: CAST(... ON DEFAULT) - WIP build on top of Error-Safe User Functions
In-Reply-To: <CACJufxHGox47X4zNtVeNw7H=SZ7ATAAD5SEYpvxa2o5mPV0XCA@mail.gmail.com>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox