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
|
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: