]> 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:33 +0000 (12:44 +0900)
committerMichael Paquier <michael@paquier.xyz>
Thu, 2 Jul 2026 03:44:33 +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 eedc5c87b05f37461927281fef4c1bacc5fcf1ce..4be288c23ebf13f32ee11a0ae9babec24a6018ae 100644 (file)
@@ -1463,15 +1463,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);
@@ -1501,18 +1497,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);
@@ -1528,7 +1517,6 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
                                                                                                         numstr, jspOperationName(jsp->type), "numeric"))));
 
                                        num = DatumGetNumeric(numdatum);
-                                       pfree(arrtypmod);
                                }
 
                                jb = &jbv;
index 58ad1a65ef7b109b73860c4945661921632a596e..e21c08c85a27b1f3902d3bbaad9520d57a04379f 100644 (file)
@@ -1320,6 +1320,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)
 {
@@ -1331,28 +1354,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 9e79fc376cbeacc2dfc55dafe75f79b3f9c9f2f0..a8a4dac4596368b280d3d14f1bd9f1d7dc4f8228 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 4bcd4e91a2991426143891eda2ae9236d6610b48..f3b605d5926b892f294c60a722d3db72d687f280 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 3e8929a5269bc9508865bbc753867562073b8911..6fa4a5c0b4e8251136a9a920a31ab34caa8fd387 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()');