v2-0001-Fix-jsonpath-decimal-to-honor-silent-mode.patch
application/octet-stream
Filename: v2-0001-Fix-jsonpath-decimal-to-honor-silent-mode.patch
Type: application/octet-stream
Part: 0
Patch
Format: format-patch
Series: patch v2-0001
Subject: Fix jsonpath .decimal() to honor silent mode
| File | + | − |
|---|---|---|
| src/backend/utils/adt/jsonpath_exec.c | 11 | 17 |
| src/backend/utils/adt/numeric.c | 30 | 22 |
| src/include/utils/numeric.h | 2 | 0 |
| src/test/regress/expected/jsonb_jsonpath.out | 32 | 0 |
| src/test/regress/sql/jsonb_jsonpath.sql | 7 | 0 |
From cb607e569e78487f93ca20f7648ac962bffcf40f Mon Sep 17 00:00:00 2001
From: Ewan Young <kdbase.hack@gmail.com>
Date: Thu, 2 Jul 2026 04:12:51 +0800
Subject: [PATCH] Fix jsonpath .decimal() to honor silent mode
The jsonpath .decimal(precision[, scale]) method built its numeric typmod
by calling numerictypmodin() through DirectFunctionCall1(), which throws a
hard error for a precision outside 1..NUMERIC_MAX_PRECISION or a scale
outside NUMERIC_MIN_SCALE..NUMERIC_MAX_SCALE. That error escaped silent
mode (silent => true, and the @? / @@ operators), unlike the neighbouring
error paths in .decimal() which already report softly, so e.g.
select jsonb_path_query('1.5', '$.decimal(0)', '{}', true);
threw "NUMERIC precision 0 must be between 1 and 1000" instead of
returning no rows.
Factor the precision/scale range checks and typmod packing out of
numerictypmodin() into a new make_numeric_typmod_safe() that reports
errors through an optional ErrorSaveContext, and call it from both
numerictypmodin() (with a NULL escontext, preserving the existing hard
errors and messages) and the .decimal() code, which now passes its
ErrorSaveContext when not throwing errors. This also lets .decimal()
drop the CString-array round-trip it used to reach numerictypmodin().
Oversight in 66ea94e8e606; same class as 954e57708ea6 (.split_part()).
---
src/backend/utils/adt/jsonpath_exec.c | 28 +++++------
src/backend/utils/adt/numeric.c | 52 +++++++++++---------
src/include/utils/numeric.h | 2 +
src/test/regress/expected/jsonb_jsonpath.out | 32 ++++++++++++
src/test/regress/sql/jsonb_jsonpath.sql | 7 +++
5 files changed, 82 insertions(+), 39 deletions(-)
diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
index 6cc2acb4254..2ab5defa175 100644
--- a/src/backend/utils/adt/jsonpath_exec.c
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -1489,14 +1489,10 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
if (jsp->type == jpiDecimal && jsp->content.args.left)
{
Datum numdatum;
- Datum dtypmod;
+ int32 dtypmod;
int32 precision;
int32 scale = 0;
bool noerr;
- ArrayType *arrtypmod;
- Datum datums[2];
- char pstr[12]; /* sign, 10 digits and '\0' */
- char sstr[12]; /* sign, 10 digits and '\0' */
ErrorSaveContext escontext = {T_ErrorSaveContext};
jspGetLeftArg(jsp, &elem);
@@ -1527,22 +1523,21 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
}
/*
- * numerictypmodin() takes the precision and scale in the
- * form of CString arrays.
+ * Pack the precision and scale into a numeric typmod.
+ * An out-of-range precision or scale is reported softly
+ * (via escontext) when not throwing errors, so that silent
+ * mode is honored; this reuses numerictypmodin()'s error
+ * messages.
*/
- pg_ltoa(precision, pstr);
- datums[0] = CStringGetDatum(pstr);
- pg_ltoa(scale, sstr);
- datums[1] = CStringGetDatum(sstr);
- arrtypmod = construct_array_builtin(datums, 2, CSTRINGOID);
-
- dtypmod = DirectFunctionCall1(numerictypmodin,
- PointerGetDatum(arrtypmod));
+ dtypmod = make_numeric_typmod_safe(precision, scale,
+ jspThrowErrors(cxt) ? NULL : (Node *) &escontext);
+ if (escontext.error_occurred)
+ return jperError;
/* Convert numstr to Numeric with typmod */
Assert(numstr != NULL);
noerr = DirectInputFunctionCallSafe(numeric_in, numstr,
- InvalidOid, DatumGetInt32(dtypmod),
+ InvalidOid, dtypmod,
(Node *) &escontext,
&numdatum);
@@ -1553,7 +1548,6 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
numstr, jspOperationName(jsp->type), "numeric"))));
num = DatumGetNumeric(numdatum);
- pfree(arrtypmod);
}
jbv.type = jbvNumeric;
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index cb23dfe9b95..db787a92105 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -1305,6 +1305,34 @@ numeric (PG_FUNCTION_ARGS)
PG_RETURN_NUMERIC(new);
}
+/*
+ * make_numeric_typmod_safe() -
+ *
+ * Validate a numeric precision and scale and pack them into a typmod value.
+ *
+ * If escontext points to an ErrorSaveContext, an out-of-range precision or
+ * scale is reported softly and the function returns -1; otherwise it throws.
+ * This lets callers that already hold the precision/scale as integers (e.g.
+ * numerictypmodin() and jsonpath's .decimal() method) share the range checks
+ * and error messages while supporting soft error reporting.
+ */
+int32
+make_numeric_typmod_safe(int32 precision, int32 scale, Node *escontext)
+{
+ if (precision < 1 || precision > NUMERIC_MAX_PRECISION)
+ ereturn(escontext, -1,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("NUMERIC precision %d must be between 1 and %d",
+ precision, NUMERIC_MAX_PRECISION)));
+ if (scale < NUMERIC_MIN_SCALE || scale > NUMERIC_MAX_SCALE)
+ ereturn(escontext, -1,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("NUMERIC scale %d must be between %d and %d",
+ scale, NUMERIC_MIN_SCALE, NUMERIC_MAX_SCALE)));
+
+ return make_numeric_typmod(precision, scale);
+}
+
Datum
numerictypmodin(PG_FUNCTION_ARGS)
{
@@ -1316,29 +1344,9 @@ numerictypmodin(PG_FUNCTION_ARGS)
tl = ArrayGetIntegerTypmods(ta, &n);
if (n == 2)
- {
- if (tl[0] < 1 || tl[0] > NUMERIC_MAX_PRECISION)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("NUMERIC precision %d must be between 1 and %d",
- tl[0], NUMERIC_MAX_PRECISION)));
- if (tl[1] < NUMERIC_MIN_SCALE || tl[1] > NUMERIC_MAX_SCALE)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("NUMERIC scale %d must be between %d and %d",
- tl[1], NUMERIC_MIN_SCALE, NUMERIC_MAX_SCALE)));
- typmod = make_numeric_typmod(tl[0], tl[1]);
- }
+ typmod = make_numeric_typmod_safe(tl[0], tl[1], NULL);
else if (n == 1)
- {
- if (tl[0] < 1 || tl[0] > NUMERIC_MAX_PRECISION)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("NUMERIC precision %d must be between 1 and %d",
- tl[0], NUMERIC_MAX_PRECISION)));
- /* scale defaults to zero */
- typmod = make_numeric_typmod(tl[0], 0);
- }
+ typmod = make_numeric_typmod_safe(tl[0], 0, NULL); /* scale defaults to zero */
else
{
ereport(ERROR,
diff --git a/src/include/utils/numeric.h b/src/include/utils/numeric.h
index b1cf40ed9fd..ea289dabfeb 100644
--- a/src/include/utils/numeric.h
+++ b/src/include/utils/numeric.h
@@ -101,6 +101,8 @@ extern Numeric numeric_div_safe(Numeric num1, Numeric num2, Node *escontext);
extern Numeric numeric_mod_safe(Numeric num1, Numeric num2, Node *escontext);
extern int32 numeric_int4_safe(Numeric num, Node *escontext);
extern int64 numeric_int8_safe(Numeric num, Node *escontext);
+extern int32 make_numeric_typmod_safe(int32 precision, int32 scale,
+ Node *escontext);
extern Numeric random_numeric(pg_prng_state *state,
Numeric rmin, Numeric rmax);
diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out
index 81efebc3d0f..1bd2dd2e63f 100644
--- a/src/test/regress/expected/jsonb_jsonpath.out
+++ b/src/test/regress/expected/jsonb_jsonpath.out
@@ -2336,6 +2336,38 @@ select jsonb_path_query('12.3', '$.decimal(12345678901,1)');
ERROR: precision of jsonpath item method .decimal() is out of range for type integer
select jsonb_path_query('12.3', '$.decimal(1,12345678901)');
ERROR: scale of jsonpath item method .decimal() is out of range for type integer
+-- an out-of-range precision or scale must be trappable in silent mode
+select jsonb_path_query('12345.678', '$.decimal(0, 6)', silent => true);
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('12345.678', '$.decimal(1001, 6)', silent => true);
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('1234.5678', '$.decimal(-6, +2)', silent => true);
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('1234.5678', '$.decimal(6, -1001)', silent => true);
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('1234.5678', '$.decimal(6, 1001)', silent => true);
+ jsonb_path_query
+------------------
+(0 rows)
+
+select '1234.5678'::jsonb @? '$.decimal(0)';
+ ?column?
+----------
+
+(1 row)
+
-- Test .integer()
select jsonb_path_query('null', '$.integer()');
ERROR: jsonpath item method .integer() can only be applied to a string or numeric value
diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql
index c1f4ab5422e..a338271bd75 100644
--- a/src/test/regress/sql/jsonb_jsonpath.sql
+++ b/src/test/regress/sql/jsonb_jsonpath.sql
@@ -523,6 +523,13 @@ select jsonb_path_query('0.0012345', '$.decimal(2,4)');
select jsonb_path_query('-0.00123456', '$.decimal(2,-4)');
select jsonb_path_query('12.3', '$.decimal(12345678901,1)');
select jsonb_path_query('12.3', '$.decimal(1,12345678901)');
+-- an out-of-range precision or scale must be trappable in silent mode
+select jsonb_path_query('12345.678', '$.decimal(0, 6)', silent => true);
+select jsonb_path_query('12345.678', '$.decimal(1001, 6)', silent => true);
+select jsonb_path_query('1234.5678', '$.decimal(-6, +2)', silent => true);
+select jsonb_path_query('1234.5678', '$.decimal(6, -1001)', silent => true);
+select jsonb_path_query('1234.5678', '$.decimal(6, 1001)', silent => true);
+select '1234.5678'::jsonb @? '$.decimal(0)';
-- Test .integer()
select jsonb_path_query('null', '$.integer()');
--
2.47.3