]> 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:36 +0000 (12:44 +0900)
committerMichael Paquier <michael@paquier.xyz>
Thu, 2 Jul 2026 03:44:36 +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 9b7187e935f28326f68393433cf9643c3c850011..3f6a2bd86584a6bdc6ce351b18f1cbe2f9eeb90c 100644 (file)
@@ -1464,15 +1464,11 @@ 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            have_error;
                                        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);
@@ -1502,18 +1498,11 @@ 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);
@@ -1529,7 +1518,6 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
                                                                                                         numstr, jspOperationName(jsp->type)))));
 
                                        num = DatumGetNumeric(numdatum);
-                                       pfree(arrtypmod);
                                }
 
                                jb = &jbv;
index 118e222e9be6c058ba9f4fe180f5d8cd5e99c0f3..d3d33aae93523198b9a44023f9460e23ff595b97 100644 (file)
@@ -1318,6 +1318,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)
 {
@@ -1329,28 +1352,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 43c75c436fe6b8873ba9aa9cb4dbb800bc1eafc9..264536d4e57b7ea04d1d3a1739424773bf7d5108 100644 (file)
@@ -17,6 +17,9 @@
 #include "common/pg_prng.h"
 #include "fmgr.h"
 
+/* forward declaration to avoid node.h include */
+typedef struct Node Node;
+
 /*
  * Limits on the precision and scale specifiable in a NUMERIC typmod.  The
  * precision is strictly positive, but the scale may be positive or negative.
@@ -103,6 +106,8 @@ extern Numeric numeric_mod_opt_error(Numeric num1, Numeric num2,
                                                                         bool *have_error);
 extern int32 numeric_int4_opt_error(Numeric num, bool *have_error);
 extern int64 numeric_int8_opt_error(Numeric num, bool *have_error);
+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 8cf6ecfc7f8a100284580b7ee9c32239b4fe64b6..7a827e5401a99f3412ac8522f8909905df2d954b 100644 (file)
@@ -2286,6 +2286,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 acb508c0dd2f9c15e4a1533942d35d61fba82e52..1072ab220749eb1745bac524d860be27a5beb69a 100644 (file)
@@ -512,6 +512,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()');