Skip to content

Commit 609935a

Browse files
jianhe-funCommitfest Bot
authored andcommitted
CAST(expr AS newtype DEFAULT ON ERROR)
Now that the type coercion node is error-safe, we also need to ensure that when a coercion fails, it falls back to evaluating the default node. draft doc also added. We cannot simply prohibit user-defined functions in pg_cast for safe cast evaluation because CREATE CAST can also utilize built-in functions. So, to completely disallow custom casts created via CREATE CAST used in safe cast evaluation, a new field in pg_cast would unfortunately be necessary. demo: SELECT CAST('1' AS date DEFAULT '2011-01-01' ON ERROR), CAST('{234,def,567}'::text[] AS integer[] DEFAULT '{-1011}' ON ERROR); date | int4 ------------+--------- 2011-01-01 | {-1011} [0]: https://git.postgresql.org/cgit/postgresql.git/commit/?id=aaaf9449ec6be62cb0d30ed3588dc384f56274b discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
1 parent 02d0507 commit 609935a

File tree

37 files changed

+2290
-188
lines changed

37 files changed

+2290
-188
lines changed

contrib/pg_stat_statements/expected/select.out

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,25 @@ SELECT 1 AS "int" OFFSET 2 FETCH FIRST 3 ROW ONLY;
7373
-----
7474
(0 rows)
7575

76+
--error safe type cast
77+
SELECT CAST('a' AS int DEFAULT 2 ON CONVERSION ERROR);
78+
int4
79+
------
80+
2
81+
(1 row)
82+
83+
SELECT CAST('11' AS int DEFAULT 2 ON CONVERSION ERROR);
84+
int4
85+
------
86+
11
87+
(1 row)
88+
89+
SELECT CAST('12' AS numeric DEFAULT 2 ON CONVERSION ERROR);
90+
numeric
91+
---------
92+
12
93+
(1 row)
94+
7695
-- DISTINCT and ORDER BY patterns
7796
-- Try some query permutations which once produced identical query IDs
7897
SELECT DISTINCT 1 AS "int";
@@ -222,6 +241,8 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
222241
2 | 2 | SELECT $1 AS "int" ORDER BY 1
223242
1 | 2 | SELECT $1 AS i UNION SELECT $2 ORDER BY i
224243
1 | 1 | SELECT $1 || $2
244+
2 | 2 | SELECT CAST($1 AS int DEFAULT $2 ON CONVERSION ERROR)
245+
1 | 1 | SELECT CAST($1 AS numeric DEFAULT $2 ON CONVERSION ERROR)
225246
2 | 2 | SELECT DISTINCT $1 AS "int"
226247
0 | 0 | SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"
227248
1 | 1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
@@ -230,7 +251,7 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
230251
| | ) +
231252
| | SELECT f FROM t ORDER BY f
232253
1 | 1 | select $1::jsonb ? $2
233-
(17 rows)
254+
(19 rows)
234255

235256
SELECT pg_stat_statements_reset() IS NOT NULL AS t;
236257
t

contrib/pg_stat_statements/sql/select.sql

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ SELECT 1 AS "int" LIMIT 3 OFFSET 3;
2525
SELECT 1 AS "int" OFFSET 1 FETCH FIRST 2 ROW ONLY;
2626
SELECT 1 AS "int" OFFSET 2 FETCH FIRST 3 ROW ONLY;
2727

28+
--error safe type cast
29+
SELECT CAST('a' AS int DEFAULT 2 ON CONVERSION ERROR);
30+
SELECT CAST('11' AS int DEFAULT 2 ON CONVERSION ERROR);
31+
SELECT CAST('12' AS numeric DEFAULT 2 ON CONVERSION ERROR);
32+
2833
-- DISTINCT and ORDER BY patterns
2934
-- Try some query permutations which once produced identical query IDs
3035
SELECT DISTINCT 1 AS "int";

doc/src/sgml/catalogs.sgml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1849,6 +1849,18 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
18491849
<literal>b</literal> means that the types are binary-coercible, thus no conversion is required.
18501850
</para></entry>
18511851
</row>
1852+
1853+
<row>
1854+
<entry role="catalog_table_entry"><para role="column_definition">
1855+
<structfield>casterrorsafe</structfield> <type>bool</type>
1856+
</para>
1857+
<para>
1858+
This indicates whether the <structfield>castfunc</structfield> function is error safe.
1859+
If the <structfield>castfunc</structfield> function is error safe, it can be used in error safe type cast.
1860+
For further details see <xref linkend="sql-syntax-type-casts-safe"/>.
1861+
</para></entry>
1862+
</row>
1863+
18521864
</tbody>
18531865
</tgroup>
18541866
</table>

doc/src/sgml/syntax.sgml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2106,6 +2106,10 @@ CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable>
21062106
The <literal>CAST</literal> syntax conforms to SQL; the syntax with
21072107
<literal>::</literal> is historical <productname>PostgreSQL</productname>
21082108
usage.
2109+
It can also be written as:
2110+
<synopsis>
2111+
CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable> ERROR ON CONVERSION ERROR )
2112+
</synopsis>
21092113
</para>
21102114

21112115
<para>
@@ -2160,6 +2164,31 @@ CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable>
21602164
<xref linkend="sql-createcast"/>.
21612165
</para>
21622166
</note>
2167+
2168+
<sect3 id="sql-syntax-type-casts-safe">
2169+
<title>Safe Type Cast</title>
2170+
<para>
2171+
Sometimes a type cast may fail; to handle such cases, an <literal>ON ERROR</literal> clause can be
2172+
specified to avoid potential errors. The syntax is:
2173+
<synopsis>
2174+
CAST ( <replaceable>expression</replaceable> AS <replaceable>type</replaceable> DEFAULT <replaceable>expression</replaceable> ON CONVERSION ERROR )
2175+
</synopsis>
2176+
If cast the source expression to target type fails, it falls back to
2177+
evaluating the optionally supplied default <replaceable>expression</replaceable>
2178+
specified in ON <literal>ON ERROR</literal> clause.
2179+
Currently, this only support built-in system type casts,
2180+
casts created using <link linkend="sql-createcast">CREATE CAST</link> are not supported.
2181+
</para>
2182+
2183+
<para>
2184+
For example, the following query attempts to cast a <type>text</type> type value to <type>integer</type> type,
2185+
but when the conversion fails, it falls back to evaluate the supplied default expression.
2186+
<programlisting>
2187+
SELECT CAST(TEXT 'error' AS integer DEFAULT NULL ON CONVERSION ERROR) IS NULL; <lineannotation>true</lineannotation>
2188+
</programlisting>
2189+
</para>
2190+
</sect3>
2191+
21632192
</sect2>
21642193

21652194
<sect2 id="sql-syntax-collate-exprs">

src/backend/catalog/pg_cast.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ CastCreate(Oid sourcetypeid, Oid targettypeid,
8484
values[Anum_pg_cast_castfunc - 1] = ObjectIdGetDatum(funcid);
8585
values[Anum_pg_cast_castcontext - 1] = CharGetDatum(castcontext);
8686
values[Anum_pg_cast_castmethod - 1] = CharGetDatum(castmethod);
87+
values[Anum_pg_cast_casterrorsafe - 1] = BoolGetDatum(false);
8788

8889
tuple = heap_form_tuple(RelationGetDescr(relation), values, nulls);
8990

src/backend/executor/execExpr.c

Lines changed: 116 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ static void ExecBuildAggTransCall(ExprState *state, AggState *aggstate,
9999
static void ExecInitJsonExpr(JsonExpr *jsexpr, ExprState *state,
100100
Datum *resv, bool *resnull,
101101
ExprEvalStep *scratch);
102+
static void ExecInitSafeTypeCastExpr(SafeTypeCastExpr *stcexpr, ExprState *state,
103+
Datum *resv, bool *resnull,
104+
ExprEvalStep *scratch);
102105
static void ExecInitJsonCoercion(ExprState *state, JsonReturning *returning,
103106
ErrorSaveContext *escontext, bool omit_quotes,
104107
bool exists_coerce,
@@ -170,6 +173,47 @@ ExecInitExpr(Expr *node, PlanState *parent)
170173
return state;
171174
}
172175

176+
/*
177+
* ExecInitExprSafe: soft error variant of ExecInitExpr.
178+
*
179+
* use it only for expression nodes support soft errors, not all expression
180+
* nodes support it.
181+
*/
182+
ExprState *
183+
ExecInitExprSafe(Expr *node, PlanState *parent)
184+
{
185+
ExprState *state;
186+
ExprEvalStep scratch = {0};
187+
188+
/* Special case: NULL expression produces a NULL ExprState pointer */
189+
if (node == NULL)
190+
return NULL;
191+
192+
/* Initialize ExprState with empty step list */
193+
state = makeNode(ExprState);
194+
state->expr = node;
195+
state->parent = parent;
196+
state->ext_params = NULL;
197+
state->escontext = makeNode(ErrorSaveContext);
198+
state->escontext->type = T_ErrorSaveContext;
199+
state->escontext->error_occurred = false;
200+
state->escontext->details_wanted = false;
201+
202+
/* Insert setup steps as needed */
203+
ExecCreateExprSetupSteps(state, (Node *) node);
204+
205+
/* Compile the expression proper */
206+
ExecInitExprRec(node, state, &state->resvalue, &state->resnull);
207+
208+
/* Finally, append a DONE step */
209+
scratch.opcode = EEOP_DONE_RETURN;
210+
ExprEvalPushStep(state, &scratch);
211+
212+
ExecReadyExpr(state);
213+
214+
return state;
215+
}
216+
173217
/*
174218
* ExecInitExprWithParams: prepare a standalone expression tree for execution
175219
*
@@ -1701,6 +1745,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
17011745

17021746
elemstate->innermost_caseval = (Datum *) palloc(sizeof(Datum));
17031747
elemstate->innermost_casenull = (bool *) palloc(sizeof(bool));
1748+
elemstate->escontext = state->escontext;
17041749

17051750
ExecInitExprRec(acoerce->elemexpr, elemstate,
17061751
&elemstate->resvalue, &elemstate->resnull);
@@ -2177,6 +2222,14 @@ ExecInitExprRec(Expr *node, ExprState *state,
21772222
break;
21782223
}
21792224

2225+
case T_SafeTypeCastExpr:
2226+
{
2227+
SafeTypeCastExpr *stcexpr = castNode(SafeTypeCastExpr, node);
2228+
2229+
ExecInitSafeTypeCastExpr(stcexpr, state, resv, resnull, &scratch);
2230+
break;
2231+
}
2232+
21802233
case T_CoalesceExpr:
21812234
{
21822235
CoalesceExpr *coalesce = (CoalesceExpr *) node;
@@ -2743,7 +2796,7 @@ ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid,
27432796

27442797
/* Initialize function call parameter structure too */
27452798
InitFunctionCallInfoData(*fcinfo, flinfo,
2746-
nargs, inputcollid, NULL, NULL);
2799+
nargs, inputcollid, (Node *) state->escontext, NULL);
27472800

27482801
/* Keep extra copies of this info to save an indirection at runtime */
27492802
scratch->d.func.fn_addr = flinfo->fn_addr;
@@ -4741,6 +4794,68 @@ ExecBuildParamSetEqual(TupleDesc desc,
47414794
return state;
47424795
}
47434796

4797+
/*
4798+
* Push steps to evaluate a SafeTypeCastExpr and its various subsidiary
4799+
* expressions.
4800+
*/
4801+
static void
4802+
ExecInitSafeTypeCastExpr(SafeTypeCastExpr *stcexpr , ExprState *state,
4803+
Datum *resv, bool *resnull,
4804+
ExprEvalStep *scratch)
4805+
{
4806+
/*
4807+
* If we can not coerce to the target type, fallback to the DEFAULT
4808+
* expression specified in the ON CONVERSION ERROR clause, and we are done.
4809+
*/
4810+
if (stcexpr->cast_expr == NULL)
4811+
{
4812+
ExecInitExprRec((Expr *) stcexpr->default_expr,
4813+
state, resv, resnull);
4814+
return;
4815+
}
4816+
else
4817+
{
4818+
ExprEvalStep *as;
4819+
SafeTypeCastState *stcstate;
4820+
ErrorSaveContext *saved_escontext;
4821+
int jumps_to_end;
4822+
4823+
stcstate = palloc0(sizeof(SafeTypeCastState));
4824+
stcstate->stcexpr = stcexpr;
4825+
stcstate->escontext.type = T_ErrorSaveContext;
4826+
state->escontext = &stcstate->escontext;
4827+
4828+
/* evaluate argument expression into step's result area */
4829+
ExecInitExprRec((Expr *) stcexpr->cast_expr,
4830+
state, resv, resnull);
4831+
4832+
scratch->opcode = EEOP_SAFETYPE_CAST;
4833+
scratch->d.stcexpr.stcstate = stcstate;
4834+
ExprEvalPushStep(state, scratch);
4835+
stcstate->jump_error = state->steps_len;
4836+
4837+
/* JUMP to end if false, that is, skip the ON ERROR expression. */
4838+
jumps_to_end = state->steps_len;
4839+
scratch->opcode = EEOP_JUMP_IF_NOT_TRUE;
4840+
scratch->resvalue = &stcstate->error.value;
4841+
scratch->resnull = &stcstate->error.isnull;
4842+
scratch->d.jump.jumpdone = -1; /* set below */
4843+
ExprEvalPushStep(state, scratch);
4844+
4845+
/* Steps to evaluate the ON ERROR expression */
4846+
saved_escontext = state->escontext;
4847+
state->escontext = NULL;
4848+
ExecInitExprRec((Expr *) stcstate->stcexpr->default_expr,
4849+
state, resv, resnull);
4850+
state->escontext = saved_escontext;
4851+
4852+
as = &state->steps[jumps_to_end];
4853+
as->d.jump.jumpdone = state->steps_len;
4854+
4855+
stcstate->jump_end = state->steps_len;
4856+
}
4857+
}
4858+
47444859
/*
47454860
* Push steps to evaluate a JsonExpr and its various subsidiary expressions.
47464861
*/

src/backend/executor/execExprInterp.c

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
568568
&&CASE_EEOP_XMLEXPR,
569569
&&CASE_EEOP_JSON_CONSTRUCTOR,
570570
&&CASE_EEOP_IS_JSON,
571+
&&CASE_EEOP_SAFETYPE_CAST,
571572
&&CASE_EEOP_JSONEXPR_PATH,
572573
&&CASE_EEOP_JSONEXPR_COERCION,
573574
&&CASE_EEOP_JSONEXPR_COERCION_FINISH,
@@ -1926,6 +1927,12 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
19261927
EEO_NEXT();
19271928
}
19281929

1930+
EEO_CASE(EEOP_SAFETYPE_CAST)
1931+
{
1932+
/* too complex for an inline implementation */
1933+
EEO_JUMP(ExecEvalSafeTypeCast(state, op));
1934+
}
1935+
19291936
EEO_CASE(EEOP_JSONEXPR_PATH)
19301937
{
19311938
/* too complex for an inline implementation */
@@ -3644,6 +3651,12 @@ ExecEvalArrayCoerce(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
36443651
econtext,
36453652
op->d.arraycoerce.resultelemtype,
36463653
op->d.arraycoerce.amstate);
3654+
3655+
if (SOFT_ERROR_OCCURRED(op->d.arraycoerce.elemexprstate->escontext))
3656+
{
3657+
*op->resvalue = (Datum) 0;
3658+
*op->resnull = true;
3659+
}
36473660
}
36483661

36493662
/*
@@ -5183,6 +5196,30 @@ GetJsonBehaviorValueString(JsonBehavior *behavior)
51835196
return pstrdup(behavior_names[behavior->btype]);
51845197
}
51855198

5199+
int
5200+
ExecEvalSafeTypeCast(ExprState *state, ExprEvalStep *op)
5201+
{
5202+
SafeTypeCastState *stcstate = op->d.stcexpr.stcstate;
5203+
5204+
if (SOFT_ERROR_OCCURRED(&stcstate->escontext))
5205+
{
5206+
*op->resvalue = (Datum) 0;
5207+
*op->resnull = true;
5208+
5209+
stcstate->error.value = BoolGetDatum(true);
5210+
5211+
/*
5212+
* Reset for next use such as for catching errors when coercing a
5213+
* expression.
5214+
*/
5215+
stcstate->escontext.error_occurred = false;
5216+
stcstate->escontext.details_wanted = false;
5217+
5218+
return stcstate->jump_error;
5219+
}
5220+
return stcstate->jump_end;
5221+
}
5222+
51865223
/*
51875224
* Checks if an error occurred in ExecEvalJsonCoercion(). If so, this sets
51885225
* JsonExprState.error to trigger the ON ERROR handling steps, unless the

0 commit comments

Comments
 (0)