Skip to content

Commit 16d489b

Browse files
committed
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
1 parent 72b6460 commit 16d489b

File tree

7 files changed

+353
-97
lines changed

7 files changed

+353
-97
lines changed

doc/src/sgml/func.sgml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12209,7 +12209,7 @@ table2-mapping
1220912209
<para>
1221012210
The <literal>@?</literal> and <literal>@@</literal> operators suppress
1221112211
errors including: lacking object field or array element, unexpected JSON
12212-
item type.
12212+
item type and numeric errors.
1221312213
This behavior might be helpful while searching over JSON document
1221412214
collections of varying structure.
1221512215
</para>

src/backend/utils/adt/float.c

Lines changed: 49 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -336,8 +336,19 @@ float8in(PG_FUNCTION_ARGS)
336336
PG_RETURN_FLOAT8(float8in_internal(num, NULL, "double precision", num));
337337
}
338338

339+
/* Convenience macro: set *have_error flag (if provided) or throw error */
340+
#define RETURN_ERROR(throw_error) \
341+
do { \
342+
if (have_error) { \
343+
*have_error = true; \
344+
return 0.0; \
345+
} else { \
346+
throw_error; \
347+
} \
348+
} while (0)
349+
339350
/*
340-
* float8in_internal - guts of float8in()
351+
* float8in_internal_opt_error - guts of float8in()
341352
*
342353
* This is exposed for use by functions that want a reasonably
343354
* platform-independent way of inputting doubles. The behavior is
@@ -353,10 +364,14 @@ float8in(PG_FUNCTION_ARGS)
353364
*
354365
* "num" could validly be declared "const char *", but that results in an
355366
* unreasonable amount of extra casting both here and in callers, so we don't.
367+
*
368+
* When "*have_error" flag is provided, it's set instead of throwing an
369+
* error. This is helpful when caller need to handle errors by itself.
356370
*/
357371
double
358-
float8in_internal(char *num, char **endptr_p,
359-
const char *type_name, const char *orig_string)
372+
float8in_internal_opt_error(char *num, char **endptr_p,
373+
const char *type_name, const char *orig_string,
374+
bool *have_error)
360375
{
361376
double val;
362377
char *endptr;
@@ -370,10 +385,10 @@ float8in_internal(char *num, char **endptr_p,
370385
* strtod() on different platforms.
371386
*/
372387
if (*num == '\0')
373-
ereport(ERROR,
374-
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
375-
errmsg("invalid input syntax for type %s: \"%s\"",
376-
type_name, orig_string)));
388+
RETURN_ERROR(ereport(ERROR,
389+
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
390+
errmsg("invalid input syntax for type %s: \"%s\"",
391+
type_name, orig_string))));
377392

378393
errno = 0;
379394
val = strtod(num, &endptr);
@@ -446,17 +461,19 @@ float8in_internal(char *num, char **endptr_p,
446461
char *errnumber = pstrdup(num);
447462

448463
errnumber[endptr - num] = '\0';
449-
ereport(ERROR,
450-
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
451-
errmsg("\"%s\" is out of range for type double precision",
452-
errnumber)));
464+
RETURN_ERROR(ereport(ERROR,
465+
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
466+
errmsg("\"%s\" is out of range for "
467+
"type double precision",
468+
errnumber))));
453469
}
454470
}
455471
else
456-
ereport(ERROR,
457-
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
458-
errmsg("invalid input syntax for type %s: \"%s\"",
459-
type_name, orig_string)));
472+
RETURN_ERROR(ereport(ERROR,
473+
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
474+
errmsg("invalid input syntax for type "
475+
"%s: \"%s\"",
476+
type_name, orig_string))));
460477
}
461478
#ifdef HAVE_BUGGY_SOLARIS_STRTOD
462479
else
@@ -479,14 +496,27 @@ float8in_internal(char *num, char **endptr_p,
479496
if (endptr_p)
480497
*endptr_p = endptr;
481498
else if (*endptr != '\0')
482-
ereport(ERROR,
483-
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
484-
errmsg("invalid input syntax for type %s: \"%s\"",
485-
type_name, orig_string)));
499+
RETURN_ERROR(ereport(ERROR,
500+
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
501+
errmsg("invalid input syntax for type "
502+
"%s: \"%s\"",
503+
type_name, orig_string))));
486504

487505
return val;
488506
}
489507

508+
/*
509+
* Interfact to float8in_internal_opt_error() without "have_error" argument.
510+
*/
511+
double
512+
float8in_internal(char *num, char **endptr_p,
513+
const char *type_name, const char *orig_string)
514+
{
515+
return float8in_internal_opt_error(num, endptr_p, type_name,
516+
orig_string, NULL);
517+
}
518+
519+
490520
/*
491521
* float8out - converts float8 number to a string
492522
* using a standard output format

src/backend/utils/adt/jsonpath_exec.c

Lines changed: 55 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ typedef JsonPathBool (*JsonPathPredicateCallback) (JsonPathItem *jsp,
179179
JsonbValue *larg,
180180
JsonbValue *rarg,
181181
void *param);
182+
typedef Numeric (*BinaryArithmFunc) (Numeric num1, Numeric num2, bool *error);
182183

183184
static JsonPathExecResult executeJsonPath(JsonPath *path, Jsonb *vars,
184185
Jsonb *json, bool throwErrors, JsonValueList *result);
@@ -212,8 +213,8 @@ static JsonPathBool executePredicate(JsonPathExecContext *cxt,
212213
JsonbValue *jb, bool unwrapRightArg,
213214
JsonPathPredicateCallback exec, void *param);
214215
static JsonPathExecResult executeBinaryArithmExpr(JsonPathExecContext *cxt,
215-
JsonPathItem *jsp, JsonbValue *jb, PGFunction func,
216-
JsonValueList *found);
216+
JsonPathItem *jsp, JsonbValue *jb,
217+
BinaryArithmFunc func, JsonValueList *found);
217218
static JsonPathExecResult executeUnaryArithmExpr(JsonPathExecContext *cxt,
218219
JsonPathItem *jsp, JsonbValue *jb, PGFunction func,
219220
JsonValueList *found);
@@ -830,23 +831,23 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
830831

831832
case jpiAdd:
832833
return executeBinaryArithmExpr(cxt, jsp, jb,
833-
numeric_add, found);
834+
numeric_add_opt_error, found);
834835

835836
case jpiSub:
836837
return executeBinaryArithmExpr(cxt, jsp, jb,
837-
numeric_sub, found);
838+
numeric_sub_opt_error, found);
838839

839840
case jpiMul:
840841
return executeBinaryArithmExpr(cxt, jsp, jb,
841-
numeric_mul, found);
842+
numeric_mul_opt_error, found);
842843

843844
case jpiDiv:
844845
return executeBinaryArithmExpr(cxt, jsp, jb,
845-
numeric_div, found);
846+
numeric_div_opt_error, found);
846847

847848
case jpiMod:
848849
return executeBinaryArithmExpr(cxt, jsp, jb,
849-
numeric_mod, found);
850+
numeric_mod_opt_error, found);
850851

851852
case jpiPlus:
852853
return executeUnaryArithmExpr(cxt, jsp, jb, NULL, found);
@@ -999,12 +1000,22 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
9991000
{
10001001
char *tmp = DatumGetCString(DirectFunctionCall1(numeric_out,
10011002
NumericGetDatum(jb->val.numeric)));
1003+
bool have_error = false;
10021004

1003-
(void) float8in_internal(tmp,
1004-
NULL,
1005-
"double precision",
1006-
tmp);
1005+
(void) float8in_internal_opt_error(tmp,
1006+
NULL,
1007+
"double precision",
1008+
tmp,
1009+
&have_error);
10071010

1011+
if (have_error)
1012+
RETURN_ERROR(ereport(ERROR,
1013+
(errcode(ERRCODE_NON_NUMERIC_JSON_ITEM),
1014+
errmsg(ERRMSG_NON_NUMERIC_JSON_ITEM),
1015+
errdetail("jsonpath item method .%s() "
1016+
"can only be applied to "
1017+
"a numeric value",
1018+
jspOperationName(jsp->type)))));
10081019
res = jperOk;
10091020
}
10101021
else if (jb->type == jbvString)
@@ -1013,13 +1024,15 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
10131024
double val;
10141025
char *tmp = pnstrdup(jb->val.string.val,
10151026
jb->val.string.len);
1027+
bool have_error = false;
10161028

1017-
val = float8in_internal(tmp,
1018-
NULL,
1019-
"double precision",
1020-
tmp);
1029+
val = float8in_internal_opt_error(tmp,
1030+
NULL,
1031+
"double precision",
1032+
tmp,
1033+
&have_error);
10211034

1022-
if (isinf(val))
1035+
if (have_error || isinf(val))
10231036
RETURN_ERROR(ereport(ERROR,
10241037
(errcode(ERRCODE_NON_NUMERIC_JSON_ITEM),
10251038
errmsg(ERRMSG_NON_NUMERIC_JSON_ITEM),
@@ -1497,7 +1510,7 @@ executePredicate(JsonPathExecContext *cxt, JsonPathItem *pred,
14971510
*/
14981511
static JsonPathExecResult
14991512
executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp,
1500-
JsonbValue *jb, PGFunction func,
1513+
JsonbValue *jb, BinaryArithmFunc func,
15011514
JsonValueList *found)
15021515
{
15031516
JsonPathExecResult jper;
@@ -1506,7 +1519,7 @@ executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp,
15061519
JsonValueList rseq = {0};
15071520
JsonbValue *lval;
15081521
JsonbValue *rval;
1509-
Datum res;
1522+
Numeric res;
15101523

15111524
jspGetLeftArg(jsp, &elem);
15121525

@@ -1542,16 +1555,26 @@ executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp,
15421555
"is not a singleton numeric value",
15431556
jspOperationName(jsp->type)))));
15441557

1545-
res = DirectFunctionCall2(func,
1546-
NumericGetDatum(lval->val.numeric),
1547-
NumericGetDatum(rval->val.numeric));
1558+
if (jspThrowErrors(cxt))
1559+
{
1560+
res = func(lval->val.numeric, rval->val.numeric, NULL);
1561+
}
1562+
else
1563+
{
1564+
bool error = false;
1565+
1566+
res = func(lval->val.numeric, rval->val.numeric, &error);
1567+
1568+
if (error)
1569+
return jperError;
1570+
}
15481571

15491572
if (!jspGetNext(jsp, &elem) && !found)
15501573
return jperOk;
15511574

15521575
lval = palloc(sizeof(*lval));
15531576
lval->type = jbvNumeric;
1554-
lval->val.numeric = DatumGetNumeric(res);
1577+
lval->val.numeric = res;
15551578

15561579
return executeNextItem(cxt, jsp, &elem, lval, found, false);
15571580
}
@@ -2108,6 +2131,7 @@ getArrayIndex(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
21082131
JsonValueList found = {0};
21092132
JsonPathExecResult res = executeItem(cxt, jsp, jb, &found);
21102133
Datum numeric_index;
2134+
bool have_error = false;
21112135

21122136
if (jperIsError(res))
21132137
return res;
@@ -2124,7 +2148,15 @@ getArrayIndex(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
21242148
NumericGetDatum(jbv->val.numeric),
21252149
Int32GetDatum(0));
21262150

2127-
*index = DatumGetInt32(DirectFunctionCall1(numeric_int4, numeric_index));
2151+
*index = numeric_int4_opt_error(DatumGetNumeric(numeric_index),
2152+
&have_error);
2153+
2154+
if (have_error)
2155+
RETURN_ERROR(ereport(ERROR,
2156+
(errcode(ERRCODE_INVALID_JSON_SUBSCRIPT),
2157+
errmsg(ERRMSG_INVALID_JSON_SUBSCRIPT),
2158+
errdetail("jsonpath array subscript is "
2159+
"out of integer range"))));
21282160

21292161
return jperOk;
21302162
}

0 commit comments

Comments
 (0)