From 01b96f92bdfc1dbbcf3b6efc8aeafb820772bf4f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1s=20Senart?= <ts@perfloop.ai>
Date: Tue, 10 Nov 2026 14:05:43 +0000
Subject: [PATCH v1] Match jsonpath variable names exactly when checking
 mutability

jspIsMutableWalker() found the PASSING argument for a jsonpath variable
with strncmp() over the length of the variable's name only.  A PASSING
argument whose name starts with the variable's name, such as "aa" for
$a, could therefore supply the datatype used to decide whether a
datetime comparison depends on the TimeZone setting.  GetJsonPathVar()
already requires an exact name match at execution time, so the
mutability check could disagree with execution.

As a result, CREATE INDEX could accept an index expression whose result
depends on TimeZone, and could reject one that is immutable.

Also require equal name lengths, as GetJsonPathVar() does.  Add
regression tests for a PASSING name that extends the variable's name
and for one that is a prefix of it.

Indexes whose definitions were incorrectly accepted as immutable can
return wrong results and should be dropped or replaced with expressions
that are truly immutable.  REINDEX does not make those definitions
safe.  Restoring their current definitions from a dump will now fail at
CREATE INDEX.

Backpatch-through: 17
---
 src/backend/utils/adt/jsonpath.c                 | 3 ++-
 src/test/regress/expected/sqljson_queryfuncs.out | 9 +++++++++
 src/test/regress/sql/sqljson_queryfuncs.sql      | 3 +++
 3 files changed, 14 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index 7bfc18c..a016565 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -1447,7 +1447,8 @@ jspIsMutableWalker(JsonPathItem *jpi, struct JsonPathMutableContext *cxt)
 						String	   *varname = lfirst_node(String, lc1);
 						Node	   *varexpr = lfirst(lc2);
 
-						if (strncmp(varname->sval, name, len))
+						if (strlen(varname->sval) != len ||
+							strncmp(varname->sval, name, len) != 0)
 							continue;
 
 						switch (exprType(varexpr))
diff --git a/src/test/regress/expected/sqljson_queryfuncs.out b/src/test/regress/expected/sqljson_queryfuncs.out
index c0086de..5322a0f 100644
--- a/src/test/regress/expected/sqljson_queryfuncs.out
+++ b/src/test/regress/expected/sqljson_queryfuncs.out
@@ -1343,6 +1343,15 @@ LINE 1: CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ?...
                                                ^
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime("HH:MI TZH") < $x' PASSING '12:34'::timetz AS x));
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime("HH:MI TZH") < $y' PASSING '12:34'::timetz AS x));
+-- PASSING values whose names extend or prefix the variable's name are not used
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime("HH:MI TZH") < $x' PASSING '12:34'::timetz AS xx, '12:34'::time AS x));
+ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.dat...
+                                               ^
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime("HH:MI TZH") < $xx' PASSING '12:34'::timetz AS x, '12:34'::time AS xx));
+ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.dat...
+                                               ^
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime() < $x' PASSING '12:34'::timetz AS x));
 ERROR:  functions in index expression must be marked IMMUTABLE
 LINE 1: CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.dat...
diff --git a/src/test/regress/sql/sqljson_queryfuncs.sql b/src/test/regress/sql/sqljson_queryfuncs.sql
index d5ea5b3..9a84270 100644
--- a/src/test/regress/sql/sqljson_queryfuncs.sql
+++ b/src/test/regress/sql/sqljson_queryfuncs.sql
@@ -430,6 +430,9 @@ CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.datetime("HH:MI"
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.datetime("HH:MI TZH") < $.datetime("YY-MM-DD HH:MI"))'));
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime("HH:MI TZH") < $x' PASSING '12:34'::timetz AS x));
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime("HH:MI TZH") < $y' PASSING '12:34'::timetz AS x));
+-- PASSING values whose names extend or prefix the variable's name are not used
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime("HH:MI TZH") < $x' PASSING '12:34'::timetz AS xx, '12:34'::time AS x));
+CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime("HH:MI TZH") < $xx' PASSING '12:34'::timetz AS x, '12:34'::time AS xx));
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime() < $x' PASSING '12:34'::timetz AS x));
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime() < $x' PASSING '1234'::int AS x));
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime() ? (@ == $x)' PASSING '12:34'::time AS x));
-- 
2.55.0

