Commit 16d489b0 authored by Alexander Korotkov's avatar Alexander Korotkov

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
parent 72b64603
...@@ -12209,7 +12209,7 @@ table2-mapping ...@@ -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>
......
...@@ -336,8 +336,19 @@ float8in(PG_FUNCTION_ARGS) ...@@ -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) ...@@ -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, ...@@ -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, ...@@ -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, ...@@ -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
......
...@@ -179,6 +179,7 @@ typedef JsonPathBool (*JsonPathPredicateCallback) (JsonPathItem *jsp, ...@@ -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, ...@@ -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, ...@@ -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, ...@@ -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, ...@@ -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, ...@@ -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, ...@@ -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, ...@@ -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, ...@@ -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, ...@@ -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;
} }
......
This diff is collapsed.
...@@ -40,6 +40,9 @@ extern PGDLLIMPORT int extra_float_digits; ...@@ -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);
......
...@@ -61,4 +61,16 @@ int32 numeric_maximum_size(int32 typmod); ...@@ -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_ */
...@@ -127,13 +127,23 @@ select jsonb_path_query('[1]', 'strict $[1]', silent => true); ...@@ -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)'; ...@@ -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()'); ...@@ -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
------------------ ------------------
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment