From d35a1af468162f510b6139bf81a7a41fd8ba8500 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Thu, 15 Dec 2022 12:18:36 -0500 Subject: [PATCH] Convert range_in and multirange_in to report errors softly. This is mostly straightforward, except that if the range type has a canonical function, that might throw an error during range input. (Such errors probably only occur for edge cases: in the in-core canonical functions, it happens only if a bound has the maximum valid value for the underlying type.) Hence, this patch extends the soft-error regime to allow canonical functions to return errors softly as well. Extensions implementing range canonical functions will need modification anyway because of the API change for range_serialize(); while at it, they might want to do something similar to what's been done here in the in-core canonical functions. Discussion: https://postgr.es/m/3284599.1671075185@sss.pgh.pa.us --- src/backend/utils/adt/multirangetypes.c | 29 ++- .../utils/adt/multirangetypes_selfuncs.c | 3 +- src/backend/utils/adt/rangetypes.c | 190 +++++++++++++----- src/backend/utils/adt/rangetypes_gist.c | 2 +- src/backend/utils/adt/rangetypes_selfuncs.c | 2 +- src/backend/utils/adt/rangetypes_spgist.c | 2 +- src/backend/utils/adt/rangetypes_typanalyze.c | 3 +- src/include/utils/rangetypes.h | 6 +- src/test/regress/expected/multirangetypes.out | 31 +++ src/test/regress/expected/rangetypes.out | 67 ++++++ src/test/regress/sql/multirangetypes.sql | 7 + src/test/regress/sql/rangetypes.sql | 13 ++ 12 files changed, 289 insertions(+), 66 deletions(-) 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..2817b5e06e 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; } @@ -1446,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; @@ -1460,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)); + 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; @@ -1491,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)); + 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; @@ -1523,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)); + PG_RETURN_RANGE_P(range_serialize(typcache, &lower, &upper, + false, escontext)); } /* @@ -1657,7 +1717,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 +1744,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,17 +1942,41 @@ 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) && !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; } @@ -2085,7 +2169,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 +2254,13 @@ range_parse_flags(const char *flags_str) * Within a , 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 +2287,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 +2304,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 +2339,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 +2350,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 +2372,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 +2397,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 +2405,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..9eb31aecfe 100644 --- a/src/test/regress/expected/rangetypes.out +++ b/src/test/regress/expected/rangetypes.out @@ -175,6 +175,73 @@ 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) + +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/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..798cd23910 100644 --- a/src/test/regress/sql/rangetypes.sql +++ b/src/test/regress/sql/rangetypes.sql @@ -40,6 +40,19 @@ 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'); +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 --