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
This commit is contained in:
Alexander Korotkov 2019-03-16 12:21:19 +03:00
parent 72b6460336
commit 16d489b0fe
7 changed files with 353 additions and 97 deletions

View File

@ -12209,7 +12209,7 @@ table2-mapping
<para> <para>
The <literal>@?</literal> and <literal>@@</literal> operators suppress The <literal>@?</literal> and <literal>@@</literal> operators suppress
errors including: lacking object field or array element, unexpected JSON errors including: lacking object field or array element, unexpected JSON
item type. item type and numeric errors.
This behavior might be helpful while searching over JSON document This behavior might be helpful while searching over JSON document
collections of varying structure. collections of varying structure.
</para> </para>

View File

@ -336,8 +336,19 @@ float8in(PG_FUNCTION_ARGS)
PG_RETURN_FLOAT8(float8in_internal(num, NULL, "double precision", num)); PG_RETURN_FLOAT8(float8in_internal(num, NULL, "double precision", num));
} }
/* Convenience macro: set *have_error flag (if provided) or throw error */
#define RETURN_ERROR(throw_error) \
do { \
if (have_error) { \
*have_error = true; \
return 0.0; \
} else { \
throw_error; \
} \
} while (0)
/* /*
* float8in_internal - guts of float8in() * float8in_internal_opt_error - guts of float8in()
* *
* This is exposed for use by functions that want a reasonably * This is exposed for use by functions that want a reasonably
* platform-independent way of inputting doubles. The behavior is * platform-independent way of inputting doubles. The behavior is
@ -353,10 +364,14 @@ float8in(PG_FUNCTION_ARGS)
* *
* "num" could validly be declared "const char *", but that results in an * "num" could validly be declared "const char *", but that results in an
* unreasonable amount of extra casting both here and in callers, so we don't. * unreasonable amount of extra casting both here and in callers, so we don't.
*
* When "*have_error" flag is provided, it's set instead of throwing an
* error. This is helpful when caller need to handle errors by itself.
*/ */
double double
float8in_internal(char *num, char **endptr_p, float8in_internal_opt_error(char *num, char **endptr_p,
const char *type_name, const char *orig_string) const char *type_name, const char *orig_string,
bool *have_error)
{ {
double val; double val;
char *endptr; char *endptr;
@ -370,10 +385,10 @@ float8in_internal(char *num, char **endptr_p,
* strtod() on different platforms. * strtod() on different platforms.
*/ */
if (*num == '\0') if (*num == '\0')
ereport(ERROR, RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type %s: \"%s\"", errmsg("invalid input syntax for type %s: \"%s\"",
type_name, orig_string))); type_name, orig_string))));
errno = 0; errno = 0;
val = strtod(num, &endptr); val = strtod(num, &endptr);
@ -446,17 +461,19 @@ float8in_internal(char *num, char **endptr_p,
char *errnumber = pstrdup(num); char *errnumber = pstrdup(num);
errnumber[endptr - num] = '\0'; errnumber[endptr - num] = '\0';
ereport(ERROR, RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("\"%s\" is out of range for type double precision", errmsg("\"%s\" is out of range for "
errnumber))); "type double precision",
errnumber))));
} }
} }
else else
ereport(ERROR, RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type %s: \"%s\"", errmsg("invalid input syntax for type "
type_name, orig_string))); "%s: \"%s\"",
type_name, orig_string))));
} }
#ifdef HAVE_BUGGY_SOLARIS_STRTOD #ifdef HAVE_BUGGY_SOLARIS_STRTOD
else else
@ -479,14 +496,27 @@ float8in_internal(char *num, char **endptr_p,
if (endptr_p) if (endptr_p)
*endptr_p = endptr; *endptr_p = endptr;
else if (*endptr != '\0') else if (*endptr != '\0')
ereport(ERROR, RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type %s: \"%s\"", errmsg("invalid input syntax for type "
type_name, orig_string))); "%s: \"%s\"",
type_name, orig_string))));
return val; return val;
} }
/*
* Interfact to float8in_internal_opt_error() without "have_error" argument.
*/
double
float8in_internal(char *num, char **endptr_p,
const char *type_name, const char *orig_string)
{
return float8in_internal_opt_error(num, endptr_p, type_name,
orig_string, NULL);
}
/* /*
* float8out - converts float8 number to a string * float8out - converts float8 number to a string
* using a standard output format * using a standard output format

View File

@ -179,6 +179,7 @@ typedef JsonPathBool (*JsonPathPredicateCallback) (JsonPathItem *jsp,
JsonbValue *larg, JsonbValue *larg,
JsonbValue *rarg, JsonbValue *rarg,
void *param); void *param);
typedef Numeric (*BinaryArithmFunc) (Numeric num1, Numeric num2, bool *error);
static JsonPathExecResult executeJsonPath(JsonPath *path, Jsonb *vars, static JsonPathExecResult executeJsonPath(JsonPath *path, Jsonb *vars,
Jsonb *json, bool throwErrors, JsonValueList *result); Jsonb *json, bool throwErrors, JsonValueList *result);
@ -212,8 +213,8 @@ static JsonPathBool executePredicate(JsonPathExecContext *cxt,
JsonbValue *jb, bool unwrapRightArg, JsonbValue *jb, bool unwrapRightArg,
JsonPathPredicateCallback exec, void *param); JsonPathPredicateCallback exec, void *param);
static JsonPathExecResult executeBinaryArithmExpr(JsonPathExecContext *cxt, static JsonPathExecResult executeBinaryArithmExpr(JsonPathExecContext *cxt,
JsonPathItem *jsp, JsonbValue *jb, PGFunction func, JsonPathItem *jsp, JsonbValue *jb,
JsonValueList *found); BinaryArithmFunc func, JsonValueList *found);
static JsonPathExecResult executeUnaryArithmExpr(JsonPathExecContext *cxt, static JsonPathExecResult executeUnaryArithmExpr(JsonPathExecContext *cxt,
JsonPathItem *jsp, JsonbValue *jb, PGFunction func, JsonPathItem *jsp, JsonbValue *jb, PGFunction func,
JsonValueList *found); JsonValueList *found);
@ -830,23 +831,23 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
case jpiAdd: case jpiAdd:
return executeBinaryArithmExpr(cxt, jsp, jb, return executeBinaryArithmExpr(cxt, jsp, jb,
numeric_add, found); numeric_add_opt_error, found);
case jpiSub: case jpiSub:
return executeBinaryArithmExpr(cxt, jsp, jb, return executeBinaryArithmExpr(cxt, jsp, jb,
numeric_sub, found); numeric_sub_opt_error, found);
case jpiMul: case jpiMul:
return executeBinaryArithmExpr(cxt, jsp, jb, return executeBinaryArithmExpr(cxt, jsp, jb,
numeric_mul, found); numeric_mul_opt_error, found);
case jpiDiv: case jpiDiv:
return executeBinaryArithmExpr(cxt, jsp, jb, return executeBinaryArithmExpr(cxt, jsp, jb,
numeric_div, found); numeric_div_opt_error, found);
case jpiMod: case jpiMod:
return executeBinaryArithmExpr(cxt, jsp, jb, return executeBinaryArithmExpr(cxt, jsp, jb,
numeric_mod, found); numeric_mod_opt_error, found);
case jpiPlus: case jpiPlus:
return executeUnaryArithmExpr(cxt, jsp, jb, NULL, found); return executeUnaryArithmExpr(cxt, jsp, jb, NULL, found);
@ -999,12 +1000,22 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
{ {
char *tmp = DatumGetCString(DirectFunctionCall1(numeric_out, char *tmp = DatumGetCString(DirectFunctionCall1(numeric_out,
NumericGetDatum(jb->val.numeric))); NumericGetDatum(jb->val.numeric)));
bool have_error = false;
(void) float8in_internal(tmp, (void) float8in_internal_opt_error(tmp,
NULL, NULL,
"double precision", "double precision",
tmp); tmp,
&have_error);
if (have_error)
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_NON_NUMERIC_JSON_ITEM),
errmsg(ERRMSG_NON_NUMERIC_JSON_ITEM),
errdetail("jsonpath item method .%s() "
"can only be applied to "
"a numeric value",
jspOperationName(jsp->type)))));
res = jperOk; res = jperOk;
} }
else if (jb->type == jbvString) else if (jb->type == jbvString)
@ -1013,13 +1024,15 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
double val; double val;
char *tmp = pnstrdup(jb->val.string.val, char *tmp = pnstrdup(jb->val.string.val,
jb->val.string.len); jb->val.string.len);
bool have_error = false;
val = float8in_internal(tmp, val = float8in_internal_opt_error(tmp,
NULL, NULL,
"double precision", "double precision",
tmp); tmp,
&have_error);
if (isinf(val)) if (have_error || isinf(val))
RETURN_ERROR(ereport(ERROR, RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_NON_NUMERIC_JSON_ITEM), (errcode(ERRCODE_NON_NUMERIC_JSON_ITEM),
errmsg(ERRMSG_NON_NUMERIC_JSON_ITEM), errmsg(ERRMSG_NON_NUMERIC_JSON_ITEM),
@ -1497,7 +1510,7 @@ executePredicate(JsonPathExecContext *cxt, JsonPathItem *pred,
*/ */
static JsonPathExecResult static JsonPathExecResult
executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp,
JsonbValue *jb, PGFunction func, JsonbValue *jb, BinaryArithmFunc func,
JsonValueList *found) JsonValueList *found)
{ {
JsonPathExecResult jper; JsonPathExecResult jper;
@ -1506,7 +1519,7 @@ executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp,
JsonValueList rseq = {0}; JsonValueList rseq = {0};
JsonbValue *lval; JsonbValue *lval;
JsonbValue *rval; JsonbValue *rval;
Datum res; Numeric res;
jspGetLeftArg(jsp, &elem); jspGetLeftArg(jsp, &elem);
@ -1542,16 +1555,26 @@ executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp,
"is not a singleton numeric value", "is not a singleton numeric value",
jspOperationName(jsp->type))))); jspOperationName(jsp->type)))));
res = DirectFunctionCall2(func, if (jspThrowErrors(cxt))
NumericGetDatum(lval->val.numeric), {
NumericGetDatum(rval->val.numeric)); res = func(lval->val.numeric, rval->val.numeric, NULL);
}
else
{
bool error = false;
res = func(lval->val.numeric, rval->val.numeric, &error);
if (error)
return jperError;
}
if (!jspGetNext(jsp, &elem) && !found) if (!jspGetNext(jsp, &elem) && !found)
return jperOk; return jperOk;
lval = palloc(sizeof(*lval)); lval = palloc(sizeof(*lval));
lval->type = jbvNumeric; lval->type = jbvNumeric;
lval->val.numeric = DatumGetNumeric(res); lval->val.numeric = res;
return executeNextItem(cxt, jsp, &elem, lval, found, false); return executeNextItem(cxt, jsp, &elem, lval, found, false);
} }
@ -2108,6 +2131,7 @@ getArrayIndex(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
JsonValueList found = {0}; JsonValueList found = {0};
JsonPathExecResult res = executeItem(cxt, jsp, jb, &found); JsonPathExecResult res = executeItem(cxt, jsp, jb, &found);
Datum numeric_index; Datum numeric_index;
bool have_error = false;
if (jperIsError(res)) if (jperIsError(res))
return res; return res;
@ -2124,7 +2148,15 @@ getArrayIndex(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
NumericGetDatum(jbv->val.numeric), NumericGetDatum(jbv->val.numeric),
Int32GetDatum(0)); Int32GetDatum(0));
*index = DatumGetInt32(DirectFunctionCall1(numeric_int4, numeric_index)); *index = numeric_int4_opt_error(DatumGetNumeric(numeric_index),
&have_error);
if (have_error)
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_JSON_SUBSCRIPT),
errmsg(ERRMSG_INVALID_JSON_SUBSCRIPT),
errdetail("jsonpath array subscript is "
"out of integer range"))));
return jperOk; return jperOk;
} }

View File

@ -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 char *get_str_from_var_sci(const NumericVar *var, int rscale);
static Numeric make_result(const NumericVar *var); 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 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 bool numericvar_to_int64(const NumericVar *var, int64 *result);
static void int64_to_numericvar(int64 val, NumericVar *var); static void int64_to_numericvar(int64 val, NumericVar *var);
#ifdef HAVE_INT128 #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 */ /* 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(&count_var);
free_var(&result_var); free_var(&result_var);
@ -2406,6 +2410,23 @@ numeric_add(PG_FUNCTION_ARGS)
{ {
Numeric num1 = PG_GETARG_NUMERIC(0); Numeric num1 = PG_GETARG_NUMERIC(0);
Numeric num2 = PG_GETARG_NUMERIC(1); 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 arg1;
NumericVar arg2; NumericVar arg2;
NumericVar result; NumericVar result;
@ -2415,7 +2436,7 @@ numeric_add(PG_FUNCTION_ARGS)
* Handle NaN * Handle NaN
*/ */
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2)) 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. * Unpack the values, let add_var() compute the result and return it.
@ -2426,11 +2447,11 @@ numeric_add(PG_FUNCTION_ARGS)
init_var(&result); init_var(&result);
add_var(&arg1, &arg2, &result); add_var(&arg1, &arg2, &result);
res = make_result(&result); res = make_result_opt_error(&result, have_error);
free_var(&result); 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 num1 = PG_GETARG_NUMERIC(0);
Numeric num2 = PG_GETARG_NUMERIC(1); 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 arg1;
NumericVar arg2; NumericVar arg2;
NumericVar result; NumericVar result;
@ -2453,7 +2492,7 @@ numeric_sub(PG_FUNCTION_ARGS)
* Handle NaN * Handle NaN
*/ */
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2)) 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. * Unpack the values, let sub_var() compute the result and return it.
@ -2464,11 +2503,11 @@ numeric_sub(PG_FUNCTION_ARGS)
init_var(&result); init_var(&result);
sub_var(&arg1, &arg2, &result); sub_var(&arg1, &arg2, &result);
res = make_result(&result); res = make_result_opt_error(&result, have_error);
free_var(&result); 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 num1 = PG_GETARG_NUMERIC(0);
Numeric num2 = PG_GETARG_NUMERIC(1); 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 arg1;
NumericVar arg2; NumericVar arg2;
NumericVar result; NumericVar result;
@ -2491,7 +2548,7 @@ numeric_mul(PG_FUNCTION_ARGS)
* Handle NaN * Handle NaN
*/ */
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2)) 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. * Unpack the values, let mul_var() compute the result and return it.
@ -2506,11 +2563,11 @@ numeric_mul(PG_FUNCTION_ARGS)
init_var(&result); init_var(&result);
mul_var(&arg1, &arg2, &result, arg1.dscale + arg2.dscale); mul_var(&arg1, &arg2, &result, arg1.dscale + arg2.dscale);
res = make_result(&result); res = make_result_opt_error(&result, have_error);
free_var(&result); 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 num1 = PG_GETARG_NUMERIC(0);
Numeric num2 = PG_GETARG_NUMERIC(1); 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 arg1;
NumericVar arg2; NumericVar arg2;
NumericVar result; NumericVar result;
@ -2534,7 +2609,7 @@ numeric_div(PG_FUNCTION_ARGS)
* Handle NaN * Handle NaN
*/ */
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2)) if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
PG_RETURN_NUMERIC(make_result(&const_nan)); return make_result(&const_nan);
/* /*
* Unpack the arguments * Unpack the arguments
@ -2549,16 +2624,25 @@ numeric_div(PG_FUNCTION_ARGS)
*/ */
rscale = select_div_scale(&arg1, &arg2); 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 * Do the divide and return the result
*/ */
div_var(&arg1, &arg2, &result, rscale, true); div_var(&arg1, &arg2, &result, rscale, true);
res = make_result(&result); res = make_result_opt_error(&result, have_error);
free_var(&result); 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 num1 = PG_GETARG_NUMERIC(0);
Numeric num2 = PG_GETARG_NUMERIC(1); Numeric num2 = PG_GETARG_NUMERIC(1);
Numeric res; 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 arg1;
NumericVar arg2; NumericVar arg2;
NumericVar result; NumericVar result;
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2)) 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(num1, &arg1);
init_var_from_num(num2, &arg2); init_var_from_num(num2, &arg2);
init_var(&result); 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); mod_var(&arg1, &arg2, &result);
res = make_result(&result); res = make_result_opt_error(&result, NULL);
free_var(&result); free_var(&result);
PG_RETURN_NUMERIC(res); return res;
} }
@ -3090,52 +3201,75 @@ int4_numeric(PG_FUNCTION_ARGS)
PG_RETURN_NUMERIC(res); PG_RETURN_NUMERIC(res);
} }
int32
Datum numeric_int4_opt_error(Numeric num, bool *have_error)
numeric_int4(PG_FUNCTION_ARGS)
{ {
Numeric num = PG_GETARG_NUMERIC(0);
NumericVar x; NumericVar x;
int32 result; int32 result;
/* XXX would it be better to return NULL? */ /* XXX would it be better to return NULL? */
if (NUMERIC_IS_NAN(num)) if (NUMERIC_IS_NAN(num))
ereport(ERROR, {
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), if (have_error)
errmsg("cannot convert NaN to integer"))); {
*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 */ /* Convert to variable format, then convert to int4 */
init_var_from_num(num, &x); 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 * Given a NumericVar, convert it to an int32. If the NumericVar
* exceeds the range of an int32, raise the appropriate error via * exceeds the range of an int32, false is returned, otherwise true is returned.
* ereport(). The input NumericVar is *not* free'd. * The input NumericVar is *not* free'd.
*/ */
static int32 static bool
numericvar_to_int32(const NumericVar *var) numericvar_to_int32(const NumericVar *var, int32 *result)
{ {
int32 result;
int64 val; int64 val;
if (!numericvar_to_int64(var, &val)) if (!numericvar_to_int64(var, &val))
ereport(ERROR, return false;
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
/* Down-convert to int4 */ /* Down-convert to int4 */
result = (int32) val; *result = (int32) val;
/* Test for overflow by reverse-conversion. */ /* Test for overflow by reverse-conversion. */
if ((int64) result != val) return ((int64) *result == val);
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
return result;
} }
Datum 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 * 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 static Numeric
make_result(const NumericVar *var) make_result_opt_error(const NumericVar *var, bool *have_error)
{ {
Numeric result; Numeric result;
NumericDigit *digits = var->digits; NumericDigit *digits = var->digits;
@ -6175,15 +6311,37 @@ make_result(const NumericVar *var)
/* Check for overflow of int16 fields */ /* Check for overflow of int16 fields */
if (NUMERIC_WEIGHT(result) != weight || if (NUMERIC_WEIGHT(result) != weight ||
NUMERIC_DSCALE(result) != var->dscale) NUMERIC_DSCALE(result) != var->dscale)
ereport(ERROR, {
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), if (have_error)
errmsg("value overflows numeric format"))); {
*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); dump_numeric("make_result()", result);
return result; return result;
} }
/*
* 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() - * apply_typmod() -
* *

View File

@ -40,6 +40,9 @@ extern PGDLLIMPORT int extra_float_digits;
extern int is_infinite(float8 val); extern int is_infinite(float8 val);
extern float8 float8in_internal(char *num, char **endptr_p, extern float8 float8in_internal(char *num, char **endptr_p,
const char *type_name, const char *orig_string); const char *type_name, const char *orig_string);
extern float8 float8in_internal_opt_error(char *num, char **endptr_p,
const char *type_name, const char *orig_string,
bool *have_error);
extern char *float8out_internal(float8 num); extern char *float8out_internal(float8 num);
extern int float4_cmp_internal(float4 a, float4 b); extern int float4_cmp_internal(float4 a, float4 b);
extern int float8_cmp_internal(float8 a, float8 b); extern int float8_cmp_internal(float8 a, float8 b);

View File

@ -61,4 +61,16 @@ int32 numeric_maximum_size(int32 typmod);
extern char *numeric_out_sci(Numeric num, int scale); extern char *numeric_out_sci(Numeric num, int scale);
extern char *numeric_normalize(Numeric num); extern char *numeric_normalize(Numeric num);
extern Numeric numeric_add_opt_error(Numeric num1, Numeric num2,
bool *have_error);
extern Numeric numeric_sub_opt_error(Numeric num1, Numeric num2,
bool *have_error);
extern Numeric numeric_mul_opt_error(Numeric num1, Numeric num2,
bool *have_error);
extern Numeric numeric_div_opt_error(Numeric num1, Numeric num2,
bool *have_error);
extern Numeric numeric_mod_opt_error(Numeric num1, Numeric num2,
bool *have_error);
extern int32 numeric_int4_opt_error(Numeric num, bool *error);
#endif /* _PG_NUMERIC_H_ */ #endif /* _PG_NUMERIC_H_ */

View File

@ -127,13 +127,23 @@ select jsonb_path_query('[1]', 'strict $[1]', silent => true);
(0 rows) (0 rows)
select jsonb '[1]' @? 'lax $[10000000000000000]'; select jsonb '[1]' @? 'lax $[10000000000000000]';
ERROR: integer out of range ?column?
----------
(1 row)
select jsonb '[1]' @? 'strict $[10000000000000000]'; select jsonb '[1]' @? 'strict $[10000000000000000]';
ERROR: integer out of range ?column?
----------
(1 row)
select jsonb_path_query('[1]', 'lax $[10000000000000000]'); select jsonb_path_query('[1]', 'lax $[10000000000000000]');
ERROR: integer out of range ERROR: invalid SQL/JSON subscript
DETAIL: jsonpath array subscript is out of integer range
select jsonb_path_query('[1]', 'strict $[10000000000000000]'); select jsonb_path_query('[1]', 'strict $[10000000000000000]');
ERROR: integer out of range ERROR: invalid SQL/JSON subscript
DETAIL: jsonpath array subscript is out of integer range
select jsonb '[1]' @? '$[0]'; select jsonb '[1]' @? '$[0]';
?column? ?column?
---------- ----------
@ -1037,9 +1047,19 @@ select jsonb '1' @? '$ ? ($ > 0)';
-- arithmetic errors -- arithmetic errors
select jsonb_path_query('[1,2,0,3]', '$[*] ? (2 / @ > 0)'); select jsonb_path_query('[1,2,0,3]', '$[*] ? (2 / @ > 0)');
ERROR: division by zero jsonb_path_query
------------------
1
2
3
(3 rows)
select jsonb_path_query('[1,2,0,3]', '$[*] ? ((2 / @ > 0) is unknown)'); select jsonb_path_query('[1,2,0,3]', '$[*] ? ((2 / @ > 0) is unknown)');
ERROR: division by zero jsonb_path_query
------------------
0
(1 row)
select jsonb_path_query('0', '1 / $'); select jsonb_path_query('0', '1 / $');
ERROR: division by zero ERROR: division by zero
select jsonb_path_query('0', '1 / $ + 2'); select jsonb_path_query('0', '1 / $ + 2');
@ -1502,7 +1522,8 @@ select jsonb_path_query('"1.23"', '$.double()');
(1 row) (1 row)
select jsonb_path_query('"1.23aaa"', '$.double()'); select jsonb_path_query('"1.23aaa"', '$.double()');
ERROR: invalid input syntax for type double precision: "1.23aaa" ERROR: non-numeric SQL/JSON item
DETAIL: jsonpath item method .double() can only be applied to a numeric value
select jsonb_path_query('"nan"', '$.double()'); select jsonb_path_query('"nan"', '$.double()');
jsonb_path_query jsonb_path_query
------------------ ------------------