diff options
author | Alexander Korotkov <akorotkov@postgresql.org> | 2019-03-16 12:21:19 +0300 |
---|---|---|
committer | Alexander Korotkov <akorotkov@postgresql.org> | 2019-03-16 12:21:19 +0300 |
commit | 16d489b0fe058e527619f5e9d92fd7ca3c6c2994 (patch) | |
tree | d0f82a4250a33db045574fc69137eb32d52d04f9 /src/backend/utils/adt/numeric.c | |
parent | 72b6460336e86ad5cafd3426af6013c7d8457367 (diff) | |
download | postgresql-16d489b0fe058e527619f5e9d92fd7ca3c6c2994.tar.gz postgresql-16d489b0fe058e527619f5e9d92fd7ca3c6c2994.zip |
Numeric error suppression in jsonpath
Add support of numeric error suppression to jsonpath as it's required by
standard. This commit doesn't use PG_TRY()/PG_CATCH() in order to implement
that. Instead, it provides internal versions of numeric functions used, which
support error suppression.
Discussion: https://postgr.es/m/fcc6fc6a-b497-f39a-923d-aa34d0c588e8%402ndQuadrant.com
Author: Alexander Korotkov, Nikita Glukhov
Reviewed-by: Tomas Vondra
Diffstat (limited to 'src/backend/utils/adt/numeric.c')
-rw-r--r-- | src/backend/utils/adt/numeric.c | 252 |
1 files changed, 205 insertions, 47 deletions
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c index 0765f2cdb59..fbc2863622e 100644 --- a/src/backend/utils/adt/numeric.c +++ b/src/backend/utils/adt/numeric.c @@ -475,10 +475,11 @@ static char *get_str_from_var(const NumericVar *var); static char *get_str_from_var_sci(const NumericVar *var, int rscale); static Numeric make_result(const NumericVar *var); +static Numeric make_result_opt_error(const NumericVar *var, bool *error); static void apply_typmod(NumericVar *var, int32 typmod); -static int32 numericvar_to_int32(const NumericVar *var); +static bool numericvar_to_int32(const NumericVar *var, int32 *result); static bool numericvar_to_int64(const NumericVar *var, int64 *result); static void int64_to_numericvar(int64 val, NumericVar *var); #ifdef HAVE_INT128 @@ -1558,7 +1559,10 @@ width_bucket_numeric(PG_FUNCTION_ARGS) } /* if result exceeds the range of a legal int4, we ereport here */ - result = numericvar_to_int32(&result_var); + if (!numericvar_to_int32(&result_var, &result)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("integer out of range"))); free_var(&count_var); free_var(&result_var); @@ -2406,6 +2410,23 @@ numeric_add(PG_FUNCTION_ARGS) { Numeric num1 = PG_GETARG_NUMERIC(0); Numeric num2 = PG_GETARG_NUMERIC(1); + Numeric res; + + res = numeric_add_opt_error(num1, num2, NULL); + + PG_RETURN_NUMERIC(res); +} + +/* + * numeric_add_opt_error() - + * + * Internal version of numeric_add(). If "*have_error" flag is provided, + * on error it's set to true, NULL returned. This is helpful when caller + * need to handle errors by itself. + */ +Numeric +numeric_add_opt_error(Numeric num1, Numeric num2, bool *have_error) +{ NumericVar arg1; NumericVar arg2; NumericVar result; @@ -2415,7 +2436,7 @@ numeric_add(PG_FUNCTION_ARGS) * Handle NaN */ if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2)) - PG_RETURN_NUMERIC(make_result(&const_nan)); + return make_result(&const_nan); /* * Unpack the values, let add_var() compute the result and return it. @@ -2426,11 +2447,11 @@ numeric_add(PG_FUNCTION_ARGS) init_var(&result); add_var(&arg1, &arg2, &result); - res = make_result(&result); + res = make_result_opt_error(&result, have_error); free_var(&result); - PG_RETURN_NUMERIC(res); + return res; } @@ -2444,6 +2465,24 @@ numeric_sub(PG_FUNCTION_ARGS) { Numeric num1 = PG_GETARG_NUMERIC(0); Numeric num2 = PG_GETARG_NUMERIC(1); + Numeric res; + + res = numeric_sub_opt_error(num1, num2, NULL); + + PG_RETURN_NUMERIC(res); +} + + +/* + * numeric_sub_opt_error() - + * + * Internal version of numeric_sub(). If "*have_error" flag is provided, + * on error it's set to true, NULL returned. This is helpful when caller + * need to handle errors by itself. + */ +Numeric +numeric_sub_opt_error(Numeric num1, Numeric num2, bool *have_error) +{ NumericVar arg1; NumericVar arg2; NumericVar result; @@ -2453,7 +2492,7 @@ numeric_sub(PG_FUNCTION_ARGS) * Handle NaN */ if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2)) - PG_RETURN_NUMERIC(make_result(&const_nan)); + return make_result(&const_nan); /* * Unpack the values, let sub_var() compute the result and return it. @@ -2464,11 +2503,11 @@ numeric_sub(PG_FUNCTION_ARGS) init_var(&result); sub_var(&arg1, &arg2, &result); - res = make_result(&result); + res = make_result_opt_error(&result, have_error); free_var(&result); - PG_RETURN_NUMERIC(res); + return res; } @@ -2482,6 +2521,24 @@ numeric_mul(PG_FUNCTION_ARGS) { Numeric num1 = PG_GETARG_NUMERIC(0); Numeric num2 = PG_GETARG_NUMERIC(1); + Numeric res; + + res = numeric_mul_opt_error(num1, num2, NULL); + + PG_RETURN_NUMERIC(res); +} + + +/* + * numeric_mul_opt_error() - + * + * Internal version of numeric_mul(). If "*have_error" flag is provided, + * on error it's set to true, NULL returned. This is helpful when caller + * need to handle errors by itself. + */ +Numeric +numeric_mul_opt_error(Numeric num1, Numeric num2, bool *have_error) +{ NumericVar arg1; NumericVar arg2; NumericVar result; @@ -2491,7 +2548,7 @@ numeric_mul(PG_FUNCTION_ARGS) * Handle NaN */ if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2)) - PG_RETURN_NUMERIC(make_result(&const_nan)); + return make_result(&const_nan); /* * Unpack the values, let mul_var() compute the result and return it. @@ -2506,11 +2563,11 @@ numeric_mul(PG_FUNCTION_ARGS) init_var(&result); mul_var(&arg1, &arg2, &result, arg1.dscale + arg2.dscale); - res = make_result(&result); + res = make_result_opt_error(&result, have_error); free_var(&result); - PG_RETURN_NUMERIC(res); + return res; } @@ -2524,6 +2581,24 @@ numeric_div(PG_FUNCTION_ARGS) { Numeric num1 = PG_GETARG_NUMERIC(0); Numeric num2 = PG_GETARG_NUMERIC(1); + Numeric res; + + res = numeric_div_opt_error(num1, num2, NULL); + + PG_RETURN_NUMERIC(res); +} + + +/* + * numeric_div_opt_error() - + * + * Internal version of numeric_div(). If "*have_error" flag is provided, + * on error it's set to true, NULL returned. This is helpful when caller + * need to handle errors by itself. + */ +Numeric +numeric_div_opt_error(Numeric num1, Numeric num2, bool *have_error) +{ NumericVar arg1; NumericVar arg2; NumericVar result; @@ -2534,7 +2609,7 @@ numeric_div(PG_FUNCTION_ARGS) * Handle NaN */ if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2)) - PG_RETURN_NUMERIC(make_result(&const_nan)); + return make_result(&const_nan); /* * Unpack the arguments @@ -2550,15 +2625,24 @@ numeric_div(PG_FUNCTION_ARGS) rscale = select_div_scale(&arg1, &arg2); /* + * If "have_error" is provided, check for division by zero here + */ + if (have_error && (arg2.ndigits == 0 || arg2.digits[0] == 0)) + { + *have_error = true; + return NULL; + } + + /* * Do the divide and return the result */ div_var(&arg1, &arg2, &result, rscale, true); - res = make_result(&result); + res = make_result_opt_error(&result, have_error); free_var(&result); - PG_RETURN_NUMERIC(res); + return res; } @@ -2615,25 +2699,52 @@ numeric_mod(PG_FUNCTION_ARGS) Numeric num1 = PG_GETARG_NUMERIC(0); Numeric num2 = PG_GETARG_NUMERIC(1); Numeric res; + + res = numeric_mod_opt_error(num1, num2, NULL); + + PG_RETURN_NUMERIC(res); +} + + +/* + * numeric_mod_opt_error() - + * + * Internal version of numeric_mod(). If "*have_error" flag is provided, + * on error it's set to true, NULL returned. This is helpful when caller + * need to handle errors by itself. + */ +Numeric +numeric_mod_opt_error(Numeric num1, Numeric num2, bool *have_error) +{ + Numeric res; NumericVar arg1; NumericVar arg2; NumericVar result; if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2)) - PG_RETURN_NUMERIC(make_result(&const_nan)); + return make_result(&const_nan); init_var_from_num(num1, &arg1); init_var_from_num(num2, &arg2); init_var(&result); + /* + * If "have_error" is provided, check for division by zero here + */ + if (have_error && (arg2.ndigits == 0 || arg2.digits[0] == 0)) + { + *have_error = true; + return NULL; + } + mod_var(&arg1, &arg2, &result); - res = make_result(&result); + res = make_result_opt_error(&result, NULL); free_var(&result); - PG_RETURN_NUMERIC(res); + return res; } @@ -3090,52 +3201,75 @@ int4_numeric(PG_FUNCTION_ARGS) PG_RETURN_NUMERIC(res); } - -Datum -numeric_int4(PG_FUNCTION_ARGS) +int32 +numeric_int4_opt_error(Numeric num, bool *have_error) { - Numeric num = PG_GETARG_NUMERIC(0); NumericVar x; int32 result; /* XXX would it be better to return NULL? */ if (NUMERIC_IS_NAN(num)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot convert NaN to integer"))); + { + if (have_error) + { + *have_error = true; + return 0; + } + else + { + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot convert NaN to integer"))); + } + } /* Convert to variable format, then convert to int4 */ init_var_from_num(num, &x); - result = numericvar_to_int32(&x); - PG_RETURN_INT32(result); + + if (!numericvar_to_int32(&x, &result)) + { + if (have_error) + { + *have_error = true; + return 0; + } + else + { + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("integer out of range"))); + } + } + + return result; +} + +Datum +numeric_int4(PG_FUNCTION_ARGS) +{ + Numeric num = PG_GETARG_NUMERIC(0); + + PG_RETURN_INT32(numeric_int4_opt_error(num, NULL)); } /* * Given a NumericVar, convert it to an int32. If the NumericVar - * exceeds the range of an int32, raise the appropriate error via - * ereport(). The input NumericVar is *not* free'd. + * exceeds the range of an int32, false is returned, otherwise true is returned. + * The input NumericVar is *not* free'd. */ -static int32 -numericvar_to_int32(const NumericVar *var) +static bool +numericvar_to_int32(const NumericVar *var, int32 *result) { - int32 result; int64 val; if (!numericvar_to_int64(var, &val)) - ereport(ERROR, - (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), - errmsg("integer out of range"))); + return false; /* Down-convert to int4 */ - result = (int32) val; + *result = (int32) val; /* Test for overflow by reverse-conversion. */ - if ((int64) result != val) - ereport(ERROR, - (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), - errmsg("integer out of range"))); - - return result; + return ((int64) *result == val); } Datum @@ -6098,13 +6232,15 @@ get_str_from_var_sci(const NumericVar *var, int rscale) /* - * make_result() - + * make_result_opt_error() - * * Create the packed db numeric format in palloc()'d memory from - * a variable. + * a variable. If "*have_error" flag is provided, on error it's set to + * true, NULL returned. This is helpful when caller need to handle errors + * by itself. */ static Numeric -make_result(const NumericVar *var) +make_result_opt_error(const NumericVar *var, bool *have_error) { Numeric result; NumericDigit *digits = var->digits; @@ -6175,9 +6311,19 @@ make_result(const NumericVar *var) /* Check for overflow of int16 fields */ if (NUMERIC_WEIGHT(result) != weight || NUMERIC_DSCALE(result) != var->dscale) - ereport(ERROR, - (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), - errmsg("value overflows numeric format"))); + { + if (have_error) + { + *have_error = true; + return NULL; + } + else + { + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value overflows numeric format"))); + } + } dump_numeric("make_result()", result); return result; @@ -6185,6 +6331,18 @@ make_result(const NumericVar *var) /* + * make_result() - + * + * An interface to make_result_opt_error() without "have_error" argument. + */ +static Numeric +make_result(const NumericVar *var) +{ + return make_result_opt_error(var, NULL); +} + + +/* * apply_typmod() - * * Do bounds checking and rounding according to the attributes |