From e84329b08f3a7d58ff115e9e479a268dd65387aa Mon Sep 17 00:00:00 2001 From: Rui Zhao Date: Tue, 20 Oct 2026 21:58:19 +0800 Subject: [PATCH v1] Fix GIN lookups for root strings in strict jsonpath queries A jsonb_ops index scan for 'strict $ == "a scalar"' misses a matching root string. Root scalars are stored in a single-element array, so gin_extract_jsonb() indexes strings with JGINFLAG_KEY. In strict mode, jsonb_ops__extract_nodes() instead searches for JGINFLAG_STR at the root. Use the key entry for root strings in strict mode. This matches existing index entries and requires no reindexing. Add tests for both @@ and @?. --- src/backend/utils/adt/jsonb_gin.c | 5 ++++- src/test/regress/expected/jsonb.out | 29 +++++++++++++++++++++++++++++ src/test/regress/sql/jsonb.sql | 13 +++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/backend/utils/adt/jsonb_gin.c b/src/backend/utils/adt/jsonb_gin.c index 96506661cb0..e1cc7df6674 100644 --- a/src/backend/utils/adt/jsonb_gin.c +++ b/src/backend/utils/adt/jsonb_gin.c @@ -442,7 +442,10 @@ jsonb_ops__extract_nodes(JsonPathGinContext *cxt, JsonPathGinPath path, if (cxt->lax) key_entry = GIN_MAYBE; else if (!last) /* root ($) */ - key_entry = GIN_FALSE; + { + /* Root scalars are stored as single-element arrays. */ + key_entry = GIN_TRUE; + } else if (last->type == jpiAnyArray || last->type == jpiIndexArray) key_entry = GIN_TRUE; else if (last->type == jpiAny) diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out index a483e4b15b0..b04387b2e2b 100644 --- a/src/test/regress/expected/jsonb.out +++ b/src/test/regress/expected/jsonb.out @@ -613,6 +613,35 @@ SELECT (test_json->>3) IS NULL AS expect_true FROM test_jsonb WHERE json_type = t (1 row) +-- Root strings must match the entries used for string array elements. +SELECT test_json FROM test_jsonb WHERE test_json @@ 'strict $ == "a scalar"'; + test_json +------------ + "a scalar" +(1 row) + +SELECT test_json FROM test_jsonb WHERE test_json @? 'strict $ ? (@ == "a scalar")'; + test_json +------------ + "a scalar" +(1 row) + +CREATE INDEX test_jsonb_idx ON test_jsonb USING gin (test_json); +SET enable_seqscan = off; +SELECT test_json FROM test_jsonb WHERE test_json @@ 'strict $ == "a scalar"'; + test_json +------------ + "a scalar" +(1 row) + +SELECT test_json FROM test_jsonb WHERE test_json @? 'strict $ ? (@ == "a scalar")'; + test_json +------------ + "a scalar" +(1 row) + +RESET enable_seqscan; +DROP INDEX test_jsonb_idx; -- corner cases select '{"a": [{"b": "c"}, {"b": "cc"}]}'::jsonb -> null::text; ?column? diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql index 791ee78f903..1939913562b 100644 --- a/src/test/regress/sql/jsonb.sql +++ b/src/test/regress/sql/jsonb.sql @@ -192,6 +192,19 @@ SELECT (test_json->>'field3') IS NULL AS expect_true FROM test_jsonb WHERE json_ SELECT (test_json->3) IS NULL AS expect_false FROM test_jsonb WHERE json_type = 'array'; SELECT (test_json->>3) IS NULL AS expect_true FROM test_jsonb WHERE json_type = 'array'; +-- Root strings must match the entries used for string array elements. +SELECT test_json FROM test_jsonb WHERE test_json @@ 'strict $ == "a scalar"'; +SELECT test_json FROM test_jsonb WHERE test_json @? 'strict $ ? (@ == "a scalar")'; + +CREATE INDEX test_jsonb_idx ON test_jsonb USING gin (test_json); +SET enable_seqscan = off; + +SELECT test_json FROM test_jsonb WHERE test_json @@ 'strict $ == "a scalar"'; +SELECT test_json FROM test_jsonb WHERE test_json @? 'strict $ ? (@ == "a scalar")'; + +RESET enable_seqscan; +DROP INDEX test_jsonb_idx; + -- corner cases select '{"a": [{"b": "c"}, {"b": "cc"}]}'::jsonb -> null::text; select '{"a": [{"b": "c"}, {"b": "cc"}]}'::jsonb -> null::int; -- 2.43.7