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: Wed, 25 Mar 2026 21:13:49 +0800
Message-ID: <CACJufxHx-UfprE6P4_ZB_cOYktHd4pLMNx=jWJFOGGGFj2YZWQ@mail.gmail.com> (raw)
In-Reply-To: <[email protected]>
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]>
On Tue, Mar 24, 2026 at 9:13 PM Peter Eisentraut <[email protected]> wrote:
>
> In patch 0012, I did not apply the changes involving
> float_*flow_error() in dtof(). These should be considered together
> with the error handling changes in patch 0018 (about which below).
>
> Patch 0004 is probably ok, I just need to study a bit more, since it's
> a bit different from the other ones.
>
> In the comment for patch 0008 you write that supporting soft errors in
> int4_cash() is not easy. I suppose this is because of the external
> function call to int8mul()? Maybe that was necessary in ancient
> times, but int8mul() is nowadays just a fmgr wrapper around
> pg_mul_s64_overflow(), which you can call directly in int4_cash(), and
> then it should be easy.
>
cash_numeric is not easy to refactor safely,
To do this, we have to refactor (numeric_round, numeric_div), which
will require refactoring more functions.
So CoercionErrorSafe_Internal still checks whether the source
element/base type is MONEYOID.
> About patch 0017 (jsonb): Calling ereturn() in a function that returns
> void, well, you make that work, but it seems a bit weird. Maybe
> cannotCastJsonbValue() could be changed to return a dummy datum, and
> then you could just call it like
>
> return cannotCastJsonbValue(...);
>
ok.
> Similarly, about patch 0018 (refactor float error): I think this is a
> fragile API. float_overflow_error() now will either ereport or just
> errsave. But nothing enforces that callers do the right thing. For
> example, in patch 0019, you change float_zero_divide_error(NULL) to
> float_zero_divide_error(escontext), but don't insert a return, so the
> code will continue to perform the division. Similar to patch 0017,
> maybe these functions should be changed to have a non-void return
> value. Or maybe make separate functions to be used in places where we
> want soft error handling.
>
Add argument to float_overflow_error, extern void
float_underflow_error, float_zero_divide_error
requires extensive refactoring.
So I have added
+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);
> In the comment for patch 0021 you write that the function casting type
> circle to type polygon cannot be error safe, because it's a SQL
> language function. I suggest to convert this to a C function and make
> the required changes there.
>
Please see 007.
Now with this entire v24 patchset, the only built-in cast function
that is not error safe is numeric_cash.
I'll think about CREATE CAST ... WITH SAFE FUNCTION syntax, later.
Right now, I am still using CoercionErrorSafe_Internal to check if CAST DEFAULT
can be applied to this combination of source type and target type.
--
jian
https://www.enterprisedb.com/
Attachments:
[text/x-patch] v24-0008-error-safe-for-casting-money-data-type.patch (3.7K, 2-v24-0008-error-safe-for-casting-money-data-type.patch)
download | inline diff:
From 11e8df6c393b886ac446f2fd9be35b6367c7b241 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Wed, 25 Mar 2026 19:12:10 +0800
Subject: [PATCH v24 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] v24-0005-refactor-point_dt.patch (8.7K, 3-v24-0005-refactor-point_dt.patch)
download | inline diff:
From 345fa4afc88a7706c54a16a5047cb7aa777ecda2 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Fri, 20 Mar 2026 10:18:01 +0800
Subject: [PATCH v24 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] v24-0002-error-safe-for-casting-jsonb-to-other-types-per-pg_cast.patch (6.4K, 4-v24-0002-error-safe-for-casting-jsonb-to-other-types-per-pg_cast.patch)
download | inline diff:
From ef8320545ada7593a2e59a300c38032e4f4cb901 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Wed, 25 Mar 2026 08:35:25 +0800
Subject: [PATCH v24 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] v24-0006-error-safe-for-casting-geometry-data-type.patch (12.5K, 5-v24-0006-error-safe-for-casting-geometry-data-type.patch)
download | inline diff:
From 01101c2f100ffbf66b9cd0e76fe66cbd18a4863f Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Fri, 20 Mar 2026 10:19:10 +0800
Subject: [PATCH v24 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] v24-0009-CAST-expr-AS-newtype-DEFAULT-expr-ON-CONVERSION-ERROR.patch (136.3K, 6-v24-0009-CAST-expr-AS-newtype-DEFAULT-expr-ON-CONVERSION-ERROR.patch)
download | inline diff:
From 884b32b9257ec5a45e35c84ddbdc2ac067343add Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Wed, 25 Mar 2026 21:05:46 +0800
Subject: [PATCH v24 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 | 66 +-
src/backend/executor/execExprInterp.c | 29 +
src/backend/jit/llvm/llvmjit_expr.c | 56 +
src/backend/nodes/makefuncs.c | 1 +
src/backend/nodes/nodeFuncs.c | 53 +
src/backend/optimizer/util/clauses.c | 80 +-
src/backend/parser/gram.y | 23 +
src/backend/parser/parse_agg.c | 9 +
src/backend/parser/parse_coerce.c | 145 ++-
src/backend/parser/parse_expr.c | 357 ++++++-
src/backend/parser/parse_func.c | 4 +
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 | 38 +
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 | 989 ++++++++++++++++++
src/test/regress/expected/create_cast.out | 5 +
src/test/regress/expected/equivclass.out | 7 +
src/test/regress/parallel_schedule | 2 +-
src/test/regress/sql/cast.sql | 401 +++++++
src/test/regress/sql/create_cast.sql | 1 +
src/test/regress/sql/equivclass.sql | 3 +
src/tools/pgindent/typedefs.list | 2 +
40 files changed, 2380 insertions(+), 88 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..c251179454a 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;
@@ -2736,6 +2749,7 @@ ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid,
FunctionCallInfo fcinfo;
int argno;
ListCell *lc;
+ Node *context = NULL;
/* Check permission to call function */
aclresult = object_aclcheck(ProcedureRelationId, funcid, GetUserId(), ACL_EXECUTE);
@@ -2743,6 +2757,9 @@ ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid,
aclcheck_error(aclresult, OBJECT_FUNCTION, get_func_name(funcid));
InvokeFunctionExecuteHook(funcid);
+ if (IsA(node, FuncExpr) && ((FuncExpr *) node)->errorsafe)
+ context = (Node *) state->escontext;
+
/*
* Safety check on nargs. Under normal circumstances this should never
* fail, as parser should check sooner. But possibly it might fail if
@@ -2769,7 +2786,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,
+ context, NULL);
/* Keep extra copies of this info to save an indirection at runtime */
scratch->d.func.fn_addr = flinfo->fn_addr;
@@ -4770,6 +4788,52 @@ 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 must be
+ * NULL, as this evaluation should not be error-safe.
+ */
+ state->escontext = NULL;
+ ExecInitExprRec(stcstate->stcexpr->defexpr, state, resv, resnull);
+ state->escontext = saved_escontext;
+
+ stcstate->jump_end = state->steps_len;
+ }
+}
+
/*
* Push steps to evaluate a JsonExpr and its various subsidiary expressions.
*/
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 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/makefuncs.c b/src/backend/nodes/makefuncs.c
index 3cd35c5c457..5a1091d0d48 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -605,6 +605,7 @@ makeFuncExpr(Oid funcid, Oid rettype, List *args,
funcexpr->funccollid = funccollid;
funcexpr->inputcollid = inputcollid;
funcexpr->args = args;
+ funcexpr->errorsafe = false;
funcexpr->location = -1;
return funcexpr;
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..8a54ba26890 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
@@ -2861,6 +2865,7 @@ eval_const_expressions_mutator(Node *node,
newexpr->funccollid = expr->funccollid;
newexpr->inputcollid = expr->inputcollid;
newexpr->args = args;
+ newexpr->errorsafe = expr->errorsafe;
newexpr->location = expr->location;
return (Node *) newexpr;
}
@@ -3136,7 +3141,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 +3161,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 +3272,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:
@@ -3419,7 +3460,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 +3656,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 +3739,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 +3837,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 +4167,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:
@@ -4546,6 +4588,7 @@ simplify_function(Oid funcid, Oid result_type, int32 result_typmod,
fexpr.funccollid = result_collid;
fexpr.inputcollid = input_collid;
fexpr.args = args;
+ fexpr.errorsafe = false;
fexpr.location = -1;
req.type = T_SupportRequestSimplify;
@@ -5226,10 +5269,11 @@ evaluate_function(Oid funcid, Oid result_type, int32 result_typmod,
newexpr->funccollid = result_collid; /* doesn't matter */
newexpr->inputcollid = input_collid;
newexpr->args = args;
+ newexpr->errorsafe = context->escontext ? true : false;
newexpr->location = -1;
return evaluate_expr((Expr *) newexpr, result_type, result_typmod,
- result_collid);
+ result_collid, (Node *) context->escontext);
}
/*
@@ -5338,6 +5382,8 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
fexpr->funccollid = result_collid; /* doesn't matter */
fexpr->inputcollid = input_collid;
fexpr->args = args;
+ /* Cannot evaluate SQL language functions in an error-safe manner. */
+ fexpr->errorsafe = false;
fexpr->location = -1;
/* Fetch the function body */
@@ -5683,10 +5729,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 +5760,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 +5780,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..cdd62044a60 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;
@@ -913,6 +982,9 @@ build_coercion_expression(Node *node,
fexpr = makeFuncExpr(funcId, targetTypeId, args,
InvalidOid, InvalidOid, cformat);
+ if (escontext)
+ fexpr->errorsafe = true;
+
fexpr->location = location;
return (Node *) fexpr;
}
@@ -950,14 +1022,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 +1213,8 @@ coerce_record_to_complex(ParseState *pstate, Node *node,
baseTypeId, baseTypeMod,
targetTypeId,
ccontext, cformat, location,
- false);
+ false,
+ NULL);
}
return (Node *) rowexpr;
@@ -1295,7 +1369,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..3d38d468c42 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,
+ bool *is_coercible);
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,14 @@ 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().
+ *
+ * is_coercible is typically NULL, except during parse analysis for
+ * CAST(... DEFAULT ... ON CONVERSION ERROR). When provided, it defaults
+ * to true but will be set to false 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, bool *is_coercible)
{
ArrayExpr *newa = makeNode(ArrayExpr);
List *newelems = NIL;
@@ -2041,6 +2057,7 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
ListCell *element;
Oid coerce_type;
bool coerce_hard;
+ ErrorSaveContext escontext = {T_ErrorSaveContext};
/*
* Transform the element expressions
@@ -2064,9 +2081,10 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
(A_ArrayExpr *) e,
array_type,
element_type,
- typmod);
+ typmod,
+ is_coercible);
/* we certainly have an array here */
- Assert(array_type == InvalidOid || array_type == exprType(newe));
+ Assert(is_coercible || array_type == InvalidOid || array_type == exprType(newe));
newa->multidims = true;
}
else
@@ -2107,6 +2125,8 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
}
else
{
+ Assert(is_coercible == NULL);
+
/* Can't handle an empty array without a target type */
if (newelems == NIL)
ereport(ERROR,
@@ -2153,28 +2173,58 @@ 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 is_coercible 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 (is_coercible && (!*is_coercible))
+ newe = e;
+ else
+ {
+ Node *ecopy = NULL;
+
+ if (is_coercible)
+ ecopy = copyObject(e);
+
+ newe = coerce_to_target_type_extended(pstate, e,
+ exprType(e),
+ coerce_type,
+ typmod,
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ -1,
+ (Node *) &escontext);
+ if (newe == NULL)
+ {
+ /*
+ * Cannot coerce. Raise an error or append the transformed
+ * element to the list.
+ */
+ if (!is_coercible)
+ 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
+ {
+ newe = ecopy;
+ *is_coercible = false;
+ }
+ }
+ }
}
else
newe = coerce_to_common_type(pstate, e,
@@ -2723,17 +2773,63 @@ 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;
+ bool is_coercible = true;
+ ErrorSaveContext escontext = {T_ErrorSaveContext};
/* 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 +2858,8 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
(A_ArrayExpr *) arg,
targetBaseType,
elementType,
- targetBaseTypmod);
+ targetBaseTypmod,
+ defexpr ? &is_coercible : NULL);
}
else
expr = transformExprRecurse(pstate, arg);
@@ -2783,20 +2880,210 @@ 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 ? expr : copyObject(expr);
+
+ if (is_coercible)
+ {
+ castexpr = coerce_to_target_type_extended(pstate, expr, inputType,
+ targetType, targetTypmod,
+ COERCION_EXPLICIT,
+ COERCE_EXPLICIT_CAST,
+ location,
+ defexpr ? (Node *) &escontext : NULL);
+
+ /* Cannot coerce. Set is_coercible to false or raise an error */
+ if (!castexpr)
+ {
+ is_coercible = false;
+
+ if (!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);
+
+ Assert(is_coercible || castexpr == NULL);
+
+ 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 +3499,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..81a4d5c58ef 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -768,6 +768,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
funcexpr->funcformat = funcformat;
/* funccollid and inputcollid will be set by parse_collate.c */
funcexpr->args = fargs;
+ funcexpr->errorsafe = false;
funcexpr->location = location;
retval = (Node *) funcexpr;
@@ -2743,6 +2744,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 b67e56e6c5a..2a8952a50c9 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
*
@@ -799,6 +835,8 @@ typedef struct FuncExpr
Oid inputcollid pg_node_attr(query_jumble_ignore);
/* arguments to the function */
List *args;
+ /* True if the function supports soft-error evaluation */
+ bool errorsafe;
/* token location, or -1 if unknown */
ParseLoc location;
} FuncExpr;
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..7cfec864448
--- /dev/null
+++ b/src/test/regress/expected/cast.out
@@ -0,0 +1,989 @@
+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 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,
+ CAST(col0 AS xml DEFAULT NULL ON CONVERSION ERROR) as to_xml
+FROM test_safecast;
+ text | to_regclass | expect_true | to_char | to_name | to_xml
+-------------------+-------------+-------------+---------+-------------------+--------
+ <value>one</value | | t | < | <value>one</value |
+(1 row)
+
+SELECT CAST('192.168.1.x' as inet DEFAULT NULL ON CONVERSION ERROR);
+ inet
+------
+
+(1 row)
+
+SELECT CAST('192.168.1.2/30' as cidr DEFAULT NULL ON CONVERSION ERROR);
+ cidr
+------
+
+(1 row)
+
+SELECT CAST('22:00:5c:08:55:08:01:02'::macaddr8 as macaddr DEFAULT NULL ON CONVERSION ERROR);
+ macaddr
+---------
+
+(1 row)
+
+-- safe cast betweeen range data types
+SELECT CAST('[1,2]'::int4range AS int4multirange DEFAULT NULL ON CONVERSION ERROR);
+ int4multirange
+----------------
+ {[1,3)}
+(1 row)
+
+SELECT CAST('[1,2]'::int8range AS int8multirange DEFAULT NULL ON CONVERSION ERROR);
+ int8multirange
+----------------
+ {[1,3)}
+(1 row)
+
+SELECT CAST('[1,2]'::numrange AS nummultirange DEFAULT NULL ON CONVERSION ERROR);
+ nummultirange
+---------------
+ {[1,2]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::daterange AS datemultirange DEFAULT NULL ON CONVERSION ERROR);
+ datemultirange
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::tsrange AS tsmultirange DEFAULT NULL ON CONVERSION ERROR);
+ tsmultirange
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+SELECT CAST('[-infinity,infinity]'::tstzrange AS tstzmultirange DEFAULT NULL ON CONVERSION ERROR);
+ tstzmultirange
+------------------------
+ {[-infinity,infinity]}
+(1 row)
+
+-- safe cast betweeen numeric data types
+CREATE TABLE test_safecast1(
+ col1 float4, col2 float8, col3 numeric, col4 numeric[],
+ col5 int2 default 32767,
+ col6 int4 default 32768,
+ col7 int8 default 4294967296);
+INSERT INTO test_safecast1 VALUES('11.1234', '11.1234', '9223372036854775808', '{11.1234}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('inf', 'inf', 'inf', '{11.1234, 12, inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('-inf', '-inf', '-inf', '{11.1234, 12, -inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('NaN', 'NaN', 'NaN', '{11.1234, 12, -inf, NaN}'::numeric[]);
+SELECT col5 as int2,
+ CAST(col5 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col5 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col5 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col5 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col5 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col5 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col5 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col5 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ int2 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+ 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767 | 32767.0
+(4 rows)
+
+SELECT col6 as int4,
+ CAST(col6 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col6 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col6 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col6 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col6 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col6 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col6 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col6 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ int4 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+ 32768 | | 32768 | 32768 | 32768 | 32768 | 32768 | 32768 | 32768.0
+(4 rows)
+
+SELECT col7 as int8,
+ CAST(col7 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col7 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col7 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col7 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col7 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col7 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col7 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col7 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ int8 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+------------+---------+---------+--------+------------+-------------+------------+------------+------------------
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+ 4294967296 | | | | 4294967296 | 4.29497e+09 | 4294967296 | 4294967296 |
+(4 rows)
+
+SELECT col3 as numeric,
+ CAST(col3 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col3 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col3 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col3 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col3 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col3 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col3 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col3 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ numeric | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+---------------------+---------+---------+--------+---------+-------------+----------------------+---------------------+------------------
+ 9223372036854775808 | | | | | 9.22337e+18 | 9.22337203685478e+18 | 9223372036854775808 |
+ Infinity | | | | | Infinity | Infinity | Infinity |
+ -Infinity | | | | | -Infinity | -Infinity | -Infinity |
+ NaN | | | | | NaN | NaN | NaN | NaN
+(4 rows)
+
+SELECT col4 as num_arr,
+ CAST(col4 AS int2[] DEFAULT NULL ON CONVERSION ERROR) as int2arr,
+ CAST(col4 AS int4[] DEFAULT NULL ON CONVERSION ERROR) as int4arr,
+ CAST(col4 as int8[] DEFAULT NULL ON CONVERSION ERROR) as int8arr,
+ CAST(col4 as float4[] DEFAULT NULL ON CONVERSION ERROR) as f4arr,
+ CAST(col4 as float8[] DEFAULT NULL ON CONVERSION ERROR) as f8arr,
+ CAST(col4 as numeric(10,1)[] DEFAULT NULL ON CONVERSION ERROR) as numarr
+FROM test_safecast1;
+ num_arr | int2arr | int4arr | int8arr | f4arr | f8arr | numarr
+----------------------------+---------+---------+---------+----------------------------+----------------------------+--------
+ {11.1234} | {11} | {11} | {11} | {11.1234} | {11.1234} | {11.1}
+ {11.1234,12,Infinity,NaN} | | | | {11.1234,12,Infinity,NaN} | {11.1234,12,Infinity,NaN} |
+ {11.1234,12,-Infinity,NaN} | | | | {11.1234,12,-Infinity,NaN} | {11.1234,12,-Infinity,NaN} |
+ {11.1234,12,-Infinity,NaN} | | | | {11.1234,12,-Infinity,NaN} | {11.1234,12,-Infinity,NaN} |
+(4 rows)
+
+SELECT col1 as float4,
+ CAST(col1 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col1 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col1 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col1 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col1 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col1 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col1 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col1 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ float4 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-----------+---------+---------+--------+---------+-----------+------------------+------------+------------------
+ 11.1234 | 11 | 11 | | 11 | 11.1234 | 11.1233997344971 | 11.1234 | 11.1
+ Infinity | | | | | Infinity | Infinity | Infinity |
+ -Infinity | | | | | -Infinity | -Infinity | -Infinity |
+ NaN | | | | | NaN | NaN | NaN | NaN
+(4 rows)
+
+SELECT col2 as float8,
+ CAST(col2 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col2 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col2 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col2 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col2 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col2 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col2 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col2 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+ float8 | to_int2 | to_int4 | to_oid | to_int8 | to_float4 | to_float8 | to_numeric | to_numeric_scale
+-----------+---------+---------+--------+---------+-----------+-----------+------------+------------------
+ 11.1234 | 11 | 11 | | 11 | 11.1234 | 11.1234 | 11.1234 | 11.1
+ Infinity | | | | | Infinity | Infinity | Infinity |
+ -Infinity | | | | | -Infinity | -Infinity | -Infinity |
+ NaN | | | | | NaN | NaN | NaN | NaN
+(4 rows)
+
+-- 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 FUNCTION ret_int8;
+DROP FUNCTION ret_setint;
+DROP TYPE comp_domain_with_typmod;
+DROP TYPE comp2;
+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/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..21c1c5c3ce0
--- /dev/null
+++ b/src/test/regress/sql/cast.sql
@@ -0,0 +1,401 @@
+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 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,
+ CAST(col0 AS xml DEFAULT NULL ON CONVERSION ERROR) as to_xml
+FROM test_safecast;
+
+SELECT CAST('192.168.1.x' as inet DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('192.168.1.2/30' as cidr DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('22:00:5c:08:55:08:01:02'::macaddr8 as macaddr DEFAULT NULL ON CONVERSION ERROR);
+
+-- safe cast betweeen range data types
+SELECT CAST('[1,2]'::int4range AS int4multirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[1,2]'::int8range AS int8multirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[1,2]'::numrange AS nummultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::daterange AS datemultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::tsrange AS tsmultirange DEFAULT NULL ON CONVERSION ERROR);
+SELECT CAST('[-infinity,infinity]'::tstzrange AS tstzmultirange DEFAULT NULL ON CONVERSION ERROR);
+
+-- safe cast betweeen numeric data types
+CREATE TABLE test_safecast1(
+ col1 float4, col2 float8, col3 numeric, col4 numeric[],
+ col5 int2 default 32767,
+ col6 int4 default 32768,
+ col7 int8 default 4294967296);
+INSERT INTO test_safecast1 VALUES('11.1234', '11.1234', '9223372036854775808', '{11.1234}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('inf', 'inf', 'inf', '{11.1234, 12, inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('-inf', '-inf', '-inf', '{11.1234, 12, -inf, NaN}'::numeric[]);
+INSERT INTO test_safecast1 VALUES('NaN', 'NaN', 'NaN', '{11.1234, 12, -inf, NaN}'::numeric[]);
+
+SELECT col5 as int2,
+ CAST(col5 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col5 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col5 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col5 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col5 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col5 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col5 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col5 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col6 as int4,
+ CAST(col6 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col6 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col6 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col6 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col6 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col6 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col6 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col6 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col7 as int8,
+ CAST(col7 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col7 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col7 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col7 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col7 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col7 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col7 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col7 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col3 as numeric,
+ CAST(col3 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col3 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col3 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col3 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col3 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col3 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col3 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col3 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col4 as num_arr,
+ CAST(col4 AS int2[] DEFAULT NULL ON CONVERSION ERROR) as int2arr,
+ CAST(col4 AS int4[] DEFAULT NULL ON CONVERSION ERROR) as int4arr,
+ CAST(col4 as int8[] DEFAULT NULL ON CONVERSION ERROR) as int8arr,
+ CAST(col4 as float4[] DEFAULT NULL ON CONVERSION ERROR) as f4arr,
+ CAST(col4 as float8[] DEFAULT NULL ON CONVERSION ERROR) as f8arr,
+ CAST(col4 as numeric(10,1)[] DEFAULT NULL ON CONVERSION ERROR) as numarr
+FROM test_safecast1;
+
+SELECT col1 as float4,
+ CAST(col1 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col1 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col1 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col1 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col1 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col1 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col1 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col1 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+SELECT col2 as float8,
+ CAST(col2 AS int2 DEFAULT NULL ON CONVERSION ERROR) as to_int2,
+ CAST(col2 AS int4 DEFAULT NULL ON CONVERSION ERROR) as to_int4,
+ CAST(col2 AS oid DEFAULT NULL ON CONVERSION ERROR) as to_oid,
+ CAST(col2 as int8 DEFAULT NULL ON CONVERSION ERROR) as to_int8,
+ CAST(col2 as float4 DEFAULT NULL ON CONVERSION ERROR) as to_float4,
+ CAST(col2 as float8 DEFAULT NULL ON CONVERSION ERROR) as to_float8,
+ CAST(col2 as numeric DEFAULT NULL ON CONVERSION ERROR) as to_numeric,
+ CAST(col2 as numeric(10,1) DEFAULT NULL ON CONVERSION ERROR) as to_numeric_scale
+FROM test_safecast1;
+
+-- 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 FUNCTION ret_int8;
+DROP FUNCTION ret_setint;
+DROP TYPE comp_domain_with_typmod;
+DROP TYPE comp2;
+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/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index dbbec84b222..ddfbd84c8d6 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2738,6 +2738,8 @@ STRLEN
SV
SYNCHRONIZATION_BARRIER
SYSTEM_INFO
+SafeTypeCastExpr
+SafeTypeCastState
SampleScan
SampleScanGetSampleSize_function
SampleScanState
--
2.34.1
[text/x-patch] v24-0004-introduce-float8-safe-function.patch (3.9K, 7-v24-0004-introduce-float8-safe-function.patch)
download | inline diff:
From 55cc9b3722729a579cae880801b757caa62a9760 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Wed, 25 Mar 2026 16:26:30 +0800
Subject: [PATCH v24 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
[text/x-patch] v24-0007-error-safe-for-casting-circle-to-polygon.patch (5.2K, 8-v24-0007-error-safe-for-casting-circle-to-polygon.patch)
download | inline diff:
From 6137e9e9bf3049f808e4637acec48fce287b3830 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Wed, 25 Mar 2026 17:19:18 +0800
Subject: [PATCH v24 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] v24-0003-extended-function-for-float-overflow-underflow-zero_divide.patch (2.3K, 9-v24-0003-extended-function-for-float-overflow-underflow-zero_divide.patch)
download | inline diff:
From c6f4e32727a742a21eaee3e546c92f92fe5bda54 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Wed, 25 Mar 2026 16:19:39 +0800
Subject: [PATCH v24 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] v24-0001-error-safe-for-casting-text-to-other-types-per-pg_cast.patch (9.7K, 10-v24-0001-error-safe-for-casting-text-to-other-types-per-pg_cast.patch)
download | inline diff:
From 808dc0dbad828897e15c7e784f9b385b6b8ee38d Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Fri, 20 Mar 2026 09:55:58 +0800
Subject: [PATCH v24 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
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: <CACJufxHx-UfprE6P4_ZB_cOYktHd4pLMNx=jWJFOGGGFj2YZWQ@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