From cb607e569e78487f93ca20f7648ac962bffcf40f Mon Sep 17 00:00:00 2001 From: Ewan Young 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