]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Fix jsonpath .decimal() to honor silent mode
authorMichael Paquier <michael@paquier.xyz>
Thu, 2 Jul 2026 03:44:29 +0000 (12:44 +0900)
committerMichael Paquier <michael@paquier.xyz>
Thu, 2 Jul 2026 03:44:29 +0000 (12:44 +0900)
The jsonpath .decimal(precision[, scale]) method built its numeric
typmod by calling numerictypmodin() through DirectFunctionCall1(), which
can throw a hard error for an incorrect set of precision and/or scale
vaulues.  This breaks the silent mode supported by this function, that
should not fail.

Most of the jsonpath code uses the soft error reporting to bypass
errors, which is what this fix does by avoiding a direct use of
numerictypmodin().  Its code is refactored to use a new routine called
make_numeric_typmod_safe(), able to take an error context in input.
numerictypmodin() sets no context, mapping to its previous behavior.
The jsonpath code sets or not a context depending on the use of the
silent mode.  This result leads to some nice simplifications:
numerictypmodin() feeds on an array, we can now pass directly values for
the scale and precision.

Oversight in 66ea94e8e606.

Author: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/CAON2xHMaigKABiyPBBq3Sjd3gp7uWMJXnnMHt=s85V1ij3KP1w@mail.gmail.com
Backpatch-through: 17

src/backend/utils/adt/jsonpath_exec.c
src/backend/utils/adt/numeric.c
src/include/utils/numeric.h
src/test/regress/expected/jsonb_jsonpath.out
src/test/regress/sql/jsonb_jsonpath.sql

index 6cc2acb4254234e4c32cdef527fca555c14153e0..9bf8ecdcd0ce240ad7f120f36c19c340b0290ef9 100644 (file)
@@ -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);
@@ -1526,23 +1522,16 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
                                                                                                                 jspOperationName(jsp->type)))));
                                        }
 
-                                       /*
-                                        * numerictypmodin() takes the precision and scale in the
-                                        * form of CString arrays.
-                                        */
-                                       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));
+                                       /* Pack the precision and scale into a numeric typmod */
+                                       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 +1542,6 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
                                                                                                         numstr, jspOperationName(jsp->type), "numeric"))));
 
                                        num = DatumGetNumeric(numdatum);
-                                       pfree(arrtypmod);
                                }
 
                                jbv.type = jbvNumeric;
index cb23dfe9b9506821e519c344ee4d955154584b4b..c9717faea2658ce6b5b292972c5889d9c194edfd 100644 (file)
@@ -1305,6 +1305,29 @@ numeric          (PG_FUNCTION_ARGS)
        PG_RETURN_NUMERIC(new);
 }
 
+/*
+ * make_numeric_typmod_safe() -
+ *
+ *     Validate a numeric precision/scale and pack them into a typmod value,
+ *     with soft error handling.
+ */
+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,28 +1339,11 @@ 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);
        }
        else
        {
index b1cf40ed9fdd34946593e202e28564a4e952d710..ea289dabfeb6c812529decb29ccbb1bec76e25e6 100644 (file)
@@ -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);
index 81efebc3d0fd48258d5860fe49a427a733b3da6c..c7b8c36c8424465a9f3ac5571ff2da89b7a7638e 100644 (file)
@@ -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 does not fail 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
index c1f4ab5422e0d614fbadf520e9af2373733f7684..c37dc3817ffd56a7dabc26404ac16681b4ca6468 100644 (file)
@@ -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 does not fail 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()');