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
<para>
The <literal>@?</literal> and <literal>@@</literal> operators suppress
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
collections of varying structure.
</para>
......
......@@ -336,8 +336,19 @@ float8in(PG_FUNCTION_ARGS)
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
* 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
* 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
float8in_internal(char *num, char **endptr_p,
const char *type_name, const char *orig_string)
float8in_internal_opt_error(char *num, char **endptr_p,
const char *type_name, const char *orig_string,
bool *have_error)
{
double val;
char *endptr;
......@@ -370,10 +385,10 @@ float8in_internal(char *num, char **endptr_p,
* strtod() on different platforms.
*/
if (*num == '\0')
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type %s: \"%s\"",
type_name, orig_string)));
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type %s: \"%s\"",
type_name, orig_string))));
errno = 0;
val = strtod(num, &endptr);
......@@ -446,17 +461,19 @@ float8in_internal(char *num, char **endptr_p,
char *errnumber = pstrdup(num);
errnumber[endptr - num] = '\0';
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("\"%s\" is out of range for type double precision",
errnumber)));
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("\"%s\" is out of range for "
"type double precision",
errnumber))));
}
}
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type %s: \"%s\"",
type_name, orig_string)));
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type "
"%s: \"%s\"",
type_name, orig_string))));
}
#ifdef HAVE_BUGGY_SOLARIS_STRTOD
else
......@@ -479,14 +496,27 @@ float8in_internal(char *num, char **endptr_p,
if (endptr_p)
*endptr_p = endptr;
else if (*endptr != '\0')
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type %s: \"%s\"",
type_name, orig_string)));
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type "
"%s: \"%s\"",
type_name, orig_string))));
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
* using a standard output format
......
......@@ -179,6 +179,7 @@ typedef JsonPathBool (*JsonPathPredicateCallback) (JsonPathItem *jsp,
JsonbValue *larg,
JsonbValue *rarg,
void *param);
typedef Numeric (*BinaryArithmFunc) (Numeric num1, Numeric num2, bool *error);
static JsonPathExecResult executeJsonPath(JsonPath *path, Jsonb *vars,
Jsonb *json, bool throwErrors, JsonValueList *result);
......@@ -212,8 +213,8 @@ static JsonPathBool executePredicate(JsonPathExecContext *cxt,
JsonbValue *jb, bool unwrapRightArg,
JsonPathPredicateCallback exec, void *param);
static JsonPathExecResult executeBinaryArithmExpr(JsonPathExecContext *cxt,
JsonPathItem *jsp, JsonbValue *jb, PGFunction func,
JsonValueList *found);
JsonPathItem *jsp, JsonbValue *jb,
BinaryArithmFunc func, JsonValueList *found);
static JsonPathExecResult executeUnaryArithmExpr(JsonPathExecContext *cxt,
JsonPathItem *jsp, JsonbValue *jb, PGFunction func,
JsonValueList *found);
......@@ -830,23 +831,23 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
case jpiAdd:
return executeBinaryArithmExpr(cxt, jsp, jb,
numeric_add, found);
numeric_add_opt_error, found);
case jpiSub:
return executeBinaryArithmExpr(cxt, jsp, jb,
numeric_sub, found);
numeric_sub_opt_error, found);
case jpiMul:
return executeBinaryArithmExpr(cxt, jsp, jb,
numeric_mul, found);
numeric_mul_opt_error, found);
case jpiDiv:
return executeBinaryArithmExpr(cxt, jsp, jb,
numeric_div, found);
numeric_div_opt_error, found);
case jpiMod:
return executeBinaryArithmExpr(cxt, jsp, jb,
numeric_mod, found);
numeric_mod_opt_error, found);
case jpiPlus:
return executeUnaryArithmExpr(cxt, jsp, jb, NULL, found);
......@@ -999,12 +1000,22 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
{
char *tmp = DatumGetCString(DirectFunctionCall1(numeric_out,
NumericGetDatum(jb->val.numeric)));
bool have_error = false;
(void) float8in_internal(tmp,
NULL,
"double precision",
tmp);
(void) float8in_internal_opt_error(tmp,
NULL,
"double precision",
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;
}
else if (jb->type == jbvString)
......@@ -1013,13 +1024,15 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
double val;
char *tmp = pnstrdup(jb->val.string.val,
jb->val.string.len);
bool have_error = false;
val = float8in_internal(tmp,
NULL,
"double precision",
tmp);
val = float8in_internal_opt_error(tmp,
NULL,
"double precision",
tmp,
&have_error);
if (isinf(val))
if (have_error || isinf(val))
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_NON_NUMERIC_JSON_ITEM),
errmsg(ERRMSG_NON_NUMERIC_JSON_ITEM),
......@@ -1497,7 +1510,7 @@ executePredicate(JsonPathExecContext *cxt, JsonPathItem *pred,
*/
static JsonPathExecResult
executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp,
JsonbValue *jb, PGFunction func,
JsonbValue *jb, BinaryArithmFunc func,
JsonValueList *found)
{
JsonPathExecResult jper;
......@@ -1506,7 +1519,7 @@ executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp,
JsonValueList rseq = {0};
JsonbValue *lval;
JsonbValue *rval;
Datum res;
Numeric res;
jspGetLeftArg(jsp, &elem);
......@@ -1542,16 +1555,26 @@ executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp,
"is not a singleton numeric value",
jspOperationName(jsp->type)))));
res = DirectFunctionCall2(func,
NumericGetDatum(lval->val.numeric),
NumericGetDatum(rval->val.numeric));
if (jspThrowErrors(cxt))
{
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)
return jperOk;
lval = palloc(sizeof(*lval));
lval->type = jbvNumeric;
lval->val.numeric = DatumGetNumeric(res);
lval->val.numeric = res;
return executeNextItem(cxt, jsp, &elem, lval, found, false);
}
......@@ -2108,6 +2131,7 @@ getArrayIndex(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
JsonValueList found = {0};
JsonPathExecResult res = executeItem(cxt, jsp, jb, &found);
Datum numeric_index;
bool have_error = false;
if (jperIsError(res))
return res;
......@@ -2124,7 +2148,15 @@ getArrayIndex(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
NumericGetDatum(jbv->val.numeric),
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;
}
......
This diff is collapsed.
......@@ -40,6 +40,9 @@ extern PGDLLIMPORT int extra_float_digits;
extern int is_infinite(float8 val);
extern float8 float8in_internal(char *num, char **endptr_p,
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 int float4_cmp_internal(float4 a, float4 b);
extern int float8_cmp_internal(float8 a, float8 b);
......
......@@ -61,4 +61,16 @@ int32 numeric_maximum_size(int32 typmod);
extern char *numeric_out_sci(Numeric num, int scale);
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_ */
......@@ -127,13 +127,23 @@ select jsonb_path_query('[1]', 'strict $[1]', silent => true);
(0 rows)
select jsonb '[1]' @? 'lax $[10000000000000000]';
ERROR: integer out of range
?column?
----------
(1 row)
select jsonb '[1]' @? 'strict $[10000000000000000]';
ERROR: integer out of range
?column?
----------
(1 row)
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]');
ERROR: integer out of range
ERROR: invalid SQL/JSON subscript
DETAIL: jsonpath array subscript is out of integer range
select jsonb '[1]' @? '$[0]';
?column?
----------
......@@ -1037,9 +1047,19 @@ select jsonb '1' @? '$ ? ($ > 0)';
-- arithmetic errors
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)');
ERROR: division by zero
jsonb_path_query
------------------
0
(1 row)
select jsonb_path_query('0', '1 / $');
ERROR: division by zero
select jsonb_path_query('0', '1 / $ + 2');
......@@ -1502,7 +1522,8 @@ select jsonb_path_query('"1.23"', '$.double()');
(1 row)
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()');
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