Skip to content

Commit 1f97d38

Browse files
author
Commitfest Bot
committed
[CF 5941] v9 - CAST(... ON DEFAULT) - WIP build on top of Error-Safe User Functions
This branch was automatically generated by a robot using patches from an email thread registered at: https://commitfest.postgresql.org/patch/5941 The branch will be overwritten each time a new patch version is posted to the thread, and also periodically to check for bitrot caused by changes on the master branch. Patch(es): https://www.postgresql.org/message-id/CACJufxHM2e3DQmbRdDZvWyG3ZCLyOg6XFifvOz_TGy1tGw7NHw@mail.gmail.com Author(s): Jian He
2 parents 65f4976 + 609935a commit 1f97d38

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+3206
-306
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/access/spgist/spgproc.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ point_box_distance(Point *point, BOX *box)
5151
else
5252
dy = 0.0;
5353

54-
return HYPOT(dx, dy);
54+
return HYPOT(dx, dy, NULL);
5555
}
5656

5757
/*

src/backend/catalog/namespace.c

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,137 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
640640
return relId;
641641
}
642642

643+
/* safe version of RangeVarGetRelidExtended */
644+
Oid
645+
RangeVarGetRelidExtendedSafe(const RangeVar *relation, LOCKMODE lockmode,
646+
uint32 flags, RangeVarGetRelidCallback callback, void *callback_arg,
647+
Node *escontext)
648+
{
649+
uint64 inval_count;
650+
Oid relId;
651+
Oid oldRelId = InvalidOid;
652+
bool retry = false;
653+
bool missing_ok = (flags & RVR_MISSING_OK) != 0;
654+
655+
/* verify that flags do no conflict */
656+
Assert(!((flags & RVR_NOWAIT) && (flags & RVR_SKIP_LOCKED)));
657+
658+
/*
659+
* We check the catalog name and then ignore it.
660+
*/
661+
if (relation->catalogname)
662+
{
663+
if (strcmp(relation->catalogname, get_database_name(MyDatabaseId)) != 0)
664+
ereturn(escontext, InvalidOid,
665+
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
666+
errmsg("cross-database references are not implemented: \"%s.%s.%s\"",
667+
relation->catalogname, relation->schemaname,
668+
relation->relname));
669+
}
670+
671+
for (;;)
672+
{
673+
inval_count = SharedInvalidMessageCounter;
674+
675+
if (relation->relpersistence == RELPERSISTENCE_TEMP)
676+
{
677+
if (!OidIsValid(myTempNamespace))
678+
relId = InvalidOid;
679+
else
680+
{
681+
if (relation->schemaname)
682+
{
683+
Oid namespaceId;
684+
685+
namespaceId = LookupExplicitNamespace(relation->schemaname, missing_ok);
686+
687+
/*
688+
* For missing_ok, allow a non-existent schema name to
689+
* return InvalidOid.
690+
*/
691+
if (namespaceId != myTempNamespace)
692+
ereturn(escontext, InvalidOid,
693+
errcode(ERRCODE_INVALID_TABLE_DEFINITION),
694+
errmsg("temporary tables cannot specify a schema name"));
695+
}
696+
697+
relId = get_relname_relid(relation->relname, myTempNamespace);
698+
}
699+
}
700+
else if (relation->schemaname)
701+
{
702+
Oid namespaceId;
703+
704+
/* use exact schema given */
705+
namespaceId = LookupExplicitNamespace(relation->schemaname, missing_ok);
706+
if (missing_ok && !OidIsValid(namespaceId))
707+
relId = InvalidOid;
708+
else
709+
relId = get_relname_relid(relation->relname, namespaceId);
710+
}
711+
else
712+
{
713+
/* search the namespace path */
714+
relId = RelnameGetRelid(relation->relname);
715+
}
716+
717+
if (callback)
718+
callback(relation, relId, oldRelId, callback_arg);
719+
720+
if (lockmode == NoLock)
721+
break;
722+
723+
if (retry)
724+
{
725+
if (relId == oldRelId)
726+
break;
727+
if (OidIsValid(oldRelId))
728+
UnlockRelationOid(oldRelId, lockmode);
729+
}
730+
731+
if (!OidIsValid(relId))
732+
AcceptInvalidationMessages();
733+
else if (!(flags & (RVR_NOWAIT | RVR_SKIP_LOCKED)))
734+
LockRelationOid(relId, lockmode);
735+
else if (!ConditionalLockRelationOid(relId, lockmode))
736+
{
737+
if (relation->schemaname)
738+
ereport(DEBUG1,
739+
errcode(ERRCODE_LOCK_NOT_AVAILABLE),
740+
errmsg("could not obtain lock on relation \"%s.%s\"",
741+
relation->schemaname, relation->relname));
742+
else
743+
ereport(DEBUG1,
744+
errcode(ERRCODE_LOCK_NOT_AVAILABLE),
745+
errmsg("could not obtain lock on relation \"%s\"",
746+
relation->relname));
747+
748+
return InvalidOid;
749+
}
750+
751+
if (inval_count == SharedInvalidMessageCounter)
752+
break;
753+
754+
retry = true;
755+
oldRelId = relId;
756+
}
757+
758+
if (!OidIsValid(relId))
759+
{
760+
if (relation->schemaname)
761+
ereturn(escontext, InvalidOid,
762+
errcode(ERRCODE_UNDEFINED_TABLE),
763+
errmsg("relation \"%s.%s\" does not exist",
764+
relation->schemaname, relation->relname));
765+
else
766+
ereturn(escontext, InvalidOid,
767+
errcode(ERRCODE_UNDEFINED_TABLE),
768+
errmsg("relation \"%s\" does not exist",
769+
relation->relname));
770+
}
771+
return relId;
772+
}
773+
643774
/*
644775
* RangeVarGetCreationNamespace
645776
* Given a RangeVar describing a to-be-created relation,
@@ -3650,6 +3781,42 @@ makeRangeVarFromNameList(const List *names)
36503781
return rel;
36513782
}
36523783

3784+
/*
3785+
* makeRangeVarFromNameListSafe
3786+
* Utility routine to convert a qualified-name list into RangeVar form.
3787+
* The result maybe NULL.
3788+
*/
3789+
RangeVar *
3790+
makeRangeVarFromNameListSafe(const List *names, Node *escontext)
3791+
{
3792+
RangeVar *rel = makeRangeVar(NULL, NULL, -1);
3793+
3794+
switch (list_length(names))
3795+
{
3796+
case 1:
3797+
rel->relname = strVal(linitial(names));
3798+
break;
3799+
case 2:
3800+
rel->schemaname = strVal(linitial(names));
3801+
rel->relname = strVal(lsecond(names));
3802+
break;
3803+
case 3:
3804+
rel->catalogname = strVal(linitial(names));
3805+
rel->schemaname = strVal(lsecond(names));
3806+
rel->relname = strVal(lthird(names));
3807+
break;
3808+
default:
3809+
errsave(escontext,
3810+
errcode(ERRCODE_SYNTAX_ERROR),
3811+
errmsg("improper relation name (too many dotted names): %s",
3812+
NameListToString(names)));
3813+
rel = NULL;
3814+
break;
3815+
}
3816+
3817+
return rel;
3818+
}
3819+
36533820
/*
36543821
* NameListToString
36553822
* Utility routine to convert a qualified-name list into a string.

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

0 commit comments

Comments
 (0)