Re: GIN (JSONPath) checks equality only on the first node of each operand

From: Rui Zhao <zhaorui126(at)gmail(dot)com>
To: Tom Lane <tgl(at)sss(dot)pgh(dot)pa(dot)us>
Cc: Chinmay Kanchi <cgkanchi(at)gmail(dot)com>, pgsql-bugs(at)lists(dot)postgresql(dot)org
Subject: Re: GIN (JSONPath) checks equality only on the first node of each operand
Date: 2026-10-20 06:59:13
Message-ID: CAHWVJhEUf0z339+bntyPkBX=ysN2iqXqofRQ7jHjEy98_KeoHw@mail.gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-bugs

Hi Tom,

Thanks for the fixes. I think v1 covers both reported problems.

While reading v1, I saw that extract_jsp_query() sends @@ expressions
to extract_jsp_bool_expr(). That function switches on node types such
as jpiAnd and jpiEqual. I wondered how it handles a predicate followed
by a method, so I tried ($.age == 25).size() in GDB. jsp->type was still
jpiEqual, with $.age on the left, 25 on the right, and size() as its
next item.

In an SQL expression tree, a function call would have a FuncExpr node
around its argument. Here, jsonpath_gram.y builds a list of the
predicate and accessor:

accessor_expr:
path_primary { $$ = list_make1($1); }
| '(' expr ')' accessor_op { $$ = list_make2($2, $4); }
| '(' predicate ')' accessor_op { $$ = list_make2($2, $4); }
| accessor_expr accessor_op { $$ = lappend($1, $2); }
;

expr:
accessor_expr { $$ = makeItemList($1); }

makeItemList() links ($.age == 25) and size() through next. The head
remains jpiEqual, but extract_jsp_bool_expr() ignores its next item.
V1 checks for following items on the literal operand, but not on the
predicate itself.

Using the existing testjsonb data from jsonb.sql:

SELECT count(*) FROM testjsonb
WHERE j @@ '($.age == 25).size().boolean()';

v1 v1 + 0001
Sequential scan 1012 1012
GIN jsonb_ops 2 1012
GIN jsonb_path_ops 2 1012

The equality takes this branch:

else if (jspIsScalar(right_item.type) &&
!jspHasNext(&right_item))
{
scalar_item = &right_item;
path_item = &left_item;
}

left_item is $.age, and right_item is the constant 25 with no next item.
The function therefore extracts $.age == 25 as the index condition,
ignoring the methods attached to the equality itself.

In this case, .size() returns 1 for both true and false, and .boolean()
turns that into true. All 1012 rows should match, but GIN returns only
the two rows with age = 25.

To be honest, this is a corner case, and I haven't found a more common
way to trigger it. It seems unlikely to occur in normal queries.

0001 adds this check at the start of extract_jsp_bool_expr():

if (jspHasNext(jsp))
return NULL;

It is on top of v1 and adds regression tests.
All 239 core regression tests passed.

Regards,
Rui

Attachment Content-Type Size
nocfbot-0001-Fix-GIN-quals-for-jsonpath-predicates-followed-by-me.patch application/octet-stream 4.2 KB

In response to

Browse pgsql-bugs by date

  From Date Subject
Next Message Michael Paquier 2026-10-20 07:12:59 Re: BUG #19763: ManualBug2 NULL pointer dereference in make_build_data via examine_expression bypass of lookup_var_a
Previous Message Fujii Masao 2026-10-20 06:16:52 Re: BUG #19644: byteaout, float8out and float4out are marked IMMUTABLE but depend on GUCs