Re: Error-safe user functions - Mailing list pgsql-hackers

From Tom Lane
Subject Re: Error-safe user functions
Date
Msg-id 3284599.1671075185@sss.pgh.pa.us
Whole thread Raw
In response to Re: Error-safe user functions  (Tom Lane <tgl@sss.pgh.pa.us>)
Responses Re: Error-safe user functions  (Amul Sul <sulamul@gmail.com>)
List pgsql-hackers
Here are some proposed patches for converting range_in and multirange_in.

0001 tackles the straightforward part, which is trapping syntax errors
and called-input-function errors.  The only thing that I think might
be controversial here is that I chose to change the signatures of
the exposed functions range_serialize and make_range rather than
inventing xxx_safe variants.  I think this is all right, because
AFAIK the only likely reason for extensions to call either of those
is that custom types' canonical functions would need to call
range_serialize --- and those will need to be touched anyway,
see 0002.

What 0001 does not cover is trapping errors occurring in range
canonicalize functions.  I'd first thought maybe doing that wasn't
worth the trouble, but it's not really very hard to fix the built-in
canonicalize functions, as shown in 0002.  Probably extensions would
not find it much harder, and in any case they're not really required
to make their errors soft.

Any objections?

            regards, tom lane

diff --git a/src/backend/utils/adt/multirangetypes.c b/src/backend/utils/adt/multirangetypes.c
index 307d087c97..ed26cfba2d 100644
--- a/src/backend/utils/adt/multirangetypes.c
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -120,6 +120,7 @@ multirange_in(PG_FUNCTION_ARGS)
     char       *input_str = PG_GETARG_CSTRING(0);
     Oid            mltrngtypoid = PG_GETARG_OID(1);
     Oid            typmod = PG_GETARG_INT32(2);
+    Node       *escontext = fcinfo->context;
     TypeCacheEntry *rangetyp;
     int32        ranges_seen = 0;
     int32        range_count = 0;
@@ -133,6 +134,7 @@ multirange_in(PG_FUNCTION_ARGS)
     const char *range_str_begin = NULL;
     int32        range_str_len;
     char       *range_str;
+    Datum        range_datum;

     cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_input);
     rangetyp = cache->typcache->rngtype;
@@ -144,7 +146,7 @@ multirange_in(PG_FUNCTION_ARGS)
     if (*ptr == '{')
         ptr++;
     else
-        ereport(ERROR,
+        ereturn(escontext, (Datum) 0,
                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                  errmsg("malformed multirange literal: \"%s\"",
                         input_str),
@@ -157,7 +159,7 @@ multirange_in(PG_FUNCTION_ARGS)
         char        ch = *ptr;

         if (ch == '\0')
-            ereport(ERROR,
+            ereturn(escontext, (Datum) 0,
                     (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                      errmsg("malformed multirange literal: \"%s\"",
                             input_str),
@@ -186,7 +188,7 @@ multirange_in(PG_FUNCTION_ARGS)
                     parse_state = MULTIRANGE_AFTER_RANGE;
                 }
                 else
-                    ereport(ERROR,
+                    ereturn(escontext, (Datum) 0,
                             (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                              errmsg("malformed multirange literal: \"%s\"",
                                     input_str),
@@ -204,10 +206,14 @@ multirange_in(PG_FUNCTION_ARGS)
                             repalloc(ranges, range_capacity * sizeof(RangeType *));
                     }
                     ranges_seen++;
-                    range = DatumGetRangeTypeP(InputFunctionCall(&cache->typioproc,
-                                                                 range_str,
-                                                                 cache->typioparam,
-                                                                 typmod));
+                    if (!InputFunctionCallSafe(&cache->typioproc,
+                                               range_str,
+                                               cache->typioparam,
+                                               typmod,
+                                               escontext,
+                                               &range_datum))
+                        PG_RETURN_NULL();
+                    range = DatumGetRangeTypeP(range_datum);
                     if (!RangeIsEmpty(range))
                         ranges[range_count++] = range;
                     parse_state = MULTIRANGE_AFTER_RANGE;
@@ -256,7 +262,7 @@ multirange_in(PG_FUNCTION_ARGS)
                 else if (ch == '}')
                     parse_state = MULTIRANGE_FINISHED;
                 else
-                    ereport(ERROR,
+                    ereturn(escontext, (Datum) 0,
                             (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                              errmsg("malformed multirange literal: \"%s\"",
                                     input_str),
@@ -280,7 +286,7 @@ multirange_in(PG_FUNCTION_ARGS)
         ptr++;

     if (*ptr != '\0')
-        ereport(ERROR,
+        ereturn(escontext, (Datum) 0,
                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                  errmsg("malformed multirange literal: \"%s\"",
                         input_str),
@@ -807,7 +813,7 @@ multirange_get_union_range(TypeCacheEntry *rangetyp,
     multirange_get_bounds(rangetyp, mr, 0, &lower, &tmp);
     multirange_get_bounds(rangetyp, mr, mr->rangeCount - 1, &tmp, &upper);

-    return make_range(rangetyp, &lower, &upper, false);
+    return make_range(rangetyp, &lower, &upper, false, NULL);
 }


@@ -2696,7 +2702,8 @@ range_merge_from_multirange(PG_FUNCTION_ARGS)
         multirange_get_bounds(typcache->rngtype, mr, mr->rangeCount - 1,
                               &lastLower, &lastUpper);

-        result = make_range(typcache->rngtype, &firstLower, &lastUpper, false);
+        result = make_range(typcache->rngtype, &firstLower, &lastUpper,
+                            false, NULL);
     }

     PG_RETURN_RANGE_P(result);
diff --git a/src/backend/utils/adt/multirangetypes_selfuncs.c b/src/backend/utils/adt/multirangetypes_selfuncs.c
index 919c8889d4..8fd6250e4a 100644
--- a/src/backend/utils/adt/multirangetypes_selfuncs.c
+++ b/src/backend/utils/adt/multirangetypes_selfuncs.c
@@ -221,7 +221,8 @@ multirangesel(PG_FUNCTION_ARGS)
             upper.val = ((Const *) other)->constvalue;
             upper.infinite = false;
             upper.lower = false;
-            constrange = range_serialize(typcache->rngtype, &lower, &upper, false);
+            constrange = range_serialize(typcache->rngtype, &lower, &upper,
+                                         false, NULL);
             constmultirange = make_multirange(typcache->type_id, typcache->rngtype,
                                               1, &constrange);
         }
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index b09cb49054..b89b07a710 100644
--- a/src/backend/utils/adt/rangetypes.c
+++ b/src/backend/utils/adt/rangetypes.c
@@ -35,6 +35,7 @@
 #include "lib/stringinfo.h"
 #include "libpq/pqformat.h"
 #include "miscadmin.h"
+#include "nodes/miscnodes.h"
 #include "port/pg_bitutils.h"
 #include "utils/builtins.h"
 #include "utils/date.h"
@@ -55,10 +56,11 @@ typedef struct RangeIOData
 static RangeIOData *get_range_io_data(FunctionCallInfo fcinfo, Oid rngtypid,
                                       IOFuncSelector func);
 static char range_parse_flags(const char *flags_str);
-static void range_parse(const char *string, char *flags, char **lbound_str,
-                        char **ubound_str);
+static bool range_parse(const char *string, char *flags, char **lbound_str,
+                        char **ubound_str, Node *escontext);
 static const char *range_parse_bound(const char *string, const char *ptr,
-                                     char **bound_str, bool *infinite);
+                                     char **bound_str, bool *infinite,
+                                     Node *escontext);
 static char *range_deparse(char flags, const char *lbound_str,
                            const char *ubound_str);
 static char *range_bound_escape(const char *value);
@@ -80,6 +82,7 @@ range_in(PG_FUNCTION_ARGS)
     char       *input_str = PG_GETARG_CSTRING(0);
     Oid            rngtypoid = PG_GETARG_OID(1);
     Oid            typmod = PG_GETARG_INT32(2);
+    Node       *escontext = fcinfo->context;
     RangeType  *range;
     RangeIOData *cache;
     char        flags;
@@ -93,15 +96,20 @@ range_in(PG_FUNCTION_ARGS)
     cache = get_range_io_data(fcinfo, rngtypoid, IOFunc_input);

     /* parse */
-    range_parse(input_str, &flags, &lbound_str, &ubound_str);
+    if (!range_parse(input_str, &flags, &lbound_str, &ubound_str, escontext))
+        PG_RETURN_NULL();

     /* call element type's input function */
     if (RANGE_HAS_LBOUND(flags))
-        lower.val = InputFunctionCall(&cache->typioproc, lbound_str,
-                                      cache->typioparam, typmod);
+        if (!InputFunctionCallSafe(&cache->typioproc, lbound_str,
+                                   cache->typioparam, typmod,
+                                   escontext, &lower.val))
+            PG_RETURN_NULL();
     if (RANGE_HAS_UBOUND(flags))
-        upper.val = InputFunctionCall(&cache->typioproc, ubound_str,
-                                      cache->typioparam, typmod);
+        if (!InputFunctionCallSafe(&cache->typioproc, ubound_str,
+                                   cache->typioparam, typmod,
+                                   escontext, &upper.val))
+            PG_RETURN_NULL();

     lower.infinite = (flags & RANGE_LB_INF) != 0;
     lower.inclusive = (flags & RANGE_LB_INC) != 0;
@@ -111,7 +119,8 @@ range_in(PG_FUNCTION_ARGS)
     upper.lower = false;

     /* serialize and canonicalize */
-    range = make_range(cache->typcache, &lower, &upper, flags & RANGE_EMPTY);
+    range = make_range(cache->typcache, &lower, &upper,
+                       flags & RANGE_EMPTY, escontext);

     PG_RETURN_RANGE_P(range);
 }
@@ -234,7 +243,8 @@ range_recv(PG_FUNCTION_ARGS)
     upper.lower = false;

     /* serialize and canonicalize */
-    range = make_range(cache->typcache, &lower, &upper, flags & RANGE_EMPTY);
+    range = make_range(cache->typcache, &lower, &upper,
+                       flags & RANGE_EMPTY, NULL);

     PG_RETURN_RANGE_P(range);
 }
@@ -378,7 +388,7 @@ range_constructor2(PG_FUNCTION_ARGS)
     upper.inclusive = false;
     upper.lower = false;

-    range = make_range(typcache, &lower, &upper, false);
+    range = make_range(typcache, &lower, &upper, false, NULL);

     PG_RETURN_RANGE_P(range);
 }
@@ -415,7 +425,7 @@ range_constructor3(PG_FUNCTION_ARGS)
     upper.inclusive = (flags & RANGE_UB_INC) != 0;
     upper.lower = false;

-    range = make_range(typcache, &lower, &upper, false);
+    range = make_range(typcache, &lower, &upper, false, NULL);

     PG_RETURN_RANGE_P(range);
 }
@@ -766,7 +776,7 @@ bounds_adjacent(TypeCacheEntry *typcache, RangeBound boundA, RangeBound boundB)
         /* change upper/lower labels to avoid Assert failures */
         boundA.lower = true;
         boundB.lower = false;
-        r = make_range(typcache, &boundA, &boundB, false);
+        r = make_range(typcache, &boundA, &boundB, false, NULL);
         return RangeIsEmpty(r);
     }
     else if (cmp == 0)
@@ -1012,14 +1022,14 @@ range_minus_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
     {
         lower2.inclusive = !lower2.inclusive;
         lower2.lower = false;    /* it will become the upper bound */
-        return make_range(typcache, &lower1, &lower2, false);
+        return make_range(typcache, &lower1, &lower2, false, NULL);
     }

     if (cmp_l1l2 >= 0 && cmp_u1u2 >= 0 && cmp_l1u2 <= 0)
     {
         upper2.inclusive = !upper2.inclusive;
         upper2.lower = true;    /* it will become the lower bound */
-        return make_range(typcache, &upper2, &upper1, false);
+        return make_range(typcache, &upper2, &upper1, false, NULL);
     }

     elog(ERROR, "unexpected case in range_minus");
@@ -1073,7 +1083,7 @@ range_union_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2,
     else
         result_upper = &upper2;

-    return make_range(typcache, result_lower, result_upper, false);
+    return make_range(typcache, result_lower, result_upper, false, NULL);
 }

 Datum
@@ -1149,7 +1159,7 @@ range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1, const Ra
     else
         result_upper = &upper2;

-    return make_range(typcache, result_lower, result_upper, false);
+    return make_range(typcache, result_lower, result_upper, false, NULL);
 }

 /* range, range -> range, range functions */
@@ -1187,8 +1197,8 @@ range_split_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeT
         upper2.inclusive = !upper2.inclusive;
         upper2.lower = true;

-        *output1 = make_range(typcache, &lower1, &lower2, false);
-        *output2 = make_range(typcache, &upper2, &upper1, false);
+        *output1 = make_range(typcache, &lower1, &lower2, false, NULL);
+        *output2 = make_range(typcache, &upper2, &upper1, false, NULL);
         return true;
     }

@@ -1470,7 +1480,7 @@ int4range_canonical(PG_FUNCTION_ARGS)
         upper.inclusive = false;
     }

-    PG_RETURN_RANGE_P(range_serialize(typcache, &lower, &upper, false));
+    PG_RETURN_RANGE_P(range_serialize(typcache, &lower, &upper, false, NULL));
 }

 Datum
@@ -1501,7 +1511,7 @@ int8range_canonical(PG_FUNCTION_ARGS)
         upper.inclusive = false;
     }

-    PG_RETURN_RANGE_P(range_serialize(typcache, &lower, &upper, false));
+    PG_RETURN_RANGE_P(range_serialize(typcache, &lower, &upper, false, NULL));
 }

 Datum
@@ -1534,7 +1544,7 @@ daterange_canonical(PG_FUNCTION_ARGS)
         upper.inclusive = false;
     }

-    PG_RETURN_RANGE_P(range_serialize(typcache, &lower, &upper, false));
+    PG_RETURN_RANGE_P(range_serialize(typcache, &lower, &upper, false, NULL));
 }

 /*
@@ -1657,7 +1667,7 @@ range_get_typcache(FunctionCallInfo fcinfo, Oid rngtypid)
  */
 RangeType *
 range_serialize(TypeCacheEntry *typcache, RangeBound *lower, RangeBound *upper,
-                bool empty)
+                bool empty, struct Node *escontext)
 {
     RangeType  *range;
     int            cmp;
@@ -1684,7 +1694,7 @@ range_serialize(TypeCacheEntry *typcache, RangeBound *lower, RangeBound *upper,

         /* error check: if lower bound value is above upper, it's wrong */
         if (cmp > 0)
-            ereport(ERROR,
+            ereturn(escontext, NULL,
                     (errcode(ERRCODE_DATA_EXCEPTION),
                      errmsg("range lower bound must be less than or equal to range upper bound")));

@@ -1882,11 +1892,14 @@ range_set_contain_empty(RangeType *range)
  */
 RangeType *
 make_range(TypeCacheEntry *typcache, RangeBound *lower, RangeBound *upper,
-           bool empty)
+           bool empty, struct Node *escontext)
 {
     RangeType  *range;

-    range = range_serialize(typcache, lower, upper, empty);
+    range = range_serialize(typcache, lower, upper, empty, escontext);
+
+    if (SOFT_ERROR_OCCURRED(escontext))
+        return NULL;

     /* no need to call canonical on empty ranges ... */
     if (OidIsValid(typcache->rng_canonical_finfo.fn_oid) &&
@@ -2085,7 +2098,7 @@ make_empty_range(TypeCacheEntry *typcache)
     upper.inclusive = false;
     upper.lower = false;

-    return make_range(typcache, &lower, &upper, true);
+    return make_range(typcache, &lower, &upper, true, NULL);
 }


@@ -2170,10 +2183,13 @@ range_parse_flags(const char *flags_str)
  * Within a <string>, special characters (such as comma, parenthesis, or
  * brackets) can be enclosed in double-quotes or escaped with backslash. Within
  * double-quotes, a double-quote can be escaped with double-quote or backslash.
+ *
+ * Returns true on success, false on failure (but failures will return only if
+ * escontext is an ErrorSaveContext).
  */
-static void
+static bool
 range_parse(const char *string, char *flags, char **lbound_str,
-            char **ubound_str)
+            char **ubound_str, Node *escontext)
 {
     const char *ptr = string;
     bool        infinite;
@@ -2200,13 +2216,13 @@ range_parse(const char *string, char *flags, char **lbound_str,

         /* should have consumed everything */
         if (*ptr != '\0')
-            ereport(ERROR,
+            ereturn(escontext, false,
                     (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                      errmsg("malformed range literal: \"%s\"",
                             string),
                      errdetail("Junk after \"empty\" key word.")));

-        return;
+        return true;
     }

     if (*ptr == '[')
@@ -2217,26 +2233,30 @@ range_parse(const char *string, char *flags, char **lbound_str,
     else if (*ptr == '(')
         ptr++;
     else
-        ereport(ERROR,
+        ereturn(escontext, false,
                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                  errmsg("malformed range literal: \"%s\"",
                         string),
                  errdetail("Missing left parenthesis or bracket.")));

-    ptr = range_parse_bound(string, ptr, lbound_str, &infinite);
+    ptr = range_parse_bound(string, ptr, lbound_str, &infinite, escontext);
+    if (ptr == NULL)
+        return false;
     if (infinite)
         *flags |= RANGE_LB_INF;

     if (*ptr == ',')
         ptr++;
     else
-        ereport(ERROR,
+        ereturn(escontext, false,
                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                  errmsg("malformed range literal: \"%s\"",
                         string),
                  errdetail("Missing comma after lower bound.")));

-    ptr = range_parse_bound(string, ptr, ubound_str, &infinite);
+    ptr = range_parse_bound(string, ptr, ubound_str, &infinite, escontext);
+    if (ptr == NULL)
+        return false;
     if (infinite)
         *flags |= RANGE_UB_INF;

@@ -2248,7 +2268,7 @@ range_parse(const char *string, char *flags, char **lbound_str,
     else if (*ptr == ')')
         ptr++;
     else                        /* must be a comma */
-        ereport(ERROR,
+        ereturn(escontext, false,
                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                  errmsg("malformed range literal: \"%s\"",
                         string),
@@ -2259,11 +2279,13 @@ range_parse(const char *string, char *flags, char **lbound_str,
         ptr++;

     if (*ptr != '\0')
-        ereport(ERROR,
+        ereturn(escontext, false,
                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                  errmsg("malformed range literal: \"%s\"",
                         string),
                  errdetail("Junk after right parenthesis or bracket.")));
+
+    return true;
 }

 /*
@@ -2279,10 +2301,11 @@ range_parse(const char *string, char *flags, char **lbound_str,
  *    *infinite: set true if no bound, else false
  *
  * The return value is the scan ptr, advanced past the bound string.
+ * However, if escontext is an ErrorSaveContext, we return NULL on failure.
  */
 static const char *
 range_parse_bound(const char *string, const char *ptr,
-                  char **bound_str, bool *infinite)
+                  char **bound_str, bool *infinite, Node *escontext)
 {
     StringInfoData buf;

@@ -2303,7 +2326,7 @@ range_parse_bound(const char *string, const char *ptr,
             char        ch = *ptr++;

             if (ch == '\0')
-                ereport(ERROR,
+                ereturn(escontext, NULL,
                         (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                          errmsg("malformed range literal: \"%s\"",
                                 string),
@@ -2311,7 +2334,7 @@ range_parse_bound(const char *string, const char *ptr,
             if (ch == '\\')
             {
                 if (*ptr == '\0')
-                    ereport(ERROR,
+                    ereturn(escontext, NULL,
                             (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                              errmsg("malformed range literal: \"%s\"",
                                     string),
diff --git a/src/backend/utils/adt/rangetypes_gist.c b/src/backend/utils/adt/rangetypes_gist.c
index 5996de417d..771c81f67b 100644
--- a/src/backend/utils/adt/rangetypes_gist.c
+++ b/src/backend/utils/adt/rangetypes_gist.c
@@ -876,7 +876,7 @@ range_super_union(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
         ((flags2 & RANGE_CONTAIN_EMPTY) || !(flags1 & RANGE_CONTAIN_EMPTY)))
         return r2;

-    result = make_range(typcache, result_lower, result_upper, false);
+    result = make_range(typcache, result_lower, result_upper, false, NULL);

     if ((flags1 & RANGE_CONTAIN_EMPTY) || (flags2 & RANGE_CONTAIN_EMPTY))
         range_set_contain_empty(result);
diff --git a/src/backend/utils/adt/rangetypes_selfuncs.c b/src/backend/utils/adt/rangetypes_selfuncs.c
index c2795f4593..89114eba71 100644
--- a/src/backend/utils/adt/rangetypes_selfuncs.c
+++ b/src/backend/utils/adt/rangetypes_selfuncs.c
@@ -190,7 +190,7 @@ rangesel(PG_FUNCTION_ARGS)
             upper.val = ((Const *) other)->constvalue;
             upper.infinite = false;
             upper.lower = false;
-            constrange = range_serialize(typcache, &lower, &upper, false);
+            constrange = range_serialize(typcache, &lower, &upper, false, NULL);
         }
     }
     else if (operator == OID_RANGE_ELEM_CONTAINED_OP)
diff --git a/src/backend/utils/adt/rangetypes_spgist.c b/src/backend/utils/adt/rangetypes_spgist.c
index a47f04d975..d4a4d35e60 100644
--- a/src/backend/utils/adt/rangetypes_spgist.c
+++ b/src/backend/utils/adt/rangetypes_spgist.c
@@ -265,7 +265,7 @@ spg_range_quad_picksplit(PG_FUNCTION_ARGS)

     /* Construct "centroid" range from medians of lower and upper bounds */
     centroid = range_serialize(typcache, &lowerBounds[nonEmptyCount / 2],
-                               &upperBounds[nonEmptyCount / 2], false);
+                               &upperBounds[nonEmptyCount / 2], false, NULL);
     out->hasPrefix = true;
     out->prefixDatum = RangeTypePGetDatum(centroid);

diff --git a/src/backend/utils/adt/rangetypes_typanalyze.c b/src/backend/utils/adt/rangetypes_typanalyze.c
index 2043d3f98b..f73c904b90 100644
--- a/src/backend/utils/adt/rangetypes_typanalyze.c
+++ b/src/backend/utils/adt/rangetypes_typanalyze.c
@@ -311,7 +311,8 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
                 bound_hist_values[i] = PointerGetDatum(range_serialize(typcache,
                                                                        &lowers[pos],
                                                                        &uppers[pos],
-                                                                       false));
+                                                                       false,
+                                                                       NULL));
                 pos += delta;
                 posfrac += deltafrac;
                 if (posfrac >= (num_hist - 1))
diff --git a/src/include/utils/rangetypes.h b/src/include/utils/rangetypes.h
index 2eee6a6c1f..3ae13cb9fb 100644
--- a/src/include/utils/rangetypes.h
+++ b/src/include/utils/rangetypes.h
@@ -143,14 +143,16 @@ extern RangeType *range_intersect_internal(TypeCacheEntry *typcache, const Range
 extern TypeCacheEntry *range_get_typcache(FunctionCallInfo fcinfo,
                                           Oid rngtypid);
 extern RangeType *range_serialize(TypeCacheEntry *typcache, RangeBound *lower,
-                                  RangeBound *upper, bool empty);
+                                  RangeBound *upper, bool empty,
+                                  struct Node *escontext);
 extern void range_deserialize(TypeCacheEntry *typcache, const RangeType *range,
                               RangeBound *lower, RangeBound *upper,
                               bool *empty);
 extern char range_get_flags(const RangeType *range);
 extern void range_set_contain_empty(RangeType *range);
 extern RangeType *make_range(TypeCacheEntry *typcache, RangeBound *lower,
-                             RangeBound *upper, bool empty);
+                             RangeBound *upper, bool empty,
+                             struct Node *escontext);
 extern int    range_cmp_bounds(TypeCacheEntry *typcache, const RangeBound *b1,
                              const RangeBound *b2);
 extern int    range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
diff --git a/src/test/regress/expected/multirangetypes.out b/src/test/regress/expected/multirangetypes.out
index ac2eb84c3a..14aa4c46cd 100644
--- a/src/test/regress/expected/multirangetypes.out
+++ b/src/test/regress/expected/multirangetypes.out
@@ -274,6 +274,37 @@ select '{(a,a)}'::textmultirange;
  {}
 (1 row)

+-- Also try it with non-error-throwing API
+select pg_input_is_valid('{[1,2], [4,5]}', 'int4multirange');
+ pg_input_is_valid
+-------------------
+ t
+(1 row)
+
+select pg_input_is_valid('{[1,2], [4,5]', 'int4multirange');
+ pg_input_is_valid
+-------------------
+ f
+(1 row)
+
+select pg_input_error_message('{[1,2], [4,5]', 'int4multirange');
+            pg_input_error_message
+-----------------------------------------------
+ malformed multirange literal: "{[1,2], [4,5]"
+(1 row)
+
+select pg_input_is_valid('{[1,2], [4,zed]}', 'int4multirange');
+ pg_input_is_valid
+-------------------
+ f
+(1 row)
+
+select pg_input_error_message('{[1,2], [4,zed]}', 'int4multirange');
+            pg_input_error_message
+----------------------------------------------
+ invalid input syntax for type integer: "zed"
+(1 row)
+
 --
 -- test the constructor
 ---
diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out
index 04ccd5d451..45b54adbed 100644
--- a/src/test/regress/expected/rangetypes.out
+++ b/src/test/regress/expected/rangetypes.out
@@ -175,6 +175,49 @@ select '(a,a)'::textrange;
  empty
 (1 row)

+-- Also try it with non-error-throwing API
+select pg_input_is_valid('(1,4)', 'int4range');
+ pg_input_is_valid
+-------------------
+ t
+(1 row)
+
+select pg_input_is_valid('(1,4', 'int4range');
+ pg_input_is_valid
+-------------------
+ f
+(1 row)
+
+select pg_input_error_message('(1,4', 'int4range');
+     pg_input_error_message
+---------------------------------
+ malformed range literal: "(1,4"
+(1 row)
+
+select pg_input_is_valid('(4,1)', 'int4range');
+ pg_input_is_valid
+-------------------
+ f
+(1 row)
+
+select pg_input_error_message('(4,1)', 'int4range');
+                      pg_input_error_message
+-------------------------------------------------------------------
+ range lower bound must be less than or equal to range upper bound
+(1 row)
+
+select pg_input_is_valid('(4,zed)', 'int4range');
+ pg_input_is_valid
+-------------------
+ f
+(1 row)
+
+select pg_input_error_message('(4,zed)', 'int4range');
+            pg_input_error_message
+----------------------------------------------
+ invalid input syntax for type integer: "zed"
+(1 row)
+
 --
 -- create some test data and test the operators
 --
diff --git a/src/test/regress/sql/multirangetypes.sql b/src/test/regress/sql/multirangetypes.sql
index 1abcaeddb5..78a650eb0f 100644
--- a/src/test/regress/sql/multirangetypes.sql
+++ b/src/test/regress/sql/multirangetypes.sql
@@ -58,6 +58,13 @@ select '{[a,a)}'::textmultirange;
 select '{(a,a]}'::textmultirange;
 select '{(a,a)}'::textmultirange;

+-- Also try it with non-error-throwing API
+select pg_input_is_valid('{[1,2], [4,5]}', 'int4multirange');
+select pg_input_is_valid('{[1,2], [4,5]', 'int4multirange');
+select pg_input_error_message('{[1,2], [4,5]', 'int4multirange');
+select pg_input_is_valid('{[1,2], [4,zed]}', 'int4multirange');
+select pg_input_error_message('{[1,2], [4,zed]}', 'int4multirange');
+
 --
 -- test the constructor
 ---
diff --git a/src/test/regress/sql/rangetypes.sql b/src/test/regress/sql/rangetypes.sql
index 1a10f67f19..d786c8f5bd 100644
--- a/src/test/regress/sql/rangetypes.sql
+++ b/src/test/regress/sql/rangetypes.sql
@@ -40,6 +40,15 @@ select '[a,a)'::textrange;
 select '(a,a]'::textrange;
 select '(a,a)'::textrange;

+-- Also try it with non-error-throwing API
+select pg_input_is_valid('(1,4)', 'int4range');
+select pg_input_is_valid('(1,4', 'int4range');
+select pg_input_error_message('(1,4', 'int4range');
+select pg_input_is_valid('(4,1)', 'int4range');
+select pg_input_error_message('(4,1)', 'int4range');
+select pg_input_is_valid('(4,zed)', 'int4range');
+select pg_input_error_message('(4,zed)', 'int4range');
+
 --
 -- create some test data and test the operators
 --
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index b89b07a710..2817b5e06e 100644
--- a/src/backend/utils/adt/rangetypes.c
+++ b/src/backend/utils/adt/rangetypes.c
@@ -1456,6 +1456,7 @@ Datum
 int4range_canonical(PG_FUNCTION_ARGS)
 {
     RangeType  *r = PG_GETARG_RANGE_P(0);
+    Node       *escontext = fcinfo->context;
     TypeCacheEntry *typcache;
     RangeBound    lower;
     RangeBound    upper;
@@ -1470,23 +1471,39 @@ int4range_canonical(PG_FUNCTION_ARGS)

     if (!lower.infinite && !lower.inclusive)
     {
-        lower.val = DirectFunctionCall2(int4pl, lower.val, Int32GetDatum(1));
+        int32        bnd = DatumGetInt32(lower.val);
+
+        /* Handle possible overflow manually */
+        if (unlikely(bnd == PG_INT32_MAX))
+            ereturn(escontext, (Datum) 0,
+                    (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+                     errmsg("integer out of range")));
+        lower.val = Int32GetDatum(bnd + 1);
         lower.inclusive = true;
     }

     if (!upper.infinite && upper.inclusive)
     {
-        upper.val = DirectFunctionCall2(int4pl, upper.val, Int32GetDatum(1));
+        int32        bnd = DatumGetInt32(upper.val);
+
+        /* Handle possible overflow manually */
+        if (unlikely(bnd == PG_INT32_MAX))
+            ereturn(escontext, (Datum) 0,
+                    (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+                     errmsg("integer out of range")));
+        upper.val = Int32GetDatum(bnd + 1);
         upper.inclusive = false;
     }

-    PG_RETURN_RANGE_P(range_serialize(typcache, &lower, &upper, false, NULL));
+    PG_RETURN_RANGE_P(range_serialize(typcache, &lower, &upper,
+                                      false, escontext));
 }

 Datum
 int8range_canonical(PG_FUNCTION_ARGS)
 {
     RangeType  *r = PG_GETARG_RANGE_P(0);
+    Node       *escontext = fcinfo->context;
     TypeCacheEntry *typcache;
     RangeBound    lower;
     RangeBound    upper;
@@ -1501,23 +1518,39 @@ int8range_canonical(PG_FUNCTION_ARGS)

     if (!lower.infinite && !lower.inclusive)
     {
-        lower.val = DirectFunctionCall2(int8pl, lower.val, Int64GetDatum(1));
+        int64        bnd = DatumGetInt64(lower.val);
+
+        /* Handle possible overflow manually */
+        if (unlikely(bnd == PG_INT64_MAX))
+            ereturn(escontext, (Datum) 0,
+                    (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+                     errmsg("bigint out of range")));
+        lower.val = Int64GetDatum(bnd + 1);
         lower.inclusive = true;
     }

     if (!upper.infinite && upper.inclusive)
     {
-        upper.val = DirectFunctionCall2(int8pl, upper.val, Int64GetDatum(1));
+        int64        bnd = DatumGetInt64(upper.val);
+
+        /* Handle possible overflow manually */
+        if (unlikely(bnd == PG_INT64_MAX))
+            ereturn(escontext, (Datum) 0,
+                    (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+                     errmsg("bigint out of range")));
+        upper.val = Int64GetDatum(bnd + 1);
         upper.inclusive = false;
     }

-    PG_RETURN_RANGE_P(range_serialize(typcache, &lower, &upper, false, NULL));
+    PG_RETURN_RANGE_P(range_serialize(typcache, &lower, &upper,
+                                      false, escontext));
 }

 Datum
 daterange_canonical(PG_FUNCTION_ARGS)
 {
     RangeType  *r = PG_GETARG_RANGE_P(0);
+    Node       *escontext = fcinfo->context;
     TypeCacheEntry *typcache;
     RangeBound    lower;
     RangeBound    upper;
@@ -1533,18 +1566,35 @@ daterange_canonical(PG_FUNCTION_ARGS)
     if (!lower.infinite && !DATE_NOT_FINITE(DatumGetDateADT(lower.val)) &&
         !lower.inclusive)
     {
-        lower.val = DirectFunctionCall2(date_pli, lower.val, Int32GetDatum(1));
+        DateADT        bnd = DatumGetDateADT(lower.val);
+
+        /* Check for overflow -- note we already eliminated PG_INT32_MAX */
+        bnd++;
+        if (unlikely(!IS_VALID_DATE(bnd)))
+            ereturn(escontext, (Datum) 0,
+                    (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                     errmsg("date out of range")));
+        lower.val = DateADTGetDatum(bnd);
         lower.inclusive = true;
     }

     if (!upper.infinite && !DATE_NOT_FINITE(DatumGetDateADT(upper.val)) &&
         upper.inclusive)
     {
-        upper.val = DirectFunctionCall2(date_pli, upper.val, Int32GetDatum(1));
+        DateADT        bnd = DatumGetDateADT(upper.val);
+
+        /* Check for overflow -- note we already eliminated PG_INT32_MAX */
+        bnd++;
+        if (unlikely(!IS_VALID_DATE(bnd)))
+            ereturn(escontext, (Datum) 0,
+                    (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                     errmsg("date out of range")));
+        upper.val = DateADTGetDatum(bnd);
         upper.inclusive = false;
     }

-    PG_RETURN_RANGE_P(range_serialize(typcache, &lower, &upper, false, NULL));
+    PG_RETURN_RANGE_P(range_serialize(typcache, &lower, &upper,
+                                      false, escontext));
 }

 /*
@@ -1904,8 +1954,29 @@ make_range(TypeCacheEntry *typcache, RangeBound *lower, RangeBound *upper,
     /* no need to call canonical on empty ranges ... */
     if (OidIsValid(typcache->rng_canonical_finfo.fn_oid) &&
         !RangeIsEmpty(range))
-        range = DatumGetRangeTypeP(FunctionCall1(&typcache->rng_canonical_finfo,
-                                                 RangeTypePGetDatum(range)));
+    {
+        /* Do this the hard way so that we can pass escontext */
+        LOCAL_FCINFO(fcinfo, 1);
+        Datum        result;
+
+        InitFunctionCallInfoData(*fcinfo, &typcache->rng_canonical_finfo, 1,
+                                 InvalidOid, escontext, NULL);
+
+        fcinfo->args[0].value = RangeTypePGetDatum(range);
+        fcinfo->args[0].isnull = false;
+
+        result = FunctionCallInvoke(fcinfo);
+
+        if (SOFT_ERROR_OCCURRED(escontext))
+            return NULL;
+
+        /* Should not get a null result if there was no error */
+        if (fcinfo->isnull)
+            elog(ERROR, "function %u returned NULL",
+                 typcache->rng_canonical_finfo.fn_oid);
+
+        range = DatumGetRangeTypeP(result);
+    }

     return range;
 }
diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out
index 45b54adbed..9eb31aecfe 100644
--- a/src/test/regress/expected/rangetypes.out
+++ b/src/test/regress/expected/rangetypes.out
@@ -218,6 +218,30 @@ select pg_input_error_message('(4,zed)', 'int4range');
  invalid input syntax for type integer: "zed"
 (1 row)

+select pg_input_is_valid('[1,2147483647]', 'int4range');
+ pg_input_is_valid
+-------------------
+ f
+(1 row)
+
+select pg_input_error_message('[1,2147483647]', 'int4range');
+ pg_input_error_message
+------------------------
+ integer out of range
+(1 row)
+
+select pg_input_is_valid('[2000-01-01,5874897-12-31]', 'daterange');
+ pg_input_is_valid
+-------------------
+ f
+(1 row)
+
+select pg_input_error_message('[2000-01-01,5874897-12-31]', 'daterange');
+ pg_input_error_message
+------------------------
+ date out of range
+(1 row)
+
 --
 -- create some test data and test the operators
 --
diff --git a/src/test/regress/sql/rangetypes.sql b/src/test/regress/sql/rangetypes.sql
index d786c8f5bd..798cd23910 100644
--- a/src/test/regress/sql/rangetypes.sql
+++ b/src/test/regress/sql/rangetypes.sql
@@ -48,6 +48,10 @@ select pg_input_is_valid('(4,1)', 'int4range');
 select pg_input_error_message('(4,1)', 'int4range');
 select pg_input_is_valid('(4,zed)', 'int4range');
 select pg_input_error_message('(4,zed)', 'int4range');
+select pg_input_is_valid('[1,2147483647]', 'int4range');
+select pg_input_error_message('[1,2147483647]', 'int4range');
+select pg_input_is_valid('[2000-01-01,5874897-12-31]', 'daterange');
+select pg_input_error_message('[2000-01-01,5874897-12-31]', 'daterange');

 --
 -- create some test data and test the operators

pgsql-hackers by date:

Previous
From: "houzj.fnst@fujitsu.com"
Date:
Subject: RE: Perform streaming logical transactions by background workers and parallel apply
Next
From: Amit Kapila
Date:
Subject: Re: Time delayed LR (WAS Re: logical replication restrictions)