public inbox for [email protected]  
help / color / mirror / Atom feed
Re: SQL:2023 JSON simplified accessor support
67+ messages / 12 participants
[nested] [flat]

* Re: SQL:2023 JSON simplified accessor support
@ 2024-11-21 20:52 Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  0 siblings, 1 reply; 67+ messages in thread

From: Alexandra Wang @ 2024-11-21 20:52 UTC (permalink / raw)
  To: Nikita Glukhov <[email protected]>; +Cc: Peter Eisentraut <[email protected]>; pgsql-hackers; Andrew Dunstan <[email protected]>; David E. Wheeler <[email protected]>; jian he <[email protected]>

Hi,

On Tue, Nov 19, 2024 at 6:06 PM Nikita Glukhov <[email protected]> wrote:
>
> Hi, hackers.
>
> I have implemented dot notation for jsonb using type subscripting back
> in April 2023, but failed post it because I left Postgres Professional
> company soon after and have not worked anywhere since, not even had
> any interest in programming.
>
> But yesterday I accidentally decided to look what is going on at
> commitfests and found this thread.  I immediately started to rebase
> code from PG16, fixed some bugs, and now I'm ready to present my
> version of the patches which is much more complex.
>
> Unfortunately, I probably won't be able to devote that much time to
> the patches as before.

Thank you so much, Nikita, for revisiting this topic and sharing your
v6 patches!

Now that we have two solutions, I’d like to summarize our current
options.

In Postgres, there are currently three ways to access json/jsonb
object fields and array elements:

1. '->' operator (Postgres-specific, predates SQL standard):

postgres=# select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::json) -> 'd'
-> 0; -- returns 1

2. jsonb subscripting (not available for the plain json type):

postgres=# select ('{"a": 1, "b": "c", "d": [1, 2,
3]}'::jsonb)['d'][0];  --returns 1

3. json_query() function:

postgres=# select json_query(jsonb '{"a": 1, "b": "c", "d": [1, 2,
3]}', 'lax $.d[0]');  --returns 1

A few weeks ago, I did the following performance benchmarking of the
three approaches:

-- setup:
create table tbl(id int, col1 jsonb);
insert into tbl select i, '{"x":"vx", "y":[{"a":[1,2,3]}, {"b":[1, 2,
{"j":"vj"}]}]}' from generate_series(1, 100000)i;

-- jsonb_operator.sql
SELECT id, col1 -> 'y' -> 1 -> 'b' -> 2 -> 'j' AS jsonb_operator FROM tbl;

-- jsonb_subscripting.sql
SELECT id, col1['y'][1]['b'][2]['j'] AS jsonb_subscript FROM tbl;

-- jsonb_path_query.sql
SELECT id, jsonb_path_query(col1, '$.y[1].b[2].j') FROM tbl;

# pgbench on my local MacOS machine, using -O3 optimization:
pgbench -n -f XXX.sql postgres -T100

Results (Latency | tps):

"->" operator: 14ms | 68
jsonb subscripting: 17ms | 58
jsonb_path_query() function: 23ms | 43

So performance from best to worst:
"->" operator > jsonb subscripting >> jsonb_path_query() function.

I’m excited to see your implementation of dot notation for jsonb using
type subscripting! This approach rounds out the three possible ways to
implement JSON simplified accessors:

## v1: json_query() implementation

Pros:
- Fully adheres to the SQL standard.

According to the SQL standard, if the JSON simplified accessor <JA> is
not a JSON item method, it is equivalent to a <JSON query>:

JSON_QUERY ( VEP, 'lax $.JC' WITH CONDITIONAL ARRAY WRAPPER NULL ON
EMPTY NULL ON ERROR)

(I’m skipping <JA> that includes a JSON item method, as it is
currently outside the scope of both sets of patches.)

- Easiest to implement

Cons:
- Slow due to function call overhead.

## v2-v5: "->" operator implementation

We initially chose this approach for its performance benefits.
However, while addressing Peter’s feedback on v5, I encountered the
following issue:

-- setup
create table test_json_dot(id serial primary key, test_json json);
insert into test_json_dot values (5, '[{"a": 1, "b": 42}, {"a": 2,
"b": {"c": 42}}]');

-- problematic query:
test1=# select id, (test_json).b, json_query(test_json, 'lax $.b' WITH
CONDITIONAL WRAPPER NULL ON EMPTY NULL ON ERROR) as expected from
test_json_dot;
 id | b |    expected
----+---+-----------------
  5 |   | [42, {"c": 42}]
(1 row)

This issue arises from the semantic differences between the "->"
operator and json_query’s "lax" mode. One possible workaround is to
redefine the "->" operator and modify its implementation. However, since
the "->" operator has been in use for a long time, such changes would
break backward compatibility.

## v6: jsonb subscription implementation

Nikita's patches pass all my functional test cases, including those
that failed with the previous approach.

Supported formats:
- JSON member accessor
- JSON wildcard member accessor (Not available in v5, so this is also a plus)
- JSON array accessor

Questions:

1. Since Nikita’s patches did not address the JSON data type, and JSON
currently does not support subscripting, should we limit the initial
feature set to JSONB dot-notation for now? In other words, if we aim
to fully support JSON simplified accessors for the plain JSON type,
should we handle support for plain JSON subscripting as a follow-up
effort?

2. I have yet to have a more thorough review of Nikita’s patches.
One area I am not familiar with is the hstore-related changes. How
relevant is hstore to the JSON simplified accessor?

Best,
Alex






^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
@ 2024-11-21 22:46 ` Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  0 siblings, 1 reply; 67+ messages in thread

From: Andrew Dunstan @ 2024-11-21 22:46 UTC (permalink / raw)
  To: Alexandra Wang <[email protected]>; Nikita Glukhov <[email protected]>; +Cc: Peter Eisentraut <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>; jian he <[email protected]>


On 2024-11-21 Th 3:52 PM, Alexandra Wang wrote:
> Hi,
>
> On Tue, Nov 19, 2024 at 6:06 PM Nikita Glukhov <[email protected]> wrote:
>> Hi, hackers.
>>
>> I have implemented dot notation for jsonb using type subscripting back
>> in April 2023, but failed post it because I left Postgres Professional
>> company soon after and have not worked anywhere since, not even had
>> any interest in programming.
>>
>> But yesterday I accidentally decided to look what is going on at
>> commitfests and found this thread.  I immediately started to rebase
>> code from PG16, fixed some bugs, and now I'm ready to present my
>> version of the patches which is much more complex.
>>
>> Unfortunately, I probably won't be able to devote that much time to
>> the patches as before.
> Thank you so much, Nikita, for revisiting this topic and sharing your
> v6 patches!
>
> Now that we have two solutions, I’d like to summarize our current
> options.
>
> In Postgres, there are currently three ways to access json/jsonb
> object fields and array elements:
>
> 1. '->' operator (Postgres-specific, predates SQL standard):
>
> postgres=# select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::json) -> 'd'
> -> 0; -- returns 1
>
> 2. jsonb subscripting (not available for the plain json type):
>
> postgres=# select ('{"a": 1, "b": "c", "d": [1, 2,
> 3]}'::jsonb)['d'][0];  --returns 1
>
> 3. json_query() function:
>
> postgres=# select json_query(jsonb '{"a": 1, "b": "c", "d": [1, 2,
> 3]}', 'lax $.d[0]');  --returns 1
>
> A few weeks ago, I did the following performance benchmarking of the
> three approaches:
>
> -- setup:
> create table tbl(id int, col1 jsonb);
> insert into tbl select i, '{"x":"vx", "y":[{"a":[1,2,3]}, {"b":[1, 2,
> {"j":"vj"}]}]}' from generate_series(1, 100000)i;
>
> -- jsonb_operator.sql
> SELECT id, col1 -> 'y' -> 1 -> 'b' -> 2 -> 'j' AS jsonb_operator FROM tbl;
>
> -- jsonb_subscripting.sql
> SELECT id, col1['y'][1]['b'][2]['j'] AS jsonb_subscript FROM tbl;
>
> -- jsonb_path_query.sql
> SELECT id, jsonb_path_query(col1, '$.y[1].b[2].j') FROM tbl;
>
> # pgbench on my local MacOS machine, using -O3 optimization:
> pgbench -n -f XXX.sql postgres -T100
>
> Results (Latency | tps):
>
> "->" operator: 14ms | 68
> jsonb subscripting: 17ms | 58
> jsonb_path_query() function: 23ms | 43
>
> So performance from best to worst:
> "->" operator > jsonb subscripting >> jsonb_path_query() function.
>
> I’m excited to see your implementation of dot notation for jsonb using
> type subscripting! This approach rounds out the three possible ways to
> implement JSON simplified accessors:
>
> ## v1: json_query() implementation
>
> Pros:
> - Fully adheres to the SQL standard.
>
> According to the SQL standard, if the JSON simplified accessor <JA> is
> not a JSON item method, it is equivalent to a <JSON query>:
>
> JSON_QUERY ( VEP, 'lax $.JC' WITH CONDITIONAL ARRAY WRAPPER NULL ON
> EMPTY NULL ON ERROR)
>
> (I’m skipping <JA> that includes a JSON item method, as it is
> currently outside the scope of both sets of patches.)
>
> - Easiest to implement
>
> Cons:
> - Slow due to function call overhead.
>
> ## v2-v5: "->" operator implementation
>
> We initially chose this approach for its performance benefits.
> However, while addressing Peter’s feedback on v5, I encountered the
> following issue:
>
> -- setup
> create table test_json_dot(id serial primary key, test_json json);
> insert into test_json_dot values (5, '[{"a": 1, "b": 42}, {"a": 2,
> "b": {"c": 42}}]');
>
> -- problematic query:
> test1=# select id, (test_json).b, json_query(test_json, 'lax $.b' WITH
> CONDITIONAL WRAPPER NULL ON EMPTY NULL ON ERROR) as expected from
> test_json_dot;
>   id | b |    expected
> ----+---+-----------------
>    5 |   | [42, {"c": 42}]
> (1 row)
>
> This issue arises from the semantic differences between the "->"
> operator and json_query’s "lax" mode. One possible workaround is to
> redefine the "->" operator and modify its implementation. However, since
> the "->" operator has been in use for a long time, such changes would
> break backward compatibility.
>
> ## v6: jsonb subscription implementation
>
> Nikita's patches pass all my functional test cases, including those
> that failed with the previous approach.
>
> Supported formats:
> - JSON member accessor
> - JSON wildcard member accessor (Not available in v5, so this is also a plus)
> - JSON array accessor
>
> Questions:
>
> 1. Since Nikita’s patches did not address the JSON data type, and JSON
> currently does not support subscripting, should we limit the initial
> feature set to JSONB dot-notation for now? In other words, if we aim
> to fully support JSON simplified accessors for the plain JSON type,
> should we handle support for plain JSON subscripting as a follow-up
> effort?
>
> 2. I have yet to have a more thorough review of Nikita’s patches.
> One area I am not familiar with is the hstore-related changes. How
> relevant is hstore to the JSON simplified accessor?
>

We can't change the way the "->" operator works, as there could well be 
uses of it in the field that rely on its current behaviour. But maybe we 
could invent a new operator which is compliant with the standard 
semantics for dot access, and call that. Then we'd get the best 
performance, and also we might be able to implement it for the plain 
JSON type. If that proves not possible we can think about not 
implementing for plain JSON, but I'd rather not go there until we have to.

I don't think we should be including hstore changes here - we should 
just be aiming at implementing the standard for JSON access. hstore 
changes if any should be a separate feature. The aren't relevant to JSON 
access, although they might use some of the same infrastructure, 
depending on implementation.


cheers


andrew


--
Andrew Dunstan
EDB: https://www.enterprisedb.com







^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
@ 2024-11-26 09:12   ` Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  0 siblings, 1 reply; 67+ messages in thread

From: Peter Eisentraut @ 2024-11-26 09:12 UTC (permalink / raw)
  To: Andrew Dunstan <[email protected]>; Alexandra Wang <[email protected]>; Nikita Glukhov <[email protected]>; +Cc: pgsql-hackers; David E. Wheeler <[email protected]>; jian he <[email protected]>

On 21.11.24 23:46, Andrew Dunstan wrote:
>> Questions:
>>
>> 1. Since Nikita’s patches did not address the JSON data type, and JSON
>> currently does not support subscripting, should we limit the initial
>> feature set to JSONB dot-notation for now? In other words, if we aim
>> to fully support JSON simplified accessors for the plain JSON type,
>> should we handle support for plain JSON subscripting as a follow-up
>> effort?
>>
>> 2. I have yet to have a more thorough review of Nikita’s patches.
>> One area I am not familiar with is the hstore-related changes. How
>> relevant is hstore to the JSON simplified accessor?
>>
> 
> We can't change the way the "->" operator works, as there could well be 
> uses of it in the field that rely on its current behaviour. But maybe we 
> could invent a new operator which is compliant with the standard 
> semantics for dot access, and call that. Then we'd get the best 
> performance, and also we might be able to implement it for the plain 
> JSON type. If that proves not possible we can think about not 
> implementing for plain JSON, but I'd rather not go there until we have to.

Yes, I think writing a custom operator that is similar to "->" but has 
the required semantics is the best way forward.  (Maybe it can be just a 
function?)

> I don't think we should be including hstore changes here - we should 
> just be aiming at implementing the standard for JSON access. hstore 
> changes if any should be a separate feature. The aren't relevant to JSON 
> access, although they might use some of the same infrastructure, 
> depending on implementation.

In a future version, the operator/function mentioned above could be a 
catalogued property of a type, similar to typsubscript.  Then you could 
also apply this to other types.  But let's leave that for later.

If I understand it correctly, Nikita's patch uses the typsubscript 
support function to handle both bracket subscripting and dot notation. 
I'm not sure if it's right to mix these two together.  Maybe I didn't 
understand that correctly.







^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
@ 2025-02-05 07:20     ` Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  0 siblings, 1 reply; 67+ messages in thread

From: Alexandra Wang @ 2025-02-05 07:20 UTC (permalink / raw)
  To: Peter Eisentraut <[email protected]>; +Cc: Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>; jian he <[email protected]>

Hi hackers,

On Tue, Nov 26, 2024 at 3:12 AM Peter Eisentraut <[email protected]>
wrote:

> On 21.11.24 23:46, Andrew Dunstan wrote:
> >> Questions:
> >>
> >> 1. Since Nikita’s patches did not address the JSON data type, and JSON
> >> currently does not support subscripting, should we limit the initial
> >> feature set to JSONB dot-notation for now? In other words, if we aim
> >> to fully support JSON simplified accessors for the plain JSON type,
> >> should we handle support for plain JSON subscripting as a follow-up
> >> effort?
> >>
> >> 2. I have yet to have a more thorough review of Nikita’s patches.
> >> One area I am not familiar with is the hstore-related changes. How
> >> relevant is hstore to the JSON simplified accessor?
> >>
> >
> > We can't change the way the "->" operator works, as there could well be
> > uses of it in the field that rely on its current behaviour. But maybe we
> > could invent a new operator which is compliant with the standard
> > semantics for dot access, and call that. Then we'd get the best
> > performance, and also we might be able to implement it for the plain
> > JSON type. If that proves not possible we can think about not
> > implementing for plain JSON, but I'd rather not go there until we have
> to.
>
> Yes, I think writing a custom operator that is similar to "->" but has
> the required semantics is the best way forward.  (Maybe it can be just a
> function?)
>
> > I don't think we should be including hstore changes here - we should
> > just be aiming at implementing the standard for JSON access. hstore
> > changes if any should be a separate feature. The aren't relevant to JSON
> > access, although they might use some of the same infrastructure,
> > depending on implementation.
>
> In a future version, the operator/function mentioned above could be a
> catalogued property of a type, similar to typsubscript.  Then you could
> also apply this to other types.  But let's leave that for later.
>
> If I understand it correctly, Nikita's patch uses the typsubscript
> support function to handle both bracket subscripting and dot notation.
> I'm not sure if it's right to mix these two together.  Maybe I didn't
> understand that correctly.
>

I’ve been working on a custom operator-like function to support dot
notation in lax mode for JSONB. However, I realized this approach has
the following drawbacks:

1. Handling both dot notation and bracket subscripting together
becomes complicated, as we still need to consider jsonb’s existing
type subscript functions.

2. Chaining N dot-access operators causes multiple unnecessary
deserialization/serialization cycles: for each operator call, the source
jsonb binary is converted to an in-memory JsonbValue, then the
relevant field is extracted, and finally it’s turned back into a
binary jsonb object. This hurts performance. A direct use of the
jsonpath functions API seems more efficient.

3. Correctly applying lax mode requires different handling for the
first, middle, and last operators, which adds further complexity.

Because of these issues, I took a closer look at Nikita’s patch. His
solution generalizes the existing jsonb typesubscript support function
to handle both bracket subscripting and dot notation. It achieves this
by translating dot notation into a jsonpath expression during
transformation, and then calls JsonPathQuery at execution.
Overall, I find this approach more efficient for chained accessors and
more flexible for future enhancements.

I attached a minimized version of Nikita’s patch (v7):

- The first three patches are refactoring steps that could be squashed
  if preferred.
- The last two patches implement dot notation and wildcard access,
  respectively.

Changes in this new version:
- Removed code handling hstore, as Andrew pointed out it isn’t
  directly relevant to JSON access and should be handled separately.
- Split tests for dot notation and wildcard access.
- Dropped the two patches in v6 that enabled non-parenthesized column
  references (per Nikita’s suggestion, this will need its own separate
discussion).

For reference, I’ve also attached the operator-like function approach
in 0001-WIP-Operator-approach-JSONB-dot-notation.txt.

I’d appreciate any feedback and thoughts!

Best,
Alex

From 56241895578e0d16d66518b8470005779cd2132c Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Tue, 14 Jan 2025 15:14:35 -0600
Subject: [PATCH] [WIP] Operator apporach: JSONB dot notation
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Implement an operator-like function `jsonb_object_field_dot()` that
accesses JSONB object fields one at a time.

Array unwrapping (lax mode) is handled differently depending on
whether the operation is the first in a chain of indirections.
Similarly, conditional wrapping is handled differently depending on
whether the operation is the last in that chain.

TODO:
- Currently, this commit might generate incorrect results when mixing
  dot notation access with existing subscripting access. Fixing this
should not be difficult. However, performance comparisons with the
alternative approach have led to postponing the fix.
- Ideally, the function would use JsonPath or a pipelined chain of
  operators instead of handling each dot access
individually—essentially what JsonPathQuery() does—leading to the
alternative approach.

Performance comparison with the generic subscripting approach:

-- setup:
create table tbl(id int, col1 jsonb);
insert into tbl select i, '{"x":"vx", "y":[{"a":[1,2,3]}, {"b":[1, 2,
{"j":"vj"}]}]}' from generate_series(1, 100000)i;

-- query 1: chained dot access
SELECT id, (col1).x.b.j AS jsonb_operator FROM tbl;

-- pgbench -n -f <query 1>.sql test -T100

This patch:
number of transactions actually processed: 6620
latency average = 15.107 ms
tps = 66.193017 (without initial connection time)

Nikita's patch (generic subscripting using json_query()):
number of transactions actually processed: 8036
latency average = 12.445 ms
tps = 80.352075 (without initial connection time)

It is expected that this patch is less performant because it currently
deserializes and serializes the nested JSONB binary three times (once
for each invocation of the operator-like function).

-- query 2: single dot access
SELECT id, (col1).x AS jsonb_operator FROM tbl;

-- pgbench -n -f <query 2>.sql test -T100

This patch:
number of transactions actually processed: 5653
latency average = 17.691 ms
tps = 56.524496 (without initial connection time)

Nikita's patch:
number of transactions actually processed: 4989
latency average = 20.044 ms
tps = 49.890189 (without initial connection time)

This patch performs slightly better for single dot accesses, though
the margin is not significant.
---
 src/backend/parser/parse_expr.c     |  33 +++-
 src/backend/parser/parse_func.c     |  36 ++++
 src/backend/utils/adt/jsonfuncs.c   | 262 ++++++++++++++++++++++++++++
 src/include/catalog/pg_operator.dat |   2 +-
 src/include/catalog/pg_proc.dat     |   4 +
 src/include/parser/parse_func.h     |   2 +
 src/test/regress/expected/jsonb.out | 103 +++++++++++
 src/test/regress/sql/jsonb.sql      |  47 +++++
 8 files changed, 481 insertions(+), 8 deletions(-)

diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index bad1df732ea..e3b6626983b 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -440,6 +440,8 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 	List	   *subscripts = NIL;
 	int			location = exprLocation(result);
 	ListCell   *i;
+	bool		json_accessor_chain_first = false;
+	bool		json_accessor_chain_last = false;
 
 	/*
 	 * We have to split any field-selection operations apart from
@@ -462,6 +464,8 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		else
 		{
 			Node	   *newresult;
+			Oid			result_typid;
+			Oid			result_basetypid;
 
 			Assert(IsA(n, String));
 
@@ -475,13 +479,28 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 															   false);
 			subscripts = NIL;
 
-			newresult = ParseFuncOrColumn(pstate,
-										  list_make1(n),
-										  list_make1(result),
-										  last_srf,
-										  NULL,
-										  false,
-										  location);
+			result_typid = exprType(result);
+			result_basetypid = (result_typid == JSONOID || result_typid == JSONBOID) ?
+				result_typid : getBaseType(result_typid);
+
+			if (result_basetypid == JSONBOID)
+			{
+				json_accessor_chain_first = (i == list_head(ind->indirection));
+				if (lnext(ind->indirection, i) == NULL)
+					json_accessor_chain_last = true;
+				newresult = ParseJsonbSimplifiedAccessorObjectField(pstate,
+																	strVal(n),
+																	result,
+																	location, result_basetypid, json_accessor_chain_first, json_accessor_chain_last);
+			}
+			else
+				newresult = ParseFuncOrColumn(pstate,
+											  list_make1(n),
+											  list_make1(result),
+											  last_srf,
+											  NULL,
+											  false,
+											  location);
 			if (newresult == NULL)
 				unknown_attribute(pstate, result, strVal(n), location);
 			result = newresult;
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 583bbbf232f..dba2a60fadc 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -1902,6 +1902,42 @@ FuncNameAsType(List *funcname)
 	return result;
 }
 
+/*
+ * ParseJsonbSimplifiedAccessorObjectField -
+ *	  handles function calls with a single argument that is of json type.
+ *	  If the function call is actually a column projection, return a suitably
+ *	  transformed expression tree.  If not, return NULL.
+ */
+Node *
+ParseJsonbSimplifiedAccessorObjectField(ParseState *pstate, const char *funcname, Node *first_arg, int location,
+										Oid basetypid, bool first_op, bool last_op)
+{
+	FuncExpr   *result;
+	Node	   *rexpr;
+
+	if (basetypid != JSONBOID)
+		elog(ERROR, "unsupported type OID: %u", basetypid);
+
+	rexpr = (Node *) makeConst(
+							   TEXTOID,
+							   -1,
+							   InvalidOid,
+							   -1,
+							   CStringGetTextDatum(funcname),
+							   false,
+							   false);
+	result = makeFuncExpr(4100,
+						  JSONBOID,
+						  list_make4(first_arg, rexpr,
+									 makeBoolConst(first_op, false),
+									 makeBoolConst(last_op, false)),
+						  InvalidOid,
+						  InvalidOid,
+						  COERCE_EXPLICIT_CALL);
+
+	return (Node *) result;
+}
+
 /*
  * ParseComplexProjection -
  *	  handles function calls with a single argument that is of complex type.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index c2e90f1a3bf..a32aef3bfac 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -879,6 +879,268 @@ jsonb_object_field(PG_FUNCTION_ARGS)
 	PG_RETURN_NULL();
 }
 
+static void
+expand_jbvBinary_in_memory(const JsonbValue *binaryVal, JsonbValue *expandedVal)
+{
+	Assert(binaryVal->type == jbvBinary);
+	JsonbContainer *container = (JsonbContainer *) binaryVal->val.binary.data;
+	JsonbIterator *it;
+	JsonbValue	v;
+	JsonbIteratorToken token;
+
+	it = JsonbIteratorInit(container);
+	token = JsonbIteratorNext(&it, &v, false);
+
+	if (token == WJB_BEGIN_OBJECT)
+	{
+		/*
+		 * We'll read all keys/values until WJB_END_OBJECT and build
+		 * expandedVal->type = jbvObject.
+		 */
+		List	   *keyvals = NIL;
+
+		while ((token = JsonbIteratorNext(&it, &v, false)) != WJB_END_OBJECT)
+		{
+			if (token == WJB_KEY)
+			{
+				JsonbValue	key;
+
+				key = v;
+				token = JsonbIteratorNext(&it, &v, true);
+				if (token == WJB_VALUE)
+				{
+					JsonbValue	val;
+					JsonbValue *objPair;
+
+					if (v.type == jbvBinary)
+					{
+						/* Recursively expand nested objects/arrays. */
+						expand_jbvBinary_in_memory(&v, &val);
+					}
+					else
+					{
+						/* Scalar or already jbvObject/jbvArray. Copy as-is. */
+						val = v;
+					}
+
+					/*
+					 * Build a small pair (key, value). We'll store them in a
+					 * list.
+					 */
+					objPair = palloc(sizeof(JsonbValue) * 2);
+					objPair[0] = key;
+					objPair[1] = val;
+					keyvals = lappend(keyvals, objPair);
+				}
+			}
+		}
+
+		/* Now convert our keyvals list into a jbvObject. */
+		int			nPairs = list_length(keyvals);
+
+		expandedVal->type = jbvObject;
+		expandedVal->val.object.nPairs = nPairs;
+		expandedVal->val.object.pairs = palloc(sizeof(JsonbPair) * nPairs);
+
+		{
+			int			i = 0;
+			ListCell   *lc;
+
+			foreach(lc, keyvals)
+			{
+				JsonbValue *kv = (JsonbValue *) lfirst(lc);
+
+				/* kv[0] = key, kv[1] = value */
+
+				expandedVal->val.object.pairs[i].key = kv[0];
+				expandedVal->val.object.pairs[i].value = kv[1];
+				expandedVal->val.object.pairs[i].order = i;
+				i++;
+			}
+		}
+	}
+	else if (token == WJB_BEGIN_ARRAY)
+	{
+		/*
+		 * We'll read array elems until WJB_END_ARRAY and build
+		 * expandedVal->type = jbvArray.
+		 */
+		List	   *elems = NIL;
+
+		while ((token = JsonbIteratorNext(&it, &v, true)) != WJB_END_ARRAY)
+		{
+			if (token == WJB_ELEM)
+			{
+				/* If it's jbvBinary, recursively expand again. */
+				JsonbValue	val;
+
+				if (v.type == jbvBinary)
+				{
+					expand_jbvBinary_in_memory(&v, &val);
+				}
+				else
+				{
+					val = v;
+				}
+				JsonbValue *elemCopy = palloc(sizeof(JsonbValue));
+
+				*elemCopy = val;
+				elems = lappend(elems, elemCopy);
+			}
+		}
+
+		expandedVal->type = jbvArray;
+		expandedVal->val.array.nElems = list_length(elems);
+		expandedVal->val.array.rawScalar = false;
+		expandedVal->val.array.elems = palloc(sizeof(JsonbValue) * expandedVal->val.array.nElems);
+
+		{
+			int			i = 0;
+			ListCell   *lc;
+
+			foreach(lc, elems)
+			{
+				JsonbValue *vptr = (JsonbValue *) lfirst(lc);
+
+				expandedVal->val.array.elems[i++] = *vptr;
+			}
+		}
+	}
+	else
+	{
+		/*
+		 * Possibly a scalar container (WJB_ELEM or WJB_VALUE with jbvNumeric,
+		 * jbvString, etc.). If this container truly only has one scalar,
+		 * token might be WJB_ELEM or similar. For simplicity, let's check
+		 * tmp.type. If it's jbvString/jbvNumeric, copy it directly.
+		 */
+		expandedVal->type = v.type;
+		expandedVal->val = v.val;
+	}
+}
+
+static List *
+jsonb_object_field_unwrap_array(JsonbContainer *jb, text *key, bool unwrap_nested)
+{
+	JsonbIterator *it;
+	JsonbValue	v;
+	JsonbIteratorToken token;
+	List	   *resultList = NIL;
+	int			arraySize;
+
+	it = JsonbIteratorInit(jb);
+	token = JsonbIteratorNext(&it, &v, false);
+
+	Assert(token == WJB_BEGIN_ARRAY);
+	arraySize = v.val.array.nElems;
+
+	/* Unwrap out-most array elements and extract the key value */
+	for (int i = 0; i < arraySize; i++)
+	{
+		token = JsonbIteratorNext(&it, &v, true);
+		if (token == WJB_ELEM && v.type == jbvBinary)
+		{
+			JsonbContainer *elemContainer = (JsonbContainer *) v.val.binary.data;
+
+			if (JsonContainerIsObject(elemContainer))
+			{
+				JsonbValue *extractedValue;
+				JsonbValue	vbuf;
+
+				extractedValue = getKeyJsonValueFromContainer(elemContainer,
+															  VARDATA_ANY(key),
+															  VARSIZE_ANY_EXHDR(key),
+															  &vbuf);
+				if (extractedValue != NULL)
+				{
+					JsonbValue *copy;
+
+					if (extractedValue->type == jbvBinary)
+					{
+						JsonbValue	expanded;
+
+						expand_jbvBinary_in_memory(extractedValue, &expanded);
+
+						copy = palloc(sizeof(expanded));
+						*copy = expanded;
+						resultList = lappend(resultList, copy);
+					}
+					else
+					{
+						copy = palloc(sizeof(*extractedValue));
+						*copy = *extractedValue;
+						resultList = lappend(resultList, copy);
+					}
+				}
+			}
+			else if (unwrap_nested && JsonContainerIsArray(elemContainer))
+			{
+				resultList = jsonb_object_field_unwrap_array(elemContainer, key, false);
+			}
+		}
+	}
+	token = JsonbIteratorNext(&it, &v, true);
+
+	return resultList;
+}
+
+Datum
+jsonb_object_field_dot(PG_FUNCTION_ARGS)
+{
+	Jsonb	   *jb = PG_GETARG_JSONB_P(0);
+	text	   *key = PG_GETARG_TEXT_PP(1);
+	bool		first_op = PG_GETARG_BOOL(2);
+	bool		last_op = PG_GETARG_BOOL(3);
+
+	if (JB_ROOT_IS_OBJECT(jb))
+	{
+		JsonbValue *v;
+		JsonbValue	vbuf;
+
+		v = getKeyJsonValueFromContainer(&jb->root,
+										 VARDATA_ANY(key),
+										 VARSIZE_ANY_EXHDR(key),
+										 &vbuf);
+
+		if (v != NULL)
+			PG_RETURN_JSONB_P(JsonbValueToJsonb(v));
+	}
+	else if (JB_ROOT_IS_ARRAY(jb))
+	{
+		List	   *resultList;
+		JsonbValue	resultVal;
+
+		resultList = jsonb_object_field_unwrap_array(&jb->root, key, !first_op);
+
+		if (list_length(resultList) == 0)
+			PG_RETURN_NULL();
+		else if (!last_op || list_length(resultList) > 1)
+		{
+			/* Conditional wrap result */
+			resultVal.type = jbvArray;
+			resultVal.val.array.rawScalar = false;
+			resultVal.val.array.nElems = list_length(resultList);
+			resultVal.val.array.elems = (JsonbValue *) palloc(sizeof(JsonbValue) * list_length(resultList));
+
+			int			idx = 0;
+			ListCell   *lc;
+			JsonbValue *elem;
+
+			foreach(lc, resultList)
+			{
+				elem = (JsonbValue *) lfirst(lc);
+				resultVal.val.array.elems[idx++] = *elem;
+			}
+		}
+		else
+			resultVal = *(JsonbValue *) linitial(resultList);
+
+		PG_RETURN_JSONB_P(JsonbValueToJsonb(&resultVal));
+	}
+
+	PG_RETURN_NULL();
+}
+
 Datum
 json_object_field_text(PG_FUNCTION_ARGS)
 {
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 6d9dc1528d6..70887d3fd4d 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3173,7 +3173,7 @@
 { oid => '3967', descr => 'get value from json as text with path elements',
   oprname => '#>>', oprleft => 'json', oprright => '_text', oprresult => 'text',
   oprcode => 'json_extract_path_text' },
-{ oid => '3211', descr => 'get jsonb object field',
+{ oid => '3211', oid_symbol => 'OID_JSONB_OBJECT_FIELD_OP', descr => 'get jsonb object field',
   oprname => '->', oprleft => 'jsonb', oprright => 'text', oprresult => 'jsonb',
   oprcode => 'jsonb_object_field' },
 { oid => '3477', descr => 'get jsonb object field as text',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 5b8c2ad2a54..cabde2e07e8 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9338,6 +9338,10 @@
 { oid => '3968', descr => 'get the type of a json value',
   proname => 'json_typeof', prorettype => 'text', proargtypes => 'json',
   prosrc => 'json_typeof' },
+{ oid => '4100',
+  proname => 'jsonb_object_field_dot', prorettype => 'jsonb',
+  proargtypes => 'jsonb text bool bool', proargnames => '{from_json, field_name, first_op, last_op}',
+  prosrc => 'jsonb_object_field_dot' },
 
 # uuid
 { oid => '2952', descr => 'I/O',
diff --git a/src/include/parser/parse_func.h b/src/include/parser/parse_func.h
index a6f24b83d84..745d2c75c93 100644
--- a/src/include/parser/parse_func.h
+++ b/src/include/parser/parse_func.h
@@ -34,6 +34,8 @@ typedef enum
 extern Node *ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
 							   Node *last_srf, FuncCall *fn, bool proc_call,
 							   int location);
+extern Node *ParseJsonbSimplifiedAccessorObjectField(ParseState *pstate, const char *funcname, Node *first_arg, int location,
+													 Oid basetypid, bool first_op, bool last_op);
 
 extern FuncDetailCode func_get_detail(List *funcname,
 									  List *fargs, List *fargnames,
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 2baff931bf2..e0c0757f475 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5781,3 +5781,106 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
  12345
 (1 row)
 
+-- simple dot notation
+-- TODO: add comments
+CREATE OR REPLACE FUNCTION test_jsonb_dot_notation(
+    vep jsonb, -- value expression primary
+    jc  text -- JSON simplified accessor operator chain
+)
+    RETURNS TABLE(dot_access jsonb, expected jsonb)
+    LANGUAGE plpgsql
+AS $$
+DECLARE
+    dyn_sql text;
+BEGIN
+    dyn_sql := format($f$
+    SELECT
+       (vep).%s AS dot_access,
+       json_query(vep, 'lax $.%s' WITH CONDITIONAL WRAPPER NULL ON EMPTY NULL ON ERROR) AS expected
+    FROM (SELECT $1::jsonb AS vep) dummy
+  $f$, jc, jc);
+
+    --     -- OPTIONAL: Just to see the constructed SQL in logs
+--     RAISE NOTICE 'Executing: %', dyn_sql;
+
+    -- Execute the dynamic query, substituting p_col as parameter #1
+    RETURN QUERY EXECUTE dyn_sql USING vep;
+END;
+$$;
+-- access member object field of a json object
+select * from test_jsonb_dot_notation('{"a": 1, "b": 42}'::jsonb, 'b');
+ dot_access | expected 
+------------+----------
+ 42         | 42
+(1 row)
+
+select * from test_jsonb_dot_notation('{"a": 1, "b": 42}'::jsonb, 'not_exist');
+ dot_access | expected 
+------------+----------
+            | 
+(1 row)
+
+select * from test_jsonb_dot_notation('{"a": 1, "b": 42, "b":12}'::jsonb, 'b'); -- return last for duplicate key
+ dot_access | expected 
+------------+----------
+ 12         | 12
+(1 row)
+
+select * from test_jsonb_dot_notation('{"a": 1, "b": 12, "b":42}'::jsonb, 'b');
+ dot_access | expected 
+------------+----------
+ 42         | 42
+(1 row)
+
+select * from test_jsonb_dot_notation('{"a": 2, "b": {"c": 42}}'::jsonb, 'b.c');
+ dot_access | expected 
+------------+----------
+ 42         | 42
+(1 row)
+
+select * from test_jsonb_dot_notation('{"a": 4, "b": {"c": {"d": [11, 12]}}}'::jsonb, 'b.c.d');
+ dot_access | expected 
+------------+----------
+ [11, 12]   | [11, 12]
+(1 row)
+
+-- access member object field of a json array: apply lax mode + conditional wrap
+-- unwrap the outer-most array into sequence and conditional wrap the results
+-- only unwrap the outer most array
+select * from test_jsonb_dot_notation('[{"x": 42}]'::jsonb, 'x');
+ dot_access | expected 
+------------+----------
+ 42         | 42
+(1 row)
+
+select * from test_jsonb_dot_notation('["x"]'::jsonb, 'x');
+ dot_access | expected 
+------------+----------
+            | 
+(1 row)
+
+select * from test_jsonb_dot_notation('[[{"x": 42}]]'::jsonb, 'x');
+ dot_access | expected 
+------------+----------
+            | 
+(1 row)
+
+-- wrap the result into an array on the conditional of more than one matched object keys
+select * from test_jsonb_dot_notation('[{"x": 42}, {"x": {"y": {"z": 12}}}]'::jsonb, 'x');
+       dot_access       |        expected        
+------------------------+------------------------
+ [42, {"y": {"z": 12}}] | [42, {"y": {"z": 12}}]
+(1 row)
+
+select * from test_jsonb_dot_notation('[{"x": 42}, [{"x": {"y": {"z": 12}}}]]'::jsonb, 'x');
+ dot_access | expected 
+------------+----------
+ 42         | 42
+(1 row)
+
+select * from test_jsonb_dot_notation('[{"x": 42}, {"x": [{"y": 12}, {"y": {"z": 12}}]}]'::jsonb, 'x.y');
+   dot_access    |    expected     
+-----------------+-----------------
+ [12, {"z": 12}] | [12, {"z": 12}]
+(1 row)
+
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 544bb610e2d..5a6d820b507 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1572,3 +1572,50 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::float8;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int2;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int4;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
+
+-- simple dot notation
+-- TODO: add comments
+
+CREATE OR REPLACE FUNCTION test_jsonb_dot_notation(
+    vep jsonb, -- value expression primary
+    jc  text -- JSON simplified accessor operator chain
+)
+    RETURNS TABLE(dot_access jsonb, expected jsonb)
+    LANGUAGE plpgsql
+AS $$
+DECLARE
+    dyn_sql text;
+BEGIN
+    dyn_sql := format($f$
+    SELECT
+       (vep).%s AS dot_access,
+       json_query(vep, 'lax $.%s' WITH CONDITIONAL WRAPPER NULL ON EMPTY NULL ON ERROR) AS expected
+    FROM (SELECT $1::jsonb AS vep) dummy
+  $f$, jc, jc);
+
+    --     -- OPTIONAL: Just to see the constructed SQL in logs
+--     RAISE NOTICE 'Executing: %', dyn_sql;
+
+    -- Execute the dynamic query, substituting p_col as parameter #1
+    RETURN QUERY EXECUTE dyn_sql USING vep;
+END;
+$$;
+
+-- access member object field of a json object
+select * from test_jsonb_dot_notation('{"a": 1, "b": 42}'::jsonb, 'b');
+select * from test_jsonb_dot_notation('{"a": 1, "b": 42}'::jsonb, 'not_exist');
+select * from test_jsonb_dot_notation('{"a": 1, "b": 42, "b":12}'::jsonb, 'b'); -- return last for duplicate key
+select * from test_jsonb_dot_notation('{"a": 1, "b": 12, "b":42}'::jsonb, 'b');
+select * from test_jsonb_dot_notation('{"a": 2, "b": {"c": 42}}'::jsonb, 'b.c');
+select * from test_jsonb_dot_notation('{"a": 4, "b": {"c": {"d": [11, 12]}}}'::jsonb, 'b.c.d');
+
+-- access member object field of a json array: apply lax mode + conditional wrap
+-- unwrap the outer-most array into sequence and conditional wrap the results
+-- only unwrap the outer most array
+select * from test_jsonb_dot_notation('[{"x": 42}]'::jsonb, 'x');
+select * from test_jsonb_dot_notation('["x"]'::jsonb, 'x');
+select * from test_jsonb_dot_notation('[[{"x": 42}]]'::jsonb, 'x');
+-- wrap the result into an array on the conditional of more than one matched object keys
+select * from test_jsonb_dot_notation('[{"x": 42}, {"x": {"y": {"z": 12}}}]'::jsonb, 'x');
+select * from test_jsonb_dot_notation('[{"x": 42}, [{"x": {"y": {"z": 12}}}]]'::jsonb, 'x');
+select * from test_jsonb_dot_notation('[{"x": 42}, {"x": [{"y": 12}, {"y": {"z": 12}}]}]'::jsonb, 'x.y');
-- 
2.39.5 (Apple Git-154)



Attachments:

  [text/plain] 0001-WIP-Operator-apporach-JSONB-dot-notation.txt (20.9K, ../../CAK98qZ3Ly6PhRwCVmMKJBba5oHVF9k370MMT2b_gep-SuQfRtg@mail.gmail.com/3-0001-WIP-Operator-apporach-JSONB-dot-notation.txt)
  download | inline diff:
From 56241895578e0d16d66518b8470005779cd2132c Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Tue, 14 Jan 2025 15:14:35 -0600
Subject: [PATCH] [WIP] Operator apporach: JSONB dot notation
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Implement an operator-like function `jsonb_object_field_dot()` that
accesses JSONB object fields one at a time.

Array unwrapping (lax mode) is handled differently depending on
whether the operation is the first in a chain of indirections.
Similarly, conditional wrapping is handled differently depending on
whether the operation is the last in that chain.

TODO:
- Currently, this commit might generate incorrect results when mixing
  dot notation access with existing subscripting access. Fixing this
should not be difficult. However, performance comparisons with the
alternative approach have led to postponing the fix.
- Ideally, the function would use JsonPath or a pipelined chain of
  operators instead of handling each dot access
individually—essentially what JsonPathQuery() does—leading to the
alternative approach.

Performance comparison with the generic subscripting approach:

-- setup:
create table tbl(id int, col1 jsonb);
insert into tbl select i, '{"x":"vx", "y":[{"a":[1,2,3]}, {"b":[1, 2,
{"j":"vj"}]}]}' from generate_series(1, 100000)i;

-- query 1: chained dot access
SELECT id, (col1).x.b.j AS jsonb_operator FROM tbl;

-- pgbench -n -f <query 1>.sql test -T100

This patch:
number of transactions actually processed: 6620
latency average = 15.107 ms
tps = 66.193017 (without initial connection time)

Nikita's patch (generic subscripting using json_query()):
number of transactions actually processed: 8036
latency average = 12.445 ms
tps = 80.352075 (without initial connection time)

It is expected that this patch is less performant because it currently
deserializes and serializes the nested JSONB binary three times (once
for each invocation of the operator-like function).

-- query 2: single dot access
SELECT id, (col1).x AS jsonb_operator FROM tbl;

-- pgbench -n -f <query 2>.sql test -T100

This patch:
number of transactions actually processed: 5653
latency average = 17.691 ms
tps = 56.524496 (without initial connection time)

Nikita's patch:
number of transactions actually processed: 4989
latency average = 20.044 ms
tps = 49.890189 (without initial connection time)

This patch performs slightly better for single dot accesses, though
the margin is not significant.
---
 src/backend/parser/parse_expr.c     |  33 +++-
 src/backend/parser/parse_func.c     |  36 ++++
 src/backend/utils/adt/jsonfuncs.c   | 262 ++++++++++++++++++++++++++++
 src/include/catalog/pg_operator.dat |   2 +-
 src/include/catalog/pg_proc.dat     |   4 +
 src/include/parser/parse_func.h     |   2 +
 src/test/regress/expected/jsonb.out | 103 +++++++++++
 src/test/regress/sql/jsonb.sql      |  47 +++++
 8 files changed, 481 insertions(+), 8 deletions(-)

diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index bad1df732ea..e3b6626983b 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -440,6 +440,8 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 	List	   *subscripts = NIL;
 	int			location = exprLocation(result);
 	ListCell   *i;
+	bool		json_accessor_chain_first = false;
+	bool		json_accessor_chain_last = false;
 
 	/*
 	 * We have to split any field-selection operations apart from
@@ -462,6 +464,8 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		else
 		{
 			Node	   *newresult;
+			Oid			result_typid;
+			Oid			result_basetypid;
 
 			Assert(IsA(n, String));
 
@@ -475,13 +479,28 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 															   false);
 			subscripts = NIL;
 
-			newresult = ParseFuncOrColumn(pstate,
-										  list_make1(n),
-										  list_make1(result),
-										  last_srf,
-										  NULL,
-										  false,
-										  location);
+			result_typid = exprType(result);
+			result_basetypid = (result_typid == JSONOID || result_typid == JSONBOID) ?
+				result_typid : getBaseType(result_typid);
+
+			if (result_basetypid == JSONBOID)
+			{
+				json_accessor_chain_first = (i == list_head(ind->indirection));
+				if (lnext(ind->indirection, i) == NULL)
+					json_accessor_chain_last = true;
+				newresult = ParseJsonbSimplifiedAccessorObjectField(pstate,
+																	strVal(n),
+																	result,
+																	location, result_basetypid, json_accessor_chain_first, json_accessor_chain_last);
+			}
+			else
+				newresult = ParseFuncOrColumn(pstate,
+											  list_make1(n),
+											  list_make1(result),
+											  last_srf,
+											  NULL,
+											  false,
+											  location);
 			if (newresult == NULL)
 				unknown_attribute(pstate, result, strVal(n), location);
 			result = newresult;
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 583bbbf232f..dba2a60fadc 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -1902,6 +1902,42 @@ FuncNameAsType(List *funcname)
 	return result;
 }
 
+/*
+ * ParseJsonbSimplifiedAccessorObjectField -
+ *	  handles function calls with a single argument that is of json type.
+ *	  If the function call is actually a column projection, return a suitably
+ *	  transformed expression tree.  If not, return NULL.
+ */
+Node *
+ParseJsonbSimplifiedAccessorObjectField(ParseState *pstate, const char *funcname, Node *first_arg, int location,
+										Oid basetypid, bool first_op, bool last_op)
+{
+	FuncExpr   *result;
+	Node	   *rexpr;
+
+	if (basetypid != JSONBOID)
+		elog(ERROR, "unsupported type OID: %u", basetypid);
+
+	rexpr = (Node *) makeConst(
+							   TEXTOID,
+							   -1,
+							   InvalidOid,
+							   -1,
+							   CStringGetTextDatum(funcname),
+							   false,
+							   false);
+	result = makeFuncExpr(4100,
+						  JSONBOID,
+						  list_make4(first_arg, rexpr,
+									 makeBoolConst(first_op, false),
+									 makeBoolConst(last_op, false)),
+						  InvalidOid,
+						  InvalidOid,
+						  COERCE_EXPLICIT_CALL);
+
+	return (Node *) result;
+}
+
 /*
  * ParseComplexProjection -
  *	  handles function calls with a single argument that is of complex type.
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index c2e90f1a3bf..a32aef3bfac 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -879,6 +879,268 @@ jsonb_object_field(PG_FUNCTION_ARGS)
 	PG_RETURN_NULL();
 }
 
+static void
+expand_jbvBinary_in_memory(const JsonbValue *binaryVal, JsonbValue *expandedVal)
+{
+	Assert(binaryVal->type == jbvBinary);
+	JsonbContainer *container = (JsonbContainer *) binaryVal->val.binary.data;
+	JsonbIterator *it;
+	JsonbValue	v;
+	JsonbIteratorToken token;
+
+	it = JsonbIteratorInit(container);
+	token = JsonbIteratorNext(&it, &v, false);
+
+	if (token == WJB_BEGIN_OBJECT)
+	{
+		/*
+		 * We'll read all keys/values until WJB_END_OBJECT and build
+		 * expandedVal->type = jbvObject.
+		 */
+		List	   *keyvals = NIL;
+
+		while ((token = JsonbIteratorNext(&it, &v, false)) != WJB_END_OBJECT)
+		{
+			if (token == WJB_KEY)
+			{
+				JsonbValue	key;
+
+				key = v;
+				token = JsonbIteratorNext(&it, &v, true);
+				if (token == WJB_VALUE)
+				{
+					JsonbValue	val;
+					JsonbValue *objPair;
+
+					if (v.type == jbvBinary)
+					{
+						/* Recursively expand nested objects/arrays. */
+						expand_jbvBinary_in_memory(&v, &val);
+					}
+					else
+					{
+						/* Scalar or already jbvObject/jbvArray. Copy as-is. */
+						val = v;
+					}
+
+					/*
+					 * Build a small pair (key, value). We'll store them in a
+					 * list.
+					 */
+					objPair = palloc(sizeof(JsonbValue) * 2);
+					objPair[0] = key;
+					objPair[1] = val;
+					keyvals = lappend(keyvals, objPair);
+				}
+			}
+		}
+
+		/* Now convert our keyvals list into a jbvObject. */
+		int			nPairs = list_length(keyvals);
+
+		expandedVal->type = jbvObject;
+		expandedVal->val.object.nPairs = nPairs;
+		expandedVal->val.object.pairs = palloc(sizeof(JsonbPair) * nPairs);
+
+		{
+			int			i = 0;
+			ListCell   *lc;
+
+			foreach(lc, keyvals)
+			{
+				JsonbValue *kv = (JsonbValue *) lfirst(lc);
+
+				/* kv[0] = key, kv[1] = value */
+
+				expandedVal->val.object.pairs[i].key = kv[0];
+				expandedVal->val.object.pairs[i].value = kv[1];
+				expandedVal->val.object.pairs[i].order = i;
+				i++;
+			}
+		}
+	}
+	else if (token == WJB_BEGIN_ARRAY)
+	{
+		/*
+		 * We'll read array elems until WJB_END_ARRAY and build
+		 * expandedVal->type = jbvArray.
+		 */
+		List	   *elems = NIL;
+
+		while ((token = JsonbIteratorNext(&it, &v, true)) != WJB_END_ARRAY)
+		{
+			if (token == WJB_ELEM)
+			{
+				/* If it's jbvBinary, recursively expand again. */
+				JsonbValue	val;
+
+				if (v.type == jbvBinary)
+				{
+					expand_jbvBinary_in_memory(&v, &val);
+				}
+				else
+				{
+					val = v;
+				}
+				JsonbValue *elemCopy = palloc(sizeof(JsonbValue));
+
+				*elemCopy = val;
+				elems = lappend(elems, elemCopy);
+			}
+		}
+
+		expandedVal->type = jbvArray;
+		expandedVal->val.array.nElems = list_length(elems);
+		expandedVal->val.array.rawScalar = false;
+		expandedVal->val.array.elems = palloc(sizeof(JsonbValue) * expandedVal->val.array.nElems);
+
+		{
+			int			i = 0;
+			ListCell   *lc;
+
+			foreach(lc, elems)
+			{
+				JsonbValue *vptr = (JsonbValue *) lfirst(lc);
+
+				expandedVal->val.array.elems[i++] = *vptr;
+			}
+		}
+	}
+	else
+	{
+		/*
+		 * Possibly a scalar container (WJB_ELEM or WJB_VALUE with jbvNumeric,
+		 * jbvString, etc.). If this container truly only has one scalar,
+		 * token might be WJB_ELEM or similar. For simplicity, let's check
+		 * tmp.type. If it's jbvString/jbvNumeric, copy it directly.
+		 */
+		expandedVal->type = v.type;
+		expandedVal->val = v.val;
+	}
+}
+
+static List *
+jsonb_object_field_unwrap_array(JsonbContainer *jb, text *key, bool unwrap_nested)
+{
+	JsonbIterator *it;
+	JsonbValue	v;
+	JsonbIteratorToken token;
+	List	   *resultList = NIL;
+	int			arraySize;
+
+	it = JsonbIteratorInit(jb);
+	token = JsonbIteratorNext(&it, &v, false);
+
+	Assert(token == WJB_BEGIN_ARRAY);
+	arraySize = v.val.array.nElems;
+
+	/* Unwrap out-most array elements and extract the key value */
+	for (int i = 0; i < arraySize; i++)
+	{
+		token = JsonbIteratorNext(&it, &v, true);
+		if (token == WJB_ELEM && v.type == jbvBinary)
+		{
+			JsonbContainer *elemContainer = (JsonbContainer *) v.val.binary.data;
+
+			if (JsonContainerIsObject(elemContainer))
+			{
+				JsonbValue *extractedValue;
+				JsonbValue	vbuf;
+
+				extractedValue = getKeyJsonValueFromContainer(elemContainer,
+															  VARDATA_ANY(key),
+															  VARSIZE_ANY_EXHDR(key),
+															  &vbuf);
+				if (extractedValue != NULL)
+				{
+					JsonbValue *copy;
+
+					if (extractedValue->type == jbvBinary)
+					{
+						JsonbValue	expanded;
+
+						expand_jbvBinary_in_memory(extractedValue, &expanded);
+
+						copy = palloc(sizeof(expanded));
+						*copy = expanded;
+						resultList = lappend(resultList, copy);
+					}
+					else
+					{
+						copy = palloc(sizeof(*extractedValue));
+						*copy = *extractedValue;
+						resultList = lappend(resultList, copy);
+					}
+				}
+			}
+			else if (unwrap_nested && JsonContainerIsArray(elemContainer))
+			{
+				resultList = jsonb_object_field_unwrap_array(elemContainer, key, false);
+			}
+		}
+	}
+	token = JsonbIteratorNext(&it, &v, true);
+
+	return resultList;
+}
+
+Datum
+jsonb_object_field_dot(PG_FUNCTION_ARGS)
+{
+	Jsonb	   *jb = PG_GETARG_JSONB_P(0);
+	text	   *key = PG_GETARG_TEXT_PP(1);
+	bool		first_op = PG_GETARG_BOOL(2);
+	bool		last_op = PG_GETARG_BOOL(3);
+
+	if (JB_ROOT_IS_OBJECT(jb))
+	{
+		JsonbValue *v;
+		JsonbValue	vbuf;
+
+		v = getKeyJsonValueFromContainer(&jb->root,
+										 VARDATA_ANY(key),
+										 VARSIZE_ANY_EXHDR(key),
+										 &vbuf);
+
+		if (v != NULL)
+			PG_RETURN_JSONB_P(JsonbValueToJsonb(v));
+	}
+	else if (JB_ROOT_IS_ARRAY(jb))
+	{
+		List	   *resultList;
+		JsonbValue	resultVal;
+
+		resultList = jsonb_object_field_unwrap_array(&jb->root, key, !first_op);
+
+		if (list_length(resultList) == 0)
+			PG_RETURN_NULL();
+		else if (!last_op || list_length(resultList) > 1)
+		{
+			/* Conditional wrap result */
+			resultVal.type = jbvArray;
+			resultVal.val.array.rawScalar = false;
+			resultVal.val.array.nElems = list_length(resultList);
+			resultVal.val.array.elems = (JsonbValue *) palloc(sizeof(JsonbValue) * list_length(resultList));
+
+			int			idx = 0;
+			ListCell   *lc;
+			JsonbValue *elem;
+
+			foreach(lc, resultList)
+			{
+				elem = (JsonbValue *) lfirst(lc);
+				resultVal.val.array.elems[idx++] = *elem;
+			}
+		}
+		else
+			resultVal = *(JsonbValue *) linitial(resultList);
+
+		PG_RETURN_JSONB_P(JsonbValueToJsonb(&resultVal));
+	}
+
+	PG_RETURN_NULL();
+}
+
 Datum
 json_object_field_text(PG_FUNCTION_ARGS)
 {
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 6d9dc1528d6..70887d3fd4d 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3173,7 +3173,7 @@
 { oid => '3967', descr => 'get value from json as text with path elements',
   oprname => '#>>', oprleft => 'json', oprright => '_text', oprresult => 'text',
   oprcode => 'json_extract_path_text' },
-{ oid => '3211', descr => 'get jsonb object field',
+{ oid => '3211', oid_symbol => 'OID_JSONB_OBJECT_FIELD_OP', descr => 'get jsonb object field',
   oprname => '->', oprleft => 'jsonb', oprright => 'text', oprresult => 'jsonb',
   oprcode => 'jsonb_object_field' },
 { oid => '3477', descr => 'get jsonb object field as text',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 5b8c2ad2a54..cabde2e07e8 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9338,6 +9338,10 @@
 { oid => '3968', descr => 'get the type of a json value',
   proname => 'json_typeof', prorettype => 'text', proargtypes => 'json',
   prosrc => 'json_typeof' },
+{ oid => '4100',
+  proname => 'jsonb_object_field_dot', prorettype => 'jsonb',
+  proargtypes => 'jsonb text bool bool', proargnames => '{from_json, field_name, first_op, last_op}',
+  prosrc => 'jsonb_object_field_dot' },
 
 # uuid
 { oid => '2952', descr => 'I/O',
diff --git a/src/include/parser/parse_func.h b/src/include/parser/parse_func.h
index a6f24b83d84..745d2c75c93 100644
--- a/src/include/parser/parse_func.h
+++ b/src/include/parser/parse_func.h
@@ -34,6 +34,8 @@ typedef enum
 extern Node *ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
 							   Node *last_srf, FuncCall *fn, bool proc_call,
 							   int location);
+extern Node *ParseJsonbSimplifiedAccessorObjectField(ParseState *pstate, const char *funcname, Node *first_arg, int location,
+													 Oid basetypid, bool first_op, bool last_op);
 
 extern FuncDetailCode func_get_detail(List *funcname,
 									  List *fargs, List *fargnames,
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 2baff931bf2..e0c0757f475 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5781,3 +5781,106 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
  12345
 (1 row)
 
+-- simple dot notation
+-- TODO: add comments
+CREATE OR REPLACE FUNCTION test_jsonb_dot_notation(
+    vep jsonb, -- value expression primary
+    jc  text -- JSON simplified accessor operator chain
+)
+    RETURNS TABLE(dot_access jsonb, expected jsonb)
+    LANGUAGE plpgsql
+AS $$
+DECLARE
+    dyn_sql text;
+BEGIN
+    dyn_sql := format($f$
+    SELECT
+       (vep).%s AS dot_access,
+       json_query(vep, 'lax $.%s' WITH CONDITIONAL WRAPPER NULL ON EMPTY NULL ON ERROR) AS expected
+    FROM (SELECT $1::jsonb AS vep) dummy
+  $f$, jc, jc);
+
+    --     -- OPTIONAL: Just to see the constructed SQL in logs
+--     RAISE NOTICE 'Executing: %', dyn_sql;
+
+    -- Execute the dynamic query, substituting p_col as parameter #1
+    RETURN QUERY EXECUTE dyn_sql USING vep;
+END;
+$$;
+-- access member object field of a json object
+select * from test_jsonb_dot_notation('{"a": 1, "b": 42}'::jsonb, 'b');
+ dot_access | expected 
+------------+----------
+ 42         | 42
+(1 row)
+
+select * from test_jsonb_dot_notation('{"a": 1, "b": 42}'::jsonb, 'not_exist');
+ dot_access | expected 
+------------+----------
+            | 
+(1 row)
+
+select * from test_jsonb_dot_notation('{"a": 1, "b": 42, "b":12}'::jsonb, 'b'); -- return last for duplicate key
+ dot_access | expected 
+------------+----------
+ 12         | 12
+(1 row)
+
+select * from test_jsonb_dot_notation('{"a": 1, "b": 12, "b":42}'::jsonb, 'b');
+ dot_access | expected 
+------------+----------
+ 42         | 42
+(1 row)
+
+select * from test_jsonb_dot_notation('{"a": 2, "b": {"c": 42}}'::jsonb, 'b.c');
+ dot_access | expected 
+------------+----------
+ 42         | 42
+(1 row)
+
+select * from test_jsonb_dot_notation('{"a": 4, "b": {"c": {"d": [11, 12]}}}'::jsonb, 'b.c.d');
+ dot_access | expected 
+------------+----------
+ [11, 12]   | [11, 12]
+(1 row)
+
+-- access member object field of a json array: apply lax mode + conditional wrap
+-- unwrap the outer-most array into sequence and conditional wrap the results
+-- only unwrap the outer most array
+select * from test_jsonb_dot_notation('[{"x": 42}]'::jsonb, 'x');
+ dot_access | expected 
+------------+----------
+ 42         | 42
+(1 row)
+
+select * from test_jsonb_dot_notation('["x"]'::jsonb, 'x');
+ dot_access | expected 
+------------+----------
+            | 
+(1 row)
+
+select * from test_jsonb_dot_notation('[[{"x": 42}]]'::jsonb, 'x');
+ dot_access | expected 
+------------+----------
+            | 
+(1 row)
+
+-- wrap the result into an array on the conditional of more than one matched object keys
+select * from test_jsonb_dot_notation('[{"x": 42}, {"x": {"y": {"z": 12}}}]'::jsonb, 'x');
+       dot_access       |        expected        
+------------------------+------------------------
+ [42, {"y": {"z": 12}}] | [42, {"y": {"z": 12}}]
+(1 row)
+
+select * from test_jsonb_dot_notation('[{"x": 42}, [{"x": {"y": {"z": 12}}}]]'::jsonb, 'x');
+ dot_access | expected 
+------------+----------
+ 42         | 42
+(1 row)
+
+select * from test_jsonb_dot_notation('[{"x": 42}, {"x": [{"y": 12}, {"y": {"z": 12}}]}]'::jsonb, 'x.y');
+   dot_access    |    expected     
+-----------------+-----------------
+ [12, {"z": 12}] | [12, {"z": 12}]
+(1 row)
+
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 544bb610e2d..5a6d820b507 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1572,3 +1572,50 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::float8;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int2;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int4;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
+
+-- simple dot notation
+-- TODO: add comments
+
+CREATE OR REPLACE FUNCTION test_jsonb_dot_notation(
+    vep jsonb, -- value expression primary
+    jc  text -- JSON simplified accessor operator chain
+)
+    RETURNS TABLE(dot_access jsonb, expected jsonb)
+    LANGUAGE plpgsql
+AS $$
+DECLARE
+    dyn_sql text;
+BEGIN
+    dyn_sql := format($f$
+    SELECT
+       (vep).%s AS dot_access,
+       json_query(vep, 'lax $.%s' WITH CONDITIONAL WRAPPER NULL ON EMPTY NULL ON ERROR) AS expected
+    FROM (SELECT $1::jsonb AS vep) dummy
+  $f$, jc, jc);
+
+    --     -- OPTIONAL: Just to see the constructed SQL in logs
+--     RAISE NOTICE 'Executing: %', dyn_sql;
+
+    -- Execute the dynamic query, substituting p_col as parameter #1
+    RETURN QUERY EXECUTE dyn_sql USING vep;
+END;
+$$;
+
+-- access member object field of a json object
+select * from test_jsonb_dot_notation('{"a": 1, "b": 42}'::jsonb, 'b');
+select * from test_jsonb_dot_notation('{"a": 1, "b": 42}'::jsonb, 'not_exist');
+select * from test_jsonb_dot_notation('{"a": 1, "b": 42, "b":12}'::jsonb, 'b'); -- return last for duplicate key
+select * from test_jsonb_dot_notation('{"a": 1, "b": 12, "b":42}'::jsonb, 'b');
+select * from test_jsonb_dot_notation('{"a": 2, "b": {"c": 42}}'::jsonb, 'b.c');
+select * from test_jsonb_dot_notation('{"a": 4, "b": {"c": {"d": [11, 12]}}}'::jsonb, 'b.c.d');
+
+-- access member object field of a json array: apply lax mode + conditional wrap
+-- unwrap the outer-most array into sequence and conditional wrap the results
+-- only unwrap the outer most array
+select * from test_jsonb_dot_notation('[{"x": 42}]'::jsonb, 'x');
+select * from test_jsonb_dot_notation('["x"]'::jsonb, 'x');
+select * from test_jsonb_dot_notation('[[{"x": 42}]]'::jsonb, 'x');
+-- wrap the result into an array on the conditional of more than one matched object keys
+select * from test_jsonb_dot_notation('[{"x": 42}, {"x": {"y": {"z": 12}}}]'::jsonb, 'x');
+select * from test_jsonb_dot_notation('[{"x": 42}, [{"x": {"y": {"z": 12}}}]]'::jsonb, 'x');
+select * from test_jsonb_dot_notation('[{"x": 42}, {"x": [{"y": 12}, {"y": {"z": 12}}]}]'::jsonb, 'x.y');
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v7-0001-Allow-transformation-only-of-a-sublist-of-subscri.patch (6.3K, ../../CAK98qZ3Ly6PhRwCVmMKJBba5oHVF9k370MMT2b_gep-SuQfRtg@mail.gmail.com/4-v7-0001-Allow-transformation-only-of-a-sublist-of-subscri.patch)
  download | inline diff:
From 5da3d365c8eb72bf467fe33c7173fedd61c9f96f Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Fri, 14 Oct 2022 15:35:22 +0300
Subject: [PATCH v7 1/5] Allow transformation only of a sublist of subscripts

---
 src/backend/parser/parse_expr.c   | 9 ++++-----
 src/backend/parser/parse_node.c   | 4 ++--
 src/backend/parser/parse_target.c | 5 ++++-
 src/backend/utils/adt/arraysubs.c | 6 ++++--
 src/backend/utils/adt/jsonbsubs.c | 6 ++++--
 src/include/nodes/subscripting.h  | 2 +-
 src/include/parser/parse_node.h   | 2 +-
 7 files changed, 20 insertions(+), 14 deletions(-)

diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index bad1df732ea..2c0f4a50b21 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -466,14 +466,13 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 			Assert(IsA(n, String));
 
 			/* process subscripts before this field selection */
-			if (subscripts)
+			while (subscripts)
 				result = (Node *) transformContainerSubscripts(pstate,
 															   result,
 															   exprType(result),
 															   exprTypmod(result),
-															   subscripts,
+															   &subscripts,
 															   false);
-			subscripts = NIL;
 
 			newresult = ParseFuncOrColumn(pstate,
 										  list_make1(n),
@@ -488,12 +487,12 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		}
 	}
 	/* process trailing subscripts, if any */
-	if (subscripts)
+	while (subscripts)
 		result = (Node *) transformContainerSubscripts(pstate,
 													   result,
 													   exprType(result),
 													   exprTypmod(result),
-													   subscripts,
+													   &subscripts,
 													   false);
 
 	return result;
diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c
index d6feb16aef3..19a6b678e67 100644
--- a/src/backend/parser/parse_node.c
+++ b/src/backend/parser/parse_node.c
@@ -244,7 +244,7 @@ transformContainerSubscripts(ParseState *pstate,
 							 Node *containerBase,
 							 Oid containerType,
 							 int32 containerTypMod,
-							 List *indirection,
+							 List **indirection,
 							 bool isAssignment)
 {
 	SubscriptingRef *sbsref;
@@ -280,7 +280,7 @@ transformContainerSubscripts(ParseState *pstate,
 	 * element.  If any of the items are slice specifiers (lower:upper), then
 	 * the subscript expression means a container slice operation.
 	 */
-	foreach(idx, indirection)
+	foreach(idx, *indirection)
 	{
 		A_Indices  *ai = lfirst_node(A_Indices, idx);
 
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 4aba0d9d4d5..39fd82f8371 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -936,9 +936,12 @@ transformAssignmentSubscripts(ParseState *pstate,
 										  basenode,
 										  containerType,
 										  containerTypMod,
-										  subscripts,
+										  &subscripts,
 										  true);
 
+	if (subscripts)
+		elog(ERROR, "subscripting assignment is not supported");
+
 	typeNeeded = sbsref->refrestype;
 	typmodNeeded = sbsref->reftypmod;
 
diff --git a/src/backend/utils/adt/arraysubs.c b/src/backend/utils/adt/arraysubs.c
index 562179b3799..edd85f7ba67 100644
--- a/src/backend/utils/adt/arraysubs.c
+++ b/src/backend/utils/adt/arraysubs.c
@@ -53,7 +53,7 @@ typedef struct ArraySubWorkspace
  */
 static void
 array_subscript_transform(SubscriptingRef *sbsref,
-						  List *indirection,
+						  List **indirection,
 						  ParseState *pstate,
 						  bool isSlice,
 						  bool isAssignment)
@@ -70,7 +70,7 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	 * indirection items to slices by treating the single subscript as the
 	 * upper bound and supplying an assumed lower bound of 1.
 	 */
-	foreach(idx, indirection)
+	foreach(idx, *indirection)
 	{
 		A_Indices  *ai = lfirst_node(A_Indices, idx);
 		Node	   *subexpr;
@@ -151,6 +151,8 @@ array_subscript_transform(SubscriptingRef *sbsref,
 						list_length(upperIndexpr), MAXDIM)));
 	/* We need not check lowerIndexpr separately */
 
+	*indirection = NIL;
+
 	/*
 	 * Determine the result type of the subscripting operation.  It's the same
 	 * as the array type if we're slicing, else it's the element type.  In
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index de64d498512..8ad6aa1ad4f 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -41,7 +41,7 @@ typedef struct JsonbSubWorkspace
  */
 static void
 jsonb_subscript_transform(SubscriptingRef *sbsref,
-						  List *indirection,
+						  List **indirection,
 						  ParseState *pstate,
 						  bool isSlice,
 						  bool isAssignment)
@@ -53,7 +53,7 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	 * Transform and convert the subscript expressions. Jsonb subscripting
 	 * does not support slices, look only and the upper index.
 	 */
-	foreach(idx, indirection)
+	foreach(idx, *indirection)
 	{
 		A_Indices  *ai = lfirst_node(A_Indices, idx);
 		Node	   *subExpr;
@@ -159,6 +159,8 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	/* Determine the result type of the subscripting operation; always jsonb */
 	sbsref->refrestype = JSONBOID;
 	sbsref->reftypmod = -1;
+
+	*indirection = NIL;
 }
 
 /*
diff --git a/src/include/nodes/subscripting.h b/src/include/nodes/subscripting.h
index 234e8ad8012..ed2f1cefb49 100644
--- a/src/include/nodes/subscripting.h
+++ b/src/include/nodes/subscripting.h
@@ -93,7 +93,7 @@ struct SubscriptExecSteps;
  * assignment must return.
  */
 typedef void (*SubscriptTransform) (SubscriptingRef *sbsref,
-									List *indirection,
+									List **indirection,
 									struct ParseState *pstate,
 									bool isSlice,
 									bool isAssignment);
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 994284019fb..5ae11ccec33 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -377,7 +377,7 @@ extern SubscriptingRef *transformContainerSubscripts(ParseState *pstate,
 													 Node *containerBase,
 													 Oid containerType,
 													 int32 containerTypMod,
-													 List *indirection,
+													 List **indirection,
 													 bool isAssignment);
 extern Const *make_const(ParseState *pstate, A_Const *aconst);
 
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v7-0004-Implement-read-only-dot-notation-for-jsonb-using-.patch (31.0K, ../../CAK98qZ3Ly6PhRwCVmMKJBba5oHVF9k370MMT2b_gep-SuQfRtg@mail.gmail.com/5-v7-0004-Implement-read-only-dot-notation-for-jsonb-using-.patch)
  download | inline diff:
From 53bc1da78b0feb7769c571fbcafb70c2214a9c46 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Sat, 1 Apr 2023 23:17:53 +0300
Subject: [PATCH v7 4/5] Implement read-only dot notation for jsonb using
 jsonpath

---
 src/backend/utils/adt/jsonbsubs.c   | 438 +++++++++++++++++++++++-----
 src/include/nodes/primnodes.h       |   2 +
 src/test/regress/expected/jsonb.out | 265 ++++++++++++++++-
 src/test/regress/sql/jsonb.sql      |  56 ++++
 4 files changed, 670 insertions(+), 91 deletions(-)

diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index a0d38a0fd80..1ececb4efa2 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -15,12 +15,14 @@
 #include "postgres.h"
 
 #include "executor/execExpr.h"
+#include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "nodes/subscripting.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_expr.h"
 #include "utils/builtins.h"
 #include "utils/jsonb.h"
+#include "utils/jsonpath.h"
 
 
 /* SubscriptingRefState.workspace for jsonb subscripting execution */
@@ -30,8 +32,261 @@ typedef struct JsonbSubWorkspace
 	Oid		   *indexOid;		/* OID of coerced subscript expression, could
 								 * be only integer or text */
 	Datum	   *index;			/* Subscript values in Datum format */
+	JsonPath   *jsonpath;		/* jsonpath for dot notation execution */
+	List		vars;			/* jsonpath vars */
 } JsonbSubWorkspace;
 
+static bool
+jsonb_check_jsonpath_needed(List *indirection)
+{
+	ListCell   *lc;
+
+	foreach(lc, indirection)
+	{
+		Node	   *accessor = lfirst(lc);
+
+		if (IsA(accessor, String) ||
+			IsA(accessor, A_Star))
+			return true;
+		else if (IsA(accessor, A_Indices))
+		{
+			A_Indices  *ai = castNode(A_Indices, accessor);
+
+			if (!ai->uidx || ai->lidx)
+			{
+				Assert(ai->is_slice);
+				return true;
+			}
+		}
+		else
+			return true;
+	}
+
+	return false;
+}
+
+static JsonPathParseItem *
+make_jsonpath_item(JsonPathItemType type)
+{
+	JsonPathParseItem *v = palloc(sizeof(*v));
+
+	v->type = type;
+	v->next = NULL;
+
+	return v;
+}
+
+static JsonPathParseItem *
+make_jsonpath_item_int(int32 val, List **exprs)
+{
+	JsonPathParseItem *jpi = make_jsonpath_item(jpiNumeric);
+
+	jpi->value.numeric =
+		DatumGetNumeric(DirectFunctionCall1(int4_numeric, Int32GetDatum(val)));
+
+	*exprs = lappend(*exprs, makeConst(INT4OID, -1, InvalidOid, 4,
+									   Int32GetDatum(val), false, true));
+
+	return jpi;
+}
+
+static Oid
+jsonb_subscript_type(Node *expr)
+{
+	if (expr && IsA(expr, String))
+		return TEXTOID;
+
+	return exprType(expr);
+}
+
+static Node *
+coerce_jsonpath_subscript(ParseState *pstate, Node *subExpr, Oid numtype)
+{
+	Oid			subExprType = jsonb_subscript_type(subExpr);
+	Oid			targetType = UNKNOWNOID;
+
+	if (subExprType != UNKNOWNOID)
+	{
+		Oid			targets[2] = {numtype, TEXTOID};
+
+		/*
+		 * Jsonb can handle multiple subscript types, but cases when a
+		 * subscript could be coerced to multiple target types must be
+		 * avoided, similar to overloaded functions. It could be
+		 * possibly extend with jsonpath in the future.
+		 */
+		for (int i = 0; i < 2; i++)
+		{
+			if (can_coerce_type(1, &subExprType, &targets[i], COERCION_IMPLICIT))
+			{
+				/*
+				 * One type has already succeeded, it means there are
+				 * two coercion targets possible, failure.
+				 */
+				if (targetType != UNKNOWNOID)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
+							 errhint("jsonb subscript must be coercible to only one type, integer or text."),
+							 parser_errposition(pstate, exprLocation(subExpr))));
+
+				targetType = targets[i];
+			}
+		}
+
+		/*
+		 * No suitable types were found, failure.
+		 */
+		if (targetType == UNKNOWNOID)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
+					 errhint("jsonb subscript must be coercible to either integer or text."),
+					 parser_errposition(pstate, exprLocation(subExpr))));
+	}
+	else
+		targetType = TEXTOID;
+
+	/*
+	 * We known from can_coerce_type that coercion will succeed, so
+	 * coerce_type could be used. Note the implicit coercion context,
+	 * which is required to handle subscripts of different types,
+	 * similar to overloaded functions.
+	 */
+	subExpr = coerce_type(pstate,
+						  subExpr, subExprType,
+						  targetType, -1,
+						  COERCION_IMPLICIT,
+						  COERCE_IMPLICIT_CAST,
+						  -1);
+	if (subExpr == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("jsonb subscript must have text type"),
+				 parser_errposition(pstate, exprLocation(subExpr))));
+
+	return subExpr;
+}
+
+static JsonPathParseItem *
+make_jsonpath_item_expr(ParseState *pstate, Node *expr, List **exprs)
+{
+	JsonPathParseItem *jpi;
+
+	expr = transformExpr(pstate, expr, pstate->p_expr_kind);
+
+	if (IsA(expr, Const))
+	{
+		Const	   *cnst = (Const *) expr;
+
+		if (cnst->consttype == INT4OID && !cnst->constisnull)
+		{
+			int32		val = DatumGetInt32(cnst->constvalue);
+
+			return make_jsonpath_item_int(val, exprs);
+		}
+	}
+
+	*exprs = lappend(*exprs, coerce_jsonpath_subscript(pstate, expr, NUMERICOID));
+
+	jpi = make_jsonpath_item(jpiVariable);
+	jpi->value.string.val = psprintf("%d", list_length(*exprs));
+	jpi->value.string.len = strlen(jpi->value.string.val);
+
+	return jpi;
+}
+
+static Node *
+jsonb_subscript_make_jsonpath(ParseState *pstate, List **indirection,
+							  List **uexprs, List **lexprs)
+{
+	JsonPathParseResult jpres;
+	JsonPathParseItem *path = make_jsonpath_item(jpiRoot);
+	ListCell   *lc;
+	Datum		jsp;
+	int			pathlen = 0;
+
+	*uexprs = NIL;
+	*lexprs = NIL;
+
+	jpres.expr = path;
+	jpres.lax = true;
+
+	foreach(lc, *indirection)
+	{
+		Node	   *accessor = lfirst(lc);
+		JsonPathParseItem *jpi;
+
+		if (IsA(accessor, String))
+		{
+			char	   *field = strVal(accessor);
+
+			jpi = make_jsonpath_item(jpiKey);
+			jpi->value.string.val = field;
+			jpi->value.string.len = strlen(field);
+
+			*uexprs = lappend(*uexprs, accessor);
+		}
+		else if (IsA(accessor, A_Star))
+		{
+			jpi = make_jsonpath_item(jpiAnyKey);
+
+			*uexprs = lappend(*uexprs, NULL);
+		}
+		else if (IsA(accessor, A_Indices))
+		{
+			A_Indices  *ai = castNode(A_Indices, accessor);
+
+			jpi = make_jsonpath_item(jpiIndexArray);
+			jpi->value.array.nelems = 1;
+			jpi->value.array.elems = palloc(sizeof(jpi->value.array.elems[0]));
+
+			if (ai->is_slice)
+			{
+				while (list_length(*lexprs) < list_length(*uexprs))
+					*lexprs = lappend(*lexprs, NULL);
+
+				if (ai->lidx)
+					jpi->value.array.elems[0].from = make_jsonpath_item_expr(pstate, ai->lidx, lexprs);
+				else
+					jpi->value.array.elems[0].from = make_jsonpath_item_int(0, lexprs);
+
+				if (ai->uidx)
+					jpi->value.array.elems[0].to = make_jsonpath_item_expr(pstate, ai->uidx, uexprs);
+				else
+				{
+					jpi->value.array.elems[0].to = make_jsonpath_item(jpiLast);
+					*uexprs = lappend(*uexprs, NULL);
+				}
+			}
+			else
+			{
+				Assert(ai->uidx && !ai->lidx);
+				jpi->value.array.elems[0].from = make_jsonpath_item_expr(pstate, ai->uidx, uexprs);
+				jpi->value.array.elems[0].to = NULL;
+			}
+		}
+		else
+			break;
+
+		/* append path item */
+		path->next = jpi;
+		path = jpi;
+		pathlen++;
+	}
+
+	if (*lexprs)
+	{
+		while (list_length(*lexprs) < list_length(*uexprs))
+			*lexprs = lappend(*lexprs, NULL);
+	}
+
+	*indirection = list_delete_first_n(*indirection, pathlen);
+
+	jsp = jsonPathFromParseResult(&jpres, 0, NULL);
+
+	return (Node *) makeConst(JSONPATHOID, -1, InvalidOid, -1, jsp, false, false);
+}
 
 /*
  * Finish parse analysis of a SubscriptingRef expression for a jsonb.
@@ -49,19 +304,35 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	List	   *upperIndexpr = NIL;
 	ListCell   *idx;
 
+	/* Determine the result type of the subscripting operation; always jsonb */
+	sbsref->refrestype = JSONBOID;
+	sbsref->reftypmod = -1;
+
+	if (jsonb_check_jsonpath_needed(*indirection))
+	{
+		sbsref->refprivate =
+			jsonb_subscript_make_jsonpath(pstate, indirection,
+										  &sbsref->refupperindexpr,
+										  &sbsref->reflowerindexpr);
+		return;
+	}
+
 	/*
 	 * Transform and convert the subscript expressions. Jsonb subscripting
 	 * does not support slices, look only and the upper index.
 	 */
 	foreach(idx, *indirection)
 	{
+		Node	   *i = lfirst(idx);
 		A_Indices  *ai;
 		Node	   *subExpr;
 
-		if (!IsA(lfirst(idx), A_Indices))
+		Assert(IsA(i, A_Indices));
+
+		if (!IsA(i, A_Indices))
 			break;
 
-		ai = lfirst_node(A_Indices, idx);
+		ai = castNode(A_Indices, i);
 
 		if (isSlice)
 		{
@@ -75,71 +346,8 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 
 		if (ai->uidx)
 		{
-			Oid			subExprType = InvalidOid,
-						targetType = UNKNOWNOID;
-
 			subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
-			subExprType = exprType(subExpr);
-
-			if (subExprType != UNKNOWNOID)
-			{
-				Oid			targets[2] = {INT4OID, TEXTOID};
-
-				/*
-				 * Jsonb can handle multiple subscript types, but cases when a
-				 * subscript could be coerced to multiple target types must be
-				 * avoided, similar to overloaded functions. It could be
-				 * possibly extend with jsonpath in the future.
-				 */
-				for (int i = 0; i < 2; i++)
-				{
-					if (can_coerce_type(1, &subExprType, &targets[i], COERCION_IMPLICIT))
-					{
-						/*
-						 * One type has already succeeded, it means there are
-						 * two coercion targets possible, failure.
-						 */
-						if (targetType != UNKNOWNOID)
-							ereport(ERROR,
-									(errcode(ERRCODE_DATATYPE_MISMATCH),
-									 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
-									 errhint("jsonb subscript must be coercible to only one type, integer or text."),
-									 parser_errposition(pstate, exprLocation(subExpr))));
-
-						targetType = targets[i];
-					}
-				}
-
-				/*
-				 * No suitable types were found, failure.
-				 */
-				if (targetType == UNKNOWNOID)
-					ereport(ERROR,
-							(errcode(ERRCODE_DATATYPE_MISMATCH),
-							 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
-							 errhint("jsonb subscript must be coercible to either integer or text."),
-							 parser_errposition(pstate, exprLocation(subExpr))));
-			}
-			else
-				targetType = TEXTOID;
-
-			/*
-			 * We known from can_coerce_type that coercion will succeed, so
-			 * coerce_type could be used. Note the implicit coercion context,
-			 * which is required to handle subscripts of different types,
-			 * similar to overloaded functions.
-			 */
-			subExpr = coerce_type(pstate,
-								  subExpr, subExprType,
-								  targetType, -1,
-								  COERCION_IMPLICIT,
-								  COERCE_IMPLICIT_CAST,
-								  -1);
-			if (subExpr == NULL)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("jsonb subscript must have text type"),
-						 parser_errposition(pstate, exprLocation(subExpr))));
+			subExpr = coerce_jsonpath_subscript(pstate, subExpr, INT4OID);
 		}
 		else
 		{
@@ -161,10 +369,6 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->refupperindexpr = upperIndexpr;
 	sbsref->reflowerindexpr = NIL;
 
-	/* Determine the result type of the subscripting operation; always jsonb */
-	sbsref->refrestype = JSONBOID;
-	sbsref->reftypmod = -1;
-
 	/* Remove processed elements */
 	if (upperIndexpr)
 		*indirection = list_delete_first_n(*indirection, list_length(upperIndexpr));
@@ -219,7 +423,7 @@ jsonb_subscript_check_subscripts(ExprState *state,
 			 * For jsonb fetch and assign functions we need to provide path in
 			 * text format. Convert if it's not already text.
 			 */
-			if (workspace->indexOid[i] == INT4OID)
+			if (!workspace->jsonpath && workspace->indexOid[i] == INT4OID)
 			{
 				Datum		datum = sbsrefstate->upperindex[i];
 				char	   *cs = DatumGetCString(DirectFunctionCall1(int4out, datum));
@@ -247,17 +451,44 @@ jsonb_subscript_fetch(ExprState *state,
 {
 	SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
 	JsonbSubWorkspace *workspace = (JsonbSubWorkspace *) sbsrefstate->workspace;
-	Jsonb	   *jsonbSource;
 
 	/* Should not get here if source jsonb (or any subscript) is null */
 	Assert(!(*op->resnull));
 
-	jsonbSource = DatumGetJsonbP(*op->resvalue);
-	*op->resvalue = jsonb_get_element(jsonbSource,
-									  workspace->index,
-									  sbsrefstate->numupper,
-									  op->resnull,
-									  false);
+	if (workspace->jsonpath)
+	{
+		bool		empty = false;
+		bool		error = false;
+		List	   *vars = &workspace->vars;
+		ListCell   *lc;
+
+		/* copy computed variable values */
+		foreach(lc, vars)
+		{
+			JsonPathVariable *var = lfirst(lc);
+			int			i = foreach_current_index(lc);
+
+			var->value = workspace->index[i];
+			var->isnull = false;
+		}
+
+		*op->resvalue = JsonPathQuery(*op->resvalue, workspace->jsonpath,
+									  JSW_CONDITIONAL,
+									  &empty, &error, vars,
+									  NULL);
+
+		*op->resnull = empty || error;
+	}
+	else
+	{
+		Jsonb	   *jsonbSource = DatumGetJsonbP(*op->resvalue);
+
+		*op->resvalue = jsonb_get_element(jsonbSource,
+										  workspace->index,
+										  sbsrefstate->numupper,
+										  op->resnull,
+										  false);
+	}
 }
 
 /*
@@ -367,12 +598,57 @@ jsonb_exec_setup(const SubscriptingRef *sbsref,
 	ListCell   *lc;
 	int			nupper = sbsref->refupperindexpr->length;
 	char	   *ptr;
+	bool		useJsonpath = sbsref->refprivate != NULL;
+	JsonPathVariable *vars;
+	int			nvars = useJsonpath ? nupper : 0;
 
 	/* Allocate type-specific workspace with space for per-subscript data */
-	workspace = palloc0(MAXALIGN(sizeof(JsonbSubWorkspace)) +
+	workspace = palloc0(MAXALIGN(offsetof(JsonbSubWorkspace, vars.initial_elements) + nvars * sizeof(ListCell)) +
+						MAXALIGN(nvars * sizeof(*vars) + nvars * 16) +
 						nupper * (sizeof(Datum) + sizeof(Oid)));
 	workspace->expectArray = false;
-	ptr = ((char *) workspace) + MAXALIGN(sizeof(JsonbSubWorkspace));
+	ptr = ((char *) workspace) +
+		MAXALIGN(offsetof(JsonbSubWorkspace, vars.initial_elements) +
+				 nvars * sizeof(ListCell));
+
+	if (!useJsonpath)
+		workspace->jsonpath = NULL;
+	else
+	{
+		workspace->jsonpath = DatumGetJsonPathP(castNode(Const, sbsref->refprivate)->constvalue);
+
+		vars = (JsonPathVariable *) ptr;
+		ptr += MAXALIGN(nvars * sizeof(*vars));
+
+		workspace->vars.type = T_List;
+		workspace->vars.length = nvars;
+		workspace->vars.max_length = nvars;
+		workspace->vars.elements = &workspace->vars.initial_elements[0];
+
+		for (int i = 0; i < nvars; i++)
+		{
+			Node	   *expr = list_nth(sbsref->refupperindexpr, i);
+
+			workspace->vars.elements[i].ptr_value = &vars[i];
+
+			if (expr && IsA(expr, String))
+			{
+				vars[i].typid = TEXTOID;
+				vars[i].typmod = -1;
+			}
+			else
+			{
+				vars[i].typid = exprType(expr);
+				vars[i].typmod = exprTypmod(expr);
+			}
+
+			vars[i].name = ptr;
+			snprintf(ptr, 16, "%d", i + 1);
+			vars[i].namelen = strlen(ptr);
+
+			ptr += 16;
+		}
+	}
 
 	/*
 	 * This coding assumes sizeof(Datum) >= sizeof(Oid), else we might
@@ -390,7 +666,7 @@ jsonb_exec_setup(const SubscriptingRef *sbsref,
 		Node	   *expr = lfirst(lc);
 		int			i = foreach_current_index(lc);
 
-		workspace->indexOid[i] = exprType(expr);
+		workspace->indexOid[i] = jsonb_subscript_type(expr);
 	}
 
 	/*
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 839e71d52f4..4b1e5de98e5 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -718,6 +718,8 @@ typedef struct SubscriptingRef
 	Expr	   *refexpr;
 	/* expression for the source value, or NULL if fetch */
 	Expr	   *refassgnexpr;
+	/* private expression */
+	Node	   *refprivate;
 } SubscriptingRef;
 
 /*
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 2baff931bf2..14123929475 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -4939,6 +4939,12 @@ select ('123'::jsonb)['a'];
  
 (1 row)
 
+select ('123'::jsonb).a;
+ a 
+---
+ 
+(1 row)
+
 select ('123'::jsonb)[0];
  jsonb 
 -------
@@ -4957,6 +4963,12 @@ select ('{"a": 1}'::jsonb)['a'];
  1
 (1 row)
 
+select ('{"a": 1}'::jsonb).a;
+ a 
+---
+ 1
+(1 row)
+
 select ('{"a": 1}'::jsonb)[0];
  jsonb 
 -------
@@ -4969,6 +4981,12 @@ select ('{"a": 1}'::jsonb)['not_exist'];
  
 (1 row)
 
+select ('{"a": 1}'::jsonb)."not_exist";
+ not_exist 
+-----------
+ 
+(1 row)
+
 select ('{"a": 1}'::jsonb)[NULL];
  jsonb 
 -------
@@ -4981,6 +4999,12 @@ select ('[1, "2", null]'::jsonb)['a'];
  
 (1 row)
 
+select ('[1, "2", null]'::jsonb).a;
+ a 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[0];
  jsonb 
 -------
@@ -4993,6 +5017,12 @@ select ('[1, "2", null]'::jsonb)['1'];
  "2"
 (1 row)
 
+select ('[1, "2", null]'::jsonb)."1";
+ 1 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[1.0];
 ERROR:  subscript type numeric is not supported
 LINE 1: select ('[1, "2", null]'::jsonb)[1.0];
@@ -5022,6 +5052,12 @@ select ('[1, "2", null]'::jsonb)[1]['a'];
  
 (1 row)
 
+select ('[1, "2", null]'::jsonb)[1].a;
+ a 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[1][0];
  jsonb 
 -------
@@ -5034,73 +5070,143 @@ select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['b'];
  "c"
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).b;
+  b  
+-----
+ "c"
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'];
    jsonb   
 -----------
  [1, 2, 3]
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d;
+     d     
+-----------
+ [1, 2, 3]
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'][1];
  jsonb 
 -------
  2
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d[1];
+ d 
+---
+ 2
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d']['a'];
  jsonb 
 -------
  
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['a'];
+ d 
+---
+ 
+(1 row)
+
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d.a;
+ a 
+---
+ 
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1'];
      jsonb     
 ---------------
  {"a2": "aaa"}
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1;
+      a1       
+---------------
+ {"a2": "aaa"}
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2'];
  jsonb 
 -------
  "aaa"
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2;
+  a2   
+-------
+ "aaa"
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']['a3'];
  jsonb 
 -------
  
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2.a3;
+ a3 
+----
+ 
+(1 row)
+
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'];
          jsonb         
 -----------------------
  ["aaa", "bbb", "ccc"]
 (1 row)
 
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1;
+          b1           
+-----------------------
+ ["aaa", "bbb", "ccc"]
+(1 row)
+
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'][2];
  jsonb 
 -------
  "ccc"
 (1 row)
 
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1[2];
+  b1   
+-------
+ "ccc"
+(1 row)
+
 -- slices are not supported
 select ('{"a": 1}'::jsonb)['a':'b'];
-ERROR:  jsonb subscript does not support slices
-LINE 1: select ('{"a": 1}'::jsonb)['a':'b'];
-                                       ^
+ jsonb 
+-------
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[1:2];
-ERROR:  jsonb subscript does not support slices
-LINE 1: select ('[1, "2", null]'::jsonb)[1:2];
-                                           ^
+    jsonb    
+-------------
+ ["2", null]
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[:2];
 ERROR:  jsonb subscript does not support slices
 LINE 1: select ('[1, "2", null]'::jsonb)[:2];
                                           ^
 select ('[1, "2", null]'::jsonb)[1:];
-ERROR:  jsonb subscript does not support slices
-LINE 1: select ('[1, "2", null]'::jsonb)[1:];
-                                         ^
+    jsonb    
+-------------
+ ["2", null]
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[:];
-ERROR:  jsonb subscript does not support slices
+     jsonb      
+----------------
+ [1, "2", null]
+(1 row)
+
 create TEMP TABLE test_jsonb_subscript (
        id int,
        test_json jsonb
@@ -5781,3 +5887,142 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
  12345
 (1 row)
 
+-- dot notation
+CREATE TABLE test_jsonb_dot_notation AS
+SELECT '{"a": [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], "b": [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]}'::jsonb jb;
+SELECT (jb).* FROM test_jsonb_dot_notation;
+ERROR:  type jsonb is not composite
+SELECT (jb).* FROM test_jsonb_dot_notation t;
+ERROR:  type jsonb is not composite
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+ERROR:  type jsonb is not composite
+SELECT (jb).* FROM test_jsonb_dot_notation;
+ERROR:  type jsonb is not composite
+SELECT (t.jb).* FROM test_jsonb_dot_notation;
+ERROR:  missing FROM-clause entry for table "t"
+LINE 1: SELECT (t.jb).* FROM test_jsonb_dot_notation;
+                ^
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+ERROR:  type jsonb is not composite
+SELECT (jb).a FROM test_jsonb_dot_notation;
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+SELECT (jb).a FROM test_jsonb_dot_notation;
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+SELECT (jb).b FROM test_jsonb_dot_notation;
+                         b                         
+---------------------------------------------------
+ [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]
+(1 row)
+
+SELECT (jb).c FROM test_jsonb_dot_notation;
+ c 
+---
+ 
+(1 row)
+
+SELECT (jb).a.b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+ERROR:  type jsonb is not composite
+SELECT (jb).a.*.b FROM test_jsonb_dot_notation;
+ERROR:  improper use of "*" at or near "FROM"
+LINE 1: SELECT (jb).a.*.b FROM test_jsonb_dot_notation;
+                          ^
+SELECT (jb).a.*.x FROM test_jsonb_dot_notation;
+ERROR:  improper use of "*" at or near "FROM"
+LINE 1: SELECT (jb).a.*.x FROM test_jsonb_dot_notation;
+                          ^
+SELECT (jb).a.*.y FROM test_jsonb_dot_notation;
+ERROR:  improper use of "*" at or near "FROM"
+LINE 1: SELECT (jb).a.*.y FROM test_jsonb_dot_notation;
+                          ^
+SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+ERROR:  improper use of "*" at or near "FROM"
+LINE 1: SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+                          ^
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+ERROR:  improper use of "*" at or near "FROM"
+LINE 1: SELECT (jb).*.x FROM test_jsonb_dot_notation;
+                        ^
+SELECT (jb).*.x FROM test_jsonb_dot_notation t;
+ERROR:  improper use of "*" at or near "FROM"
+LINE 1: SELECT (jb).*.x FROM test_jsonb_dot_notation t;
+                        ^
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+ERROR:  row expansion via "*" is not supported here
+LINE 1: SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+                 ^
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+ERROR:  row expansion via "*" is not supported here
+LINE 1: SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+                 ^
+SELECT ((jb).*)[:].x FROM test_jsonb_dot_notation t;
+ERROR:  row expansion via "*" is not supported here
+LINE 1: SELECT ((jb).*)[:].x FROM test_jsonb_dot_notation t;
+                 ^
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+ERROR:  improper use of "*" at or near "FROM"
+LINE 1: SELECT (jb).*.x FROM test_jsonb_dot_notation;
+                        ^
+SELECT (jb).*.x.* FROM test_jsonb_dot_notation;
+ERROR:  improper use of "*" at or near "FROM"
+LINE 1: SELECT (jb).*.x.* FROM test_jsonb_dot_notation;
+                          ^
+SELECT (jb).*.x.y FROM test_jsonb_dot_notation;
+ERROR:  improper use of "*" at or near "FROM"
+LINE 1: SELECT (jb).*.x.y FROM test_jsonb_dot_notation;
+                          ^
+SELECT (jb).*.x.z FROM test_jsonb_dot_notation;
+ERROR:  improper use of "*" at or near "FROM"
+LINE 1: SELECT (jb).*.x.z FROM test_jsonb_dot_notation;
+                          ^
+SELECT (jb).*.*.y FROM test_jsonb_dot_notation;
+ERROR:  improper use of "*" at or near "FROM"
+LINE 1: SELECT (jb).*.*.y FROM test_jsonb_dot_notation;
+                          ^
+SELECT (jb).*.*.* FROM test_jsonb_dot_notation;
+ERROR:  improper use of "*" at or near "FROM"
+LINE 1: SELECT (jb).*.*.* FROM test_jsonb_dot_notation;
+                          ^
+SELECT (jb).*.*.*.* FROM test_jsonb_dot_notation;
+ERROR:  improper use of "*" at or near "FROM"
+LINE 1: SELECT (jb).*.*.*.* FROM test_jsonb_dot_notation;
+                            ^
+SELECT (jb).a.b.c.* FROM test_jsonb_dot_notation;
+ERROR:  type jsonb is not composite
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).* FROM test_jsonb_dot_notation;
+ERROR:  type jsonb is not composite
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: jb.a
+(2 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: jb.a[1]
+(2 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*['b'] FROM test_jsonb_dot_notation;
+ERROR:  improper use of "*" at or near "FROM"
+LINE 1: EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*['b'] FROM test_...
+                                                          ^
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:2]['b'].b FROM test_jsonb_dot_notation;
+ERROR:  improper use of "*" at or near "FROM"
+LINE 1: ... (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:2]['b'].b FROM test_...
+                                                             ^
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 544bb610e2d..4b49d59222b 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1286,30 +1286,46 @@ select jsonb_insert('{"a": {"b": "value"}}', '{a, b}', '"new_value"', true);
 
 -- jsonb subscript
 select ('123'::jsonb)['a'];
+select ('123'::jsonb).a;
 select ('123'::jsonb)[0];
 select ('123'::jsonb)[NULL];
 select ('{"a": 1}'::jsonb)['a'];
+select ('{"a": 1}'::jsonb).a;
 select ('{"a": 1}'::jsonb)[0];
 select ('{"a": 1}'::jsonb)['not_exist'];
+select ('{"a": 1}'::jsonb)."not_exist";
 select ('{"a": 1}'::jsonb)[NULL];
 select ('[1, "2", null]'::jsonb)['a'];
+select ('[1, "2", null]'::jsonb).a;
 select ('[1, "2", null]'::jsonb)[0];
 select ('[1, "2", null]'::jsonb)['1'];
+select ('[1, "2", null]'::jsonb)."1";
 select ('[1, "2", null]'::jsonb)[1.0];
 select ('[1, "2", null]'::jsonb)[2];
 select ('[1, "2", null]'::jsonb)[3];
 select ('[1, "2", null]'::jsonb)[-2];
 select ('[1, "2", null]'::jsonb)[1]['a'];
+select ('[1, "2", null]'::jsonb)[1].a;
 select ('[1, "2", null]'::jsonb)[1][0];
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['b'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).b;
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d;
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'][1];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d[1];
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d']['a'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['a'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d.a;
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1;
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2;
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']['a3'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2.a3;
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'];
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1;
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'][2];
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1[2];
 
 -- slices are not supported
 select ('{"a": 1}'::jsonb)['a':'b'];
@@ -1572,3 +1588,43 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::float8;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int2;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int4;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
+
+-- dot notation
+CREATE TABLE test_jsonb_dot_notation AS
+SELECT '{"a": [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], "b": [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]}'::jsonb jb;
+
+SELECT (jb).* FROM test_jsonb_dot_notation;
+SELECT (jb).* FROM test_jsonb_dot_notation t;
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+SELECT (jb).* FROM test_jsonb_dot_notation;
+SELECT (t.jb).* FROM test_jsonb_dot_notation;
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+SELECT (jb).a FROM test_jsonb_dot_notation;
+SELECT (jb).a FROM test_jsonb_dot_notation;
+SELECT (jb).b FROM test_jsonb_dot_notation;
+SELECT (jb).c FROM test_jsonb_dot_notation;
+SELECT (jb).a.b FROM test_jsonb_dot_notation;
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.b FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.x FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.y FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+SELECT (jb).*.x FROM test_jsonb_dot_notation t;
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+SELECT ((jb).*)[:].x FROM test_jsonb_dot_notation t;
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+SELECT (jb).*.x.* FROM test_jsonb_dot_notation;
+SELECT (jb).*.x.y FROM test_jsonb_dot_notation;
+SELECT (jb).*.x.z FROM test_jsonb_dot_notation;
+SELECT (jb).*.*.y FROM test_jsonb_dot_notation;
+SELECT (jb).*.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).*.*.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.b.c.* FROM test_jsonb_dot_notation;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).* FROM test_jsonb_dot_notation;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a FROM test_jsonb_dot_notation;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*['b'] FROM test_jsonb_dot_notation;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:2]['b'].b FROM test_jsonb_dot_notation;
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v7-0005-Allow-processing-of-.-by-generic-subscripting.patch (18.0K, ../../CAK98qZ3Ly6PhRwCVmMKJBba5oHVF9k370MMT2b_gep-SuQfRtg@mail.gmail.com/6-v7-0005-Allow-processing-of-.-by-generic-subscripting.patch)
  download | inline diff:
From b376b023aed5b4769cb29daa7f55e6eb22b90afd Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Sat, 1 Apr 2023 23:15:26 +0300
Subject: [PATCH v7 5/5] Allow processing of .* by generic subscripting

---
 src/backend/parser/gram.y           |   2 +
 src/backend/parser/parse_expr.c     |  34 +++--
 src/backend/parser/parse_target.c   |  60 ++++++---
 src/backend/utils/adt/ruleutils.c   |   6 +-
 src/include/parser/parse_expr.h     |   3 +
 src/test/regress/expected/jsonb.out | 195 +++++++++++++++++++---------
 6 files changed, 206 insertions(+), 94 deletions(-)

diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index d7f9c00c409..5f69ac661bd 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -18959,6 +18959,7 @@ check_func_name(List *names, core_yyscan_t yyscanner)
 static List *
 check_indirection(List *indirection, core_yyscan_t yyscanner)
 {
+#if 0
 	ListCell   *l;
 
 	foreach(l, indirection)
@@ -18969,6 +18970,7 @@ check_indirection(List *indirection, core_yyscan_t yyscanner)
 				parser_yyerror("improper use of \"*\"");
 		}
 	}
+#endif
 	return indirection;
 }
 
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index afe953fdbea..b0c38529872 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -74,7 +74,6 @@ static Node *transformColumnRef(ParseState *pstate, ColumnRef *cref);
 static Node *transformWholeRowRef(ParseState *pstate,
 								  ParseNamespaceItem *nsitem,
 								  int sublevels_up, int location);
-static Node *transformIndirection(ParseState *pstate, A_Indirection *ind);
 static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
 static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
 static Node *transformJsonObjectConstructor(ParseState *pstate,
@@ -158,7 +157,7 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 			break;
 
 		case T_A_Indirection:
-			result = transformIndirection(pstate, (A_Indirection *) expr);
+			result = transformIndirection(pstate, (A_Indirection *) expr, NULL);
 			break;
 
 		case T_A_ArrayExpr:
@@ -432,8 +431,9 @@ unknown_attribute(ParseState *pstate, Node *relref, const char *attname,
 	}
 }
 
-static Node *
-transformIndirection(ParseState *pstate, A_Indirection *ind)
+Node *
+transformIndirection(ParseState *pstate, A_Indirection *ind,
+					 bool *trailing_star_expansion)
 {
 	Node	   *last_srf = pstate->p_last_srf;
 	Node	   *result = transformExprRecurse(pstate, ind->arg);
@@ -453,12 +453,7 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		if (IsA(n, A_Indices))
 			subscripts = lappend(subscripts, n);
 		else if (IsA(n, A_Star))
-		{
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("row expansion via \"*\" is not supported here"),
-					 parser_errposition(pstate, location)));
-		}
+			subscripts = lappend(subscripts, n);
 		else
 		{
 			Assert(IsA(n, String));
@@ -487,7 +482,21 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 
 			n = linitial(subscripts);
 
-			if (!IsA(n, String))
+			if (IsA(n, A_Star))
+			{
+				/* Success, if trailing star expansion is allowed */
+				if (trailing_star_expansion && list_length(subscripts) == 1)
+				{
+					*trailing_star_expansion = true;
+					return result;
+				}
+
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("row expansion via \"*\" is not supported here"),
+						 parser_errposition(pstate, location)));
+			}
+			else if (!IsA(n, String))
 				ereport(ERROR,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
 						 errmsg("cannot subscript type %s because it does not support subscripting",
@@ -513,6 +522,9 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		result = newresult;
 	}
 
+	if (trailing_star_expansion)
+		*trailing_star_expansion = false;
+
 	return result;
 }
 
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 5e126145ea5..965e2f06e7b 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -48,7 +48,7 @@ static Node *transformAssignmentSubscripts(ParseState *pstate,
 static List *ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
 								 bool make_target_entry);
 static List *ExpandAllTables(ParseState *pstate, int location);
-static List *ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
+static Node *ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
 								   bool make_target_entry, ParseExprKind exprKind);
 static List *ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 							   int sublevels_up, int location,
@@ -134,6 +134,7 @@ transformTargetList(ParseState *pstate, List *targetlist,
 	foreach(o_target, targetlist)
 	{
 		ResTarget  *res = (ResTarget *) lfirst(o_target);
+		Node	   *transformed = NULL;
 
 		/*
 		 * Check for "something.*".  Depending on the complexity of the
@@ -162,13 +163,19 @@ transformTargetList(ParseState *pstate, List *targetlist,
 
 				if (IsA(llast(ind->indirection), A_Star))
 				{
-					/* It is something.*, expand into multiple items */
-					p_target = list_concat(p_target,
-										   ExpandIndirectionStar(pstate,
-																 ind,
-																 true,
-																 exprKind));
-					continue;
+					Node	   *columns = ExpandIndirectionStar(pstate,
+																ind,
+																true,
+																exprKind);
+
+					if (IsA(columns, List))
+					{
+						/* It is something.*, expand into multiple items */
+						p_target = list_concat(p_target, (List *) columns);
+						continue;
+					}
+
+					transformed = (Node *) columns;
 				}
 			}
 		}
@@ -180,7 +187,7 @@ transformTargetList(ParseState *pstate, List *targetlist,
 		p_target = lappend(p_target,
 						   transformTargetEntry(pstate,
 												res->val,
-												NULL,
+												transformed,
 												exprKind,
 												res->name,
 												false));
@@ -251,10 +258,15 @@ transformExpressionList(ParseState *pstate, List *exprlist,
 
 			if (IsA(llast(ind->indirection), A_Star))
 			{
-				/* It is something.*, expand into multiple items */
-				result = list_concat(result,
-									 ExpandIndirectionStar(pstate, ind,
-														   false, exprKind));
+				Node	   *cols = ExpandIndirectionStar(pstate, ind,
+														 false, exprKind);
+
+				if (!cols || IsA(cols, List))
+					/* It is something.*, expand into multiple items */
+					result = list_concat(result, (List *) cols);
+				else
+					result = lappend(result, cols);
+
 				continue;
 			}
 		}
@@ -1348,22 +1360,30 @@ ExpandAllTables(ParseState *pstate, int location)
  * For robustness, we use a separate "make_target_entry" flag to control
  * this rather than relying on exprKind.
  */
-static List *
+static Node *
 ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
 					  bool make_target_entry, ParseExprKind exprKind)
 {
 	Node	   *expr;
+	ParseExprKind sv_expr_kind;
+	bool		trailing_star_expansion = false;
+
+	/* Save and restore identity of expression type we're parsing */
+	Assert(exprKind != EXPR_KIND_NONE);
+	sv_expr_kind = pstate->p_expr_kind;
+	pstate->p_expr_kind = exprKind;
 
 	/* Strip off the '*' to create a reference to the rowtype object */
-	ind = copyObject(ind);
-	ind->indirection = list_truncate(ind->indirection,
-									 list_length(ind->indirection) - 1);
+	expr = transformIndirection(pstate, ind, &trailing_star_expansion);
+
+	pstate->p_expr_kind = sv_expr_kind;
 
-	/* And transform that */
-	expr = transformExpr(pstate, (Node *) ind, exprKind);
+	/* '*' was consumed by generic type subscripting */
+	if (!trailing_star_expansion)
+		return expr;
 
 	/* Expand the rowtype expression into individual fields */
-	return ExpandRowReference(pstate, expr, make_target_entry);
+	return (Node *) ExpandRowReference(pstate, expr, make_target_entry);
 }
 
 /*
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index af5417d0859..10006b749dc 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -12919,7 +12919,11 @@ printSubscripts(SubscriptingRef *sbsref, deparse_context *context)
 	{
 		Node	   *up = (Node *) lfirst(uplist_item);
 
-		if (IsA(up, String))
+		if (!up)
+		{
+			appendStringInfoString(buf, ".*");
+		}
+		else if (IsA(up, String))
 		{
 			appendStringInfoChar(buf, '.');
 			appendStringInfoString(buf, quote_identifier(strVal(up)));
diff --git a/src/include/parser/parse_expr.h b/src/include/parser/parse_expr.h
index efbaff8e710..c9f6a7724c0 100644
--- a/src/include/parser/parse_expr.h
+++ b/src/include/parser/parse_expr.h
@@ -22,4 +22,7 @@ extern Node *transformExpr(ParseState *pstate, Node *expr, ParseExprKind exprKin
 
 extern const char *ParseExprKindName(ParseExprKind exprKind);
 
+extern Node *transformIndirection(ParseState *pstate, A_Indirection *ind,
+								  bool *trailing_star_expansion);
+
 #endif							/* PARSE_EXPR_H */
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 14123929475..9057186bdf3 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5891,19 +5891,39 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
 CREATE TABLE test_jsonb_dot_notation AS
 SELECT '{"a": [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], "b": [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]}'::jsonb jb;
 SELECT (jb).* FROM test_jsonb_dot_notation;
-ERROR:  type jsonb is not composite
+                                                              jb                                                              
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
 SELECT (jb).* FROM test_jsonb_dot_notation t;
-ERROR:  type jsonb is not composite
+                                                              jb                                                              
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
 SELECT (t.jb).* FROM test_jsonb_dot_notation t;
-ERROR:  type jsonb is not composite
+                                                              jb                                                              
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
 SELECT (jb).* FROM test_jsonb_dot_notation;
-ERROR:  type jsonb is not composite
+                                                              jb                                                              
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
 SELECT (t.jb).* FROM test_jsonb_dot_notation;
 ERROR:  missing FROM-clause entry for table "t"
 LINE 1: SELECT (t.jb).* FROM test_jsonb_dot_notation;
                 ^
 SELECT (t.jb).* FROM test_jsonb_dot_notation t;
-ERROR:  type jsonb is not composite
+                                                              jb                                                              
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
 SELECT (jb).a FROM test_jsonb_dot_notation;
                                     a                                    
 -------------------------------------------------------------------------
@@ -5935,75 +5955,120 @@ SELECT (jb).a.b FROM test_jsonb_dot_notation;
 (1 row)
 
 SELECT (jb).a.* FROM test_jsonb_dot_notation;
-ERROR:  type jsonb is not composite
+                     a                     
+-------------------------------------------
+ ["c", "d", "f", {"y": "yyy", "z": "zzz"}]
+(1 row)
+
 SELECT (jb).a.*.b FROM test_jsonb_dot_notation;
-ERROR:  improper use of "*" at or near "FROM"
-LINE 1: SELECT (jb).a.*.b FROM test_jsonb_dot_notation;
-                          ^
+ b 
+---
+ 
+(1 row)
+
 SELECT (jb).a.*.x FROM test_jsonb_dot_notation;
-ERROR:  improper use of "*" at or near "FROM"
-LINE 1: SELECT (jb).a.*.x FROM test_jsonb_dot_notation;
-                          ^
+ x 
+---
+ 
+(1 row)
+
 SELECT (jb).a.*.y FROM test_jsonb_dot_notation;
-ERROR:  improper use of "*" at or near "FROM"
-LINE 1: SELECT (jb).a.*.y FROM test_jsonb_dot_notation;
-                          ^
+   y   
+-------
+ "yyy"
+(1 row)
+
 SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
-ERROR:  improper use of "*" at or near "FROM"
-LINE 1: SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
-                          ^
+       a        
+----------------
+ ["yyy", "zzz"]
+(1 row)
+
 SELECT (jb).*.x FROM test_jsonb_dot_notation;
-ERROR:  improper use of "*" at or near "FROM"
-LINE 1: SELECT (jb).*.x FROM test_jsonb_dot_notation;
-                        ^
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
 SELECT (jb).*.x FROM test_jsonb_dot_notation t;
-ERROR:  improper use of "*" at or near "FROM"
-LINE 1: SELECT (jb).*.x FROM test_jsonb_dot_notation t;
-                        ^
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
 SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
-ERROR:  row expansion via "*" is not supported here
-LINE 1: SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
-                 ^
+ x 
+---
+ 
+(1 row)
+
 SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
-ERROR:  row expansion via "*" is not supported here
-LINE 1: SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
-                 ^
+ x 
+---
+ 
+(1 row)
+
 SELECT ((jb).*)[:].x FROM test_jsonb_dot_notation t;
-ERROR:  row expansion via "*" is not supported here
-LINE 1: SELECT ((jb).*)[:].x FROM test_jsonb_dot_notation t;
-                 ^
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
 SELECT (jb).*.x FROM test_jsonb_dot_notation;
-ERROR:  improper use of "*" at or near "FROM"
-LINE 1: SELECT (jb).*.x FROM test_jsonb_dot_notation;
-                        ^
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
 SELECT (jb).*.x.* FROM test_jsonb_dot_notation;
-ERROR:  improper use of "*" at or near "FROM"
-LINE 1: SELECT (jb).*.x.* FROM test_jsonb_dot_notation;
-                          ^
+              x               
+------------------------------
+ ["yyy", "zzz", "YYY", "ZZZ"]
+(1 row)
+
 SELECT (jb).*.x.y FROM test_jsonb_dot_notation;
-ERROR:  improper use of "*" at or near "FROM"
-LINE 1: SELECT (jb).*.x.y FROM test_jsonb_dot_notation;
-                          ^
+       y        
+----------------
+ ["yyy", "YYY"]
+(1 row)
+
 SELECT (jb).*.x.z FROM test_jsonb_dot_notation;
-ERROR:  improper use of "*" at or near "FROM"
-LINE 1: SELECT (jb).*.x.z FROM test_jsonb_dot_notation;
-                          ^
+       z        
+----------------
+ ["zzz", "ZZZ"]
+(1 row)
+
 SELECT (jb).*.*.y FROM test_jsonb_dot_notation;
-ERROR:  improper use of "*" at or near "FROM"
-LINE 1: SELECT (jb).*.*.y FROM test_jsonb_dot_notation;
-                          ^
+       y        
+----------------
+ ["yyy", "YYY"]
+(1 row)
+
 SELECT (jb).*.*.* FROM test_jsonb_dot_notation;
-ERROR:  improper use of "*" at or near "FROM"
-LINE 1: SELECT (jb).*.*.* FROM test_jsonb_dot_notation;
-                          ^
+              jb              
+------------------------------
+ ["yyy", "zzz", "YYY", "ZZZ"]
+(1 row)
+
 SELECT (jb).*.*.*.* FROM test_jsonb_dot_notation;
-ERROR:  improper use of "*" at or near "FROM"
-LINE 1: SELECT (jb).*.*.*.* FROM test_jsonb_dot_notation;
-                            ^
+ jb 
+----
+ 
+(1 row)
+
 SELECT (jb).a.b.c.* FROM test_jsonb_dot_notation;
-ERROR:  type jsonb is not composite
+ c 
+---
+ 
+(1 row)
+
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).* FROM test_jsonb_dot_notation;
-ERROR:  type jsonb is not composite
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: jb.*
+(2 rows)
+
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a FROM test_jsonb_dot_notation;
                  QUERY PLAN                 
 --------------------------------------------
@@ -6019,10 +6084,16 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
 (2 rows)
 
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*['b'] FROM test_jsonb_dot_notation;
-ERROR:  improper use of "*" at or near "FROM"
-LINE 1: EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*['b'] FROM test_...
-                                                          ^
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: jb.a.*['b'::text]
+(2 rows)
+
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:2]['b'].b FROM test_jsonb_dot_notation;
-ERROR:  improper use of "*" at or near "FROM"
-LINE 1: ... (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:2]['b'].b FROM test_...
-                                                             ^
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: jb.a.*[1:2][:'b'::text].b
+(2 rows)
+
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v7-0003-Export-jsonPathFromParseResult.patch (2.4K, ../../CAK98qZ3Ly6PhRwCVmMKJBba5oHVF9k370MMT2b_gep-SuQfRtg@mail.gmail.com/7-v7-0003-Export-jsonPathFromParseResult.patch)
  download | inline diff:
From 015f01b44fa99fe1242b21cb83736e4d31fa5671 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Sat, 1 Apr 2023 23:15:55 +0300
Subject: [PATCH v7 3/5] Export jsonPathFromParseResult()

---
 src/backend/utils/adt/jsonpath.c | 24 ++++++++++++++++++------
 src/include/utils/jsonpath.h     |  4 ++++
 2 files changed, 22 insertions(+), 6 deletions(-)

diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index 762f7e8a09d..a3270bdaba3 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -165,16 +165,12 @@ jsonpath_send(PG_FUNCTION_ARGS)
 /*
  * Converts C-string to a jsonpath value.
  *
- * Uses jsonpath parser to turn string into an AST, then
- * flattenJsonPathParseItem() does second pass turning AST into binary
- * representation of jsonpath.
+ * Uses jsonpath parser to turn string into an AST.
  */
 static Datum
 jsonPathFromCstring(char *in, int len, struct Node *escontext)
 {
 	JsonPathParseResult *jsonpath = parsejsonpath(in, len, escontext);
-	JsonPath   *res;
-	StringInfoData buf;
 
 	if (SOFT_ERROR_OCCURRED(escontext))
 		return (Datum) 0;
@@ -185,8 +181,24 @@ jsonPathFromCstring(char *in, int len, struct Node *escontext)
 				 errmsg("invalid input syntax for type %s: \"%s\"", "jsonpath",
 						in)));
 
+	return jsonPathFromParseResult(jsonpath, 4 * len, escontext);
+}
+
+/*
+ * Converts jsonpath AST to a jsonpath value.
+ *
+ * flattenJsonPathParseItem() does second pass turning AST into binary
+ * representation of jsonpath.
+ */
+Datum
+jsonPathFromParseResult(JsonPathParseResult *jsonpath, int estimated_len,
+						struct Node *escontext)
+{
+	JsonPath   *res;
+	StringInfoData buf;
+
 	initStringInfo(&buf);
-	enlargeStringInfo(&buf, 4 * len /* estimation */ );
+	enlargeStringInfo(&buf, estimated_len);
 
 	appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
 
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index 23a76d233e9..e05941623e7 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -281,6 +281,10 @@ extern JsonPathParseResult *parsejsonpath(const char *str, int len,
 extern bool jspConvertRegexFlags(uint32 xflags, int *result,
 								 struct Node *escontext);
 
+extern Datum jsonPathFromParseResult(JsonPathParseResult *jsonpath,
+									 int estimated_len,
+									 struct Node *escontext);
+
 /*
  * Struct for details about external variables passed into jsonpath executor
  */
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v7-0002-Pass-field-accessors-to-generic-subscripting.patch (15.7K, ../../CAK98qZ3Ly6PhRwCVmMKJBba5oHVF9k370MMT2b_gep-SuQfRtg@mail.gmail.com/8-v7-0002-Pass-field-accessors-to-generic-subscripting.patch)
  download | inline diff:
From 29845033cce1de0e80b3c19d4e56b514d094e956 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Sat, 1 Apr 2023 16:21:20 +0300
Subject: [PATCH v7 2/5] Pass field accessors to generic subscripting

---
 src/backend/executor/execExpr.c    | 24 +++++++---
 src/backend/nodes/nodeFuncs.c      | 73 ++++++++++++++++++++++++++----
 src/backend/parser/parse_collate.c | 22 +++++++--
 src/backend/parser/parse_expr.c    | 58 ++++++++++++++++--------
 src/backend/parser/parse_node.c    | 39 ++++++++++++++--
 src/backend/parser/parse_target.c  |  3 +-
 src/backend/utils/adt/arraysubs.c  | 13 ++++--
 src/backend/utils/adt/jsonbsubs.c  | 11 ++++-
 src/backend/utils/adt/ruleutils.c  | 29 ++++++++----
 src/include/parser/parse_node.h    |  3 +-
 10 files changed, 218 insertions(+), 57 deletions(-)

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 03566c4d181..be4213455e5 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -3328,9 +3328,15 @@ ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref,
 		{
 			sbsrefstate->upperprovided[i] = true;
 			/* Each subscript is evaluated into appropriate array entry */
-			ExecInitExprRec(e, state,
-							&sbsrefstate->upperindex[i],
-							&sbsrefstate->upperindexnull[i]);
+			if (IsA(e, String))
+			{
+				sbsrefstate->upperindex[i] = CStringGetTextDatum(strVal(e));
+				sbsrefstate->upperindexnull[i] = false;
+			}
+			else
+				ExecInitExprRec(e, state,
+								&sbsrefstate->upperindex[i],
+								&sbsrefstate->upperindexnull[i]);
 		}
 		i++;
 	}
@@ -3351,9 +3357,15 @@ ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref,
 		{
 			sbsrefstate->lowerprovided[i] = true;
 			/* Each subscript is evaluated into appropriate array entry */
-			ExecInitExprRec(e, state,
-							&sbsrefstate->lowerindex[i],
-							&sbsrefstate->lowerindexnull[i]);
+			if (IsA(e, String))
+			{
+				sbsrefstate->lowerindex[i] = CStringGetTextDatum(strVal(e));
+				sbsrefstate->lowerindexnull[i] = false;
+			}
+			else
+				ExecInitExprRec(e, state,
+								&sbsrefstate->lowerindex[i],
+								&sbsrefstate->lowerindexnull[i]);
 		}
 		i++;
 	}
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 7bc823507f1..a9c29ab8f29 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -2182,12 +2182,28 @@ expression_tree_walker_impl(Node *node,
 		case T_SubscriptingRef:
 			{
 				SubscriptingRef *sbsref = (SubscriptingRef *) node;
+				ListCell   *lc;
+
+				/*
+				 * Recurse directly for upper/lower container index lists,
+				 * skipping String subscripts used for dot notation.
+				 */
+				foreach(lc, sbsref->refupperindexpr)
+				{
+					Node	   *expr = lfirst(lc);
+
+					if (expr && !IsA(expr, String) && WALK(expr))
+						return true;
+				}
+
+				foreach(lc, sbsref->reflowerindexpr)
+				{
+					Node	   *expr = lfirst(lc);
+
+					if (expr && !IsA(expr, String) && WALK(expr))
+						return true;
+				}
 
-				/* recurse directly for upper/lower container index lists */
-				if (LIST_WALK(sbsref->refupperindexpr))
-					return true;
-				if (LIST_WALK(sbsref->reflowerindexpr))
-					return true;
 				/* walker must see the refexpr and refassgnexpr, however */
 				if (WALK(sbsref->refexpr))
 					return true;
@@ -3082,12 +3098,51 @@ expression_tree_mutator_impl(Node *node,
 			{
 				SubscriptingRef *sbsref = (SubscriptingRef *) node;
 				SubscriptingRef *newnode;
+				ListCell   *lc;
+				List	   *exprs = NIL;
 
 				FLATCOPY(newnode, sbsref, SubscriptingRef);
-				MUTATE(newnode->refupperindexpr, sbsref->refupperindexpr,
-					   List *);
-				MUTATE(newnode->reflowerindexpr, sbsref->reflowerindexpr,
-					   List *);
+
+				foreach(lc, sbsref->refupperindexpr)
+				{
+					Node	   *expr = lfirst(lc);
+
+					if (expr && IsA(expr, String))
+					{
+						String	   *str;
+
+						FLATCOPY(str, expr, String);
+						expr = (Node *) str;
+					}
+					else
+						expr = mutator(expr, context);
+
+					exprs = lappend(exprs, expr);
+				}
+
+				newnode->refupperindexpr = exprs;
+
+				exprs = NIL;
+
+				foreach(lc, sbsref->reflowerindexpr)
+				{
+					Node	   *expr = lfirst(lc);
+
+					if (expr && IsA(expr, String))
+					{
+						String	   *str;
+
+						FLATCOPY(str, expr, String);
+						expr = (Node *) str;
+					}
+					else
+						expr = mutator(expr, context);
+
+					exprs = lappend(exprs, expr);
+				}
+
+				newnode->reflowerindexpr = exprs;
+
 				MUTATE(newnode->refexpr, sbsref->refexpr,
 					   Expr *);
 				MUTATE(newnode->refassgnexpr, sbsref->refassgnexpr,
diff --git a/src/backend/parser/parse_collate.c b/src/backend/parser/parse_collate.c
index d2e218353f3..be6dea6ffd2 100644
--- a/src/backend/parser/parse_collate.c
+++ b/src/backend/parser/parse_collate.c
@@ -680,11 +680,25 @@ assign_collations_walker(Node *node, assign_collations_context *context)
 							 * contribute anything.)
 							 */
 							SubscriptingRef *sbsref = (SubscriptingRef *) node;
+							ListCell   *lc;
+
+							/* skip String subscripts used for dot notation */
+							foreach(lc, sbsref->refupperindexpr)
+							{
+								Node	   *expr = lfirst(lc);
+
+								if (expr && !IsA(expr, String))
+									assign_expr_collations(context->pstate, expr);
+							}
+
+							foreach(lc, sbsref->reflowerindexpr)
+							{
+								Node	   *expr = lfirst(lc);
+
+								if (expr && !IsA(expr, String))
+									assign_expr_collations(context->pstate, expr);
+							}
 
-							assign_expr_collations(context->pstate,
-												   (Node *) sbsref->refupperindexpr);
-							assign_expr_collations(context->pstate,
-												   (Node *) sbsref->reflowerindexpr);
 							(void) assign_collations_walker((Node *) sbsref->refexpr,
 															&loccontext);
 							(void) assign_collations_walker((Node *) sbsref->refassgnexpr,
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 2c0f4a50b21..afe953fdbea 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -461,19 +461,40 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		}
 		else
 		{
-			Node	   *newresult;
-
 			Assert(IsA(n, String));
+			subscripts = lappend(subscripts, n);
+		}
+	}
+
+	/* process trailing subscripts, if any */
+	while (subscripts)
+	{
+		Node	   *newresult = (Node *)
+			transformContainerSubscripts(pstate,
+										 result,
+										 exprType(result),
+										 exprTypmod(result),
+										 &subscripts,
+										 false,
+										 true);
+
+		if (!newresult)
+		{
+			/* generic subscripting failed */
+			Node	   *n;
+
+			Assert(subscripts);
+
+			n = linitial(subscripts);
 
-			/* process subscripts before this field selection */
-			while (subscripts)
-				result = (Node *) transformContainerSubscripts(pstate,
-															   result,
-															   exprType(result),
-															   exprTypmod(result),
-															   &subscripts,
-															   false);
+			if (!IsA(n, String))
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("cannot subscript type %s because it does not support subscripting",
+								format_type_be(exprType(result))),
+						 parser_errposition(pstate, exprLocation(result))));
 
+			/* try to find function for field selection */
 			newresult = ParseFuncOrColumn(pstate,
 										  list_make1(n),
 										  list_make1(result),
@@ -481,19 +502,16 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 										  NULL,
 										  false,
 										  location);
-			if (newresult == NULL)
+
+			if (!newresult)
 				unknown_attribute(pstate, result, strVal(n), location);
-			result = newresult;
+
+			/* consume field select */
+			subscripts = list_delete_first(subscripts);
 		}
+
+		result = newresult;
 	}
-	/* process trailing subscripts, if any */
-	while (subscripts)
-		result = (Node *) transformContainerSubscripts(pstate,
-													   result,
-													   exprType(result),
-													   exprTypmod(result),
-													   &subscripts,
-													   false);
 
 	return result;
 }
diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c
index 19a6b678e67..c1f8055564f 100644
--- a/src/backend/parser/parse_node.c
+++ b/src/backend/parser/parse_node.c
@@ -245,13 +245,15 @@ transformContainerSubscripts(ParseState *pstate,
 							 Oid containerType,
 							 int32 containerTypMod,
 							 List **indirection,
-							 bool isAssignment)
+							 bool isAssignment,
+							 bool noError)
 {
 	SubscriptingRef *sbsref;
 	const SubscriptRoutines *sbsroutines;
 	Oid			elementType;
 	bool		isSlice = false;
 	ListCell   *idx;
+	int			indirection_length = list_length(*indirection);
 
 	/*
 	 * Determine the actual container type, smashing any domain.  In the
@@ -267,11 +269,16 @@ transformContainerSubscripts(ParseState *pstate,
 	 */
 	sbsroutines = getSubscriptingRoutines(containerType, &elementType);
 	if (!sbsroutines)
+	{
+		if (noError)
+			return NULL;
+
 		ereport(ERROR,
 				(errcode(ERRCODE_DATATYPE_MISMATCH),
 				 errmsg("cannot subscript type %s because it does not support subscripting",
 						format_type_be(containerType)),
 				 parser_errposition(pstate, exprLocation(containerBase))));
+	}
 
 	/*
 	 * Detect whether any of the indirection items are slice specifiers.
@@ -282,9 +289,9 @@ transformContainerSubscripts(ParseState *pstate,
 	 */
 	foreach(idx, *indirection)
 	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
+		Node	   *ai = lfirst(idx);
 
-		if (ai->is_slice)
+		if (IsA(ai, A_Indices) && castNode(A_Indices, ai)->is_slice)
 		{
 			isSlice = true;
 			break;
@@ -312,6 +319,32 @@ transformContainerSubscripts(ParseState *pstate,
 	sbsroutines->transform(sbsref, indirection, pstate,
 						   isSlice, isAssignment);
 
+	/*
+	 * Error out, if datatyoe falied to consume any indirection elements.
+	 */
+	if (list_length(*indirection) == indirection_length)
+	{
+		Node	   *ind = linitial(*indirection);
+
+		if (noError)
+			return NULL;
+
+		if (IsA(ind, String))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("type %s does not support column notation",
+							format_type_be(containerType)),
+					 parser_errposition(pstate, exprLocation(containerBase))));
+		else if (IsA(ind, A_Indices))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("type %s does not support array subscripting",
+							format_type_be(containerType)),
+					 parser_errposition(pstate, exprLocation(containerBase))));
+		else
+			elog(ERROR, "invalid indirection operation: %d", nodeTag(ind));
+	}
+
 	/*
 	 * Verify we got a valid type (this defends, for example, against someone
 	 * using array_subscript_handler as typsubscript without setting typelem).
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 39fd82f8371..5e126145ea5 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -937,7 +937,8 @@ transformAssignmentSubscripts(ParseState *pstate,
 										  containerType,
 										  containerTypMod,
 										  &subscripts,
-										  true);
+										  true,
+										  false);
 
 	if (subscripts)
 		elog(ERROR, "subscripting assignment is not supported");
diff --git a/src/backend/utils/adt/arraysubs.c b/src/backend/utils/adt/arraysubs.c
index edd85f7ba67..fe18df86e45 100644
--- a/src/backend/utils/adt/arraysubs.c
+++ b/src/backend/utils/adt/arraysubs.c
@@ -61,6 +61,7 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	List	   *upperIndexpr = NIL;
 	List	   *lowerIndexpr = NIL;
 	ListCell   *idx;
+	int			ndim;
 
 	/*
 	 * Transform the subscript expressions, and separate upper and lower
@@ -72,9 +73,14 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	 */
 	foreach(idx, *indirection)
 	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
+		A_Indices  *ai;
 		Node	   *subexpr;
 
+		if (!IsA(lfirst(idx), A_Indices))
+			break;
+
+		ai = lfirst_node(A_Indices, idx);
+
 		if (isSlice)
 		{
 			if (ai->lidx)
@@ -144,14 +150,15 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->reflowerindexpr = lowerIndexpr;
 
 	/* Verify subscript list lengths are within implementation limit */
-	if (list_length(upperIndexpr) > MAXDIM)
+	ndim = list_length(upperIndexpr);
+	if (ndim > MAXDIM)
 		ereport(ERROR,
 				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 				 errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
 						list_length(upperIndexpr), MAXDIM)));
 	/* We need not check lowerIndexpr separately */
 
-	*indirection = NIL;
+	*indirection = list_delete_first_n(*indirection, ndim);
 
 	/*
 	 * Determine the result type of the subscripting operation.  It's the same
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index 8ad6aa1ad4f..a0d38a0fd80 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -55,9 +55,14 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	 */
 	foreach(idx, *indirection)
 	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
+		A_Indices  *ai;
 		Node	   *subExpr;
 
+		if (!IsA(lfirst(idx), A_Indices))
+			break;
+
+		ai = lfirst_node(A_Indices, idx);
+
 		if (isSlice)
 		{
 			Node	   *expr = ai->uidx ? ai->uidx : ai->lidx;
@@ -160,7 +165,9 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->refrestype = JSONBOID;
 	sbsref->reftypmod = -1;
 
-	*indirection = NIL;
+	/* Remove processed elements */
+	if (upperIndexpr)
+		*indirection = list_delete_first_n(*indirection, list_length(upperIndexpr));
 }
 
 /*
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 54dad975553..af5417d0859 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -47,6 +47,7 @@
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "nodes/pathnodes.h"
+#include "nodes/subscripting.h"
 #include "optimizer/optimizer.h"
 #include "parser/parse_agg.h"
 #include "parser/parse_func.h"
@@ -12916,17 +12917,29 @@ printSubscripts(SubscriptingRef *sbsref, deparse_context *context)
 	lowlist_item = list_head(sbsref->reflowerindexpr);	/* could be NULL */
 	foreach(uplist_item, sbsref->refupperindexpr)
 	{
-		appendStringInfoChar(buf, '[');
-		if (lowlist_item)
+		Node	   *up = (Node *) lfirst(uplist_item);
+
+		if (IsA(up, String))
+		{
+			appendStringInfoChar(buf, '.');
+			appendStringInfoString(buf, quote_identifier(strVal(up)));
+		}
+		else
 		{
+			appendStringInfoChar(buf, '[');
+			if (lowlist_item)
+			{
+				/* If subexpression is NULL, get_rule_expr prints nothing */
+				get_rule_expr((Node *) lfirst(lowlist_item), context, false);
+				appendStringInfoChar(buf, ':');
+			}
 			/* If subexpression is NULL, get_rule_expr prints nothing */
-			get_rule_expr((Node *) lfirst(lowlist_item), context, false);
-			appendStringInfoChar(buf, ':');
-			lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
+			get_rule_expr((Node *) lfirst(uplist_item), context, false);
+			appendStringInfoChar(buf, ']');
 		}
-		/* If subexpression is NULL, get_rule_expr prints nothing */
-		get_rule_expr((Node *) lfirst(uplist_item), context, false);
-		appendStringInfoChar(buf, ']');
+
+		if (lowlist_item)
+			lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
 	}
 }
 
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 5ae11ccec33..71b04bd503c 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -378,7 +378,8 @@ extern SubscriptingRef *transformContainerSubscripts(ParseState *pstate,
 													 Oid containerType,
 													 int32 containerTypMod,
 													 List **indirection,
-													 bool isAssignment);
+													 bool isAssignment,
+													 bool noError);
 extern Const *make_const(ParseState *pstate, A_Const *aconst);
 
 #endif							/* PARSE_NODE_H */
-- 
2.39.5 (Apple Git-154)



^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
@ 2025-02-05 13:39       ` Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  0 siblings, 1 reply; 67+ messages in thread

From: Alexandra Wang @ 2025-02-05 13:39 UTC (permalink / raw)
  To: Peter Eisentraut <[email protected]>; +Cc: Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>; jian he <[email protected]>

Hi,

On Wed, Feb 5, 2025 at 1:20 AM Alexandra Wang <[email protected]>
wrote:

> I attached a minimized version of Nikita’s patch (v7):
>
> - The first three patches are refactoring steps that could be squashed
>   if preferred.
> - The last two patches implement dot notation and wildcard access,
>   respectively.
>
> Changes in this new version:
> - Removed code handling hstore, as Andrew pointed out it isn’t
>   directly relevant to JSON access and should be handled separately.
> - Split tests for dot notation and wildcard access.
> - Dropped the two patches in v6 that enabled non-parenthesized column
>   references (per Nikita’s suggestion, this will need its own separate
> discussion).
>

It appears that the Commitfest app selected the wrong patch. Sorry
about the confusion! I'm reposting the patches to correct this.


Attachments:

  [application/octet-stream] v7-0002-Pass-field-accessors-to-generic-subscripting.patch (15.7K, ../../CAK98qZ1za8XgOLY+2hQMPGoxVYFyh=dDkM2dZPVeJK4J6poyvA@mail.gmail.com/3-v7-0002-Pass-field-accessors-to-generic-subscripting.patch)
  download | inline diff:
From 29845033cce1de0e80b3c19d4e56b514d094e956 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Sat, 1 Apr 2023 16:21:20 +0300
Subject: [PATCH v7 2/5] Pass field accessors to generic subscripting

---
 src/backend/executor/execExpr.c    | 24 +++++++---
 src/backend/nodes/nodeFuncs.c      | 73 ++++++++++++++++++++++++++----
 src/backend/parser/parse_collate.c | 22 +++++++--
 src/backend/parser/parse_expr.c    | 58 ++++++++++++++++--------
 src/backend/parser/parse_node.c    | 39 ++++++++++++++--
 src/backend/parser/parse_target.c  |  3 +-
 src/backend/utils/adt/arraysubs.c  | 13 ++++--
 src/backend/utils/adt/jsonbsubs.c  | 11 ++++-
 src/backend/utils/adt/ruleutils.c  | 29 ++++++++----
 src/include/parser/parse_node.h    |  3 +-
 10 files changed, 218 insertions(+), 57 deletions(-)

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 03566c4d181..be4213455e5 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -3328,9 +3328,15 @@ ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref,
 		{
 			sbsrefstate->upperprovided[i] = true;
 			/* Each subscript is evaluated into appropriate array entry */
-			ExecInitExprRec(e, state,
-							&sbsrefstate->upperindex[i],
-							&sbsrefstate->upperindexnull[i]);
+			if (IsA(e, String))
+			{
+				sbsrefstate->upperindex[i] = CStringGetTextDatum(strVal(e));
+				sbsrefstate->upperindexnull[i] = false;
+			}
+			else
+				ExecInitExprRec(e, state,
+								&sbsrefstate->upperindex[i],
+								&sbsrefstate->upperindexnull[i]);
 		}
 		i++;
 	}
@@ -3351,9 +3357,15 @@ ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref,
 		{
 			sbsrefstate->lowerprovided[i] = true;
 			/* Each subscript is evaluated into appropriate array entry */
-			ExecInitExprRec(e, state,
-							&sbsrefstate->lowerindex[i],
-							&sbsrefstate->lowerindexnull[i]);
+			if (IsA(e, String))
+			{
+				sbsrefstate->lowerindex[i] = CStringGetTextDatum(strVal(e));
+				sbsrefstate->lowerindexnull[i] = false;
+			}
+			else
+				ExecInitExprRec(e, state,
+								&sbsrefstate->lowerindex[i],
+								&sbsrefstate->lowerindexnull[i]);
 		}
 		i++;
 	}
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 7bc823507f1..a9c29ab8f29 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -2182,12 +2182,28 @@ expression_tree_walker_impl(Node *node,
 		case T_SubscriptingRef:
 			{
 				SubscriptingRef *sbsref = (SubscriptingRef *) node;
+				ListCell   *lc;
+
+				/*
+				 * Recurse directly for upper/lower container index lists,
+				 * skipping String subscripts used for dot notation.
+				 */
+				foreach(lc, sbsref->refupperindexpr)
+				{
+					Node	   *expr = lfirst(lc);
+
+					if (expr && !IsA(expr, String) && WALK(expr))
+						return true;
+				}
+
+				foreach(lc, sbsref->reflowerindexpr)
+				{
+					Node	   *expr = lfirst(lc);
+
+					if (expr && !IsA(expr, String) && WALK(expr))
+						return true;
+				}
 
-				/* recurse directly for upper/lower container index lists */
-				if (LIST_WALK(sbsref->refupperindexpr))
-					return true;
-				if (LIST_WALK(sbsref->reflowerindexpr))
-					return true;
 				/* walker must see the refexpr and refassgnexpr, however */
 				if (WALK(sbsref->refexpr))
 					return true;
@@ -3082,12 +3098,51 @@ expression_tree_mutator_impl(Node *node,
 			{
 				SubscriptingRef *sbsref = (SubscriptingRef *) node;
 				SubscriptingRef *newnode;
+				ListCell   *lc;
+				List	   *exprs = NIL;
 
 				FLATCOPY(newnode, sbsref, SubscriptingRef);
-				MUTATE(newnode->refupperindexpr, sbsref->refupperindexpr,
-					   List *);
-				MUTATE(newnode->reflowerindexpr, sbsref->reflowerindexpr,
-					   List *);
+
+				foreach(lc, sbsref->refupperindexpr)
+				{
+					Node	   *expr = lfirst(lc);
+
+					if (expr && IsA(expr, String))
+					{
+						String	   *str;
+
+						FLATCOPY(str, expr, String);
+						expr = (Node *) str;
+					}
+					else
+						expr = mutator(expr, context);
+
+					exprs = lappend(exprs, expr);
+				}
+
+				newnode->refupperindexpr = exprs;
+
+				exprs = NIL;
+
+				foreach(lc, sbsref->reflowerindexpr)
+				{
+					Node	   *expr = lfirst(lc);
+
+					if (expr && IsA(expr, String))
+					{
+						String	   *str;
+
+						FLATCOPY(str, expr, String);
+						expr = (Node *) str;
+					}
+					else
+						expr = mutator(expr, context);
+
+					exprs = lappend(exprs, expr);
+				}
+
+				newnode->reflowerindexpr = exprs;
+
 				MUTATE(newnode->refexpr, sbsref->refexpr,
 					   Expr *);
 				MUTATE(newnode->refassgnexpr, sbsref->refassgnexpr,
diff --git a/src/backend/parser/parse_collate.c b/src/backend/parser/parse_collate.c
index d2e218353f3..be6dea6ffd2 100644
--- a/src/backend/parser/parse_collate.c
+++ b/src/backend/parser/parse_collate.c
@@ -680,11 +680,25 @@ assign_collations_walker(Node *node, assign_collations_context *context)
 							 * contribute anything.)
 							 */
 							SubscriptingRef *sbsref = (SubscriptingRef *) node;
+							ListCell   *lc;
+
+							/* skip String subscripts used for dot notation */
+							foreach(lc, sbsref->refupperindexpr)
+							{
+								Node	   *expr = lfirst(lc);
+
+								if (expr && !IsA(expr, String))
+									assign_expr_collations(context->pstate, expr);
+							}
+
+							foreach(lc, sbsref->reflowerindexpr)
+							{
+								Node	   *expr = lfirst(lc);
+
+								if (expr && !IsA(expr, String))
+									assign_expr_collations(context->pstate, expr);
+							}
 
-							assign_expr_collations(context->pstate,
-												   (Node *) sbsref->refupperindexpr);
-							assign_expr_collations(context->pstate,
-												   (Node *) sbsref->reflowerindexpr);
 							(void) assign_collations_walker((Node *) sbsref->refexpr,
 															&loccontext);
 							(void) assign_collations_walker((Node *) sbsref->refassgnexpr,
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 2c0f4a50b21..afe953fdbea 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -461,19 +461,40 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		}
 		else
 		{
-			Node	   *newresult;
-
 			Assert(IsA(n, String));
+			subscripts = lappend(subscripts, n);
+		}
+	}
+
+	/* process trailing subscripts, if any */
+	while (subscripts)
+	{
+		Node	   *newresult = (Node *)
+			transformContainerSubscripts(pstate,
+										 result,
+										 exprType(result),
+										 exprTypmod(result),
+										 &subscripts,
+										 false,
+										 true);
+
+		if (!newresult)
+		{
+			/* generic subscripting failed */
+			Node	   *n;
+
+			Assert(subscripts);
+
+			n = linitial(subscripts);
 
-			/* process subscripts before this field selection */
-			while (subscripts)
-				result = (Node *) transformContainerSubscripts(pstate,
-															   result,
-															   exprType(result),
-															   exprTypmod(result),
-															   &subscripts,
-															   false);
+			if (!IsA(n, String))
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("cannot subscript type %s because it does not support subscripting",
+								format_type_be(exprType(result))),
+						 parser_errposition(pstate, exprLocation(result))));
 
+			/* try to find function for field selection */
 			newresult = ParseFuncOrColumn(pstate,
 										  list_make1(n),
 										  list_make1(result),
@@ -481,19 +502,16 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 										  NULL,
 										  false,
 										  location);
-			if (newresult == NULL)
+
+			if (!newresult)
 				unknown_attribute(pstate, result, strVal(n), location);
-			result = newresult;
+
+			/* consume field select */
+			subscripts = list_delete_first(subscripts);
 		}
+
+		result = newresult;
 	}
-	/* process trailing subscripts, if any */
-	while (subscripts)
-		result = (Node *) transformContainerSubscripts(pstate,
-													   result,
-													   exprType(result),
-													   exprTypmod(result),
-													   &subscripts,
-													   false);
 
 	return result;
 }
diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c
index 19a6b678e67..c1f8055564f 100644
--- a/src/backend/parser/parse_node.c
+++ b/src/backend/parser/parse_node.c
@@ -245,13 +245,15 @@ transformContainerSubscripts(ParseState *pstate,
 							 Oid containerType,
 							 int32 containerTypMod,
 							 List **indirection,
-							 bool isAssignment)
+							 bool isAssignment,
+							 bool noError)
 {
 	SubscriptingRef *sbsref;
 	const SubscriptRoutines *sbsroutines;
 	Oid			elementType;
 	bool		isSlice = false;
 	ListCell   *idx;
+	int			indirection_length = list_length(*indirection);
 
 	/*
 	 * Determine the actual container type, smashing any domain.  In the
@@ -267,11 +269,16 @@ transformContainerSubscripts(ParseState *pstate,
 	 */
 	sbsroutines = getSubscriptingRoutines(containerType, &elementType);
 	if (!sbsroutines)
+	{
+		if (noError)
+			return NULL;
+
 		ereport(ERROR,
 				(errcode(ERRCODE_DATATYPE_MISMATCH),
 				 errmsg("cannot subscript type %s because it does not support subscripting",
 						format_type_be(containerType)),
 				 parser_errposition(pstate, exprLocation(containerBase))));
+	}
 
 	/*
 	 * Detect whether any of the indirection items are slice specifiers.
@@ -282,9 +289,9 @@ transformContainerSubscripts(ParseState *pstate,
 	 */
 	foreach(idx, *indirection)
 	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
+		Node	   *ai = lfirst(idx);
 
-		if (ai->is_slice)
+		if (IsA(ai, A_Indices) && castNode(A_Indices, ai)->is_slice)
 		{
 			isSlice = true;
 			break;
@@ -312,6 +319,32 @@ transformContainerSubscripts(ParseState *pstate,
 	sbsroutines->transform(sbsref, indirection, pstate,
 						   isSlice, isAssignment);
 
+	/*
+	 * Error out, if datatyoe falied to consume any indirection elements.
+	 */
+	if (list_length(*indirection) == indirection_length)
+	{
+		Node	   *ind = linitial(*indirection);
+
+		if (noError)
+			return NULL;
+
+		if (IsA(ind, String))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("type %s does not support column notation",
+							format_type_be(containerType)),
+					 parser_errposition(pstate, exprLocation(containerBase))));
+		else if (IsA(ind, A_Indices))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("type %s does not support array subscripting",
+							format_type_be(containerType)),
+					 parser_errposition(pstate, exprLocation(containerBase))));
+		else
+			elog(ERROR, "invalid indirection operation: %d", nodeTag(ind));
+	}
+
 	/*
 	 * Verify we got a valid type (this defends, for example, against someone
 	 * using array_subscript_handler as typsubscript without setting typelem).
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 39fd82f8371..5e126145ea5 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -937,7 +937,8 @@ transformAssignmentSubscripts(ParseState *pstate,
 										  containerType,
 										  containerTypMod,
 										  &subscripts,
-										  true);
+										  true,
+										  false);
 
 	if (subscripts)
 		elog(ERROR, "subscripting assignment is not supported");
diff --git a/src/backend/utils/adt/arraysubs.c b/src/backend/utils/adt/arraysubs.c
index edd85f7ba67..fe18df86e45 100644
--- a/src/backend/utils/adt/arraysubs.c
+++ b/src/backend/utils/adt/arraysubs.c
@@ -61,6 +61,7 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	List	   *upperIndexpr = NIL;
 	List	   *lowerIndexpr = NIL;
 	ListCell   *idx;
+	int			ndim;
 
 	/*
 	 * Transform the subscript expressions, and separate upper and lower
@@ -72,9 +73,14 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	 */
 	foreach(idx, *indirection)
 	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
+		A_Indices  *ai;
 		Node	   *subexpr;
 
+		if (!IsA(lfirst(idx), A_Indices))
+			break;
+
+		ai = lfirst_node(A_Indices, idx);
+
 		if (isSlice)
 		{
 			if (ai->lidx)
@@ -144,14 +150,15 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->reflowerindexpr = lowerIndexpr;
 
 	/* Verify subscript list lengths are within implementation limit */
-	if (list_length(upperIndexpr) > MAXDIM)
+	ndim = list_length(upperIndexpr);
+	if (ndim > MAXDIM)
 		ereport(ERROR,
 				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 				 errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
 						list_length(upperIndexpr), MAXDIM)));
 	/* We need not check lowerIndexpr separately */
 
-	*indirection = NIL;
+	*indirection = list_delete_first_n(*indirection, ndim);
 
 	/*
 	 * Determine the result type of the subscripting operation.  It's the same
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index 8ad6aa1ad4f..a0d38a0fd80 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -55,9 +55,14 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	 */
 	foreach(idx, *indirection)
 	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
+		A_Indices  *ai;
 		Node	   *subExpr;
 
+		if (!IsA(lfirst(idx), A_Indices))
+			break;
+
+		ai = lfirst_node(A_Indices, idx);
+
 		if (isSlice)
 		{
 			Node	   *expr = ai->uidx ? ai->uidx : ai->lidx;
@@ -160,7 +165,9 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->refrestype = JSONBOID;
 	sbsref->reftypmod = -1;
 
-	*indirection = NIL;
+	/* Remove processed elements */
+	if (upperIndexpr)
+		*indirection = list_delete_first_n(*indirection, list_length(upperIndexpr));
 }
 
 /*
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 54dad975553..af5417d0859 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -47,6 +47,7 @@
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "nodes/pathnodes.h"
+#include "nodes/subscripting.h"
 #include "optimizer/optimizer.h"
 #include "parser/parse_agg.h"
 #include "parser/parse_func.h"
@@ -12916,17 +12917,29 @@ printSubscripts(SubscriptingRef *sbsref, deparse_context *context)
 	lowlist_item = list_head(sbsref->reflowerindexpr);	/* could be NULL */
 	foreach(uplist_item, sbsref->refupperindexpr)
 	{
-		appendStringInfoChar(buf, '[');
-		if (lowlist_item)
+		Node	   *up = (Node *) lfirst(uplist_item);
+
+		if (IsA(up, String))
+		{
+			appendStringInfoChar(buf, '.');
+			appendStringInfoString(buf, quote_identifier(strVal(up)));
+		}
+		else
 		{
+			appendStringInfoChar(buf, '[');
+			if (lowlist_item)
+			{
+				/* If subexpression is NULL, get_rule_expr prints nothing */
+				get_rule_expr((Node *) lfirst(lowlist_item), context, false);
+				appendStringInfoChar(buf, ':');
+			}
 			/* If subexpression is NULL, get_rule_expr prints nothing */
-			get_rule_expr((Node *) lfirst(lowlist_item), context, false);
-			appendStringInfoChar(buf, ':');
-			lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
+			get_rule_expr((Node *) lfirst(uplist_item), context, false);
+			appendStringInfoChar(buf, ']');
 		}
-		/* If subexpression is NULL, get_rule_expr prints nothing */
-		get_rule_expr((Node *) lfirst(uplist_item), context, false);
-		appendStringInfoChar(buf, ']');
+
+		if (lowlist_item)
+			lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
 	}
 }
 
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 5ae11ccec33..71b04bd503c 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -378,7 +378,8 @@ extern SubscriptingRef *transformContainerSubscripts(ParseState *pstate,
 													 Oid containerType,
 													 int32 containerTypMod,
 													 List **indirection,
-													 bool isAssignment);
+													 bool isAssignment,
+													 bool noError);
 extern Const *make_const(ParseState *pstate, A_Const *aconst);
 
 #endif							/* PARSE_NODE_H */
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v7-0005-Allow-processing-of-.-by-generic-subscripting.patch (18.0K, ../../CAK98qZ1za8XgOLY+2hQMPGoxVYFyh=dDkM2dZPVeJK4J6poyvA@mail.gmail.com/4-v7-0005-Allow-processing-of-.-by-generic-subscripting.patch)
  download | inline diff:
From b376b023aed5b4769cb29daa7f55e6eb22b90afd Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Sat, 1 Apr 2023 23:15:26 +0300
Subject: [PATCH v7 5/5] Allow processing of .* by generic subscripting

---
 src/backend/parser/gram.y           |   2 +
 src/backend/parser/parse_expr.c     |  34 +++--
 src/backend/parser/parse_target.c   |  60 ++++++---
 src/backend/utils/adt/ruleutils.c   |   6 +-
 src/include/parser/parse_expr.h     |   3 +
 src/test/regress/expected/jsonb.out | 195 +++++++++++++++++++---------
 6 files changed, 206 insertions(+), 94 deletions(-)

diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index d7f9c00c409..5f69ac661bd 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -18959,6 +18959,7 @@ check_func_name(List *names, core_yyscan_t yyscanner)
 static List *
 check_indirection(List *indirection, core_yyscan_t yyscanner)
 {
+#if 0
 	ListCell   *l;
 
 	foreach(l, indirection)
@@ -18969,6 +18970,7 @@ check_indirection(List *indirection, core_yyscan_t yyscanner)
 				parser_yyerror("improper use of \"*\"");
 		}
 	}
+#endif
 	return indirection;
 }
 
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index afe953fdbea..b0c38529872 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -74,7 +74,6 @@ static Node *transformColumnRef(ParseState *pstate, ColumnRef *cref);
 static Node *transformWholeRowRef(ParseState *pstate,
 								  ParseNamespaceItem *nsitem,
 								  int sublevels_up, int location);
-static Node *transformIndirection(ParseState *pstate, A_Indirection *ind);
 static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
 static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
 static Node *transformJsonObjectConstructor(ParseState *pstate,
@@ -158,7 +157,7 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 			break;
 
 		case T_A_Indirection:
-			result = transformIndirection(pstate, (A_Indirection *) expr);
+			result = transformIndirection(pstate, (A_Indirection *) expr, NULL);
 			break;
 
 		case T_A_ArrayExpr:
@@ -432,8 +431,9 @@ unknown_attribute(ParseState *pstate, Node *relref, const char *attname,
 	}
 }
 
-static Node *
-transformIndirection(ParseState *pstate, A_Indirection *ind)
+Node *
+transformIndirection(ParseState *pstate, A_Indirection *ind,
+					 bool *trailing_star_expansion)
 {
 	Node	   *last_srf = pstate->p_last_srf;
 	Node	   *result = transformExprRecurse(pstate, ind->arg);
@@ -453,12 +453,7 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		if (IsA(n, A_Indices))
 			subscripts = lappend(subscripts, n);
 		else if (IsA(n, A_Star))
-		{
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("row expansion via \"*\" is not supported here"),
-					 parser_errposition(pstate, location)));
-		}
+			subscripts = lappend(subscripts, n);
 		else
 		{
 			Assert(IsA(n, String));
@@ -487,7 +482,21 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 
 			n = linitial(subscripts);
 
-			if (!IsA(n, String))
+			if (IsA(n, A_Star))
+			{
+				/* Success, if trailing star expansion is allowed */
+				if (trailing_star_expansion && list_length(subscripts) == 1)
+				{
+					*trailing_star_expansion = true;
+					return result;
+				}
+
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("row expansion via \"*\" is not supported here"),
+						 parser_errposition(pstate, location)));
+			}
+			else if (!IsA(n, String))
 				ereport(ERROR,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
 						 errmsg("cannot subscript type %s because it does not support subscripting",
@@ -513,6 +522,9 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		result = newresult;
 	}
 
+	if (trailing_star_expansion)
+		*trailing_star_expansion = false;
+
 	return result;
 }
 
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 5e126145ea5..965e2f06e7b 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -48,7 +48,7 @@ static Node *transformAssignmentSubscripts(ParseState *pstate,
 static List *ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
 								 bool make_target_entry);
 static List *ExpandAllTables(ParseState *pstate, int location);
-static List *ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
+static Node *ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
 								   bool make_target_entry, ParseExprKind exprKind);
 static List *ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 							   int sublevels_up, int location,
@@ -134,6 +134,7 @@ transformTargetList(ParseState *pstate, List *targetlist,
 	foreach(o_target, targetlist)
 	{
 		ResTarget  *res = (ResTarget *) lfirst(o_target);
+		Node	   *transformed = NULL;
 
 		/*
 		 * Check for "something.*".  Depending on the complexity of the
@@ -162,13 +163,19 @@ transformTargetList(ParseState *pstate, List *targetlist,
 
 				if (IsA(llast(ind->indirection), A_Star))
 				{
-					/* It is something.*, expand into multiple items */
-					p_target = list_concat(p_target,
-										   ExpandIndirectionStar(pstate,
-																 ind,
-																 true,
-																 exprKind));
-					continue;
+					Node	   *columns = ExpandIndirectionStar(pstate,
+																ind,
+																true,
+																exprKind);
+
+					if (IsA(columns, List))
+					{
+						/* It is something.*, expand into multiple items */
+						p_target = list_concat(p_target, (List *) columns);
+						continue;
+					}
+
+					transformed = (Node *) columns;
 				}
 			}
 		}
@@ -180,7 +187,7 @@ transformTargetList(ParseState *pstate, List *targetlist,
 		p_target = lappend(p_target,
 						   transformTargetEntry(pstate,
 												res->val,
-												NULL,
+												transformed,
 												exprKind,
 												res->name,
 												false));
@@ -251,10 +258,15 @@ transformExpressionList(ParseState *pstate, List *exprlist,
 
 			if (IsA(llast(ind->indirection), A_Star))
 			{
-				/* It is something.*, expand into multiple items */
-				result = list_concat(result,
-									 ExpandIndirectionStar(pstate, ind,
-														   false, exprKind));
+				Node	   *cols = ExpandIndirectionStar(pstate, ind,
+														 false, exprKind);
+
+				if (!cols || IsA(cols, List))
+					/* It is something.*, expand into multiple items */
+					result = list_concat(result, (List *) cols);
+				else
+					result = lappend(result, cols);
+
 				continue;
 			}
 		}
@@ -1348,22 +1360,30 @@ ExpandAllTables(ParseState *pstate, int location)
  * For robustness, we use a separate "make_target_entry" flag to control
  * this rather than relying on exprKind.
  */
-static List *
+static Node *
 ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
 					  bool make_target_entry, ParseExprKind exprKind)
 {
 	Node	   *expr;
+	ParseExprKind sv_expr_kind;
+	bool		trailing_star_expansion = false;
+
+	/* Save and restore identity of expression type we're parsing */
+	Assert(exprKind != EXPR_KIND_NONE);
+	sv_expr_kind = pstate->p_expr_kind;
+	pstate->p_expr_kind = exprKind;
 
 	/* Strip off the '*' to create a reference to the rowtype object */
-	ind = copyObject(ind);
-	ind->indirection = list_truncate(ind->indirection,
-									 list_length(ind->indirection) - 1);
+	expr = transformIndirection(pstate, ind, &trailing_star_expansion);
+
+	pstate->p_expr_kind = sv_expr_kind;
 
-	/* And transform that */
-	expr = transformExpr(pstate, (Node *) ind, exprKind);
+	/* '*' was consumed by generic type subscripting */
+	if (!trailing_star_expansion)
+		return expr;
 
 	/* Expand the rowtype expression into individual fields */
-	return ExpandRowReference(pstate, expr, make_target_entry);
+	return (Node *) ExpandRowReference(pstate, expr, make_target_entry);
 }
 
 /*
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index af5417d0859..10006b749dc 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -12919,7 +12919,11 @@ printSubscripts(SubscriptingRef *sbsref, deparse_context *context)
 	{
 		Node	   *up = (Node *) lfirst(uplist_item);
 
-		if (IsA(up, String))
+		if (!up)
+		{
+			appendStringInfoString(buf, ".*");
+		}
+		else if (IsA(up, String))
 		{
 			appendStringInfoChar(buf, '.');
 			appendStringInfoString(buf, quote_identifier(strVal(up)));
diff --git a/src/include/parser/parse_expr.h b/src/include/parser/parse_expr.h
index efbaff8e710..c9f6a7724c0 100644
--- a/src/include/parser/parse_expr.h
+++ b/src/include/parser/parse_expr.h
@@ -22,4 +22,7 @@ extern Node *transformExpr(ParseState *pstate, Node *expr, ParseExprKind exprKin
 
 extern const char *ParseExprKindName(ParseExprKind exprKind);
 
+extern Node *transformIndirection(ParseState *pstate, A_Indirection *ind,
+								  bool *trailing_star_expansion);
+
 #endif							/* PARSE_EXPR_H */
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 14123929475..9057186bdf3 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5891,19 +5891,39 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
 CREATE TABLE test_jsonb_dot_notation AS
 SELECT '{"a": [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], "b": [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]}'::jsonb jb;
 SELECT (jb).* FROM test_jsonb_dot_notation;
-ERROR:  type jsonb is not composite
+                                                              jb                                                              
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
 SELECT (jb).* FROM test_jsonb_dot_notation t;
-ERROR:  type jsonb is not composite
+                                                              jb                                                              
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
 SELECT (t.jb).* FROM test_jsonb_dot_notation t;
-ERROR:  type jsonb is not composite
+                                                              jb                                                              
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
 SELECT (jb).* FROM test_jsonb_dot_notation;
-ERROR:  type jsonb is not composite
+                                                              jb                                                              
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
 SELECT (t.jb).* FROM test_jsonb_dot_notation;
 ERROR:  missing FROM-clause entry for table "t"
 LINE 1: SELECT (t.jb).* FROM test_jsonb_dot_notation;
                 ^
 SELECT (t.jb).* FROM test_jsonb_dot_notation t;
-ERROR:  type jsonb is not composite
+                                                              jb                                                              
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
 SELECT (jb).a FROM test_jsonb_dot_notation;
                                     a                                    
 -------------------------------------------------------------------------
@@ -5935,75 +5955,120 @@ SELECT (jb).a.b FROM test_jsonb_dot_notation;
 (1 row)
 
 SELECT (jb).a.* FROM test_jsonb_dot_notation;
-ERROR:  type jsonb is not composite
+                     a                     
+-------------------------------------------
+ ["c", "d", "f", {"y": "yyy", "z": "zzz"}]
+(1 row)
+
 SELECT (jb).a.*.b FROM test_jsonb_dot_notation;
-ERROR:  improper use of "*" at or near "FROM"
-LINE 1: SELECT (jb).a.*.b FROM test_jsonb_dot_notation;
-                          ^
+ b 
+---
+ 
+(1 row)
+
 SELECT (jb).a.*.x FROM test_jsonb_dot_notation;
-ERROR:  improper use of "*" at or near "FROM"
-LINE 1: SELECT (jb).a.*.x FROM test_jsonb_dot_notation;
-                          ^
+ x 
+---
+ 
+(1 row)
+
 SELECT (jb).a.*.y FROM test_jsonb_dot_notation;
-ERROR:  improper use of "*" at or near "FROM"
-LINE 1: SELECT (jb).a.*.y FROM test_jsonb_dot_notation;
-                          ^
+   y   
+-------
+ "yyy"
+(1 row)
+
 SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
-ERROR:  improper use of "*" at or near "FROM"
-LINE 1: SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
-                          ^
+       a        
+----------------
+ ["yyy", "zzz"]
+(1 row)
+
 SELECT (jb).*.x FROM test_jsonb_dot_notation;
-ERROR:  improper use of "*" at or near "FROM"
-LINE 1: SELECT (jb).*.x FROM test_jsonb_dot_notation;
-                        ^
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
 SELECT (jb).*.x FROM test_jsonb_dot_notation t;
-ERROR:  improper use of "*" at or near "FROM"
-LINE 1: SELECT (jb).*.x FROM test_jsonb_dot_notation t;
-                        ^
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
 SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
-ERROR:  row expansion via "*" is not supported here
-LINE 1: SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
-                 ^
+ x 
+---
+ 
+(1 row)
+
 SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
-ERROR:  row expansion via "*" is not supported here
-LINE 1: SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
-                 ^
+ x 
+---
+ 
+(1 row)
+
 SELECT ((jb).*)[:].x FROM test_jsonb_dot_notation t;
-ERROR:  row expansion via "*" is not supported here
-LINE 1: SELECT ((jb).*)[:].x FROM test_jsonb_dot_notation t;
-                 ^
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
 SELECT (jb).*.x FROM test_jsonb_dot_notation;
-ERROR:  improper use of "*" at or near "FROM"
-LINE 1: SELECT (jb).*.x FROM test_jsonb_dot_notation;
-                        ^
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
 SELECT (jb).*.x.* FROM test_jsonb_dot_notation;
-ERROR:  improper use of "*" at or near "FROM"
-LINE 1: SELECT (jb).*.x.* FROM test_jsonb_dot_notation;
-                          ^
+              x               
+------------------------------
+ ["yyy", "zzz", "YYY", "ZZZ"]
+(1 row)
+
 SELECT (jb).*.x.y FROM test_jsonb_dot_notation;
-ERROR:  improper use of "*" at or near "FROM"
-LINE 1: SELECT (jb).*.x.y FROM test_jsonb_dot_notation;
-                          ^
+       y        
+----------------
+ ["yyy", "YYY"]
+(1 row)
+
 SELECT (jb).*.x.z FROM test_jsonb_dot_notation;
-ERROR:  improper use of "*" at or near "FROM"
-LINE 1: SELECT (jb).*.x.z FROM test_jsonb_dot_notation;
-                          ^
+       z        
+----------------
+ ["zzz", "ZZZ"]
+(1 row)
+
 SELECT (jb).*.*.y FROM test_jsonb_dot_notation;
-ERROR:  improper use of "*" at or near "FROM"
-LINE 1: SELECT (jb).*.*.y FROM test_jsonb_dot_notation;
-                          ^
+       y        
+----------------
+ ["yyy", "YYY"]
+(1 row)
+
 SELECT (jb).*.*.* FROM test_jsonb_dot_notation;
-ERROR:  improper use of "*" at or near "FROM"
-LINE 1: SELECT (jb).*.*.* FROM test_jsonb_dot_notation;
-                          ^
+              jb              
+------------------------------
+ ["yyy", "zzz", "YYY", "ZZZ"]
+(1 row)
+
 SELECT (jb).*.*.*.* FROM test_jsonb_dot_notation;
-ERROR:  improper use of "*" at or near "FROM"
-LINE 1: SELECT (jb).*.*.*.* FROM test_jsonb_dot_notation;
-                            ^
+ jb 
+----
+ 
+(1 row)
+
 SELECT (jb).a.b.c.* FROM test_jsonb_dot_notation;
-ERROR:  type jsonb is not composite
+ c 
+---
+ 
+(1 row)
+
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).* FROM test_jsonb_dot_notation;
-ERROR:  type jsonb is not composite
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: jb.*
+(2 rows)
+
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a FROM test_jsonb_dot_notation;
                  QUERY PLAN                 
 --------------------------------------------
@@ -6019,10 +6084,16 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
 (2 rows)
 
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*['b'] FROM test_jsonb_dot_notation;
-ERROR:  improper use of "*" at or near "FROM"
-LINE 1: EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*['b'] FROM test_...
-                                                          ^
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: jb.a.*['b'::text]
+(2 rows)
+
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:2]['b'].b FROM test_jsonb_dot_notation;
-ERROR:  improper use of "*" at or near "FROM"
-LINE 1: ... (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:2]['b'].b FROM test_...
-                                                             ^
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: jb.a.*[1:2][:'b'::text].b
+(2 rows)
+
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v7-0001-Allow-transformation-only-of-a-sublist-of-subscri.patch (6.3K, ../../CAK98qZ1za8XgOLY+2hQMPGoxVYFyh=dDkM2dZPVeJK4J6poyvA@mail.gmail.com/5-v7-0001-Allow-transformation-only-of-a-sublist-of-subscri.patch)
  download | inline diff:
From 5da3d365c8eb72bf467fe33c7173fedd61c9f96f Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Fri, 14 Oct 2022 15:35:22 +0300
Subject: [PATCH v7 1/5] Allow transformation only of a sublist of subscripts

---
 src/backend/parser/parse_expr.c   | 9 ++++-----
 src/backend/parser/parse_node.c   | 4 ++--
 src/backend/parser/parse_target.c | 5 ++++-
 src/backend/utils/adt/arraysubs.c | 6 ++++--
 src/backend/utils/adt/jsonbsubs.c | 6 ++++--
 src/include/nodes/subscripting.h  | 2 +-
 src/include/parser/parse_node.h   | 2 +-
 7 files changed, 20 insertions(+), 14 deletions(-)

diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index bad1df732ea..2c0f4a50b21 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -466,14 +466,13 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 			Assert(IsA(n, String));
 
 			/* process subscripts before this field selection */
-			if (subscripts)
+			while (subscripts)
 				result = (Node *) transformContainerSubscripts(pstate,
 															   result,
 															   exprType(result),
 															   exprTypmod(result),
-															   subscripts,
+															   &subscripts,
 															   false);
-			subscripts = NIL;
 
 			newresult = ParseFuncOrColumn(pstate,
 										  list_make1(n),
@@ -488,12 +487,12 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		}
 	}
 	/* process trailing subscripts, if any */
-	if (subscripts)
+	while (subscripts)
 		result = (Node *) transformContainerSubscripts(pstate,
 													   result,
 													   exprType(result),
 													   exprTypmod(result),
-													   subscripts,
+													   &subscripts,
 													   false);
 
 	return result;
diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c
index d6feb16aef3..19a6b678e67 100644
--- a/src/backend/parser/parse_node.c
+++ b/src/backend/parser/parse_node.c
@@ -244,7 +244,7 @@ transformContainerSubscripts(ParseState *pstate,
 							 Node *containerBase,
 							 Oid containerType,
 							 int32 containerTypMod,
-							 List *indirection,
+							 List **indirection,
 							 bool isAssignment)
 {
 	SubscriptingRef *sbsref;
@@ -280,7 +280,7 @@ transformContainerSubscripts(ParseState *pstate,
 	 * element.  If any of the items are slice specifiers (lower:upper), then
 	 * the subscript expression means a container slice operation.
 	 */
-	foreach(idx, indirection)
+	foreach(idx, *indirection)
 	{
 		A_Indices  *ai = lfirst_node(A_Indices, idx);
 
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 4aba0d9d4d5..39fd82f8371 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -936,9 +936,12 @@ transformAssignmentSubscripts(ParseState *pstate,
 										  basenode,
 										  containerType,
 										  containerTypMod,
-										  subscripts,
+										  &subscripts,
 										  true);
 
+	if (subscripts)
+		elog(ERROR, "subscripting assignment is not supported");
+
 	typeNeeded = sbsref->refrestype;
 	typmodNeeded = sbsref->reftypmod;
 
diff --git a/src/backend/utils/adt/arraysubs.c b/src/backend/utils/adt/arraysubs.c
index 562179b3799..edd85f7ba67 100644
--- a/src/backend/utils/adt/arraysubs.c
+++ b/src/backend/utils/adt/arraysubs.c
@@ -53,7 +53,7 @@ typedef struct ArraySubWorkspace
  */
 static void
 array_subscript_transform(SubscriptingRef *sbsref,
-						  List *indirection,
+						  List **indirection,
 						  ParseState *pstate,
 						  bool isSlice,
 						  bool isAssignment)
@@ -70,7 +70,7 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	 * indirection items to slices by treating the single subscript as the
 	 * upper bound and supplying an assumed lower bound of 1.
 	 */
-	foreach(idx, indirection)
+	foreach(idx, *indirection)
 	{
 		A_Indices  *ai = lfirst_node(A_Indices, idx);
 		Node	   *subexpr;
@@ -151,6 +151,8 @@ array_subscript_transform(SubscriptingRef *sbsref,
 						list_length(upperIndexpr), MAXDIM)));
 	/* We need not check lowerIndexpr separately */
 
+	*indirection = NIL;
+
 	/*
 	 * Determine the result type of the subscripting operation.  It's the same
 	 * as the array type if we're slicing, else it's the element type.  In
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index de64d498512..8ad6aa1ad4f 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -41,7 +41,7 @@ typedef struct JsonbSubWorkspace
  */
 static void
 jsonb_subscript_transform(SubscriptingRef *sbsref,
-						  List *indirection,
+						  List **indirection,
 						  ParseState *pstate,
 						  bool isSlice,
 						  bool isAssignment)
@@ -53,7 +53,7 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	 * Transform and convert the subscript expressions. Jsonb subscripting
 	 * does not support slices, look only and the upper index.
 	 */
-	foreach(idx, indirection)
+	foreach(idx, *indirection)
 	{
 		A_Indices  *ai = lfirst_node(A_Indices, idx);
 		Node	   *subExpr;
@@ -159,6 +159,8 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	/* Determine the result type of the subscripting operation; always jsonb */
 	sbsref->refrestype = JSONBOID;
 	sbsref->reftypmod = -1;
+
+	*indirection = NIL;
 }
 
 /*
diff --git a/src/include/nodes/subscripting.h b/src/include/nodes/subscripting.h
index 234e8ad8012..ed2f1cefb49 100644
--- a/src/include/nodes/subscripting.h
+++ b/src/include/nodes/subscripting.h
@@ -93,7 +93,7 @@ struct SubscriptExecSteps;
  * assignment must return.
  */
 typedef void (*SubscriptTransform) (SubscriptingRef *sbsref,
-									List *indirection,
+									List **indirection,
 									struct ParseState *pstate,
 									bool isSlice,
 									bool isAssignment);
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 994284019fb..5ae11ccec33 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -377,7 +377,7 @@ extern SubscriptingRef *transformContainerSubscripts(ParseState *pstate,
 													 Node *containerBase,
 													 Oid containerType,
 													 int32 containerTypMod,
-													 List *indirection,
+													 List **indirection,
 													 bool isAssignment);
 extern Const *make_const(ParseState *pstate, A_Const *aconst);
 
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v7-0003-Export-jsonPathFromParseResult.patch (2.4K, ../../CAK98qZ1za8XgOLY+2hQMPGoxVYFyh=dDkM2dZPVeJK4J6poyvA@mail.gmail.com/6-v7-0003-Export-jsonPathFromParseResult.patch)
  download | inline diff:
From 015f01b44fa99fe1242b21cb83736e4d31fa5671 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Sat, 1 Apr 2023 23:15:55 +0300
Subject: [PATCH v7 3/5] Export jsonPathFromParseResult()

---
 src/backend/utils/adt/jsonpath.c | 24 ++++++++++++++++++------
 src/include/utils/jsonpath.h     |  4 ++++
 2 files changed, 22 insertions(+), 6 deletions(-)

diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index 762f7e8a09d..a3270bdaba3 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -165,16 +165,12 @@ jsonpath_send(PG_FUNCTION_ARGS)
 /*
  * Converts C-string to a jsonpath value.
  *
- * Uses jsonpath parser to turn string into an AST, then
- * flattenJsonPathParseItem() does second pass turning AST into binary
- * representation of jsonpath.
+ * Uses jsonpath parser to turn string into an AST.
  */
 static Datum
 jsonPathFromCstring(char *in, int len, struct Node *escontext)
 {
 	JsonPathParseResult *jsonpath = parsejsonpath(in, len, escontext);
-	JsonPath   *res;
-	StringInfoData buf;
 
 	if (SOFT_ERROR_OCCURRED(escontext))
 		return (Datum) 0;
@@ -185,8 +181,24 @@ jsonPathFromCstring(char *in, int len, struct Node *escontext)
 				 errmsg("invalid input syntax for type %s: \"%s\"", "jsonpath",
 						in)));
 
+	return jsonPathFromParseResult(jsonpath, 4 * len, escontext);
+}
+
+/*
+ * Converts jsonpath AST to a jsonpath value.
+ *
+ * flattenJsonPathParseItem() does second pass turning AST into binary
+ * representation of jsonpath.
+ */
+Datum
+jsonPathFromParseResult(JsonPathParseResult *jsonpath, int estimated_len,
+						struct Node *escontext)
+{
+	JsonPath   *res;
+	StringInfoData buf;
+
 	initStringInfo(&buf);
-	enlargeStringInfo(&buf, 4 * len /* estimation */ );
+	enlargeStringInfo(&buf, estimated_len);
 
 	appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
 
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index 23a76d233e9..e05941623e7 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -281,6 +281,10 @@ extern JsonPathParseResult *parsejsonpath(const char *str, int len,
 extern bool jspConvertRegexFlags(uint32 xflags, int *result,
 								 struct Node *escontext);
 
+extern Datum jsonPathFromParseResult(JsonPathParseResult *jsonpath,
+									 int estimated_len,
+									 struct Node *escontext);
+
 /*
  * Struct for details about external variables passed into jsonpath executor
  */
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v7-0004-Implement-read-only-dot-notation-for-jsonb-using-.patch (31.0K, ../../CAK98qZ1za8XgOLY+2hQMPGoxVYFyh=dDkM2dZPVeJK4J6poyvA@mail.gmail.com/7-v7-0004-Implement-read-only-dot-notation-for-jsonb-using-.patch)
  download | inline diff:
From 53bc1da78b0feb7769c571fbcafb70c2214a9c46 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Sat, 1 Apr 2023 23:17:53 +0300
Subject: [PATCH v7 4/5] Implement read-only dot notation for jsonb using
 jsonpath

---
 src/backend/utils/adt/jsonbsubs.c   | 438 +++++++++++++++++++++++-----
 src/include/nodes/primnodes.h       |   2 +
 src/test/regress/expected/jsonb.out | 265 ++++++++++++++++-
 src/test/regress/sql/jsonb.sql      |  56 ++++
 4 files changed, 670 insertions(+), 91 deletions(-)

diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index a0d38a0fd80..1ececb4efa2 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -15,12 +15,14 @@
 #include "postgres.h"
 
 #include "executor/execExpr.h"
+#include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "nodes/subscripting.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_expr.h"
 #include "utils/builtins.h"
 #include "utils/jsonb.h"
+#include "utils/jsonpath.h"
 
 
 /* SubscriptingRefState.workspace for jsonb subscripting execution */
@@ -30,8 +32,261 @@ typedef struct JsonbSubWorkspace
 	Oid		   *indexOid;		/* OID of coerced subscript expression, could
 								 * be only integer or text */
 	Datum	   *index;			/* Subscript values in Datum format */
+	JsonPath   *jsonpath;		/* jsonpath for dot notation execution */
+	List		vars;			/* jsonpath vars */
 } JsonbSubWorkspace;
 
+static bool
+jsonb_check_jsonpath_needed(List *indirection)
+{
+	ListCell   *lc;
+
+	foreach(lc, indirection)
+	{
+		Node	   *accessor = lfirst(lc);
+
+		if (IsA(accessor, String) ||
+			IsA(accessor, A_Star))
+			return true;
+		else if (IsA(accessor, A_Indices))
+		{
+			A_Indices  *ai = castNode(A_Indices, accessor);
+
+			if (!ai->uidx || ai->lidx)
+			{
+				Assert(ai->is_slice);
+				return true;
+			}
+		}
+		else
+			return true;
+	}
+
+	return false;
+}
+
+static JsonPathParseItem *
+make_jsonpath_item(JsonPathItemType type)
+{
+	JsonPathParseItem *v = palloc(sizeof(*v));
+
+	v->type = type;
+	v->next = NULL;
+
+	return v;
+}
+
+static JsonPathParseItem *
+make_jsonpath_item_int(int32 val, List **exprs)
+{
+	JsonPathParseItem *jpi = make_jsonpath_item(jpiNumeric);
+
+	jpi->value.numeric =
+		DatumGetNumeric(DirectFunctionCall1(int4_numeric, Int32GetDatum(val)));
+
+	*exprs = lappend(*exprs, makeConst(INT4OID, -1, InvalidOid, 4,
+									   Int32GetDatum(val), false, true));
+
+	return jpi;
+}
+
+static Oid
+jsonb_subscript_type(Node *expr)
+{
+	if (expr && IsA(expr, String))
+		return TEXTOID;
+
+	return exprType(expr);
+}
+
+static Node *
+coerce_jsonpath_subscript(ParseState *pstate, Node *subExpr, Oid numtype)
+{
+	Oid			subExprType = jsonb_subscript_type(subExpr);
+	Oid			targetType = UNKNOWNOID;
+
+	if (subExprType != UNKNOWNOID)
+	{
+		Oid			targets[2] = {numtype, TEXTOID};
+
+		/*
+		 * Jsonb can handle multiple subscript types, but cases when a
+		 * subscript could be coerced to multiple target types must be
+		 * avoided, similar to overloaded functions. It could be
+		 * possibly extend with jsonpath in the future.
+		 */
+		for (int i = 0; i < 2; i++)
+		{
+			if (can_coerce_type(1, &subExprType, &targets[i], COERCION_IMPLICIT))
+			{
+				/*
+				 * One type has already succeeded, it means there are
+				 * two coercion targets possible, failure.
+				 */
+				if (targetType != UNKNOWNOID)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
+							 errhint("jsonb subscript must be coercible to only one type, integer or text."),
+							 parser_errposition(pstate, exprLocation(subExpr))));
+
+				targetType = targets[i];
+			}
+		}
+
+		/*
+		 * No suitable types were found, failure.
+		 */
+		if (targetType == UNKNOWNOID)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
+					 errhint("jsonb subscript must be coercible to either integer or text."),
+					 parser_errposition(pstate, exprLocation(subExpr))));
+	}
+	else
+		targetType = TEXTOID;
+
+	/*
+	 * We known from can_coerce_type that coercion will succeed, so
+	 * coerce_type could be used. Note the implicit coercion context,
+	 * which is required to handle subscripts of different types,
+	 * similar to overloaded functions.
+	 */
+	subExpr = coerce_type(pstate,
+						  subExpr, subExprType,
+						  targetType, -1,
+						  COERCION_IMPLICIT,
+						  COERCE_IMPLICIT_CAST,
+						  -1);
+	if (subExpr == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("jsonb subscript must have text type"),
+				 parser_errposition(pstate, exprLocation(subExpr))));
+
+	return subExpr;
+}
+
+static JsonPathParseItem *
+make_jsonpath_item_expr(ParseState *pstate, Node *expr, List **exprs)
+{
+	JsonPathParseItem *jpi;
+
+	expr = transformExpr(pstate, expr, pstate->p_expr_kind);
+
+	if (IsA(expr, Const))
+	{
+		Const	   *cnst = (Const *) expr;
+
+		if (cnst->consttype == INT4OID && !cnst->constisnull)
+		{
+			int32		val = DatumGetInt32(cnst->constvalue);
+
+			return make_jsonpath_item_int(val, exprs);
+		}
+	}
+
+	*exprs = lappend(*exprs, coerce_jsonpath_subscript(pstate, expr, NUMERICOID));
+
+	jpi = make_jsonpath_item(jpiVariable);
+	jpi->value.string.val = psprintf("%d", list_length(*exprs));
+	jpi->value.string.len = strlen(jpi->value.string.val);
+
+	return jpi;
+}
+
+static Node *
+jsonb_subscript_make_jsonpath(ParseState *pstate, List **indirection,
+							  List **uexprs, List **lexprs)
+{
+	JsonPathParseResult jpres;
+	JsonPathParseItem *path = make_jsonpath_item(jpiRoot);
+	ListCell   *lc;
+	Datum		jsp;
+	int			pathlen = 0;
+
+	*uexprs = NIL;
+	*lexprs = NIL;
+
+	jpres.expr = path;
+	jpres.lax = true;
+
+	foreach(lc, *indirection)
+	{
+		Node	   *accessor = lfirst(lc);
+		JsonPathParseItem *jpi;
+
+		if (IsA(accessor, String))
+		{
+			char	   *field = strVal(accessor);
+
+			jpi = make_jsonpath_item(jpiKey);
+			jpi->value.string.val = field;
+			jpi->value.string.len = strlen(field);
+
+			*uexprs = lappend(*uexprs, accessor);
+		}
+		else if (IsA(accessor, A_Star))
+		{
+			jpi = make_jsonpath_item(jpiAnyKey);
+
+			*uexprs = lappend(*uexprs, NULL);
+		}
+		else if (IsA(accessor, A_Indices))
+		{
+			A_Indices  *ai = castNode(A_Indices, accessor);
+
+			jpi = make_jsonpath_item(jpiIndexArray);
+			jpi->value.array.nelems = 1;
+			jpi->value.array.elems = palloc(sizeof(jpi->value.array.elems[0]));
+
+			if (ai->is_slice)
+			{
+				while (list_length(*lexprs) < list_length(*uexprs))
+					*lexprs = lappend(*lexprs, NULL);
+
+				if (ai->lidx)
+					jpi->value.array.elems[0].from = make_jsonpath_item_expr(pstate, ai->lidx, lexprs);
+				else
+					jpi->value.array.elems[0].from = make_jsonpath_item_int(0, lexprs);
+
+				if (ai->uidx)
+					jpi->value.array.elems[0].to = make_jsonpath_item_expr(pstate, ai->uidx, uexprs);
+				else
+				{
+					jpi->value.array.elems[0].to = make_jsonpath_item(jpiLast);
+					*uexprs = lappend(*uexprs, NULL);
+				}
+			}
+			else
+			{
+				Assert(ai->uidx && !ai->lidx);
+				jpi->value.array.elems[0].from = make_jsonpath_item_expr(pstate, ai->uidx, uexprs);
+				jpi->value.array.elems[0].to = NULL;
+			}
+		}
+		else
+			break;
+
+		/* append path item */
+		path->next = jpi;
+		path = jpi;
+		pathlen++;
+	}
+
+	if (*lexprs)
+	{
+		while (list_length(*lexprs) < list_length(*uexprs))
+			*lexprs = lappend(*lexprs, NULL);
+	}
+
+	*indirection = list_delete_first_n(*indirection, pathlen);
+
+	jsp = jsonPathFromParseResult(&jpres, 0, NULL);
+
+	return (Node *) makeConst(JSONPATHOID, -1, InvalidOid, -1, jsp, false, false);
+}
 
 /*
  * Finish parse analysis of a SubscriptingRef expression for a jsonb.
@@ -49,19 +304,35 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	List	   *upperIndexpr = NIL;
 	ListCell   *idx;
 
+	/* Determine the result type of the subscripting operation; always jsonb */
+	sbsref->refrestype = JSONBOID;
+	sbsref->reftypmod = -1;
+
+	if (jsonb_check_jsonpath_needed(*indirection))
+	{
+		sbsref->refprivate =
+			jsonb_subscript_make_jsonpath(pstate, indirection,
+										  &sbsref->refupperindexpr,
+										  &sbsref->reflowerindexpr);
+		return;
+	}
+
 	/*
 	 * Transform and convert the subscript expressions. Jsonb subscripting
 	 * does not support slices, look only and the upper index.
 	 */
 	foreach(idx, *indirection)
 	{
+		Node	   *i = lfirst(idx);
 		A_Indices  *ai;
 		Node	   *subExpr;
 
-		if (!IsA(lfirst(idx), A_Indices))
+		Assert(IsA(i, A_Indices));
+
+		if (!IsA(i, A_Indices))
 			break;
 
-		ai = lfirst_node(A_Indices, idx);
+		ai = castNode(A_Indices, i);
 
 		if (isSlice)
 		{
@@ -75,71 +346,8 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 
 		if (ai->uidx)
 		{
-			Oid			subExprType = InvalidOid,
-						targetType = UNKNOWNOID;
-
 			subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
-			subExprType = exprType(subExpr);
-
-			if (subExprType != UNKNOWNOID)
-			{
-				Oid			targets[2] = {INT4OID, TEXTOID};
-
-				/*
-				 * Jsonb can handle multiple subscript types, but cases when a
-				 * subscript could be coerced to multiple target types must be
-				 * avoided, similar to overloaded functions. It could be
-				 * possibly extend with jsonpath in the future.
-				 */
-				for (int i = 0; i < 2; i++)
-				{
-					if (can_coerce_type(1, &subExprType, &targets[i], COERCION_IMPLICIT))
-					{
-						/*
-						 * One type has already succeeded, it means there are
-						 * two coercion targets possible, failure.
-						 */
-						if (targetType != UNKNOWNOID)
-							ereport(ERROR,
-									(errcode(ERRCODE_DATATYPE_MISMATCH),
-									 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
-									 errhint("jsonb subscript must be coercible to only one type, integer or text."),
-									 parser_errposition(pstate, exprLocation(subExpr))));
-
-						targetType = targets[i];
-					}
-				}
-
-				/*
-				 * No suitable types were found, failure.
-				 */
-				if (targetType == UNKNOWNOID)
-					ereport(ERROR,
-							(errcode(ERRCODE_DATATYPE_MISMATCH),
-							 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
-							 errhint("jsonb subscript must be coercible to either integer or text."),
-							 parser_errposition(pstate, exprLocation(subExpr))));
-			}
-			else
-				targetType = TEXTOID;
-
-			/*
-			 * We known from can_coerce_type that coercion will succeed, so
-			 * coerce_type could be used. Note the implicit coercion context,
-			 * which is required to handle subscripts of different types,
-			 * similar to overloaded functions.
-			 */
-			subExpr = coerce_type(pstate,
-								  subExpr, subExprType,
-								  targetType, -1,
-								  COERCION_IMPLICIT,
-								  COERCE_IMPLICIT_CAST,
-								  -1);
-			if (subExpr == NULL)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("jsonb subscript must have text type"),
-						 parser_errposition(pstate, exprLocation(subExpr))));
+			subExpr = coerce_jsonpath_subscript(pstate, subExpr, INT4OID);
 		}
 		else
 		{
@@ -161,10 +369,6 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->refupperindexpr = upperIndexpr;
 	sbsref->reflowerindexpr = NIL;
 
-	/* Determine the result type of the subscripting operation; always jsonb */
-	sbsref->refrestype = JSONBOID;
-	sbsref->reftypmod = -1;
-
 	/* Remove processed elements */
 	if (upperIndexpr)
 		*indirection = list_delete_first_n(*indirection, list_length(upperIndexpr));
@@ -219,7 +423,7 @@ jsonb_subscript_check_subscripts(ExprState *state,
 			 * For jsonb fetch and assign functions we need to provide path in
 			 * text format. Convert if it's not already text.
 			 */
-			if (workspace->indexOid[i] == INT4OID)
+			if (!workspace->jsonpath && workspace->indexOid[i] == INT4OID)
 			{
 				Datum		datum = sbsrefstate->upperindex[i];
 				char	   *cs = DatumGetCString(DirectFunctionCall1(int4out, datum));
@@ -247,17 +451,44 @@ jsonb_subscript_fetch(ExprState *state,
 {
 	SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
 	JsonbSubWorkspace *workspace = (JsonbSubWorkspace *) sbsrefstate->workspace;
-	Jsonb	   *jsonbSource;
 
 	/* Should not get here if source jsonb (or any subscript) is null */
 	Assert(!(*op->resnull));
 
-	jsonbSource = DatumGetJsonbP(*op->resvalue);
-	*op->resvalue = jsonb_get_element(jsonbSource,
-									  workspace->index,
-									  sbsrefstate->numupper,
-									  op->resnull,
-									  false);
+	if (workspace->jsonpath)
+	{
+		bool		empty = false;
+		bool		error = false;
+		List	   *vars = &workspace->vars;
+		ListCell   *lc;
+
+		/* copy computed variable values */
+		foreach(lc, vars)
+		{
+			JsonPathVariable *var = lfirst(lc);
+			int			i = foreach_current_index(lc);
+
+			var->value = workspace->index[i];
+			var->isnull = false;
+		}
+
+		*op->resvalue = JsonPathQuery(*op->resvalue, workspace->jsonpath,
+									  JSW_CONDITIONAL,
+									  &empty, &error, vars,
+									  NULL);
+
+		*op->resnull = empty || error;
+	}
+	else
+	{
+		Jsonb	   *jsonbSource = DatumGetJsonbP(*op->resvalue);
+
+		*op->resvalue = jsonb_get_element(jsonbSource,
+										  workspace->index,
+										  sbsrefstate->numupper,
+										  op->resnull,
+										  false);
+	}
 }
 
 /*
@@ -367,12 +598,57 @@ jsonb_exec_setup(const SubscriptingRef *sbsref,
 	ListCell   *lc;
 	int			nupper = sbsref->refupperindexpr->length;
 	char	   *ptr;
+	bool		useJsonpath = sbsref->refprivate != NULL;
+	JsonPathVariable *vars;
+	int			nvars = useJsonpath ? nupper : 0;
 
 	/* Allocate type-specific workspace with space for per-subscript data */
-	workspace = palloc0(MAXALIGN(sizeof(JsonbSubWorkspace)) +
+	workspace = palloc0(MAXALIGN(offsetof(JsonbSubWorkspace, vars.initial_elements) + nvars * sizeof(ListCell)) +
+						MAXALIGN(nvars * sizeof(*vars) + nvars * 16) +
 						nupper * (sizeof(Datum) + sizeof(Oid)));
 	workspace->expectArray = false;
-	ptr = ((char *) workspace) + MAXALIGN(sizeof(JsonbSubWorkspace));
+	ptr = ((char *) workspace) +
+		MAXALIGN(offsetof(JsonbSubWorkspace, vars.initial_elements) +
+				 nvars * sizeof(ListCell));
+
+	if (!useJsonpath)
+		workspace->jsonpath = NULL;
+	else
+	{
+		workspace->jsonpath = DatumGetJsonPathP(castNode(Const, sbsref->refprivate)->constvalue);
+
+		vars = (JsonPathVariable *) ptr;
+		ptr += MAXALIGN(nvars * sizeof(*vars));
+
+		workspace->vars.type = T_List;
+		workspace->vars.length = nvars;
+		workspace->vars.max_length = nvars;
+		workspace->vars.elements = &workspace->vars.initial_elements[0];
+
+		for (int i = 0; i < nvars; i++)
+		{
+			Node	   *expr = list_nth(sbsref->refupperindexpr, i);
+
+			workspace->vars.elements[i].ptr_value = &vars[i];
+
+			if (expr && IsA(expr, String))
+			{
+				vars[i].typid = TEXTOID;
+				vars[i].typmod = -1;
+			}
+			else
+			{
+				vars[i].typid = exprType(expr);
+				vars[i].typmod = exprTypmod(expr);
+			}
+
+			vars[i].name = ptr;
+			snprintf(ptr, 16, "%d", i + 1);
+			vars[i].namelen = strlen(ptr);
+
+			ptr += 16;
+		}
+	}
 
 	/*
 	 * This coding assumes sizeof(Datum) >= sizeof(Oid), else we might
@@ -390,7 +666,7 @@ jsonb_exec_setup(const SubscriptingRef *sbsref,
 		Node	   *expr = lfirst(lc);
 		int			i = foreach_current_index(lc);
 
-		workspace->indexOid[i] = exprType(expr);
+		workspace->indexOid[i] = jsonb_subscript_type(expr);
 	}
 
 	/*
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 839e71d52f4..4b1e5de98e5 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -718,6 +718,8 @@ typedef struct SubscriptingRef
 	Expr	   *refexpr;
 	/* expression for the source value, or NULL if fetch */
 	Expr	   *refassgnexpr;
+	/* private expression */
+	Node	   *refprivate;
 } SubscriptingRef;
 
 /*
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 2baff931bf2..14123929475 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -4939,6 +4939,12 @@ select ('123'::jsonb)['a'];
  
 (1 row)
 
+select ('123'::jsonb).a;
+ a 
+---
+ 
+(1 row)
+
 select ('123'::jsonb)[0];
  jsonb 
 -------
@@ -4957,6 +4963,12 @@ select ('{"a": 1}'::jsonb)['a'];
  1
 (1 row)
 
+select ('{"a": 1}'::jsonb).a;
+ a 
+---
+ 1
+(1 row)
+
 select ('{"a": 1}'::jsonb)[0];
  jsonb 
 -------
@@ -4969,6 +4981,12 @@ select ('{"a": 1}'::jsonb)['not_exist'];
  
 (1 row)
 
+select ('{"a": 1}'::jsonb)."not_exist";
+ not_exist 
+-----------
+ 
+(1 row)
+
 select ('{"a": 1}'::jsonb)[NULL];
  jsonb 
 -------
@@ -4981,6 +4999,12 @@ select ('[1, "2", null]'::jsonb)['a'];
  
 (1 row)
 
+select ('[1, "2", null]'::jsonb).a;
+ a 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[0];
  jsonb 
 -------
@@ -4993,6 +5017,12 @@ select ('[1, "2", null]'::jsonb)['1'];
  "2"
 (1 row)
 
+select ('[1, "2", null]'::jsonb)."1";
+ 1 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[1.0];
 ERROR:  subscript type numeric is not supported
 LINE 1: select ('[1, "2", null]'::jsonb)[1.0];
@@ -5022,6 +5052,12 @@ select ('[1, "2", null]'::jsonb)[1]['a'];
  
 (1 row)
 
+select ('[1, "2", null]'::jsonb)[1].a;
+ a 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[1][0];
  jsonb 
 -------
@@ -5034,73 +5070,143 @@ select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['b'];
  "c"
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).b;
+  b  
+-----
+ "c"
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'];
    jsonb   
 -----------
  [1, 2, 3]
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d;
+     d     
+-----------
+ [1, 2, 3]
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'][1];
  jsonb 
 -------
  2
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d[1];
+ d 
+---
+ 2
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d']['a'];
  jsonb 
 -------
  
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['a'];
+ d 
+---
+ 
+(1 row)
+
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d.a;
+ a 
+---
+ 
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1'];
      jsonb     
 ---------------
  {"a2": "aaa"}
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1;
+      a1       
+---------------
+ {"a2": "aaa"}
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2'];
  jsonb 
 -------
  "aaa"
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2;
+  a2   
+-------
+ "aaa"
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']['a3'];
  jsonb 
 -------
  
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2.a3;
+ a3 
+----
+ 
+(1 row)
+
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'];
          jsonb         
 -----------------------
  ["aaa", "bbb", "ccc"]
 (1 row)
 
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1;
+          b1           
+-----------------------
+ ["aaa", "bbb", "ccc"]
+(1 row)
+
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'][2];
  jsonb 
 -------
  "ccc"
 (1 row)
 
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1[2];
+  b1   
+-------
+ "ccc"
+(1 row)
+
 -- slices are not supported
 select ('{"a": 1}'::jsonb)['a':'b'];
-ERROR:  jsonb subscript does not support slices
-LINE 1: select ('{"a": 1}'::jsonb)['a':'b'];
-                                       ^
+ jsonb 
+-------
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[1:2];
-ERROR:  jsonb subscript does not support slices
-LINE 1: select ('[1, "2", null]'::jsonb)[1:2];
-                                           ^
+    jsonb    
+-------------
+ ["2", null]
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[:2];
 ERROR:  jsonb subscript does not support slices
 LINE 1: select ('[1, "2", null]'::jsonb)[:2];
                                           ^
 select ('[1, "2", null]'::jsonb)[1:];
-ERROR:  jsonb subscript does not support slices
-LINE 1: select ('[1, "2", null]'::jsonb)[1:];
-                                         ^
+    jsonb    
+-------------
+ ["2", null]
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[:];
-ERROR:  jsonb subscript does not support slices
+     jsonb      
+----------------
+ [1, "2", null]
+(1 row)
+
 create TEMP TABLE test_jsonb_subscript (
        id int,
        test_json jsonb
@@ -5781,3 +5887,142 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
  12345
 (1 row)
 
+-- dot notation
+CREATE TABLE test_jsonb_dot_notation AS
+SELECT '{"a": [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], "b": [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]}'::jsonb jb;
+SELECT (jb).* FROM test_jsonb_dot_notation;
+ERROR:  type jsonb is not composite
+SELECT (jb).* FROM test_jsonb_dot_notation t;
+ERROR:  type jsonb is not composite
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+ERROR:  type jsonb is not composite
+SELECT (jb).* FROM test_jsonb_dot_notation;
+ERROR:  type jsonb is not composite
+SELECT (t.jb).* FROM test_jsonb_dot_notation;
+ERROR:  missing FROM-clause entry for table "t"
+LINE 1: SELECT (t.jb).* FROM test_jsonb_dot_notation;
+                ^
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+ERROR:  type jsonb is not composite
+SELECT (jb).a FROM test_jsonb_dot_notation;
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+SELECT (jb).a FROM test_jsonb_dot_notation;
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+SELECT (jb).b FROM test_jsonb_dot_notation;
+                         b                         
+---------------------------------------------------
+ [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]
+(1 row)
+
+SELECT (jb).c FROM test_jsonb_dot_notation;
+ c 
+---
+ 
+(1 row)
+
+SELECT (jb).a.b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+ERROR:  type jsonb is not composite
+SELECT (jb).a.*.b FROM test_jsonb_dot_notation;
+ERROR:  improper use of "*" at or near "FROM"
+LINE 1: SELECT (jb).a.*.b FROM test_jsonb_dot_notation;
+                          ^
+SELECT (jb).a.*.x FROM test_jsonb_dot_notation;
+ERROR:  improper use of "*" at or near "FROM"
+LINE 1: SELECT (jb).a.*.x FROM test_jsonb_dot_notation;
+                          ^
+SELECT (jb).a.*.y FROM test_jsonb_dot_notation;
+ERROR:  improper use of "*" at or near "FROM"
+LINE 1: SELECT (jb).a.*.y FROM test_jsonb_dot_notation;
+                          ^
+SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+ERROR:  improper use of "*" at or near "FROM"
+LINE 1: SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+                          ^
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+ERROR:  improper use of "*" at or near "FROM"
+LINE 1: SELECT (jb).*.x FROM test_jsonb_dot_notation;
+                        ^
+SELECT (jb).*.x FROM test_jsonb_dot_notation t;
+ERROR:  improper use of "*" at or near "FROM"
+LINE 1: SELECT (jb).*.x FROM test_jsonb_dot_notation t;
+                        ^
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+ERROR:  row expansion via "*" is not supported here
+LINE 1: SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+                 ^
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+ERROR:  row expansion via "*" is not supported here
+LINE 1: SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+                 ^
+SELECT ((jb).*)[:].x FROM test_jsonb_dot_notation t;
+ERROR:  row expansion via "*" is not supported here
+LINE 1: SELECT ((jb).*)[:].x FROM test_jsonb_dot_notation t;
+                 ^
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+ERROR:  improper use of "*" at or near "FROM"
+LINE 1: SELECT (jb).*.x FROM test_jsonb_dot_notation;
+                        ^
+SELECT (jb).*.x.* FROM test_jsonb_dot_notation;
+ERROR:  improper use of "*" at or near "FROM"
+LINE 1: SELECT (jb).*.x.* FROM test_jsonb_dot_notation;
+                          ^
+SELECT (jb).*.x.y FROM test_jsonb_dot_notation;
+ERROR:  improper use of "*" at or near "FROM"
+LINE 1: SELECT (jb).*.x.y FROM test_jsonb_dot_notation;
+                          ^
+SELECT (jb).*.x.z FROM test_jsonb_dot_notation;
+ERROR:  improper use of "*" at or near "FROM"
+LINE 1: SELECT (jb).*.x.z FROM test_jsonb_dot_notation;
+                          ^
+SELECT (jb).*.*.y FROM test_jsonb_dot_notation;
+ERROR:  improper use of "*" at or near "FROM"
+LINE 1: SELECT (jb).*.*.y FROM test_jsonb_dot_notation;
+                          ^
+SELECT (jb).*.*.* FROM test_jsonb_dot_notation;
+ERROR:  improper use of "*" at or near "FROM"
+LINE 1: SELECT (jb).*.*.* FROM test_jsonb_dot_notation;
+                          ^
+SELECT (jb).*.*.*.* FROM test_jsonb_dot_notation;
+ERROR:  improper use of "*" at or near "FROM"
+LINE 1: SELECT (jb).*.*.*.* FROM test_jsonb_dot_notation;
+                            ^
+SELECT (jb).a.b.c.* FROM test_jsonb_dot_notation;
+ERROR:  type jsonb is not composite
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).* FROM test_jsonb_dot_notation;
+ERROR:  type jsonb is not composite
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: jb.a
+(2 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: jb.a[1]
+(2 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*['b'] FROM test_jsonb_dot_notation;
+ERROR:  improper use of "*" at or near "FROM"
+LINE 1: EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*['b'] FROM test_...
+                                                          ^
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:2]['b'].b FROM test_jsonb_dot_notation;
+ERROR:  improper use of "*" at or near "FROM"
+LINE 1: ... (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:2]['b'].b FROM test_...
+                                                             ^
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 544bb610e2d..4b49d59222b 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1286,30 +1286,46 @@ select jsonb_insert('{"a": {"b": "value"}}', '{a, b}', '"new_value"', true);
 
 -- jsonb subscript
 select ('123'::jsonb)['a'];
+select ('123'::jsonb).a;
 select ('123'::jsonb)[0];
 select ('123'::jsonb)[NULL];
 select ('{"a": 1}'::jsonb)['a'];
+select ('{"a": 1}'::jsonb).a;
 select ('{"a": 1}'::jsonb)[0];
 select ('{"a": 1}'::jsonb)['not_exist'];
+select ('{"a": 1}'::jsonb)."not_exist";
 select ('{"a": 1}'::jsonb)[NULL];
 select ('[1, "2", null]'::jsonb)['a'];
+select ('[1, "2", null]'::jsonb).a;
 select ('[1, "2", null]'::jsonb)[0];
 select ('[1, "2", null]'::jsonb)['1'];
+select ('[1, "2", null]'::jsonb)."1";
 select ('[1, "2", null]'::jsonb)[1.0];
 select ('[1, "2", null]'::jsonb)[2];
 select ('[1, "2", null]'::jsonb)[3];
 select ('[1, "2", null]'::jsonb)[-2];
 select ('[1, "2", null]'::jsonb)[1]['a'];
+select ('[1, "2", null]'::jsonb)[1].a;
 select ('[1, "2", null]'::jsonb)[1][0];
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['b'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).b;
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d;
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'][1];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d[1];
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d']['a'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['a'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d.a;
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1;
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2;
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']['a3'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2.a3;
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'];
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1;
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'][2];
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1[2];
 
 -- slices are not supported
 select ('{"a": 1}'::jsonb)['a':'b'];
@@ -1572,3 +1588,43 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::float8;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int2;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int4;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
+
+-- dot notation
+CREATE TABLE test_jsonb_dot_notation AS
+SELECT '{"a": [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], "b": [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]}'::jsonb jb;
+
+SELECT (jb).* FROM test_jsonb_dot_notation;
+SELECT (jb).* FROM test_jsonb_dot_notation t;
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+SELECT (jb).* FROM test_jsonb_dot_notation;
+SELECT (t.jb).* FROM test_jsonb_dot_notation;
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+SELECT (jb).a FROM test_jsonb_dot_notation;
+SELECT (jb).a FROM test_jsonb_dot_notation;
+SELECT (jb).b FROM test_jsonb_dot_notation;
+SELECT (jb).c FROM test_jsonb_dot_notation;
+SELECT (jb).a.b FROM test_jsonb_dot_notation;
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.b FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.x FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.y FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+SELECT (jb).*.x FROM test_jsonb_dot_notation t;
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+SELECT ((jb).*)[:].x FROM test_jsonb_dot_notation t;
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+SELECT (jb).*.x.* FROM test_jsonb_dot_notation;
+SELECT (jb).*.x.y FROM test_jsonb_dot_notation;
+SELECT (jb).*.x.z FROM test_jsonb_dot_notation;
+SELECT (jb).*.*.y FROM test_jsonb_dot_notation;
+SELECT (jb).*.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).*.*.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.b.c.* FROM test_jsonb_dot_notation;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).* FROM test_jsonb_dot_notation;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a FROM test_jsonb_dot_notation;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*['b'] FROM test_jsonb_dot_notation;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:2]['b'].b FROM test_jsonb_dot_notation;
-- 
2.39.5 (Apple Git-154)



^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
@ 2025-02-27 15:46         ` Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  0 siblings, 1 reply; 67+ messages in thread

From: Alexandra Wang @ 2025-02-27 15:46 UTC (permalink / raw)
  To: Peter Eisentraut <[email protected]>; +Cc: Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>; jian he <[email protected]>; [email protected]

Hello hackers,

I’ve fixed the compilation failure for hstore and updated the patches.
In this version, I’ve further cleaned up the code and added more
comments. I hope this helps!

Summary of changes:

v8-0001 through v8-0005:
Refactoring and preparatory steps for the actual implementation.

v8-0006 (Implement read-only dot notation for JSONB):
I removed the vars field (introduced in v7) from JsonbSubWorkspace
after realizing that JsonPathVariable is not actually needed for
dot-notation.

v8-0007 (Allow wildcard member access for JSONB):
I'm aware that the #if 0 in check_indirection() is not ideal. I
haven’t removed it yet because I’m still reviewing other cases—beyond
our JSONB simplified accessor use case—where this check should remain
strict. I’ll post an additional patch to address this.

Looking forward to comments and feedback!

Thanks,
Alex


Attachments:

  [application/octet-stream] v8-0002-Pass-field-accessors-to-generic-subscripting.patch (9.8K, ../../CAK98qZ0EfrPcv3ZwGdeDiLPEpXYfHOEc8S2D=OCPJGcyWy5d-g@mail.gmail.com/3-v8-0002-Pass-field-accessors-to-generic-subscripting.patch)
  download | inline diff:
From 282521568617a704df3e09279f568f31b1871411 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Sat, 1 Apr 2023 16:21:20 +0300
Subject: [PATCH v8 2/7] Pass field accessors to generic subscripting

---
 src/backend/parser/parse_expr.c   | 66 ++++++++++++++++++++-----------
 src/backend/parser/parse_node.c   | 41 +++++++++++++++++--
 src/backend/parser/parse_target.c |  3 +-
 src/backend/utils/adt/arraysubs.c | 13 ++++--
 src/backend/utils/adt/jsonbsubs.c | 11 +++++-
 src/include/parser/parse_node.h   |  3 +-
 6 files changed, 105 insertions(+), 32 deletions(-)

diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 2c0f4a50b21..8ea51176196 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -442,8 +442,9 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 	ListCell   *i;
 
 	/*
-	 * We have to split any field-selection operations apart from
-	 * subscripting.  Adjacent A_Indices nodes have to be treated as a single
+	 * Combine field names and subscripts into a single indirection list, as
+	 * some subscripting containers, such as jsonb, support field access using
+	 * dot notation. Adjacent A_Indices nodes have to be treated as a single
 	 * multidimensional subscript operation.
 	 */
 	foreach(i, ind->indirection)
@@ -461,19 +462,43 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		}
 		else
 		{
-			Node	   *newresult;
-
 			Assert(IsA(n, String));
+			subscripts = lappend(subscripts, n);
+		}
+	}
+
+	while (subscripts)
+	{
+		/* try processing container subscripts first */
+		Node	   *newresult = (Node *)
+			transformContainerSubscripts(pstate,
+										 result,
+										 exprType(result),
+										 exprTypmod(result),
+										 &subscripts,
+										 false,
+										 true);
+
+		if (!newresult)
+		{
+			/*
+			 * generic subscripting failed; falling back to function call or
+			 * field selection for a composite type.
+			 */
+			Node	   *n;
+
+			Assert(subscripts);
 
-			/* process subscripts before this field selection */
-			while (subscripts)
-				result = (Node *) transformContainerSubscripts(pstate,
-															   result,
-															   exprType(result),
-															   exprTypmod(result),
-															   &subscripts,
-															   false);
+			n = linitial(subscripts);
+
+			if (!IsA(n, String))
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("cannot subscript type %s because it does not support subscripting",
+								format_type_be(exprType(result))),
+						 parser_errposition(pstate, exprLocation(result))));
 
+			/* try to find function for field selection */
 			newresult = ParseFuncOrColumn(pstate,
 										  list_make1(n),
 										  list_make1(result),
@@ -481,19 +506,16 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 										  NULL,
 										  false,
 										  location);
-			if (newresult == NULL)
+
+			if (!newresult)
 				unknown_attribute(pstate, result, strVal(n), location);
-			result = newresult;
+
+			/* consume field select */
+			subscripts = list_delete_first(subscripts);
 		}
+
+		result = newresult;
 	}
-	/* process trailing subscripts, if any */
-	while (subscripts)
-		result = (Node *) transformContainerSubscripts(pstate,
-													   result,
-													   exprType(result),
-													   exprTypmod(result),
-													   &subscripts,
-													   false);
 
 	return result;
 }
diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c
index 19a6b678e67..b3e476eb181 100644
--- a/src/backend/parser/parse_node.c
+++ b/src/backend/parser/parse_node.c
@@ -238,6 +238,8 @@ transformContainerType(Oid *containerType, int32 *containerTypmod)
  * containerTypMod	typmod for the container
  * indirection		Untransformed list of subscripts (must not be NIL)
  * isAssignment		True if this will become a container assignment.
+ * noError			True for return NULL with no error, if the container type
+ * 					is not subscriptable.
  */
 SubscriptingRef *
 transformContainerSubscripts(ParseState *pstate,
@@ -245,13 +247,15 @@ transformContainerSubscripts(ParseState *pstate,
 							 Oid containerType,
 							 int32 containerTypMod,
 							 List **indirection,
-							 bool isAssignment)
+							 bool isAssignment,
+							 bool noError)
 {
 	SubscriptingRef *sbsref;
 	const SubscriptRoutines *sbsroutines;
 	Oid			elementType;
 	bool		isSlice = false;
 	ListCell   *idx;
+	int			indirection_length = list_length(*indirection);
 
 	/*
 	 * Determine the actual container type, smashing any domain.  In the
@@ -267,11 +271,16 @@ transformContainerSubscripts(ParseState *pstate,
 	 */
 	sbsroutines = getSubscriptingRoutines(containerType, &elementType);
 	if (!sbsroutines)
+	{
+		if (noError)
+			return NULL;
+
 		ereport(ERROR,
 				(errcode(ERRCODE_DATATYPE_MISMATCH),
 				 errmsg("cannot subscript type %s because it does not support subscripting",
 						format_type_be(containerType)),
 				 parser_errposition(pstate, exprLocation(containerBase))));
+	}
 
 	/*
 	 * Detect whether any of the indirection items are slice specifiers.
@@ -282,9 +291,9 @@ transformContainerSubscripts(ParseState *pstate,
 	 */
 	foreach(idx, *indirection)
 	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
+		Node	   *ai = lfirst(idx);
 
-		if (ai->is_slice)
+		if (IsA(ai, A_Indices) && castNode(A_Indices, ai)->is_slice)
 		{
 			isSlice = true;
 			break;
@@ -312,6 +321,32 @@ transformContainerSubscripts(ParseState *pstate,
 	sbsroutines->transform(sbsref, indirection, pstate,
 						   isSlice, isAssignment);
 
+	/*
+	 * Error out, if datatype failed to consume any indirection elements.
+	 */
+	if (list_length(*indirection) == indirection_length)
+	{
+		Node	   *ind = linitial(*indirection);
+
+		if (noError)
+			return NULL;
+
+		if (IsA(ind, String))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("type %s does not support dot notation",
+							format_type_be(containerType)),
+					 parser_errposition(pstate, exprLocation(containerBase))));
+		else if (IsA(ind, A_Indices))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("type %s does not support array subscripting",
+							format_type_be(containerType)),
+					 parser_errposition(pstate, exprLocation(containerBase))));
+		else
+			elog(ERROR, "invalid indirection operation: %d", nodeTag(ind));
+	}
+
 	/*
 	 * Verify we got a valid type (this defends, for example, against someone
 	 * using array_subscript_handler as typsubscript without setting typelem).
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 4675a523045..3ef5897f2eb 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -937,7 +937,8 @@ transformAssignmentSubscripts(ParseState *pstate,
 										  containerType,
 										  containerTypMod,
 										  &subscripts,
-										  true);
+										  true,
+										  false);
 
 	typeNeeded = sbsref->refrestype;
 	typmodNeeded = sbsref->reftypmod;
diff --git a/src/backend/utils/adt/arraysubs.c b/src/backend/utils/adt/arraysubs.c
index 234c2c278c1..d03d3519dfd 100644
--- a/src/backend/utils/adt/arraysubs.c
+++ b/src/backend/utils/adt/arraysubs.c
@@ -62,6 +62,7 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	List	   *upperIndexpr = NIL;
 	List	   *lowerIndexpr = NIL;
 	ListCell   *idx;
+	int			ndim;
 
 	/*
 	 * Transform the subscript expressions, and separate upper and lower
@@ -73,9 +74,14 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	 */
 	foreach(idx, *indirection)
 	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
+		A_Indices  *ai;
 		Node	   *subexpr;
 
+		if (!IsA(lfirst(idx), A_Indices))
+			break;
+
+		ai = lfirst_node(A_Indices, idx);
+
 		if (isSlice)
 		{
 			if (ai->lidx)
@@ -145,14 +151,15 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->reflowerindexpr = lowerIndexpr;
 
 	/* Verify subscript list lengths are within implementation limit */
-	if (list_length(upperIndexpr) > MAXDIM)
+	ndim = list_length(upperIndexpr);
+	if (ndim > MAXDIM)
 		ereport(ERROR,
 				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 				 errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
 						list_length(upperIndexpr), MAXDIM)));
 	/* We need not check lowerIndexpr separately */
 
-	*indirection = NIL;
+	*indirection = list_delete_first_n(*indirection, ndim);
 
 	/*
 	 * Determine the result type of the subscripting operation.  It's the same
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index 8ad6aa1ad4f..a0d38a0fd80 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -55,9 +55,14 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	 */
 	foreach(idx, *indirection)
 	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
+		A_Indices  *ai;
 		Node	   *subExpr;
 
+		if (!IsA(lfirst(idx), A_Indices))
+			break;
+
+		ai = lfirst_node(A_Indices, idx);
+
 		if (isSlice)
 		{
 			Node	   *expr = ai->uidx ? ai->uidx : ai->lidx;
@@ -160,7 +165,9 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->refrestype = JSONBOID;
 	sbsref->reftypmod = -1;
 
-	*indirection = NIL;
+	/* Remove processed elements */
+	if (upperIndexpr)
+		*indirection = list_delete_first_n(*indirection, list_length(upperIndexpr));
 }
 
 /*
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 5ae11ccec33..71b04bd503c 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -378,7 +378,8 @@ extern SubscriptingRef *transformContainerSubscripts(ParseState *pstate,
 													 Oid containerType,
 													 int32 containerTypMod,
 													 List **indirection,
-													 bool isAssignment);
+													 bool isAssignment,
+													 bool noError);
 extern Const *make_const(ParseState *pstate, A_Const *aconst);
 
 #endif							/* PARSE_NODE_H */
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v8-0004-Extract-coerce_jsonpath_subscript.patch (5.4K, ../../CAK98qZ0EfrPcv3ZwGdeDiLPEpXYfHOEc8S2D=OCPJGcyWy5d-g@mail.gmail.com/4-v8-0004-Extract-coerce_jsonpath_subscript.patch)
  download | inline diff:
From d0db105c2abc7059f5765eb490b6102151162691 Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Wed, 26 Feb 2025 13:03:27 -0600
Subject: [PATCH v8 4/7] Extract coerce_jsonpath_subscript()

---
 src/backend/utils/adt/jsonbsubs.c | 142 ++++++++++++++++--------------
 1 file changed, 78 insertions(+), 64 deletions(-)

diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index a0d38a0fd80..3ffe40cfa40 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -32,6 +32,83 @@ typedef struct JsonbSubWorkspace
 	Datum	   *index;			/* Subscript values in Datum format */
 } JsonbSubWorkspace;
 
+static Oid
+jsonb_subscript_type(Node *expr)
+{
+	if (expr && IsA(expr, String))
+		return TEXTOID;
+
+	return exprType(expr);
+}
+
+static Node *
+coerce_jsonpath_subscript(ParseState *pstate, Node *subExpr, Oid numtype)
+{
+	Oid			subExprType = jsonb_subscript_type(subExpr);
+	Oid			targetType = UNKNOWNOID;
+
+	if (subExprType != UNKNOWNOID)
+	{
+		Oid			targets[2] = {numtype, TEXTOID};
+
+		/*
+		 * Jsonb can handle multiple subscript types, but cases when a
+		 * subscript could be coerced to multiple target types must be
+		 * avoided, similar to overloaded functions. It could be possibly
+		 * extend with jsonpath in the future.
+		 */
+		for (int i = 0; i < 2; i++)
+		{
+			if (can_coerce_type(1, &subExprType, &targets[i], COERCION_IMPLICIT))
+			{
+				/*
+				 * One type has already succeeded, it means there are two
+				 * coercion targets possible, failure.
+				 */
+				if (targetType != UNKNOWNOID)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
+							 errhint("jsonb subscript must be coercible to only one type, integer or text."),
+							 parser_errposition(pstate, exprLocation(subExpr))));
+
+				targetType = targets[i];
+			}
+		}
+
+		/*
+		 * No suitable types were found, failure.
+		 */
+		if (targetType == UNKNOWNOID)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
+					 errhint("jsonb subscript must be coercible to either integer or text."),
+					 parser_errposition(pstate, exprLocation(subExpr))));
+	}
+	else
+		targetType = TEXTOID;
+
+	/*
+	 * We known from can_coerce_type that coercion will succeed, so
+	 * coerce_type could be used. Note the implicit coercion context, which is
+	 * required to handle subscripts of different types, similar to overloaded
+	 * functions.
+	 */
+	subExpr = coerce_type(pstate,
+						  subExpr, subExprType,
+						  targetType, -1,
+						  COERCION_IMPLICIT,
+						  COERCE_IMPLICIT_CAST,
+						  -1);
+	if (subExpr == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("jsonb subscript must have text type"),
+				 parser_errposition(pstate, exprLocation(subExpr))));
+
+	return subExpr;
+}
 
 /*
  * Finish parse analysis of a SubscriptingRef expression for a jsonb.
@@ -75,71 +152,8 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 
 		if (ai->uidx)
 		{
-			Oid			subExprType = InvalidOid,
-						targetType = UNKNOWNOID;
-
 			subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
-			subExprType = exprType(subExpr);
-
-			if (subExprType != UNKNOWNOID)
-			{
-				Oid			targets[2] = {INT4OID, TEXTOID};
-
-				/*
-				 * Jsonb can handle multiple subscript types, but cases when a
-				 * subscript could be coerced to multiple target types must be
-				 * avoided, similar to overloaded functions. It could be
-				 * possibly extend with jsonpath in the future.
-				 */
-				for (int i = 0; i < 2; i++)
-				{
-					if (can_coerce_type(1, &subExprType, &targets[i], COERCION_IMPLICIT))
-					{
-						/*
-						 * One type has already succeeded, it means there are
-						 * two coercion targets possible, failure.
-						 */
-						if (targetType != UNKNOWNOID)
-							ereport(ERROR,
-									(errcode(ERRCODE_DATATYPE_MISMATCH),
-									 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
-									 errhint("jsonb subscript must be coercible to only one type, integer or text."),
-									 parser_errposition(pstate, exprLocation(subExpr))));
-
-						targetType = targets[i];
-					}
-				}
-
-				/*
-				 * No suitable types were found, failure.
-				 */
-				if (targetType == UNKNOWNOID)
-					ereport(ERROR,
-							(errcode(ERRCODE_DATATYPE_MISMATCH),
-							 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
-							 errhint("jsonb subscript must be coercible to either integer or text."),
-							 parser_errposition(pstate, exprLocation(subExpr))));
-			}
-			else
-				targetType = TEXTOID;
-
-			/*
-			 * We known from can_coerce_type that coercion will succeed, so
-			 * coerce_type could be used. Note the implicit coercion context,
-			 * which is required to handle subscripts of different types,
-			 * similar to overloaded functions.
-			 */
-			subExpr = coerce_type(pstate,
-								  subExpr, subExprType,
-								  targetType, -1,
-								  COERCION_IMPLICIT,
-								  COERCE_IMPLICIT_CAST,
-								  -1);
-			if (subExpr == NULL)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("jsonb subscript must have text type"),
-						 parser_errposition(pstate, exprLocation(subExpr))));
+			subExpr = coerce_jsonpath_subscript(pstate, subExpr, INT4OID);
 		}
 		else
 		{
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v8-0001-Allow-transformation-only-of-a-sublist-of-subscri.patch (8.3K, ../../CAK98qZ0EfrPcv3ZwGdeDiLPEpXYfHOEc8S2D=OCPJGcyWy5d-g@mail.gmail.com/5-v8-0001-Allow-transformation-only-of-a-sublist-of-subscri.patch)
  download | inline diff:
From 0dc5c192db78e352201f5a778eb0f545835fdb56 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Fri, 14 Oct 2022 15:35:22 +0300
Subject: [PATCH v8 1/7] Allow transformation only of a sublist of subscripts

---
 contrib/hstore/hstore_subs.c      | 10 ++++++----
 src/backend/parser/parse_expr.c   |  9 ++++-----
 src/backend/parser/parse_node.c   |  4 ++--
 src/backend/parser/parse_target.c |  2 +-
 src/backend/utils/adt/arraysubs.c |  6 ++++--
 src/backend/utils/adt/jsonbsubs.c |  6 ++++--
 src/include/nodes/subscripting.h  |  7 ++++++-
 src/include/parser/parse_node.h   |  2 +-
 8 files changed, 28 insertions(+), 18 deletions(-)

diff --git a/contrib/hstore/hstore_subs.c b/contrib/hstore/hstore_subs.c
index 3d03f66fa0d..1b29543ab67 100644
--- a/contrib/hstore/hstore_subs.c
+++ b/contrib/hstore/hstore_subs.c
@@ -40,7 +40,7 @@
  */
 static void
 hstore_subscript_transform(SubscriptingRef *sbsref,
-						   List *indirection,
+						   List **indirection,
 						   ParseState *pstate,
 						   bool isSlice,
 						   bool isAssignment)
@@ -49,15 +49,15 @@ hstore_subscript_transform(SubscriptingRef *sbsref,
 	Node	   *subexpr;
 
 	/* We support only single-subscript, non-slice cases */
-	if (isSlice || list_length(indirection) != 1)
+	if (isSlice || list_length(*indirection) != 1)
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("hstore allows only one subscript"),
 				 parser_errposition(pstate,
-									exprLocation((Node *) indirection))));
+									exprLocation((Node *) *indirection))));
 
 	/* Transform the subscript expression to type text */
-	ai = linitial_node(A_Indices, indirection);
+	ai = linitial_node(A_Indices, *indirection);
 	Assert(ai->uidx != NULL && ai->lidx == NULL && !ai->is_slice);
 
 	subexpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
@@ -81,6 +81,8 @@ hstore_subscript_transform(SubscriptingRef *sbsref,
 	/* Determine the result type of the subscripting operation; always text */
 	sbsref->refrestype = TEXTOID;
 	sbsref->reftypmod = -1;
+
+	*indirection = NIL;
 }
 
 /*
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index bad1df732ea..2c0f4a50b21 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -466,14 +466,13 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 			Assert(IsA(n, String));
 
 			/* process subscripts before this field selection */
-			if (subscripts)
+			while (subscripts)
 				result = (Node *) transformContainerSubscripts(pstate,
 															   result,
 															   exprType(result),
 															   exprTypmod(result),
-															   subscripts,
+															   &subscripts,
 															   false);
-			subscripts = NIL;
 
 			newresult = ParseFuncOrColumn(pstate,
 										  list_make1(n),
@@ -488,12 +487,12 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		}
 	}
 	/* process trailing subscripts, if any */
-	if (subscripts)
+	while (subscripts)
 		result = (Node *) transformContainerSubscripts(pstate,
 													   result,
 													   exprType(result),
 													   exprTypmod(result),
-													   subscripts,
+													   &subscripts,
 													   false);
 
 	return result;
diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c
index d6feb16aef3..19a6b678e67 100644
--- a/src/backend/parser/parse_node.c
+++ b/src/backend/parser/parse_node.c
@@ -244,7 +244,7 @@ transformContainerSubscripts(ParseState *pstate,
 							 Node *containerBase,
 							 Oid containerType,
 							 int32 containerTypMod,
-							 List *indirection,
+							 List **indirection,
 							 bool isAssignment)
 {
 	SubscriptingRef *sbsref;
@@ -280,7 +280,7 @@ transformContainerSubscripts(ParseState *pstate,
 	 * element.  If any of the items are slice specifiers (lower:upper), then
 	 * the subscript expression means a container slice operation.
 	 */
-	foreach(idx, indirection)
+	foreach(idx, *indirection)
 	{
 		A_Indices  *ai = lfirst_node(A_Indices, idx);
 
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 4aba0d9d4d5..4675a523045 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -936,7 +936,7 @@ transformAssignmentSubscripts(ParseState *pstate,
 										  basenode,
 										  containerType,
 										  containerTypMod,
-										  subscripts,
+										  &subscripts,
 										  true);
 
 	typeNeeded = sbsref->refrestype;
diff --git a/src/backend/utils/adt/arraysubs.c b/src/backend/utils/adt/arraysubs.c
index 2940fb8e8d7..234c2c278c1 100644
--- a/src/backend/utils/adt/arraysubs.c
+++ b/src/backend/utils/adt/arraysubs.c
@@ -54,7 +54,7 @@ typedef struct ArraySubWorkspace
  */
 static void
 array_subscript_transform(SubscriptingRef *sbsref,
-						  List *indirection,
+						  List **indirection,
 						  ParseState *pstate,
 						  bool isSlice,
 						  bool isAssignment)
@@ -71,7 +71,7 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	 * indirection items to slices by treating the single subscript as the
 	 * upper bound and supplying an assumed lower bound of 1.
 	 */
-	foreach(idx, indirection)
+	foreach(idx, *indirection)
 	{
 		A_Indices  *ai = lfirst_node(A_Indices, idx);
 		Node	   *subexpr;
@@ -152,6 +152,8 @@ array_subscript_transform(SubscriptingRef *sbsref,
 						list_length(upperIndexpr), MAXDIM)));
 	/* We need not check lowerIndexpr separately */
 
+	*indirection = NIL;
+
 	/*
 	 * Determine the result type of the subscripting operation.  It's the same
 	 * as the array type if we're slicing, else it's the element type.  In
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index de64d498512..8ad6aa1ad4f 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -41,7 +41,7 @@ typedef struct JsonbSubWorkspace
  */
 static void
 jsonb_subscript_transform(SubscriptingRef *sbsref,
-						  List *indirection,
+						  List **indirection,
 						  ParseState *pstate,
 						  bool isSlice,
 						  bool isAssignment)
@@ -53,7 +53,7 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	 * Transform and convert the subscript expressions. Jsonb subscripting
 	 * does not support slices, look only and the upper index.
 	 */
-	foreach(idx, indirection)
+	foreach(idx, *indirection)
 	{
 		A_Indices  *ai = lfirst_node(A_Indices, idx);
 		Node	   *subExpr;
@@ -159,6 +159,8 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	/* Determine the result type of the subscripting operation; always jsonb */
 	sbsref->refrestype = JSONBOID;
 	sbsref->reftypmod = -1;
+
+	*indirection = NIL;
 }
 
 /*
diff --git a/src/include/nodes/subscripting.h b/src/include/nodes/subscripting.h
index 234e8ad8012..5d576af346f 100644
--- a/src/include/nodes/subscripting.h
+++ b/src/include/nodes/subscripting.h
@@ -71,6 +71,11 @@ struct SubscriptExecSteps;
  * does not care to support slicing, it can just throw an error if isSlice.)
  * See array_subscript_transform() for sample code.
  *
+ * The transform method receives a pointer to a list of raw indirections.
+ * This allows the method to parse a sublist of the indirections (typically
+ * the prefix) and modify the original list in place, enabling the caller to
+ * either process the remaining indirections differently or raise an error.
+ *
  * The transform method is also responsible for identifying the result type
  * of the subscripting operation.  At call, refcontainertype and reftypmod
  * describe the container type (this will be a base type not a domain), and
@@ -93,7 +98,7 @@ struct SubscriptExecSteps;
  * assignment must return.
  */
 typedef void (*SubscriptTransform) (SubscriptingRef *sbsref,
-									List *indirection,
+									List **indirection,
 									struct ParseState *pstate,
 									bool isSlice,
 									bool isAssignment);
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 994284019fb..5ae11ccec33 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -377,7 +377,7 @@ extern SubscriptingRef *transformContainerSubscripts(ParseState *pstate,
 													 Node *containerBase,
 													 Oid containerType,
 													 int32 containerTypMod,
-													 List *indirection,
+													 List **indirection,
 													 bool isAssignment);
 extern Const *make_const(ParseState *pstate, A_Const *aconst);
 
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v8-0005-Eanble-String-node-as-field-accessors-in-generic-.patch (7.5K, ../../CAK98qZ0EfrPcv3ZwGdeDiLPEpXYfHOEc8S2D=OCPJGcyWy5d-g@mail.gmail.com/6-v8-0005-Eanble-String-node-as-field-accessors-in-generic-.patch)
  download | inline diff:
From 7a1ee3d55222625976854c53d985a823cd018407 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Sat, 1 Apr 2023 16:21:20 +0300
Subject: [PATCH v8 5/7] Eanble String node as field accessors in generic
 subscripting

Field accessors are represented as String nodes in refupperexprs for
distinguishing from ordinary text subscripts which can be needed for
correct EXPLAIN.

Strings node is no longer a valid expression nodes, so added special
handling for them in walkers in nodeFuncs etc.
---
 src/backend/executor/execExpr.c    | 24 +++++++---
 src/backend/nodes/nodeFuncs.c      | 73 ++++++++++++++++++++++++++----
 src/backend/parser/parse_collate.c | 22 +++++++--
 src/backend/utils/adt/ruleutils.c  | 29 ++++++++----
 4 files changed, 121 insertions(+), 27 deletions(-)

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 03566c4d181..be4213455e5 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -3328,9 +3328,15 @@ ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref,
 		{
 			sbsrefstate->upperprovided[i] = true;
 			/* Each subscript is evaluated into appropriate array entry */
-			ExecInitExprRec(e, state,
-							&sbsrefstate->upperindex[i],
-							&sbsrefstate->upperindexnull[i]);
+			if (IsA(e, String))
+			{
+				sbsrefstate->upperindex[i] = CStringGetTextDatum(strVal(e));
+				sbsrefstate->upperindexnull[i] = false;
+			}
+			else
+				ExecInitExprRec(e, state,
+								&sbsrefstate->upperindex[i],
+								&sbsrefstate->upperindexnull[i]);
 		}
 		i++;
 	}
@@ -3351,9 +3357,15 @@ ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref,
 		{
 			sbsrefstate->lowerprovided[i] = true;
 			/* Each subscript is evaluated into appropriate array entry */
-			ExecInitExprRec(e, state,
-							&sbsrefstate->lowerindex[i],
-							&sbsrefstate->lowerindexnull[i]);
+			if (IsA(e, String))
+			{
+				sbsrefstate->lowerindex[i] = CStringGetTextDatum(strVal(e));
+				sbsrefstate->lowerindexnull[i] = false;
+			}
+			else
+				ExecInitExprRec(e, state,
+								&sbsrefstate->lowerindex[i],
+								&sbsrefstate->lowerindexnull[i]);
 		}
 		i++;
 	}
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 7bc823507f1..a9c29ab8f29 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -2182,12 +2182,28 @@ expression_tree_walker_impl(Node *node,
 		case T_SubscriptingRef:
 			{
 				SubscriptingRef *sbsref = (SubscriptingRef *) node;
+				ListCell   *lc;
+
+				/*
+				 * Recurse directly for upper/lower container index lists,
+				 * skipping String subscripts used for dot notation.
+				 */
+				foreach(lc, sbsref->refupperindexpr)
+				{
+					Node	   *expr = lfirst(lc);
+
+					if (expr && !IsA(expr, String) && WALK(expr))
+						return true;
+				}
+
+				foreach(lc, sbsref->reflowerindexpr)
+				{
+					Node	   *expr = lfirst(lc);
+
+					if (expr && !IsA(expr, String) && WALK(expr))
+						return true;
+				}
 
-				/* recurse directly for upper/lower container index lists */
-				if (LIST_WALK(sbsref->refupperindexpr))
-					return true;
-				if (LIST_WALK(sbsref->reflowerindexpr))
-					return true;
 				/* walker must see the refexpr and refassgnexpr, however */
 				if (WALK(sbsref->refexpr))
 					return true;
@@ -3082,12 +3098,51 @@ expression_tree_mutator_impl(Node *node,
 			{
 				SubscriptingRef *sbsref = (SubscriptingRef *) node;
 				SubscriptingRef *newnode;
+				ListCell   *lc;
+				List	   *exprs = NIL;
 
 				FLATCOPY(newnode, sbsref, SubscriptingRef);
-				MUTATE(newnode->refupperindexpr, sbsref->refupperindexpr,
-					   List *);
-				MUTATE(newnode->reflowerindexpr, sbsref->reflowerindexpr,
-					   List *);
+
+				foreach(lc, sbsref->refupperindexpr)
+				{
+					Node	   *expr = lfirst(lc);
+
+					if (expr && IsA(expr, String))
+					{
+						String	   *str;
+
+						FLATCOPY(str, expr, String);
+						expr = (Node *) str;
+					}
+					else
+						expr = mutator(expr, context);
+
+					exprs = lappend(exprs, expr);
+				}
+
+				newnode->refupperindexpr = exprs;
+
+				exprs = NIL;
+
+				foreach(lc, sbsref->reflowerindexpr)
+				{
+					Node	   *expr = lfirst(lc);
+
+					if (expr && IsA(expr, String))
+					{
+						String	   *str;
+
+						FLATCOPY(str, expr, String);
+						expr = (Node *) str;
+					}
+					else
+						expr = mutator(expr, context);
+
+					exprs = lappend(exprs, expr);
+				}
+
+				newnode->reflowerindexpr = exprs;
+
 				MUTATE(newnode->refexpr, sbsref->refexpr,
 					   Expr *);
 				MUTATE(newnode->refassgnexpr, sbsref->refassgnexpr,
diff --git a/src/backend/parser/parse_collate.c b/src/backend/parser/parse_collate.c
index d2e218353f3..be6dea6ffd2 100644
--- a/src/backend/parser/parse_collate.c
+++ b/src/backend/parser/parse_collate.c
@@ -680,11 +680,25 @@ assign_collations_walker(Node *node, assign_collations_context *context)
 							 * contribute anything.)
 							 */
 							SubscriptingRef *sbsref = (SubscriptingRef *) node;
+							ListCell   *lc;
+
+							/* skip String subscripts used for dot notation */
+							foreach(lc, sbsref->refupperindexpr)
+							{
+								Node	   *expr = lfirst(lc);
+
+								if (expr && !IsA(expr, String))
+									assign_expr_collations(context->pstate, expr);
+							}
+
+							foreach(lc, sbsref->reflowerindexpr)
+							{
+								Node	   *expr = lfirst(lc);
+
+								if (expr && !IsA(expr, String))
+									assign_expr_collations(context->pstate, expr);
+							}
 
-							assign_expr_collations(context->pstate,
-												   (Node *) sbsref->refupperindexpr);
-							assign_expr_collations(context->pstate,
-												   (Node *) sbsref->reflowerindexpr);
 							(void) assign_collations_walker((Node *) sbsref->refexpr,
 															&loccontext);
 							(void) assign_collations_walker((Node *) sbsref->refassgnexpr,
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index d11a8a20eea..cfae8159a76 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -47,6 +47,7 @@
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "nodes/pathnodes.h"
+#include "nodes/subscripting.h"
 #include "optimizer/optimizer.h"
 #include "parser/parse_agg.h"
 #include "parser/parse_func.h"
@@ -12923,17 +12924,29 @@ printSubscripts(SubscriptingRef *sbsref, deparse_context *context)
 	lowlist_item = list_head(sbsref->reflowerindexpr);	/* could be NULL */
 	foreach(uplist_item, sbsref->refupperindexpr)
 	{
-		appendStringInfoChar(buf, '[');
-		if (lowlist_item)
+		Node	   *up = (Node *) lfirst(uplist_item);
+
+		if (IsA(up, String))
+		{
+			appendStringInfoChar(buf, '.');
+			appendStringInfoString(buf, quote_identifier(strVal(up)));
+		}
+		else
 		{
+			appendStringInfoChar(buf, '[');
+			if (lowlist_item)
+			{
+				/* If subexpression is NULL, get_rule_expr prints nothing */
+				get_rule_expr((Node *) lfirst(lowlist_item), context, false);
+				appendStringInfoChar(buf, ':');
+			}
 			/* If subexpression is NULL, get_rule_expr prints nothing */
-			get_rule_expr((Node *) lfirst(lowlist_item), context, false);
-			appendStringInfoChar(buf, ':');
-			lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
+			get_rule_expr((Node *) lfirst(uplist_item), context, false);
+			appendStringInfoChar(buf, ']');
 		}
-		/* If subexpression is NULL, get_rule_expr prints nothing */
-		get_rule_expr((Node *) lfirst(uplist_item), context, false);
-		appendStringInfoChar(buf, ']');
+
+		if (lowlist_item)
+			lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
 	}
 }
 
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v8-0006-Implement-read-only-dot-notation-for-jsonb.patch (26.1K, ../../CAK98qZ0EfrPcv3ZwGdeDiLPEpXYfHOEc8S2D=OCPJGcyWy5d-g@mail.gmail.com/7-v8-0006-Implement-read-only-dot-notation-for-jsonb.patch)
  download | inline diff:
From 82a22db3f286764934ff5fa6b611a6b7af2c0fc8 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Sat, 1 Apr 2023 23:17:53 +0300
Subject: [PATCH v8 6/7] Implement read-only dot notation for jsonb

This patch introduces JSONB member access using dot notation, wildcard
access, and array subscripting with slicing, aligning with the JSON
simplified accessor specified in SQL:2023. Specifically, the following
syntax enhancements are added:

1. Simple dot-notation access to JSONB object fields
2. Wildcard dot-notation access to JSONB object fields
2. Subscripting for index range access to JSONB array elements

Examples:

-- Setup
create table t(x int, y jsonb);
insert into t select 1, '{"a": 1, "b": 42}'::jsonb;
insert into t select 1, '{"a": 2, "b": {"c": 42}}'::jsonb;
insert into t select 1, '{"a": 3, "b": {"c": "42"}, "d":[11, 12]}'::jsonb;

-- Existing syntax predates the SQL standard:
select (t.y)->'b' from t;
select (t.y)->'b'->'c' from t;
select (t.y)->'d'->0 from t;

-- JSON simplified accessor specified by the SQL standard:
select (t.y).b from t;
select (t.y).b.c from t;
select (t.y).d[0] from t;

The SQL standard states that simplified access is equivalent to:
JSON_QUERY (VEP, 'lax $.JC' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR)

where:
  VEP = <value expression primary>
  JC = <JSON simplified accessor op chain>

For example, the JSON_QUERY equivalents of the above queries are:

select json_query(y, 'lax $.b' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t;
select json_query(y, 'lax $.b.c' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t;
select json_query(y, 'lax $.d[0]' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t;

Implementation details:

Extends the existing container subscripting interface to support
container-specific information, specifically a JSONPath expression for
jsonb.

During query transformation, detects dot-notation, wildcard access,
and sliced subscripting. If any of these accessors are present,
constructs a JSONPath expression representing the access chain.

During execution, if a JSONPath expression is present in
JsonbSubWorkspace, executes it via JsonPathQuery().

Does not transform accessors directly into JSON_QUERY during
transformation to preserve the original query structure for EXPLAIN
and CREATE VIEW.
---
 src/backend/utils/adt/jsonbsubs.c   | 293 ++++++++++++++++++++++++++--
 src/include/nodes/primnodes.h       |   7 +
 src/test/regress/expected/jsonb.out | 239 ++++++++++++++++++++++-
 src/test/regress/sql/jsonb.sql      |  43 ++++
 4 files changed, 556 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index 3ffe40cfa40..409cb20539c 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -15,21 +15,30 @@
 #include "postgres.h"
 
 #include "executor/execExpr.h"
+#include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "nodes/subscripting.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_expr.h"
 #include "utils/builtins.h"
 #include "utils/jsonb.h"
+#include "utils/jsonpath.h"
 
 
-/* SubscriptingRefState.workspace for jsonb subscripting execution */
+/*
+ * SubscriptingRefState.workspace for generic jsonb subscripting execution.
+ *
+ * Stores state for both jsonb simple subscripting and dot notation access.
+ * Dot notation additionally uses `jsonpath` for JsonPath evaluation.
+ */
 typedef struct JsonbSubWorkspace
 {
 	bool		expectArray;	/* jsonb root is expected to be an array */
 	Oid		   *indexOid;		/* OID of coerced subscript expression, could
 								 * be only integer or text */
 	Datum	   *index;			/* Subscript values in Datum format */
+	JsonPath   *jsonpath;		/* JsonPath for dot notation execution via
+								 * JsonPathQuery() */
 } JsonbSubWorkspace;
 
 static Oid
@@ -110,6 +119,228 @@ coerce_jsonpath_subscript(ParseState *pstate, Node *subExpr, Oid numtype)
 	return subExpr;
 }
 
+/*
+ * During transformation, determine whether to build a JsonPath
+ * for JsonPathQuery() execution.
+ *
+ * JsonPath is needed if the indirection list includes:
+ * - String-based access (dot notation)
+ * - Wildcard (`*`)
+ * - Slice-based subscripting
+ *
+ * Otherwise, simple jsonb subscripting is sufficient.
+ */
+static bool
+jsonb_check_jsonpath_needed(List *indirection)
+{
+	ListCell   *lc;
+
+	foreach(lc, indirection)
+	{
+		Node	   *accessor = lfirst(lc);
+
+		if (IsA(accessor, String) ||
+			IsA(accessor, A_Star))
+			return true;
+		else
+		{
+			A_Indices  *ai;
+
+			Assert(IsA(accessor, A_Indices));
+			ai = castNode(A_Indices, accessor);
+
+			if (!ai->uidx || ai->lidx)
+			{
+				Assert(ai->is_slice);
+				return true;
+			}
+		}
+	}
+
+	return false;
+}
+
+/*
+ * Helper functions for constructing JsonPath expressions.
+ *
+ * The following functions create various types of JsonPathParseItem nodes,
+ * which are used to build JsonPath expressions for jsonb simplified accessor.
+ */
+
+static JsonPathParseItem *
+make_jsonpath_item(JsonPathItemType type)
+{
+	JsonPathParseItem *v = palloc(sizeof(*v));
+
+	v->type = type;
+	v->next = NULL;
+
+	return v;
+}
+
+static JsonPathParseItem *
+make_jsonpath_item_int(int32 val, List **exprs)
+{
+	JsonPathParseItem *jpi = make_jsonpath_item(jpiNumeric);
+
+	jpi->value.numeric =
+		DatumGetNumeric(DirectFunctionCall1(int4_numeric, Int32GetDatum(val)));
+
+	*exprs = lappend(*exprs, makeConst(INT4OID, -1, InvalidOid, 4,
+									   Int32GetDatum(val), false, true));
+
+	return jpi;
+}
+
+/*
+ * Convert an expression into a JsonPathParseItem.
+ * If the expression is a constant integer, create a direct numeric item.
+ * Otherwise, create a variable reference and add it to the expression list.
+ */
+static JsonPathParseItem *
+make_jsonpath_item_expr(ParseState *pstate, Node *expr, List **exprs)
+{
+	Const	   *cnst;
+
+	expr = transformExpr(pstate, expr, pstate->p_expr_kind);
+
+	if (!IsA(expr, Const))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("jsonb simplified accessor supports subscripting in const int4, got type: %s",
+						format_type_be(exprType(expr))),
+				 parser_errposition(pstate, exprLocation(expr))));
+
+	cnst = (Const *) expr;
+
+	if (cnst->consttype == INT4OID && !cnst->constisnull)
+	{
+		int32		val = DatumGetInt32(cnst->constvalue);
+
+		return make_jsonpath_item_int(val, exprs);
+	}
+
+	ereport(ERROR,
+			(errcode(ERRCODE_DATATYPE_MISMATCH),
+			 errmsg("jsonb simplified accessor supports subscripting in type: INT4, got type: %s",
+					format_type_be(cnst->consttype)),
+			 parser_errposition(pstate, exprLocation(expr))));
+}
+
+/*
+ * jsonb_subscript_make_jsonpath
+ *
+ * Constructs a JsonPath expression from a list of indirections.
+ * This function is used when jsonb subscripting involves dot notation,
+ * wildcards (*), or slice-based subscripting, requiring JsonPath-based
+ * evaluation.
+ *
+ * The function modifies the indirection list in place, removing processed
+ * elements as it converts them into JsonPath components, as follows:
+ * - String keys (dot notation) -> jpiKey items.
+ * - Wildcard (*) -> jpiAnyKey item.
+ * - Array indices and slices -> jpiIndexArray items.
+ *
+ * Parameters:
+ * - pstate: Parse state context.
+ * - indirection: List of subscripting expressions (modified in-place).
+ * - uexprs: Upper-bound expressions extracted from subscripts.
+ * - lexprs: Lower-bound expressions extracted from subscripts.
+ * Returns:
+ * - a Const node containing the transformed JsonPath expression.
+ */
+static Node *
+jsonb_subscript_make_jsonpath(ParseState *pstate, List **indirection,
+							  List **uexprs, List **lexprs)
+{
+	JsonPathParseResult jpres;
+	JsonPathParseItem *path = make_jsonpath_item(jpiRoot);
+	ListCell   *lc;
+	Datum		jsp;
+	int			pathlen = 0;
+
+	*uexprs = NIL;
+	*lexprs = NIL;
+
+	jpres.expr = path;
+	jpres.lax = true;
+
+	foreach(lc, *indirection)
+	{
+		Node	   *accessor = lfirst(lc);
+		JsonPathParseItem *jpi;
+
+		if (IsA(accessor, String))
+		{
+			char	   *field = strVal(accessor);
+
+			jpi = make_jsonpath_item(jpiKey);
+			jpi->value.string.val = field;
+			jpi->value.string.len = strlen(field);
+
+			*uexprs = lappend(*uexprs, accessor);
+		}
+		else if (IsA(accessor, A_Star))
+		{
+			jpi = make_jsonpath_item(jpiAnyKey);
+
+			*uexprs = lappend(*uexprs, NULL);
+		}
+		else if (IsA(accessor, A_Indices))
+		{
+			A_Indices  *ai = castNode(A_Indices, accessor);
+
+			jpi = make_jsonpath_item(jpiIndexArray);
+			jpi->value.array.nelems = 1;
+			jpi->value.array.elems = palloc(sizeof(jpi->value.array.elems[0]));
+
+			if (ai->is_slice)
+			{
+				while (list_length(*lexprs) < list_length(*uexprs))
+					*lexprs = lappend(*lexprs, NULL);
+
+				if (ai->lidx)
+					jpi->value.array.elems[0].from = make_jsonpath_item_expr(pstate, ai->lidx, lexprs);
+				else
+					jpi->value.array.elems[0].from = make_jsonpath_item_int(0, lexprs);
+
+				if (ai->uidx)
+					jpi->value.array.elems[0].to = make_jsonpath_item_expr(pstate, ai->uidx, uexprs);
+				else
+				{
+					jpi->value.array.elems[0].to = make_jsonpath_item(jpiLast);
+					*uexprs = lappend(*uexprs, NULL);
+				}
+			}
+			else
+			{
+				Assert(ai->uidx && !ai->lidx);
+				jpi->value.array.elems[0].from = make_jsonpath_item_expr(pstate, ai->uidx, uexprs);
+				jpi->value.array.elems[0].to = NULL;
+			}
+		}
+		else
+			break;
+
+		/* append path item */
+		path->next = jpi;
+		path = jpi;
+		pathlen++;
+	}
+
+	if (*lexprs)
+	{
+		while (list_length(*lexprs) < list_length(*uexprs))
+			*lexprs = lappend(*lexprs, NULL);
+	}
+
+	*indirection = list_delete_first_n(*indirection, pathlen);
+
+	jsp = jsonPathFromParseResult(&jpres, 0, NULL);
+
+	return (Node *) makeConst(JSONPATHOID, -1, InvalidOid, -1, jsp, false, false);
+}
+
 /*
  * Finish parse analysis of a SubscriptingRef expression for a jsonb.
  *
@@ -126,19 +357,32 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	List	   *upperIndexpr = NIL;
 	ListCell   *idx;
 
+	/* Determine the result type of the subscripting operation; always jsonb */
+	sbsref->refrestype = JSONBOID;
+	sbsref->reftypmod = -1;
+
+	if (jsonb_check_jsonpath_needed(*indirection))
+	{
+		sbsref->refjsonbpath =
+			jsonb_subscript_make_jsonpath(pstate, indirection,
+										  &sbsref->refupperindexpr,
+										  &sbsref->reflowerindexpr);
+		return;
+	}
+
 	/*
 	 * Transform and convert the subscript expressions. Jsonb subscripting
 	 * does not support slices, look only and the upper index.
 	 */
 	foreach(idx, *indirection)
 	{
+		Node	   *i = lfirst(idx);
 		A_Indices  *ai;
 		Node	   *subExpr;
 
-		if (!IsA(lfirst(idx), A_Indices))
-			break;
+		Assert(IsA(i, A_Indices));
 
-		ai = lfirst_node(A_Indices, idx);
+		ai = castNode(A_Indices, i);
 
 		if (isSlice)
 		{
@@ -175,10 +419,6 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->refupperindexpr = upperIndexpr;
 	sbsref->reflowerindexpr = NIL;
 
-	/* Determine the result type of the subscripting operation; always jsonb */
-	sbsref->refrestype = JSONBOID;
-	sbsref->reftypmod = -1;
-
 	/* Remove processed elements */
 	if (upperIndexpr)
 		*indirection = list_delete_first_n(*indirection, list_length(upperIndexpr));
@@ -233,7 +473,7 @@ jsonb_subscript_check_subscripts(ExprState *state,
 			 * For jsonb fetch and assign functions we need to provide path in
 			 * text format. Convert if it's not already text.
 			 */
-			if (workspace->indexOid[i] == INT4OID)
+			if (!workspace->jsonpath && workspace->indexOid[i] == INT4OID)
 			{
 				Datum		datum = sbsrefstate->upperindex[i];
 				char	   *cs = DatumGetCString(DirectFunctionCall1(int4out, datum));
@@ -261,17 +501,32 @@ jsonb_subscript_fetch(ExprState *state,
 {
 	SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
 	JsonbSubWorkspace *workspace = (JsonbSubWorkspace *) sbsrefstate->workspace;
-	Jsonb	   *jsonbSource;
 
 	/* Should not get here if source jsonb (or any subscript) is null */
 	Assert(!(*op->resnull));
 
-	jsonbSource = DatumGetJsonbP(*op->resvalue);
-	*op->resvalue = jsonb_get_element(jsonbSource,
-									  workspace->index,
-									  sbsrefstate->numupper,
-									  op->resnull,
-									  false);
+	if (workspace->jsonpath)
+	{
+		bool		empty = false;
+		bool		error = false;
+
+		*op->resvalue = JsonPathQuery(*op->resvalue, workspace->jsonpath,
+									  JSW_CONDITIONAL,
+									  &empty, &error, NULL,
+									  NULL);
+
+		*op->resnull = empty || error;
+	}
+	else
+	{
+		Jsonb	   *jsonbSource = DatumGetJsonbP(*op->resvalue);
+
+		*op->resvalue = jsonb_get_element(jsonbSource,
+										  workspace->index,
+										  sbsrefstate->numupper,
+										  op->resnull,
+										  false);
+	}
 }
 
 /*
@@ -381,6 +636,7 @@ jsonb_exec_setup(const SubscriptingRef *sbsref,
 	ListCell   *lc;
 	int			nupper = sbsref->refupperindexpr->length;
 	char	   *ptr;
+	bool		useJsonpath = sbsref->refjsonbpath != NULL;
 
 	/* Allocate type-specific workspace with space for per-subscript data */
 	workspace = palloc0(MAXALIGN(sizeof(JsonbSubWorkspace)) +
@@ -388,6 +644,9 @@ jsonb_exec_setup(const SubscriptingRef *sbsref,
 	workspace->expectArray = false;
 	ptr = ((char *) workspace) + MAXALIGN(sizeof(JsonbSubWorkspace));
 
+	if (useJsonpath)
+		workspace->jsonpath = DatumGetJsonPathP(castNode(Const, sbsref->refjsonbpath)->constvalue);
+
 	/*
 	 * This coding assumes sizeof(Datum) >= sizeof(Oid), else we might
 	 * misalign the indexOid pointer
@@ -404,7 +663,7 @@ jsonb_exec_setup(const SubscriptingRef *sbsref,
 		Node	   *expr = lfirst(lc);
 		int			i = foreach_current_index(lc);
 
-		workspace->indexOid[i] = exprType(expr);
+		workspace->indexOid[i] = jsonb_subscript_type(expr);
 	}
 
 	/*
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index d0576da3e25..9d380ed60d6 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -718,6 +718,13 @@ typedef struct SubscriptingRef
 	Expr	   *refexpr;
 	/* expression for the source value, or NULL if fetch */
 	Expr	   *refassgnexpr;
+
+	/*
+	 * container-specific extra information, currently used only by jsonb.
+	 * stores a JsonPath expression when jsonb dot notation is used. NULL for
+	 * simple subscripting.
+	 */
+	Node	   *refjsonbpath;
 } SubscriptingRef;
 
 /*
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 2baff931bf2..0edd19bed9c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -4939,6 +4939,12 @@ select ('123'::jsonb)['a'];
  
 (1 row)
 
+select ('123'::jsonb).a;
+ a 
+---
+ 
+(1 row)
+
 select ('123'::jsonb)[0];
  jsonb 
 -------
@@ -4951,12 +4957,24 @@ select ('123'::jsonb)[NULL];
  
 (1 row)
 
+select ('123'::jsonb).NULL;
+ null 
+------
+ 
+(1 row)
+
 select ('{"a": 1}'::jsonb)['a'];
  jsonb 
 -------
  1
 (1 row)
 
+select ('{"a": 1}'::jsonb).a;
+ a 
+---
+ 1
+(1 row)
+
 select ('{"a": 1}'::jsonb)[0];
  jsonb 
 -------
@@ -4969,6 +4987,12 @@ select ('{"a": 1}'::jsonb)['not_exist'];
  
 (1 row)
 
+select ('{"a": 1}'::jsonb)."not_exist";
+ not_exist 
+-----------
+ 
+(1 row)
+
 select ('{"a": 1}'::jsonb)[NULL];
  jsonb 
 -------
@@ -4981,6 +5005,12 @@ select ('[1, "2", null]'::jsonb)['a'];
  
 (1 row)
 
+select ('[1, "2", null]'::jsonb).a;
+ a 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[0];
  jsonb 
 -------
@@ -4993,6 +5023,12 @@ select ('[1, "2", null]'::jsonb)['1'];
  "2"
 (1 row)
 
+select ('[1, "2", null]'::jsonb)."1";
+ 1 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[1.0];
 ERROR:  subscript type numeric is not supported
 LINE 1: select ('[1, "2", null]'::jsonb)[1.0];
@@ -5022,6 +5058,12 @@ select ('[1, "2", null]'::jsonb)[1]['a'];
  
 (1 row)
 
+select ('[1, "2", null]'::jsonb)[1].a;
+ a 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[1][0];
  jsonb 
 -------
@@ -5034,73 +5076,139 @@ select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['b'];
  "c"
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).b;
+  b  
+-----
+ "c"
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'];
    jsonb   
 -----------
  [1, 2, 3]
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d;
+     d     
+-----------
+ [1, 2, 3]
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'][1];
  jsonb 
 -------
  2
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d[1];
+ d 
+---
+ 2
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d']['a'];
  jsonb 
 -------
  
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['a'];
+ERROR:  jsonb simplified accessor supports subscripting in type: INT4, got type: unknown
+LINE 1: select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['a'];
+                                                               ^
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d.a;
+ a 
+---
+ 
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1'];
      jsonb     
 ---------------
  {"a2": "aaa"}
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1;
+      a1       
+---------------
+ {"a2": "aaa"}
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2'];
  jsonb 
 -------
  "aaa"
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2;
+  a2   
+-------
+ "aaa"
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']['a3'];
  jsonb 
 -------
  
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2.a3;
+ a3 
+----
+ 
+(1 row)
+
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'];
          jsonb         
 -----------------------
  ["aaa", "bbb", "ccc"]
 (1 row)
 
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1;
+          b1           
+-----------------------
+ ["aaa", "bbb", "ccc"]
+(1 row)
+
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'][2];
  jsonb 
 -------
  "ccc"
 (1 row)
 
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1[2];
+  b1   
+-------
+ "ccc"
+(1 row)
+
 -- slices are not supported
 select ('{"a": 1}'::jsonb)['a':'b'];
-ERROR:  jsonb subscript does not support slices
+ERROR:  jsonb simplified accessor supports subscripting in type: INT4, got type: unknown
 LINE 1: select ('{"a": 1}'::jsonb)['a':'b'];
-                                       ^
+                                   ^
 select ('[1, "2", null]'::jsonb)[1:2];
-ERROR:  jsonb subscript does not support slices
-LINE 1: select ('[1, "2", null]'::jsonb)[1:2];
-                                           ^
+    jsonb    
+-------------
+ ["2", null]
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[:2];
 ERROR:  jsonb subscript does not support slices
 LINE 1: select ('[1, "2", null]'::jsonb)[:2];
                                           ^
 select ('[1, "2", null]'::jsonb)[1:];
-ERROR:  jsonb subscript does not support slices
-LINE 1: select ('[1, "2", null]'::jsonb)[1:];
-                                         ^
+    jsonb    
+-------------
+ ["2", null]
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[:];
-ERROR:  jsonb subscript does not support slices
+     jsonb      
+----------------
+ [1, "2", null]
+(1 row)
+
 create TEMP TABLE test_jsonb_subscript (
        id int,
        test_json jsonb
@@ -5781,3 +5889,116 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
  12345
 (1 row)
 
+-- dot notation
+CREATE TABLE test_jsonb_dot_notation AS
+SELECT '{"a": [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], "b": [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]}'::jsonb jb;
+SELECT (jb).a FROM test_jsonb_dot_notation;
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+SELECT (jb).b FROM test_jsonb_dot_notation;
+                         b                         
+---------------------------------------------------
+ [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]
+(1 row)
+
+SELECT (jb).c FROM test_jsonb_dot_notation;
+ c 
+---
+ 
+(1 row)
+
+SELECT (jb).a.b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT (jb).a[2].b FROM test_jsonb_dot_notation;
+  b  
+-----
+ "c"
+(1 row)
+
+SELECT (jb).a[2:3].b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT (jb).a[2:].b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT (jb).a[:2].b FROM test_jsonb_dot_notation;
+  b  
+-----
+ "c"
+(1 row)
+
+SELECT (jb).a[:].b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT (jb).a.x.y FROM test_jsonb_dot_notation;
+   y   
+-------
+ "yyy"
+(1 row)
+
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
+            x             
+--------------------------
+ {"y": "YYY", "z": "ZZZ"}
+(1 row)
+
+SELECT (jb).b.x.z FROM test_jsonb_dot_notation;
+   z   
+-------
+ "ZZZ"
+(1 row)
+
+SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
+ c 
+---
+ 
+(1 row)
+
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+ERROR:  type jsonb is not composite
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).* FROM test_jsonb_dot_notation;
+ERROR:  type jsonb is not composite
+SELECT (jb).* FROM test_jsonb_dot_notation;
+ERROR:  type jsonb is not composite
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+                  QUERY PLAN                  
+----------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation t
+   Output: jb.a
+(2 rows)
+
+SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: jb.a[1]
+(2 rows)
+
+SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+ a 
+---
+ 2
+(1 row)
+
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 544bb610e2d..37d40e76610 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1286,30 +1286,47 @@ select jsonb_insert('{"a": {"b": "value"}}', '{a, b}', '"new_value"', true);
 
 -- jsonb subscript
 select ('123'::jsonb)['a'];
+select ('123'::jsonb).a;
 select ('123'::jsonb)[0];
 select ('123'::jsonb)[NULL];
+select ('123'::jsonb).NULL;
 select ('{"a": 1}'::jsonb)['a'];
+select ('{"a": 1}'::jsonb).a;
 select ('{"a": 1}'::jsonb)[0];
 select ('{"a": 1}'::jsonb)['not_exist'];
+select ('{"a": 1}'::jsonb)."not_exist";
 select ('{"a": 1}'::jsonb)[NULL];
 select ('[1, "2", null]'::jsonb)['a'];
+select ('[1, "2", null]'::jsonb).a;
 select ('[1, "2", null]'::jsonb)[0];
 select ('[1, "2", null]'::jsonb)['1'];
+select ('[1, "2", null]'::jsonb)."1";
 select ('[1, "2", null]'::jsonb)[1.0];
 select ('[1, "2", null]'::jsonb)[2];
 select ('[1, "2", null]'::jsonb)[3];
 select ('[1, "2", null]'::jsonb)[-2];
 select ('[1, "2", null]'::jsonb)[1]['a'];
+select ('[1, "2", null]'::jsonb)[1].a;
 select ('[1, "2", null]'::jsonb)[1][0];
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['b'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).b;
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d;
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'][1];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d[1];
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d']['a'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['a'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d.a;
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1;
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2;
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']['a3'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2.a3;
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'];
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1;
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'][2];
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1[2];
 
 -- slices are not supported
 select ('{"a": 1}'::jsonb)['a':'b'];
@@ -1572,3 +1589,29 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::float8;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int2;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int4;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
+
+-- dot notation
+CREATE TABLE test_jsonb_dot_notation AS
+SELECT '{"a": [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], "b": [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]}'::jsonb jb;
+
+SELECT (jb).a FROM test_jsonb_dot_notation;
+SELECT (jb).b FROM test_jsonb_dot_notation;
+SELECT (jb).c FROM test_jsonb_dot_notation;
+SELECT (jb).a.b FROM test_jsonb_dot_notation;
+SELECT (jb).a[2].b FROM test_jsonb_dot_notation;
+SELECT (jb).a[2:3].b FROM test_jsonb_dot_notation;
+SELECT (jb).a[2:].b FROM test_jsonb_dot_notation;
+SELECT (jb).a[:2].b FROM test_jsonb_dot_notation;
+SELECT (jb).a[:].b FROM test_jsonb_dot_notation;
+SELECT (jb).a.x.y FROM test_jsonb_dot_notation;
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
+SELECT (jb).b.x.z FROM test_jsonb_dot_notation;
+SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).* FROM test_jsonb_dot_notation;
+SELECT (jb).* FROM test_jsonb_dot_notation;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+SELECT (jb).a[1] FROM test_jsonb_dot_notation;
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v8-0003-Export-jsonPathFromParseResult.patch (2.4K, ../../CAK98qZ0EfrPcv3ZwGdeDiLPEpXYfHOEc8S2D=OCPJGcyWy5d-g@mail.gmail.com/8-v8-0003-Export-jsonPathFromParseResult.patch)
  download | inline diff:
From 2d1b29146ee6b4caa21dfef1475bf133b6322105 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Sat, 1 Apr 2023 23:15:55 +0300
Subject: [PATCH v8 3/7] Export jsonPathFromParseResult()

---
 src/backend/utils/adt/jsonpath.c | 19 +++++++++++++++----
 src/include/utils/jsonpath.h     |  4 ++++
 2 files changed, 19 insertions(+), 4 deletions(-)

diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index 762f7e8a09d..1536797cf23 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -166,15 +166,13 @@ jsonpath_send(PG_FUNCTION_ARGS)
  * Converts C-string to a jsonpath value.
  *
  * Uses jsonpath parser to turn string into an AST, then
- * flattenJsonPathParseItem() does second pass turning AST into binary
+ * jsonPathFromParseResult() does second pass turning AST into binary
  * representation of jsonpath.
  */
 static Datum
 jsonPathFromCstring(char *in, int len, struct Node *escontext)
 {
 	JsonPathParseResult *jsonpath = parsejsonpath(in, len, escontext);
-	JsonPath   *res;
-	StringInfoData buf;
 
 	if (SOFT_ERROR_OCCURRED(escontext))
 		return (Datum) 0;
@@ -185,8 +183,21 @@ jsonPathFromCstring(char *in, int len, struct Node *escontext)
 				 errmsg("invalid input syntax for type %s: \"%s\"", "jsonpath",
 						in)));
 
+	return jsonPathFromParseResult(jsonpath, 4 * len, escontext);
+}
+
+/*
+ * Converts jsonpath AST into jsonpath value in binary.
+ */
+Datum
+jsonPathFromParseResult(JsonPathParseResult *jsonpath, int estimated_len,
+						struct Node *escontext)
+{
+	JsonPath   *res;
+	StringInfoData buf;
+
 	initStringInfo(&buf);
-	enlargeStringInfo(&buf, 4 * len /* estimation */ );
+	enlargeStringInfo(&buf, estimated_len);
 
 	appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
 
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index 23a76d233e9..e05941623e7 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -281,6 +281,10 @@ extern JsonPathParseResult *parsejsonpath(const char *str, int len,
 extern bool jspConvertRegexFlags(uint32 xflags, int *result,
 								 struct Node *escontext);
 
+extern Datum jsonPathFromParseResult(JsonPathParseResult *jsonpath,
+									 int estimated_len,
+									 struct Node *escontext);
+
 /*
  * Struct for details about external variables passed into jsonpath executor
  */
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v8-0007-Allow-wild-card-member-access-for-jsonb.patch (18.1K, ../../CAK98qZ0EfrPcv3ZwGdeDiLPEpXYfHOEc8S2D=OCPJGcyWy5d-g@mail.gmail.com/9-v8-0007-Allow-wild-card-member-access-for-jsonb.patch)
  download | inline diff:
From 47c48c12204e3ea4dc172004d54a94e5c773b9df Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Sat, 1 Apr 2023 23:15:26 +0300
Subject: [PATCH v8 7/7] Allow wild card member access for jsonb

---
 src/backend/parser/gram.y           |   2 +
 src/backend/parser/parse_expr.c     |  34 +++--
 src/backend/parser/parse_target.c   |  60 ++++++---
 src/backend/utils/adt/ruleutils.c   |   6 +-
 src/include/parser/parse_expr.h     |   3 +
 src/test/regress/expected/jsonb.out | 188 +++++++++++++++++++++++++++-
 src/test/regress/sql/jsonb.sql      |  30 +++++
 7 files changed, 288 insertions(+), 35 deletions(-)

diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7d99c9355c6..041968c35db 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -18968,6 +18968,7 @@ check_func_name(List *names, core_yyscan_t yyscanner)
 static List *
 check_indirection(List *indirection, core_yyscan_t yyscanner)
 {
+#if 0
 	ListCell   *l;
 
 	foreach(l, indirection)
@@ -18978,6 +18979,7 @@ check_indirection(List *indirection, core_yyscan_t yyscanner)
 				parser_yyerror("improper use of \"*\"");
 		}
 	}
+#endif
 	return indirection;
 }
 
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 8ea51176196..512ac5b4970 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -74,7 +74,6 @@ static Node *transformColumnRef(ParseState *pstate, ColumnRef *cref);
 static Node *transformWholeRowRef(ParseState *pstate,
 								  ParseNamespaceItem *nsitem,
 								  int sublevels_up, int location);
-static Node *transformIndirection(ParseState *pstate, A_Indirection *ind);
 static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
 static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
 static Node *transformJsonObjectConstructor(ParseState *pstate,
@@ -158,7 +157,7 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 			break;
 
 		case T_A_Indirection:
-			result = transformIndirection(pstate, (A_Indirection *) expr);
+			result = transformIndirection(pstate, (A_Indirection *) expr, NULL);
 			break;
 
 		case T_A_ArrayExpr:
@@ -432,8 +431,9 @@ unknown_attribute(ParseState *pstate, Node *relref, const char *attname,
 	}
 }
 
-static Node *
-transformIndirection(ParseState *pstate, A_Indirection *ind)
+Node *
+transformIndirection(ParseState *pstate, A_Indirection *ind,
+					 bool *trailing_star_expansion)
 {
 	Node	   *last_srf = pstate->p_last_srf;
 	Node	   *result = transformExprRecurse(pstate, ind->arg);
@@ -454,12 +454,7 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		if (IsA(n, A_Indices))
 			subscripts = lappend(subscripts, n);
 		else if (IsA(n, A_Star))
-		{
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("row expansion via \"*\" is not supported here"),
-					 parser_errposition(pstate, location)));
-		}
+			subscripts = lappend(subscripts, n);
 		else
 		{
 			Assert(IsA(n, String));
@@ -491,7 +486,21 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 
 			n = linitial(subscripts);
 
-			if (!IsA(n, String))
+			if (IsA(n, A_Star))
+			{
+				/* Success, if trailing star expansion is allowed */
+				if (trailing_star_expansion && list_length(subscripts) == 1)
+				{
+					*trailing_star_expansion = true;
+					return result;
+				}
+
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("row expansion via \"*\" is not supported here"),
+						 parser_errposition(pstate, location)));
+			}
+			else if (!IsA(n, String))
 				ereport(ERROR,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
 						 errmsg("cannot subscript type %s because it does not support subscripting",
@@ -517,6 +526,9 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		result = newresult;
 	}
 
+	if (trailing_star_expansion)
+		*trailing_star_expansion = false;
+
 	return result;
 }
 
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 3ef5897f2eb..64e0f01c0f0 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -48,7 +48,7 @@ static Node *transformAssignmentSubscripts(ParseState *pstate,
 static List *ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
 								 bool make_target_entry);
 static List *ExpandAllTables(ParseState *pstate, int location);
-static List *ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
+static Node *ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
 								   bool make_target_entry, ParseExprKind exprKind);
 static List *ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 							   int sublevels_up, int location,
@@ -134,6 +134,7 @@ transformTargetList(ParseState *pstate, List *targetlist,
 	foreach(o_target, targetlist)
 	{
 		ResTarget  *res = (ResTarget *) lfirst(o_target);
+		Node	   *transformed = NULL;
 
 		/*
 		 * Check for "something.*".  Depending on the complexity of the
@@ -162,13 +163,19 @@ transformTargetList(ParseState *pstate, List *targetlist,
 
 				if (IsA(llast(ind->indirection), A_Star))
 				{
-					/* It is something.*, expand into multiple items */
-					p_target = list_concat(p_target,
-										   ExpandIndirectionStar(pstate,
-																 ind,
-																 true,
-																 exprKind));
-					continue;
+					Node	   *columns = ExpandIndirectionStar(pstate,
+																ind,
+																true,
+																exprKind);
+
+					if (IsA(columns, List))
+					{
+						/* It is something.*, expand into multiple items */
+						p_target = list_concat(p_target, (List *) columns);
+						continue;
+					}
+
+					transformed = (Node *) columns;
 				}
 			}
 		}
@@ -180,7 +187,7 @@ transformTargetList(ParseState *pstate, List *targetlist,
 		p_target = lappend(p_target,
 						   transformTargetEntry(pstate,
 												res->val,
-												NULL,
+												transformed,
 												exprKind,
 												res->name,
 												false));
@@ -251,10 +258,15 @@ transformExpressionList(ParseState *pstate, List *exprlist,
 
 			if (IsA(llast(ind->indirection), A_Star))
 			{
-				/* It is something.*, expand into multiple items */
-				result = list_concat(result,
-									 ExpandIndirectionStar(pstate, ind,
-														   false, exprKind));
+				Node	   *cols = ExpandIndirectionStar(pstate, ind,
+														 false, exprKind);
+
+				if (!cols || IsA(cols, List))
+					/* It is something.*, expand into multiple items */
+					result = list_concat(result, (List *) cols);
+				else
+					result = lappend(result, cols);
+
 				continue;
 			}
 		}
@@ -1345,22 +1357,30 @@ ExpandAllTables(ParseState *pstate, int location)
  * For robustness, we use a separate "make_target_entry" flag to control
  * this rather than relying on exprKind.
  */
-static List *
+static Node *
 ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
 					  bool make_target_entry, ParseExprKind exprKind)
 {
 	Node	   *expr;
+	ParseExprKind sv_expr_kind;
+	bool		trailing_star_expansion = false;
+
+	/* Save and restore identity of expression type we're parsing */
+	Assert(exprKind != EXPR_KIND_NONE);
+	sv_expr_kind = pstate->p_expr_kind;
+	pstate->p_expr_kind = exprKind;
 
 	/* Strip off the '*' to create a reference to the rowtype object */
-	ind = copyObject(ind);
-	ind->indirection = list_truncate(ind->indirection,
-									 list_length(ind->indirection) - 1);
+	expr = transformIndirection(pstate, ind, &trailing_star_expansion);
+
+	pstate->p_expr_kind = sv_expr_kind;
 
-	/* And transform that */
-	expr = transformExpr(pstate, (Node *) ind, exprKind);
+	/* '*' was consumed by generic type subscripting */
+	if (!trailing_star_expansion)
+		return expr;
 
 	/* Expand the rowtype expression into individual fields */
-	return ExpandRowReference(pstate, expr, make_target_entry);
+	return (Node *) ExpandRowReference(pstate, expr, make_target_entry);
 }
 
 /*
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index cfae8159a76..1bc292c5c45 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -12926,7 +12926,11 @@ printSubscripts(SubscriptingRef *sbsref, deparse_context *context)
 	{
 		Node	   *up = (Node *) lfirst(uplist_item);
 
-		if (IsA(up, String))
+		if (!up)
+		{
+			appendStringInfoString(buf, ".*");
+		}
+		else if (IsA(up, String))
 		{
 			appendStringInfoChar(buf, '.');
 			appendStringInfoString(buf, quote_identifier(strVal(up)));
diff --git a/src/include/parser/parse_expr.h b/src/include/parser/parse_expr.h
index efbaff8e710..c9f6a7724c0 100644
--- a/src/include/parser/parse_expr.h
+++ b/src/include/parser/parse_expr.h
@@ -22,4 +22,7 @@ extern Node *transformExpr(ParseState *pstate, Node *expr, ParseExprKind exprKin
 
 extern const char *ParseExprKindName(ParseExprKind exprKind);
 
+extern Node *transformIndirection(ParseState *pstate, A_Indirection *ind,
+								  bool *trailing_star_expansion);
+
 #endif							/* PARSE_EXPR_H */
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 0edd19bed9c..91aa0511a18 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5970,12 +5970,168 @@ SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
  
 (1 row)
 
+/* wild card member access */
 SELECT (jb).a.* FROM test_jsonb_dot_notation;
-ERROR:  type jsonb is not composite
+                     a                     
+-------------------------------------------
+ ["c", "d", "f", {"y": "yyy", "z": "zzz"}]
+(1 row)
+
+SELECT (jb).* FROM test_jsonb_dot_notation;
+                                                              jb                                                              
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (jb).* FROM test_jsonb_dot_notation t;
+                                                              jb                                                              
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+                                                              jb                                                              
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (jb).* FROM test_jsonb_dot_notation;
+                                                              jb                                                              
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (t.jb).* FROM test_jsonb_dot_notation;
+ERROR:  missing FROM-clause entry for table "t"
+LINE 1: SELECT (t.jb).* FROM test_jsonb_dot_notation;
+                ^
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+                                                              jb                                                              
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+                     a                     
+-------------------------------------------
+ ["c", "d", "f", {"y": "yyy", "z": "zzz"}]
+(1 row)
+
+SELECT (jb).a.*.b FROM test_jsonb_dot_notation;
+ b 
+---
+ 
+(1 row)
+
+SELECT (jb).a.*.x FROM test_jsonb_dot_notation;
+ x 
+---
+ 
+(1 row)
+
+SELECT (jb).a.*.y FROM test_jsonb_dot_notation;
+   y   
+-------
+ "yyy"
+(1 row)
+
+SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+       a        
+----------------
+ ["yyy", "zzz"]
+(1 row)
+
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT (jb).*.x FROM test_jsonb_dot_notation t;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+ x 
+---
+ 
+(1 row)
+
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+ x 
+---
+ 
+(1 row)
+
+SELECT ((jb).*)[:].x FROM test_jsonb_dot_notation t;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT (jb).*.x.* FROM test_jsonb_dot_notation;
+              x               
+------------------------------
+ ["yyy", "zzz", "YYY", "ZZZ"]
+(1 row)
+
+SELECT (jb).*.x.y FROM test_jsonb_dot_notation;
+       y        
+----------------
+ ["yyy", "YYY"]
+(1 row)
+
+SELECT (jb).*.x.z FROM test_jsonb_dot_notation;
+       z        
+----------------
+ ["zzz", "ZZZ"]
+(1 row)
+
+SELECT (jb).*.*.y FROM test_jsonb_dot_notation;
+       y        
+----------------
+ ["yyy", "YYY"]
+(1 row)
+
+SELECT (jb).*.*.* FROM test_jsonb_dot_notation;
+              jb              
+------------------------------
+ ["yyy", "zzz", "YYY", "ZZZ"]
+(1 row)
+
+SELECT (jb).*.*.*.* FROM test_jsonb_dot_notation;
+ jb 
+----
+ 
+(1 row)
+
+SELECT (jb).a.b.c.* FROM test_jsonb_dot_notation;
+ c 
+---
+ 
+(1 row)
+
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).* FROM test_jsonb_dot_notation;
-ERROR:  type jsonb is not composite
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: jb.*
+(2 rows)
+
 SELECT (jb).* FROM test_jsonb_dot_notation;
-ERROR:  type jsonb is not composite
+                                                              jb                                                              
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
                   QUERY PLAN                  
 ----------------------------------------------
@@ -6002,3 +6158,29 @@ SELECT (jb).a[1] FROM test_jsonb_dot_notation;
  2
 (1 row)
 
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.* FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: jb.a.*
+(2 rows)
+
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+                     a                     
+-------------------------------------------
+ ["c", "d", "f", {"y": "yyy", "z": "zzz"}]
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: jb.a.*[1:2].*.b
+(2 rows)
+
+SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
+ b 
+---
+ 
+(1 row)
+
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 37d40e76610..5b44e791849 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1607,7 +1607,33 @@ SELECT (jb).a.x.y FROM test_jsonb_dot_notation;
 SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
 SELECT (jb).b.x.z FROM test_jsonb_dot_notation;
 SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
+
+/* wild card member access */
 SELECT (jb).a.* FROM test_jsonb_dot_notation;
+SELECT (jb).* FROM test_jsonb_dot_notation;
+SELECT (jb).* FROM test_jsonb_dot_notation t;
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+SELECT (jb).* FROM test_jsonb_dot_notation;
+SELECT (t.jb).* FROM test_jsonb_dot_notation;
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.b FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.x FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.y FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+SELECT (jb).*.x FROM test_jsonb_dot_notation t;
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+SELECT ((jb).*)[:].x FROM test_jsonb_dot_notation t;
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+SELECT (jb).*.x.* FROM test_jsonb_dot_notation;
+SELECT (jb).*.x.y FROM test_jsonb_dot_notation;
+SELECT (jb).*.x.z FROM test_jsonb_dot_notation;
+SELECT (jb).*.*.y FROM test_jsonb_dot_notation;
+SELECT (jb).*.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).*.*.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.b.c.* FROM test_jsonb_dot_notation;
 
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).* FROM test_jsonb_dot_notation;
 SELECT (jb).* FROM test_jsonb_dot_notation;
@@ -1615,3 +1641,7 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
 SELECT (t.jb).a FROM test_jsonb_dot_notation t;
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
 SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
+SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
-- 
2.39.5 (Apple Git-154)



^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
@ 2025-02-27 23:23           ` Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  0 siblings, 1 reply; 67+ messages in thread

From: Alexandra Wang @ 2025-02-27 23:23 UTC (permalink / raw)
  To: Peter Eisentraut <[email protected]>; +Cc: Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>; jian he <[email protected]>; [email protected]

Hello hackers,

On Thu, Feb 27, 2025 at 9:46 AM Alexandra Wang <[email protected]>
wrote:

> Summary of changes:
>
> v8-0001 through v8-0005:
> Refactoring and preparatory steps for the actual implementation.
>
> v8-0006 (Implement read-only dot notation for JSONB):
> I removed the vars field (introduced in v7) from JsonbSubWorkspace
> after realizing that JsonPathVariable is not actually needed for
> dot-notation.
>
> v8-0007 (Allow wildcard member access for JSONB):
> I'm aware that the #if 0 in check_indirection() is not ideal. I
> haven’t removed it yet because I’m still reviewing other cases—beyond
> our JSONB simplified accessor use case—where this check should remain
> strict. I’ll post an additional patch to address this.
>

I made the following minor changes in v9:
- More detailed commit messages
- Additional tests
- Use "?column?" as the column name for trailing .*.

Other than that, the patches remain the same as the previous
version:
0001 - 0005: preparation steps for the actual implementation and do
not change or add new behavior.
0006: jsonb dot notation and sliced subscripting
0007: jsonb wildcard member access

Thanks,
Alex


Attachments:

  [application/octet-stream] v9-0003-Export-jsonPathFromParseResult.patch (2.4K, ../../CAK98qZ1rZaVNy6ViangQom4iinZVH7=ebqhTsPxMQbN5ZtE9XQ@mail.gmail.com/3-v9-0003-Export-jsonPathFromParseResult.patch)
  download | inline diff:
From 6029a27fcad003f1c6643ddba011fc3660db21e3 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Sat, 1 Apr 2023 23:15:55 +0300
Subject: [PATCH v9 3/7] Export jsonPathFromParseResult()

This is a preparation step for a future commit that will reuse the
aforementioned function.
---
 src/backend/utils/adt/jsonpath.c | 19 +++++++++++++++----
 src/include/utils/jsonpath.h     |  4 ++++
 2 files changed, 19 insertions(+), 4 deletions(-)

diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index 762f7e8a09d..1536797cf23 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -166,15 +166,13 @@ jsonpath_send(PG_FUNCTION_ARGS)
  * Converts C-string to a jsonpath value.
  *
  * Uses jsonpath parser to turn string into an AST, then
- * flattenJsonPathParseItem() does second pass turning AST into binary
+ * jsonPathFromParseResult() does second pass turning AST into binary
  * representation of jsonpath.
  */
 static Datum
 jsonPathFromCstring(char *in, int len, struct Node *escontext)
 {
 	JsonPathParseResult *jsonpath = parsejsonpath(in, len, escontext);
-	JsonPath   *res;
-	StringInfoData buf;
 
 	if (SOFT_ERROR_OCCURRED(escontext))
 		return (Datum) 0;
@@ -185,8 +183,21 @@ jsonPathFromCstring(char *in, int len, struct Node *escontext)
 				 errmsg("invalid input syntax for type %s: \"%s\"", "jsonpath",
 						in)));
 
+	return jsonPathFromParseResult(jsonpath, 4 * len, escontext);
+}
+
+/*
+ * Converts jsonpath AST into jsonpath value in binary.
+ */
+Datum
+jsonPathFromParseResult(JsonPathParseResult *jsonpath, int estimated_len,
+						struct Node *escontext)
+{
+	JsonPath   *res;
+	StringInfoData buf;
+
 	initStringInfo(&buf);
-	enlargeStringInfo(&buf, 4 * len /* estimation */ );
+	enlargeStringInfo(&buf, estimated_len);
 
 	appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
 
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index 23a76d233e9..e05941623e7 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -281,6 +281,10 @@ extern JsonPathParseResult *parsejsonpath(const char *str, int len,
 extern bool jspConvertRegexFlags(uint32 xflags, int *result,
 								 struct Node *escontext);
 
+extern Datum jsonPathFromParseResult(JsonPathParseResult *jsonpath,
+									 int estimated_len,
+									 struct Node *escontext);
+
 /*
  * Struct for details about external variables passed into jsonpath executor
  */
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v9-0002-Allow-Generic-Type-Subscripting-to-Accept-Dot-Not.patch (10.3K, ../../CAK98qZ1rZaVNy6ViangQom4iinZVH7=ebqhTsPxMQbN5ZtE9XQ@mail.gmail.com/4-v9-0002-Allow-Generic-Type-Subscripting-to-Accept-Dot-Not.patch)
  download | inline diff:
From 050703ccb4e1786710722b451eabcf1a908cd763 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Sat, 1 Apr 2023 16:21:20 +0300
Subject: [PATCH v9 2/7] Allow Generic Type Subscripting to Accept Dot Notation
 (.) as Input

This change extends generic type subscripting to recognize dot
notation (.) in addition to bracket notation ([]). While this does not
yet provide full support for dot notation, it enables subscripting
containers to process it in the future.

For now, container-specific transform functions only handle
subscripting indices and stop processing when encountering dot
notation. It is up to individual containers to decide how to transform
dot notation in subsequent updates.
---
 src/backend/parser/parse_expr.c   | 66 ++++++++++++++++++++-----------
 src/backend/parser/parse_node.c   | 41 +++++++++++++++++--
 src/backend/parser/parse_target.c |  3 +-
 src/backend/utils/adt/arraysubs.c | 13 ++++--
 src/backend/utils/adt/jsonbsubs.c | 11 +++++-
 src/include/parser/parse_node.h   |  3 +-
 6 files changed, 105 insertions(+), 32 deletions(-)

diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 2c0f4a50b21..8ea51176196 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -442,8 +442,9 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 	ListCell   *i;
 
 	/*
-	 * We have to split any field-selection operations apart from
-	 * subscripting.  Adjacent A_Indices nodes have to be treated as a single
+	 * Combine field names and subscripts into a single indirection list, as
+	 * some subscripting containers, such as jsonb, support field access using
+	 * dot notation. Adjacent A_Indices nodes have to be treated as a single
 	 * multidimensional subscript operation.
 	 */
 	foreach(i, ind->indirection)
@@ -461,19 +462,43 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		}
 		else
 		{
-			Node	   *newresult;
-
 			Assert(IsA(n, String));
+			subscripts = lappend(subscripts, n);
+		}
+	}
+
+	while (subscripts)
+	{
+		/* try processing container subscripts first */
+		Node	   *newresult = (Node *)
+			transformContainerSubscripts(pstate,
+										 result,
+										 exprType(result),
+										 exprTypmod(result),
+										 &subscripts,
+										 false,
+										 true);
+
+		if (!newresult)
+		{
+			/*
+			 * generic subscripting failed; falling back to function call or
+			 * field selection for a composite type.
+			 */
+			Node	   *n;
+
+			Assert(subscripts);
 
-			/* process subscripts before this field selection */
-			while (subscripts)
-				result = (Node *) transformContainerSubscripts(pstate,
-															   result,
-															   exprType(result),
-															   exprTypmod(result),
-															   &subscripts,
-															   false);
+			n = linitial(subscripts);
+
+			if (!IsA(n, String))
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("cannot subscript type %s because it does not support subscripting",
+								format_type_be(exprType(result))),
+						 parser_errposition(pstate, exprLocation(result))));
 
+			/* try to find function for field selection */
 			newresult = ParseFuncOrColumn(pstate,
 										  list_make1(n),
 										  list_make1(result),
@@ -481,19 +506,16 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 										  NULL,
 										  false,
 										  location);
-			if (newresult == NULL)
+
+			if (!newresult)
 				unknown_attribute(pstate, result, strVal(n), location);
-			result = newresult;
+
+			/* consume field select */
+			subscripts = list_delete_first(subscripts);
 		}
+
+		result = newresult;
 	}
-	/* process trailing subscripts, if any */
-	while (subscripts)
-		result = (Node *) transformContainerSubscripts(pstate,
-													   result,
-													   exprType(result),
-													   exprTypmod(result),
-													   &subscripts,
-													   false);
 
 	return result;
 }
diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c
index 19a6b678e67..b3e476eb181 100644
--- a/src/backend/parser/parse_node.c
+++ b/src/backend/parser/parse_node.c
@@ -238,6 +238,8 @@ transformContainerType(Oid *containerType, int32 *containerTypmod)
  * containerTypMod	typmod for the container
  * indirection		Untransformed list of subscripts (must not be NIL)
  * isAssignment		True if this will become a container assignment.
+ * noError			True for return NULL with no error, if the container type
+ * 					is not subscriptable.
  */
 SubscriptingRef *
 transformContainerSubscripts(ParseState *pstate,
@@ -245,13 +247,15 @@ transformContainerSubscripts(ParseState *pstate,
 							 Oid containerType,
 							 int32 containerTypMod,
 							 List **indirection,
-							 bool isAssignment)
+							 bool isAssignment,
+							 bool noError)
 {
 	SubscriptingRef *sbsref;
 	const SubscriptRoutines *sbsroutines;
 	Oid			elementType;
 	bool		isSlice = false;
 	ListCell   *idx;
+	int			indirection_length = list_length(*indirection);
 
 	/*
 	 * Determine the actual container type, smashing any domain.  In the
@@ -267,11 +271,16 @@ transformContainerSubscripts(ParseState *pstate,
 	 */
 	sbsroutines = getSubscriptingRoutines(containerType, &elementType);
 	if (!sbsroutines)
+	{
+		if (noError)
+			return NULL;
+
 		ereport(ERROR,
 				(errcode(ERRCODE_DATATYPE_MISMATCH),
 				 errmsg("cannot subscript type %s because it does not support subscripting",
 						format_type_be(containerType)),
 				 parser_errposition(pstate, exprLocation(containerBase))));
+	}
 
 	/*
 	 * Detect whether any of the indirection items are slice specifiers.
@@ -282,9 +291,9 @@ transformContainerSubscripts(ParseState *pstate,
 	 */
 	foreach(idx, *indirection)
 	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
+		Node	   *ai = lfirst(idx);
 
-		if (ai->is_slice)
+		if (IsA(ai, A_Indices) && castNode(A_Indices, ai)->is_slice)
 		{
 			isSlice = true;
 			break;
@@ -312,6 +321,32 @@ transformContainerSubscripts(ParseState *pstate,
 	sbsroutines->transform(sbsref, indirection, pstate,
 						   isSlice, isAssignment);
 
+	/*
+	 * Error out, if datatype failed to consume any indirection elements.
+	 */
+	if (list_length(*indirection) == indirection_length)
+	{
+		Node	   *ind = linitial(*indirection);
+
+		if (noError)
+			return NULL;
+
+		if (IsA(ind, String))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("type %s does not support dot notation",
+							format_type_be(containerType)),
+					 parser_errposition(pstate, exprLocation(containerBase))));
+		else if (IsA(ind, A_Indices))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("type %s does not support array subscripting",
+							format_type_be(containerType)),
+					 parser_errposition(pstate, exprLocation(containerBase))));
+		else
+			elog(ERROR, "invalid indirection operation: %d", nodeTag(ind));
+	}
+
 	/*
 	 * Verify we got a valid type (this defends, for example, against someone
 	 * using array_subscript_handler as typsubscript without setting typelem).
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 4675a523045..3ef5897f2eb 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -937,7 +937,8 @@ transformAssignmentSubscripts(ParseState *pstate,
 										  containerType,
 										  containerTypMod,
 										  &subscripts,
-										  true);
+										  true,
+										  false);
 
 	typeNeeded = sbsref->refrestype;
 	typmodNeeded = sbsref->reftypmod;
diff --git a/src/backend/utils/adt/arraysubs.c b/src/backend/utils/adt/arraysubs.c
index 234c2c278c1..d03d3519dfd 100644
--- a/src/backend/utils/adt/arraysubs.c
+++ b/src/backend/utils/adt/arraysubs.c
@@ -62,6 +62,7 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	List	   *upperIndexpr = NIL;
 	List	   *lowerIndexpr = NIL;
 	ListCell   *idx;
+	int			ndim;
 
 	/*
 	 * Transform the subscript expressions, and separate upper and lower
@@ -73,9 +74,14 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	 */
 	foreach(idx, *indirection)
 	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
+		A_Indices  *ai;
 		Node	   *subexpr;
 
+		if (!IsA(lfirst(idx), A_Indices))
+			break;
+
+		ai = lfirst_node(A_Indices, idx);
+
 		if (isSlice)
 		{
 			if (ai->lidx)
@@ -145,14 +151,15 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->reflowerindexpr = lowerIndexpr;
 
 	/* Verify subscript list lengths are within implementation limit */
-	if (list_length(upperIndexpr) > MAXDIM)
+	ndim = list_length(upperIndexpr);
+	if (ndim > MAXDIM)
 		ereport(ERROR,
 				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 				 errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
 						list_length(upperIndexpr), MAXDIM)));
 	/* We need not check lowerIndexpr separately */
 
-	*indirection = NIL;
+	*indirection = list_delete_first_n(*indirection, ndim);
 
 	/*
 	 * Determine the result type of the subscripting operation.  It's the same
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index 8ad6aa1ad4f..a0d38a0fd80 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -55,9 +55,14 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	 */
 	foreach(idx, *indirection)
 	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
+		A_Indices  *ai;
 		Node	   *subExpr;
 
+		if (!IsA(lfirst(idx), A_Indices))
+			break;
+
+		ai = lfirst_node(A_Indices, idx);
+
 		if (isSlice)
 		{
 			Node	   *expr = ai->uidx ? ai->uidx : ai->lidx;
@@ -160,7 +165,9 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->refrestype = JSONBOID;
 	sbsref->reftypmod = -1;
 
-	*indirection = NIL;
+	/* Remove processed elements */
+	if (upperIndexpr)
+		*indirection = list_delete_first_n(*indirection, list_length(upperIndexpr));
 }
 
 /*
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 5ae11ccec33..71b04bd503c 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -378,7 +378,8 @@ extern SubscriptingRef *transformContainerSubscripts(ParseState *pstate,
 													 Oid containerType,
 													 int32 containerTypMod,
 													 List **indirection,
-													 bool isAssignment);
+													 bool isAssignment,
+													 bool noError);
 extern Const *make_const(ParseState *pstate, A_Const *aconst);
 
 #endif							/* PARSE_NODE_H */
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v9-0004-Extract-coerce_jsonpath_subscript.patch (5.5K, ../../CAK98qZ1rZaVNy6ViangQom4iinZVH7=ebqhTsPxMQbN5ZtE9XQ@mail.gmail.com/5-v9-0004-Extract-coerce_jsonpath_subscript.patch)
  download | inline diff:
From f03fa1630122561006254b958ab9c14e4cb5bc06 Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Wed, 26 Feb 2025 13:03:27 -0600
Subject: [PATCH v9 4/7] Extract coerce_jsonpath_subscript()

This is a preparation step for a future commit that will reuse the
aforementioned function.
---
 src/backend/utils/adt/jsonbsubs.c | 142 ++++++++++++++++--------------
 1 file changed, 78 insertions(+), 64 deletions(-)

diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index a0d38a0fd80..3ffe40cfa40 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -32,6 +32,83 @@ typedef struct JsonbSubWorkspace
 	Datum	   *index;			/* Subscript values in Datum format */
 } JsonbSubWorkspace;
 
+static Oid
+jsonb_subscript_type(Node *expr)
+{
+	if (expr && IsA(expr, String))
+		return TEXTOID;
+
+	return exprType(expr);
+}
+
+static Node *
+coerce_jsonpath_subscript(ParseState *pstate, Node *subExpr, Oid numtype)
+{
+	Oid			subExprType = jsonb_subscript_type(subExpr);
+	Oid			targetType = UNKNOWNOID;
+
+	if (subExprType != UNKNOWNOID)
+	{
+		Oid			targets[2] = {numtype, TEXTOID};
+
+		/*
+		 * Jsonb can handle multiple subscript types, but cases when a
+		 * subscript could be coerced to multiple target types must be
+		 * avoided, similar to overloaded functions. It could be possibly
+		 * extend with jsonpath in the future.
+		 */
+		for (int i = 0; i < 2; i++)
+		{
+			if (can_coerce_type(1, &subExprType, &targets[i], COERCION_IMPLICIT))
+			{
+				/*
+				 * One type has already succeeded, it means there are two
+				 * coercion targets possible, failure.
+				 */
+				if (targetType != UNKNOWNOID)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
+							 errhint("jsonb subscript must be coercible to only one type, integer or text."),
+							 parser_errposition(pstate, exprLocation(subExpr))));
+
+				targetType = targets[i];
+			}
+		}
+
+		/*
+		 * No suitable types were found, failure.
+		 */
+		if (targetType == UNKNOWNOID)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
+					 errhint("jsonb subscript must be coercible to either integer or text."),
+					 parser_errposition(pstate, exprLocation(subExpr))));
+	}
+	else
+		targetType = TEXTOID;
+
+	/*
+	 * We known from can_coerce_type that coercion will succeed, so
+	 * coerce_type could be used. Note the implicit coercion context, which is
+	 * required to handle subscripts of different types, similar to overloaded
+	 * functions.
+	 */
+	subExpr = coerce_type(pstate,
+						  subExpr, subExprType,
+						  targetType, -1,
+						  COERCION_IMPLICIT,
+						  COERCE_IMPLICIT_CAST,
+						  -1);
+	if (subExpr == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("jsonb subscript must have text type"),
+				 parser_errposition(pstate, exprLocation(subExpr))));
+
+	return subExpr;
+}
 
 /*
  * Finish parse analysis of a SubscriptingRef expression for a jsonb.
@@ -75,71 +152,8 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 
 		if (ai->uidx)
 		{
-			Oid			subExprType = InvalidOid,
-						targetType = UNKNOWNOID;
-
 			subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
-			subExprType = exprType(subExpr);
-
-			if (subExprType != UNKNOWNOID)
-			{
-				Oid			targets[2] = {INT4OID, TEXTOID};
-
-				/*
-				 * Jsonb can handle multiple subscript types, but cases when a
-				 * subscript could be coerced to multiple target types must be
-				 * avoided, similar to overloaded functions. It could be
-				 * possibly extend with jsonpath in the future.
-				 */
-				for (int i = 0; i < 2; i++)
-				{
-					if (can_coerce_type(1, &subExprType, &targets[i], COERCION_IMPLICIT))
-					{
-						/*
-						 * One type has already succeeded, it means there are
-						 * two coercion targets possible, failure.
-						 */
-						if (targetType != UNKNOWNOID)
-							ereport(ERROR,
-									(errcode(ERRCODE_DATATYPE_MISMATCH),
-									 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
-									 errhint("jsonb subscript must be coercible to only one type, integer or text."),
-									 parser_errposition(pstate, exprLocation(subExpr))));
-
-						targetType = targets[i];
-					}
-				}
-
-				/*
-				 * No suitable types were found, failure.
-				 */
-				if (targetType == UNKNOWNOID)
-					ereport(ERROR,
-							(errcode(ERRCODE_DATATYPE_MISMATCH),
-							 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
-							 errhint("jsonb subscript must be coercible to either integer or text."),
-							 parser_errposition(pstate, exprLocation(subExpr))));
-			}
-			else
-				targetType = TEXTOID;
-
-			/*
-			 * We known from can_coerce_type that coercion will succeed, so
-			 * coerce_type could be used. Note the implicit coercion context,
-			 * which is required to handle subscripts of different types,
-			 * similar to overloaded functions.
-			 */
-			subExpr = coerce_type(pstate,
-								  subExpr, subExprType,
-								  targetType, -1,
-								  COERCION_IMPLICIT,
-								  COERCE_IMPLICIT_CAST,
-								  -1);
-			if (subExpr == NULL)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("jsonb subscript must have text type"),
-						 parser_errposition(pstate, exprLocation(subExpr))));
+			subExpr = coerce_jsonpath_subscript(pstate, subExpr, INT4OID);
 		}
 		else
 		{
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v9-0001-Allow-transformation-of-only-a-sublist-of-subscri.patch (8.8K, ../../CAK98qZ1rZaVNy6ViangQom4iinZVH7=ebqhTsPxMQbN5ZtE9XQ@mail.gmail.com/6-v9-0001-Allow-transformation-of-only-a-sublist-of-subscri.patch)
  download | inline diff:
From bd946db88d0f149f073f76eaa2501e4845b9b2b5 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Fri, 14 Oct 2022 15:35:22 +0300
Subject: [PATCH v9 1/7] Allow transformation of only a sublist of subscripts

This is a preparation step for allowing subscripting containers to
transform only a prefix of an indirection list and modify the list
in-place by removing the processed elements. Currently, all elements
are consumed, and the list is set to NIL after transformation.

In the following commit, subscripting containers will gain the
flexibility to stop transformation when encountering an unsupported
indirection and return the remaining indirections to the caller.
---
 contrib/hstore/hstore_subs.c      | 10 ++++++----
 src/backend/parser/parse_expr.c   |  9 ++++-----
 src/backend/parser/parse_node.c   |  4 ++--
 src/backend/parser/parse_target.c |  2 +-
 src/backend/utils/adt/arraysubs.c |  6 ++++--
 src/backend/utils/adt/jsonbsubs.c |  6 ++++--
 src/include/nodes/subscripting.h  |  7 ++++++-
 src/include/parser/parse_node.h   |  2 +-
 8 files changed, 28 insertions(+), 18 deletions(-)

diff --git a/contrib/hstore/hstore_subs.c b/contrib/hstore/hstore_subs.c
index 3d03f66fa0d..1b29543ab67 100644
--- a/contrib/hstore/hstore_subs.c
+++ b/contrib/hstore/hstore_subs.c
@@ -40,7 +40,7 @@
  */
 static void
 hstore_subscript_transform(SubscriptingRef *sbsref,
-						   List *indirection,
+						   List **indirection,
 						   ParseState *pstate,
 						   bool isSlice,
 						   bool isAssignment)
@@ -49,15 +49,15 @@ hstore_subscript_transform(SubscriptingRef *sbsref,
 	Node	   *subexpr;
 
 	/* We support only single-subscript, non-slice cases */
-	if (isSlice || list_length(indirection) != 1)
+	if (isSlice || list_length(*indirection) != 1)
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("hstore allows only one subscript"),
 				 parser_errposition(pstate,
-									exprLocation((Node *) indirection))));
+									exprLocation((Node *) *indirection))));
 
 	/* Transform the subscript expression to type text */
-	ai = linitial_node(A_Indices, indirection);
+	ai = linitial_node(A_Indices, *indirection);
 	Assert(ai->uidx != NULL && ai->lidx == NULL && !ai->is_slice);
 
 	subexpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
@@ -81,6 +81,8 @@ hstore_subscript_transform(SubscriptingRef *sbsref,
 	/* Determine the result type of the subscripting operation; always text */
 	sbsref->refrestype = TEXTOID;
 	sbsref->reftypmod = -1;
+
+	*indirection = NIL;
 }
 
 /*
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index bad1df732ea..2c0f4a50b21 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -466,14 +466,13 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 			Assert(IsA(n, String));
 
 			/* process subscripts before this field selection */
-			if (subscripts)
+			while (subscripts)
 				result = (Node *) transformContainerSubscripts(pstate,
 															   result,
 															   exprType(result),
 															   exprTypmod(result),
-															   subscripts,
+															   &subscripts,
 															   false);
-			subscripts = NIL;
 
 			newresult = ParseFuncOrColumn(pstate,
 										  list_make1(n),
@@ -488,12 +487,12 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		}
 	}
 	/* process trailing subscripts, if any */
-	if (subscripts)
+	while (subscripts)
 		result = (Node *) transformContainerSubscripts(pstate,
 													   result,
 													   exprType(result),
 													   exprTypmod(result),
-													   subscripts,
+													   &subscripts,
 													   false);
 
 	return result;
diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c
index d6feb16aef3..19a6b678e67 100644
--- a/src/backend/parser/parse_node.c
+++ b/src/backend/parser/parse_node.c
@@ -244,7 +244,7 @@ transformContainerSubscripts(ParseState *pstate,
 							 Node *containerBase,
 							 Oid containerType,
 							 int32 containerTypMod,
-							 List *indirection,
+							 List **indirection,
 							 bool isAssignment)
 {
 	SubscriptingRef *sbsref;
@@ -280,7 +280,7 @@ transformContainerSubscripts(ParseState *pstate,
 	 * element.  If any of the items are slice specifiers (lower:upper), then
 	 * the subscript expression means a container slice operation.
 	 */
-	foreach(idx, indirection)
+	foreach(idx, *indirection)
 	{
 		A_Indices  *ai = lfirst_node(A_Indices, idx);
 
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 4aba0d9d4d5..4675a523045 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -936,7 +936,7 @@ transformAssignmentSubscripts(ParseState *pstate,
 										  basenode,
 										  containerType,
 										  containerTypMod,
-										  subscripts,
+										  &subscripts,
 										  true);
 
 	typeNeeded = sbsref->refrestype;
diff --git a/src/backend/utils/adt/arraysubs.c b/src/backend/utils/adt/arraysubs.c
index 2940fb8e8d7..234c2c278c1 100644
--- a/src/backend/utils/adt/arraysubs.c
+++ b/src/backend/utils/adt/arraysubs.c
@@ -54,7 +54,7 @@ typedef struct ArraySubWorkspace
  */
 static void
 array_subscript_transform(SubscriptingRef *sbsref,
-						  List *indirection,
+						  List **indirection,
 						  ParseState *pstate,
 						  bool isSlice,
 						  bool isAssignment)
@@ -71,7 +71,7 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	 * indirection items to slices by treating the single subscript as the
 	 * upper bound and supplying an assumed lower bound of 1.
 	 */
-	foreach(idx, indirection)
+	foreach(idx, *indirection)
 	{
 		A_Indices  *ai = lfirst_node(A_Indices, idx);
 		Node	   *subexpr;
@@ -152,6 +152,8 @@ array_subscript_transform(SubscriptingRef *sbsref,
 						list_length(upperIndexpr), MAXDIM)));
 	/* We need not check lowerIndexpr separately */
 
+	*indirection = NIL;
+
 	/*
 	 * Determine the result type of the subscripting operation.  It's the same
 	 * as the array type if we're slicing, else it's the element type.  In
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index de64d498512..8ad6aa1ad4f 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -41,7 +41,7 @@ typedef struct JsonbSubWorkspace
  */
 static void
 jsonb_subscript_transform(SubscriptingRef *sbsref,
-						  List *indirection,
+						  List **indirection,
 						  ParseState *pstate,
 						  bool isSlice,
 						  bool isAssignment)
@@ -53,7 +53,7 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	 * Transform and convert the subscript expressions. Jsonb subscripting
 	 * does not support slices, look only and the upper index.
 	 */
-	foreach(idx, indirection)
+	foreach(idx, *indirection)
 	{
 		A_Indices  *ai = lfirst_node(A_Indices, idx);
 		Node	   *subExpr;
@@ -159,6 +159,8 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	/* Determine the result type of the subscripting operation; always jsonb */
 	sbsref->refrestype = JSONBOID;
 	sbsref->reftypmod = -1;
+
+	*indirection = NIL;
 }
 
 /*
diff --git a/src/include/nodes/subscripting.h b/src/include/nodes/subscripting.h
index 234e8ad8012..5d576af346f 100644
--- a/src/include/nodes/subscripting.h
+++ b/src/include/nodes/subscripting.h
@@ -71,6 +71,11 @@ struct SubscriptExecSteps;
  * does not care to support slicing, it can just throw an error if isSlice.)
  * See array_subscript_transform() for sample code.
  *
+ * The transform method receives a pointer to a list of raw indirections.
+ * This allows the method to parse a sublist of the indirections (typically
+ * the prefix) and modify the original list in place, enabling the caller to
+ * either process the remaining indirections differently or raise an error.
+ *
  * The transform method is also responsible for identifying the result type
  * of the subscripting operation.  At call, refcontainertype and reftypmod
  * describe the container type (this will be a base type not a domain), and
@@ -93,7 +98,7 @@ struct SubscriptExecSteps;
  * assignment must return.
  */
 typedef void (*SubscriptTransform) (SubscriptingRef *sbsref,
-									List *indirection,
+									List **indirection,
 									struct ParseState *pstate,
 									bool isSlice,
 									bool isAssignment);
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 994284019fb..5ae11ccec33 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -377,7 +377,7 @@ extern SubscriptingRef *transformContainerSubscripts(ParseState *pstate,
 													 Node *containerBase,
 													 Oid containerType,
 													 int32 containerTypMod,
-													 List *indirection,
+													 List **indirection,
 													 bool isAssignment);
 extern Const *make_const(ParseState *pstate, A_Const *aconst);
 
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v9-0006-Implement-read-only-dot-notation-for-jsonb.patch (26.8K, ../../CAK98qZ1rZaVNy6ViangQom4iinZVH7=ebqhTsPxMQbN5ZtE9XQ@mail.gmail.com/7-v9-0006-Implement-read-only-dot-notation-for-jsonb.patch)
  download | inline diff:
From 11321c2c93db9a6f63217d78456ccbcd771cdb6c Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Sat, 1 Apr 2023 23:17:53 +0300
Subject: [PATCH v9 6/7] Implement read-only dot notation for jsonb

This patch introduces JSONB member access using dot notation, wildcard
access, and array subscripting with slicing, aligning with the JSON
simplified accessor specified in SQL:2023. Specifically, the following
syntax enhancements are added:

1. Simple dot-notation access to JSONB object fields
2. Wildcard dot-notation access to JSONB object fields
2. Subscripting for index range access to JSONB array elements

Examples:

-- Setup
create table t(x int, y jsonb);
insert into t select 1, '{"a": 1, "b": 42}'::jsonb;
insert into t select 1, '{"a": 2, "b": {"c": 42}}'::jsonb;
insert into t select 1, '{"a": 3, "b": {"c": "42"}, "d":[11, 12]}'::jsonb;

-- Existing syntax predates the SQL standard:
select (t.y)->'b' from t;
select (t.y)->'b'->'c' from t;
select (t.y)->'d'->0 from t;

-- JSON simplified accessor specified by the SQL standard:
select (t.y).b from t;
select (t.y).b.c from t;
select (t.y).d[0] from t;

The SQL standard states that simplified access is equivalent to:
JSON_QUERY (VEP, 'lax $.JC' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR)

where:
  VEP = <value expression primary>
  JC = <JSON simplified accessor op chain>

For example, the JSON_QUERY equivalents of the above queries are:

select json_query(y, 'lax $.b' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t;
select json_query(y, 'lax $.b.c' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t;
select json_query(y, 'lax $.d[0]' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t;

Implementation details:

Extends the existing container subscripting interface to support
container-specific information, specifically a JSONPath expression for
jsonb.

During query transformation, detects dot-notation, wildcard access,
and sliced subscripting. If any of these accessors are present,
constructs a JSONPath expression representing the access chain.

During execution, if a JSONPath expression is present in
JsonbSubWorkspace, executes it via JsonPathQuery().

Does not transform accessors directly into JSON_QUERY during
transformation to preserve the original query structure for EXPLAIN
and CREATE VIEW.
---
 src/backend/utils/adt/jsonbsubs.c   | 293 ++++++++++++++++++++++++++--
 src/include/nodes/primnodes.h       |   7 +
 src/test/regress/expected/jsonb.out | 249 ++++++++++++++++++++++-
 src/test/regress/sql/jsonb.sql      |  45 +++++
 4 files changed, 568 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index 3ffe40cfa40..409cb20539c 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -15,21 +15,30 @@
 #include "postgres.h"
 
 #include "executor/execExpr.h"
+#include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "nodes/subscripting.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_expr.h"
 #include "utils/builtins.h"
 #include "utils/jsonb.h"
+#include "utils/jsonpath.h"
 
 
-/* SubscriptingRefState.workspace for jsonb subscripting execution */
+/*
+ * SubscriptingRefState.workspace for generic jsonb subscripting execution.
+ *
+ * Stores state for both jsonb simple subscripting and dot notation access.
+ * Dot notation additionally uses `jsonpath` for JsonPath evaluation.
+ */
 typedef struct JsonbSubWorkspace
 {
 	bool		expectArray;	/* jsonb root is expected to be an array */
 	Oid		   *indexOid;		/* OID of coerced subscript expression, could
 								 * be only integer or text */
 	Datum	   *index;			/* Subscript values in Datum format */
+	JsonPath   *jsonpath;		/* JsonPath for dot notation execution via
+								 * JsonPathQuery() */
 } JsonbSubWorkspace;
 
 static Oid
@@ -110,6 +119,228 @@ coerce_jsonpath_subscript(ParseState *pstate, Node *subExpr, Oid numtype)
 	return subExpr;
 }
 
+/*
+ * During transformation, determine whether to build a JsonPath
+ * for JsonPathQuery() execution.
+ *
+ * JsonPath is needed if the indirection list includes:
+ * - String-based access (dot notation)
+ * - Wildcard (`*`)
+ * - Slice-based subscripting
+ *
+ * Otherwise, simple jsonb subscripting is sufficient.
+ */
+static bool
+jsonb_check_jsonpath_needed(List *indirection)
+{
+	ListCell   *lc;
+
+	foreach(lc, indirection)
+	{
+		Node	   *accessor = lfirst(lc);
+
+		if (IsA(accessor, String) ||
+			IsA(accessor, A_Star))
+			return true;
+		else
+		{
+			A_Indices  *ai;
+
+			Assert(IsA(accessor, A_Indices));
+			ai = castNode(A_Indices, accessor);
+
+			if (!ai->uidx || ai->lidx)
+			{
+				Assert(ai->is_slice);
+				return true;
+			}
+		}
+	}
+
+	return false;
+}
+
+/*
+ * Helper functions for constructing JsonPath expressions.
+ *
+ * The following functions create various types of JsonPathParseItem nodes,
+ * which are used to build JsonPath expressions for jsonb simplified accessor.
+ */
+
+static JsonPathParseItem *
+make_jsonpath_item(JsonPathItemType type)
+{
+	JsonPathParseItem *v = palloc(sizeof(*v));
+
+	v->type = type;
+	v->next = NULL;
+
+	return v;
+}
+
+static JsonPathParseItem *
+make_jsonpath_item_int(int32 val, List **exprs)
+{
+	JsonPathParseItem *jpi = make_jsonpath_item(jpiNumeric);
+
+	jpi->value.numeric =
+		DatumGetNumeric(DirectFunctionCall1(int4_numeric, Int32GetDatum(val)));
+
+	*exprs = lappend(*exprs, makeConst(INT4OID, -1, InvalidOid, 4,
+									   Int32GetDatum(val), false, true));
+
+	return jpi;
+}
+
+/*
+ * Convert an expression into a JsonPathParseItem.
+ * If the expression is a constant integer, create a direct numeric item.
+ * Otherwise, create a variable reference and add it to the expression list.
+ */
+static JsonPathParseItem *
+make_jsonpath_item_expr(ParseState *pstate, Node *expr, List **exprs)
+{
+	Const	   *cnst;
+
+	expr = transformExpr(pstate, expr, pstate->p_expr_kind);
+
+	if (!IsA(expr, Const))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("jsonb simplified accessor supports subscripting in const int4, got type: %s",
+						format_type_be(exprType(expr))),
+				 parser_errposition(pstate, exprLocation(expr))));
+
+	cnst = (Const *) expr;
+
+	if (cnst->consttype == INT4OID && !cnst->constisnull)
+	{
+		int32		val = DatumGetInt32(cnst->constvalue);
+
+		return make_jsonpath_item_int(val, exprs);
+	}
+
+	ereport(ERROR,
+			(errcode(ERRCODE_DATATYPE_MISMATCH),
+			 errmsg("jsonb simplified accessor supports subscripting in type: INT4, got type: %s",
+					format_type_be(cnst->consttype)),
+			 parser_errposition(pstate, exprLocation(expr))));
+}
+
+/*
+ * jsonb_subscript_make_jsonpath
+ *
+ * Constructs a JsonPath expression from a list of indirections.
+ * This function is used when jsonb subscripting involves dot notation,
+ * wildcards (*), or slice-based subscripting, requiring JsonPath-based
+ * evaluation.
+ *
+ * The function modifies the indirection list in place, removing processed
+ * elements as it converts them into JsonPath components, as follows:
+ * - String keys (dot notation) -> jpiKey items.
+ * - Wildcard (*) -> jpiAnyKey item.
+ * - Array indices and slices -> jpiIndexArray items.
+ *
+ * Parameters:
+ * - pstate: Parse state context.
+ * - indirection: List of subscripting expressions (modified in-place).
+ * - uexprs: Upper-bound expressions extracted from subscripts.
+ * - lexprs: Lower-bound expressions extracted from subscripts.
+ * Returns:
+ * - a Const node containing the transformed JsonPath expression.
+ */
+static Node *
+jsonb_subscript_make_jsonpath(ParseState *pstate, List **indirection,
+							  List **uexprs, List **lexprs)
+{
+	JsonPathParseResult jpres;
+	JsonPathParseItem *path = make_jsonpath_item(jpiRoot);
+	ListCell   *lc;
+	Datum		jsp;
+	int			pathlen = 0;
+
+	*uexprs = NIL;
+	*lexprs = NIL;
+
+	jpres.expr = path;
+	jpres.lax = true;
+
+	foreach(lc, *indirection)
+	{
+		Node	   *accessor = lfirst(lc);
+		JsonPathParseItem *jpi;
+
+		if (IsA(accessor, String))
+		{
+			char	   *field = strVal(accessor);
+
+			jpi = make_jsonpath_item(jpiKey);
+			jpi->value.string.val = field;
+			jpi->value.string.len = strlen(field);
+
+			*uexprs = lappend(*uexprs, accessor);
+		}
+		else if (IsA(accessor, A_Star))
+		{
+			jpi = make_jsonpath_item(jpiAnyKey);
+
+			*uexprs = lappend(*uexprs, NULL);
+		}
+		else if (IsA(accessor, A_Indices))
+		{
+			A_Indices  *ai = castNode(A_Indices, accessor);
+
+			jpi = make_jsonpath_item(jpiIndexArray);
+			jpi->value.array.nelems = 1;
+			jpi->value.array.elems = palloc(sizeof(jpi->value.array.elems[0]));
+
+			if (ai->is_slice)
+			{
+				while (list_length(*lexprs) < list_length(*uexprs))
+					*lexprs = lappend(*lexprs, NULL);
+
+				if (ai->lidx)
+					jpi->value.array.elems[0].from = make_jsonpath_item_expr(pstate, ai->lidx, lexprs);
+				else
+					jpi->value.array.elems[0].from = make_jsonpath_item_int(0, lexprs);
+
+				if (ai->uidx)
+					jpi->value.array.elems[0].to = make_jsonpath_item_expr(pstate, ai->uidx, uexprs);
+				else
+				{
+					jpi->value.array.elems[0].to = make_jsonpath_item(jpiLast);
+					*uexprs = lappend(*uexprs, NULL);
+				}
+			}
+			else
+			{
+				Assert(ai->uidx && !ai->lidx);
+				jpi->value.array.elems[0].from = make_jsonpath_item_expr(pstate, ai->uidx, uexprs);
+				jpi->value.array.elems[0].to = NULL;
+			}
+		}
+		else
+			break;
+
+		/* append path item */
+		path->next = jpi;
+		path = jpi;
+		pathlen++;
+	}
+
+	if (*lexprs)
+	{
+		while (list_length(*lexprs) < list_length(*uexprs))
+			*lexprs = lappend(*lexprs, NULL);
+	}
+
+	*indirection = list_delete_first_n(*indirection, pathlen);
+
+	jsp = jsonPathFromParseResult(&jpres, 0, NULL);
+
+	return (Node *) makeConst(JSONPATHOID, -1, InvalidOid, -1, jsp, false, false);
+}
+
 /*
  * Finish parse analysis of a SubscriptingRef expression for a jsonb.
  *
@@ -126,19 +357,32 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	List	   *upperIndexpr = NIL;
 	ListCell   *idx;
 
+	/* Determine the result type of the subscripting operation; always jsonb */
+	sbsref->refrestype = JSONBOID;
+	sbsref->reftypmod = -1;
+
+	if (jsonb_check_jsonpath_needed(*indirection))
+	{
+		sbsref->refjsonbpath =
+			jsonb_subscript_make_jsonpath(pstate, indirection,
+										  &sbsref->refupperindexpr,
+										  &sbsref->reflowerindexpr);
+		return;
+	}
+
 	/*
 	 * Transform and convert the subscript expressions. Jsonb subscripting
 	 * does not support slices, look only and the upper index.
 	 */
 	foreach(idx, *indirection)
 	{
+		Node	   *i = lfirst(idx);
 		A_Indices  *ai;
 		Node	   *subExpr;
 
-		if (!IsA(lfirst(idx), A_Indices))
-			break;
+		Assert(IsA(i, A_Indices));
 
-		ai = lfirst_node(A_Indices, idx);
+		ai = castNode(A_Indices, i);
 
 		if (isSlice)
 		{
@@ -175,10 +419,6 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->refupperindexpr = upperIndexpr;
 	sbsref->reflowerindexpr = NIL;
 
-	/* Determine the result type of the subscripting operation; always jsonb */
-	sbsref->refrestype = JSONBOID;
-	sbsref->reftypmod = -1;
-
 	/* Remove processed elements */
 	if (upperIndexpr)
 		*indirection = list_delete_first_n(*indirection, list_length(upperIndexpr));
@@ -233,7 +473,7 @@ jsonb_subscript_check_subscripts(ExprState *state,
 			 * For jsonb fetch and assign functions we need to provide path in
 			 * text format. Convert if it's not already text.
 			 */
-			if (workspace->indexOid[i] == INT4OID)
+			if (!workspace->jsonpath && workspace->indexOid[i] == INT4OID)
 			{
 				Datum		datum = sbsrefstate->upperindex[i];
 				char	   *cs = DatumGetCString(DirectFunctionCall1(int4out, datum));
@@ -261,17 +501,32 @@ jsonb_subscript_fetch(ExprState *state,
 {
 	SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
 	JsonbSubWorkspace *workspace = (JsonbSubWorkspace *) sbsrefstate->workspace;
-	Jsonb	   *jsonbSource;
 
 	/* Should not get here if source jsonb (or any subscript) is null */
 	Assert(!(*op->resnull));
 
-	jsonbSource = DatumGetJsonbP(*op->resvalue);
-	*op->resvalue = jsonb_get_element(jsonbSource,
-									  workspace->index,
-									  sbsrefstate->numupper,
-									  op->resnull,
-									  false);
+	if (workspace->jsonpath)
+	{
+		bool		empty = false;
+		bool		error = false;
+
+		*op->resvalue = JsonPathQuery(*op->resvalue, workspace->jsonpath,
+									  JSW_CONDITIONAL,
+									  &empty, &error, NULL,
+									  NULL);
+
+		*op->resnull = empty || error;
+	}
+	else
+	{
+		Jsonb	   *jsonbSource = DatumGetJsonbP(*op->resvalue);
+
+		*op->resvalue = jsonb_get_element(jsonbSource,
+										  workspace->index,
+										  sbsrefstate->numupper,
+										  op->resnull,
+										  false);
+	}
 }
 
 /*
@@ -381,6 +636,7 @@ jsonb_exec_setup(const SubscriptingRef *sbsref,
 	ListCell   *lc;
 	int			nupper = sbsref->refupperindexpr->length;
 	char	   *ptr;
+	bool		useJsonpath = sbsref->refjsonbpath != NULL;
 
 	/* Allocate type-specific workspace with space for per-subscript data */
 	workspace = palloc0(MAXALIGN(sizeof(JsonbSubWorkspace)) +
@@ -388,6 +644,9 @@ jsonb_exec_setup(const SubscriptingRef *sbsref,
 	workspace->expectArray = false;
 	ptr = ((char *) workspace) + MAXALIGN(sizeof(JsonbSubWorkspace));
 
+	if (useJsonpath)
+		workspace->jsonpath = DatumGetJsonPathP(castNode(Const, sbsref->refjsonbpath)->constvalue);
+
 	/*
 	 * This coding assumes sizeof(Datum) >= sizeof(Oid), else we might
 	 * misalign the indexOid pointer
@@ -404,7 +663,7 @@ jsonb_exec_setup(const SubscriptingRef *sbsref,
 		Node	   *expr = lfirst(lc);
 		int			i = foreach_current_index(lc);
 
-		workspace->indexOid[i] = exprType(expr);
+		workspace->indexOid[i] = jsonb_subscript_type(expr);
 	}
 
 	/*
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index d0576da3e25..9d380ed60d6 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -718,6 +718,13 @@ typedef struct SubscriptingRef
 	Expr	   *refexpr;
 	/* expression for the source value, or NULL if fetch */
 	Expr	   *refassgnexpr;
+
+	/*
+	 * container-specific extra information, currently used only by jsonb.
+	 * stores a JsonPath expression when jsonb dot notation is used. NULL for
+	 * simple subscripting.
+	 */
+	Node	   *refjsonbpath;
 } SubscriptingRef;
 
 /*
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 2baff931bf2..480ecb44a94 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -4939,6 +4939,12 @@ select ('123'::jsonb)['a'];
  
 (1 row)
 
+select ('123'::jsonb).a;
+ a 
+---
+ 
+(1 row)
+
 select ('123'::jsonb)[0];
  jsonb 
 -------
@@ -4951,12 +4957,24 @@ select ('123'::jsonb)[NULL];
  
 (1 row)
 
+select ('123'::jsonb).NULL;
+ null 
+------
+ 
+(1 row)
+
 select ('{"a": 1}'::jsonb)['a'];
  jsonb 
 -------
  1
 (1 row)
 
+select ('{"a": 1}'::jsonb).a;
+ a 
+---
+ 1
+(1 row)
+
 select ('{"a": 1}'::jsonb)[0];
  jsonb 
 -------
@@ -4969,6 +4987,12 @@ select ('{"a": 1}'::jsonb)['not_exist'];
  
 (1 row)
 
+select ('{"a": 1}'::jsonb)."not_exist";
+ not_exist 
+-----------
+ 
+(1 row)
+
 select ('{"a": 1}'::jsonb)[NULL];
  jsonb 
 -------
@@ -4981,6 +5005,12 @@ select ('[1, "2", null]'::jsonb)['a'];
  
 (1 row)
 
+select ('[1, "2", null]'::jsonb).a;
+ a 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[0];
  jsonb 
 -------
@@ -4993,6 +5023,12 @@ select ('[1, "2", null]'::jsonb)['1'];
  "2"
 (1 row)
 
+select ('[1, "2", null]'::jsonb)."1";
+ 1 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[1.0];
 ERROR:  subscript type numeric is not supported
 LINE 1: select ('[1, "2", null]'::jsonb)[1.0];
@@ -5022,6 +5058,12 @@ select ('[1, "2", null]'::jsonb)[1]['a'];
  
 (1 row)
 
+select ('[1, "2", null]'::jsonb)[1].a;
+ a 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[1][0];
  jsonb 
 -------
@@ -5034,73 +5076,139 @@ select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['b'];
  "c"
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).b;
+  b  
+-----
+ "c"
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'];
    jsonb   
 -----------
  [1, 2, 3]
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d;
+     d     
+-----------
+ [1, 2, 3]
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'][1];
  jsonb 
 -------
  2
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d[1];
+ d 
+---
+ 2
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d']['a'];
  jsonb 
 -------
  
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['a'];
+ERROR:  jsonb simplified accessor supports subscripting in type: INT4, got type: unknown
+LINE 1: select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['a'];
+                                                               ^
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d.a;
+ a 
+---
+ 
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1'];
      jsonb     
 ---------------
  {"a2": "aaa"}
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1;
+      a1       
+---------------
+ {"a2": "aaa"}
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2'];
  jsonb 
 -------
  "aaa"
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2;
+  a2   
+-------
+ "aaa"
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']['a3'];
  jsonb 
 -------
  
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2.a3;
+ a3 
+----
+ 
+(1 row)
+
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'];
          jsonb         
 -----------------------
  ["aaa", "bbb", "ccc"]
 (1 row)
 
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1;
+          b1           
+-----------------------
+ ["aaa", "bbb", "ccc"]
+(1 row)
+
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'][2];
  jsonb 
 -------
  "ccc"
 (1 row)
 
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1[2];
+  b1   
+-------
+ "ccc"
+(1 row)
+
 -- slices are not supported
 select ('{"a": 1}'::jsonb)['a':'b'];
-ERROR:  jsonb subscript does not support slices
+ERROR:  jsonb simplified accessor supports subscripting in type: INT4, got type: unknown
 LINE 1: select ('{"a": 1}'::jsonb)['a':'b'];
-                                       ^
+                                   ^
 select ('[1, "2", null]'::jsonb)[1:2];
-ERROR:  jsonb subscript does not support slices
-LINE 1: select ('[1, "2", null]'::jsonb)[1:2];
-                                           ^
+    jsonb    
+-------------
+ ["2", null]
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[:2];
 ERROR:  jsonb subscript does not support slices
 LINE 1: select ('[1, "2", null]'::jsonb)[:2];
                                           ^
 select ('[1, "2", null]'::jsonb)[1:];
-ERROR:  jsonb subscript does not support slices
-LINE 1: select ('[1, "2", null]'::jsonb)[1:];
-                                         ^
+    jsonb    
+-------------
+ ["2", null]
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[:];
-ERROR:  jsonb subscript does not support slices
+     jsonb      
+----------------
+ [1, "2", null]
+(1 row)
+
 create TEMP TABLE test_jsonb_subscript (
        id int,
        test_json jsonb
@@ -5781,3 +5889,126 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
  12345
 (1 row)
 
+-- dot notation
+CREATE TABLE test_jsonb_dot_notation AS
+SELECT '{"a": [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], "b": [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]}'::jsonb jb;
+SELECT (jb).a FROM test_jsonb_dot_notation;
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+SELECT (jb)."a" FROM test_jsonb_dot_notation; -- double quote should work
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+SELECT (jb).'a' FROM test_jsonb_dot_notation; -- single quote should not work
+ERROR:  syntax error at or near "'a'"
+LINE 1: SELECT (jb).'a' FROM test_jsonb_dot_notation;
+                    ^
+SELECT (jb).b FROM test_jsonb_dot_notation;
+                         b                         
+---------------------------------------------------
+ [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]
+(1 row)
+
+SELECT (jb).c FROM test_jsonb_dot_notation;
+ c 
+---
+ 
+(1 row)
+
+SELECT (jb).a.b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT (jb).a[2].b FROM test_jsonb_dot_notation;
+  b  
+-----
+ "c"
+(1 row)
+
+SELECT (jb).a[2:3].b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT (jb).a[2:].b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT (jb).a[:2].b FROM test_jsonb_dot_notation;
+  b  
+-----
+ "c"
+(1 row)
+
+SELECT (jb).a[:].b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT (jb).a.x.y FROM test_jsonb_dot_notation;
+   y   
+-------
+ "yyy"
+(1 row)
+
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
+            x             
+--------------------------
+ {"y": "YYY", "z": "ZZZ"}
+(1 row)
+
+SELECT (jb).b.x.z FROM test_jsonb_dot_notation;
+   z   
+-------
+ "ZZZ"
+(1 row)
+
+SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
+ c 
+---
+ 
+(1 row)
+
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+ERROR:  type jsonb is not composite
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).* FROM test_jsonb_dot_notation;
+ERROR:  type jsonb is not composite
+SELECT (jb).* FROM test_jsonb_dot_notation;
+ERROR:  type jsonb is not composite
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+                  QUERY PLAN                  
+----------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation t
+   Output: jb.a
+(2 rows)
+
+SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: jb.a[1]
+(2 rows)
+
+SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+ a 
+---
+ 2
+(1 row)
+
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 544bb610e2d..55b6c07f462 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1286,30 +1286,47 @@ select jsonb_insert('{"a": {"b": "value"}}', '{a, b}', '"new_value"', true);
 
 -- jsonb subscript
 select ('123'::jsonb)['a'];
+select ('123'::jsonb).a;
 select ('123'::jsonb)[0];
 select ('123'::jsonb)[NULL];
+select ('123'::jsonb).NULL;
 select ('{"a": 1}'::jsonb)['a'];
+select ('{"a": 1}'::jsonb).a;
 select ('{"a": 1}'::jsonb)[0];
 select ('{"a": 1}'::jsonb)['not_exist'];
+select ('{"a": 1}'::jsonb)."not_exist";
 select ('{"a": 1}'::jsonb)[NULL];
 select ('[1, "2", null]'::jsonb)['a'];
+select ('[1, "2", null]'::jsonb).a;
 select ('[1, "2", null]'::jsonb)[0];
 select ('[1, "2", null]'::jsonb)['1'];
+select ('[1, "2", null]'::jsonb)."1";
 select ('[1, "2", null]'::jsonb)[1.0];
 select ('[1, "2", null]'::jsonb)[2];
 select ('[1, "2", null]'::jsonb)[3];
 select ('[1, "2", null]'::jsonb)[-2];
 select ('[1, "2", null]'::jsonb)[1]['a'];
+select ('[1, "2", null]'::jsonb)[1].a;
 select ('[1, "2", null]'::jsonb)[1][0];
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['b'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).b;
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d;
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'][1];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d[1];
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d']['a'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['a'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d.a;
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1;
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2;
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']['a3'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2.a3;
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'];
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1;
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'][2];
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1[2];
 
 -- slices are not supported
 select ('{"a": 1}'::jsonb)['a':'b'];
@@ -1572,3 +1589,31 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::float8;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int2;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int4;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
+
+-- dot notation
+CREATE TABLE test_jsonb_dot_notation AS
+SELECT '{"a": [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], "b": [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]}'::jsonb jb;
+
+SELECT (jb).a FROM test_jsonb_dot_notation;
+SELECT (jb)."a" FROM test_jsonb_dot_notation; -- double quote should work
+SELECT (jb).'a' FROM test_jsonb_dot_notation; -- single quote should not work
+SELECT (jb).b FROM test_jsonb_dot_notation;
+SELECT (jb).c FROM test_jsonb_dot_notation;
+SELECT (jb).a.b FROM test_jsonb_dot_notation;
+SELECT (jb).a[2].b FROM test_jsonb_dot_notation;
+SELECT (jb).a[2:3].b FROM test_jsonb_dot_notation;
+SELECT (jb).a[2:].b FROM test_jsonb_dot_notation;
+SELECT (jb).a[:2].b FROM test_jsonb_dot_notation;
+SELECT (jb).a[:].b FROM test_jsonb_dot_notation;
+SELECT (jb).a.x.y FROM test_jsonb_dot_notation;
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
+SELECT (jb).b.x.z FROM test_jsonb_dot_notation;
+SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).* FROM test_jsonb_dot_notation;
+SELECT (jb).* FROM test_jsonb_dot_notation;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+SELECT (jb).a[1] FROM test_jsonb_dot_notation;
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v9-0007-Allow-wild-card-member-access-for-jsonb.patch (18.9K, ../../CAK98qZ1rZaVNy6ViangQom4iinZVH7=ebqhTsPxMQbN5ZtE9XQ@mail.gmail.com/8-v9-0007-Allow-wild-card-member-access-for-jsonb.patch)
  download | inline diff:
From 26c41cc638cf989bc5e50d9bda78894979873f08 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Sat, 1 Apr 2023 23:15:26 +0300
Subject: [PATCH v9 7/7] Allow wild card member access for jsonb

---
 src/backend/parser/gram.y           |   2 +
 src/backend/parser/parse_expr.c     |  34 +++--
 src/backend/parser/parse_target.c   |  67 +++++++---
 src/backend/utils/adt/ruleutils.c   |   6 +-
 src/include/parser/parse_expr.h     |   3 +
 src/test/regress/expected/jsonb.out | 192 +++++++++++++++++++++++++++-
 src/test/regress/sql/jsonb.sql      |  31 +++++
 7 files changed, 299 insertions(+), 36 deletions(-)

diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7d99c9355c6..041968c35db 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -18968,6 +18968,7 @@ check_func_name(List *names, core_yyscan_t yyscanner)
 static List *
 check_indirection(List *indirection, core_yyscan_t yyscanner)
 {
+#if 0
 	ListCell   *l;
 
 	foreach(l, indirection)
@@ -18978,6 +18979,7 @@ check_indirection(List *indirection, core_yyscan_t yyscanner)
 				parser_yyerror("improper use of \"*\"");
 		}
 	}
+#endif
 	return indirection;
 }
 
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 8ea51176196..512ac5b4970 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -74,7 +74,6 @@ static Node *transformColumnRef(ParseState *pstate, ColumnRef *cref);
 static Node *transformWholeRowRef(ParseState *pstate,
 								  ParseNamespaceItem *nsitem,
 								  int sublevels_up, int location);
-static Node *transformIndirection(ParseState *pstate, A_Indirection *ind);
 static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
 static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
 static Node *transformJsonObjectConstructor(ParseState *pstate,
@@ -158,7 +157,7 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 			break;
 
 		case T_A_Indirection:
-			result = transformIndirection(pstate, (A_Indirection *) expr);
+			result = transformIndirection(pstate, (A_Indirection *) expr, NULL);
 			break;
 
 		case T_A_ArrayExpr:
@@ -432,8 +431,9 @@ unknown_attribute(ParseState *pstate, Node *relref, const char *attname,
 	}
 }
 
-static Node *
-transformIndirection(ParseState *pstate, A_Indirection *ind)
+Node *
+transformIndirection(ParseState *pstate, A_Indirection *ind,
+					 bool *trailing_star_expansion)
 {
 	Node	   *last_srf = pstate->p_last_srf;
 	Node	   *result = transformExprRecurse(pstate, ind->arg);
@@ -454,12 +454,7 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		if (IsA(n, A_Indices))
 			subscripts = lappend(subscripts, n);
 		else if (IsA(n, A_Star))
-		{
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("row expansion via \"*\" is not supported here"),
-					 parser_errposition(pstate, location)));
-		}
+			subscripts = lappend(subscripts, n);
 		else
 		{
 			Assert(IsA(n, String));
@@ -491,7 +486,21 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 
 			n = linitial(subscripts);
 
-			if (!IsA(n, String))
+			if (IsA(n, A_Star))
+			{
+				/* Success, if trailing star expansion is allowed */
+				if (trailing_star_expansion && list_length(subscripts) == 1)
+				{
+					*trailing_star_expansion = true;
+					return result;
+				}
+
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("row expansion via \"*\" is not supported here"),
+						 parser_errposition(pstate, location)));
+			}
+			else if (!IsA(n, String))
 				ereport(ERROR,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
 						 errmsg("cannot subscript type %s because it does not support subscripting",
@@ -517,6 +526,9 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		result = newresult;
 	}
 
+	if (trailing_star_expansion)
+		*trailing_star_expansion = false;
+
 	return result;
 }
 
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 3ef5897f2eb..141fc1dfeb1 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -48,7 +48,7 @@ static Node *transformAssignmentSubscripts(ParseState *pstate,
 static List *ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
 								 bool make_target_entry);
 static List *ExpandAllTables(ParseState *pstate, int location);
-static List *ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
+static Node *ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
 								   bool make_target_entry, ParseExprKind exprKind);
 static List *ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 							   int sublevels_up, int location,
@@ -134,6 +134,7 @@ transformTargetList(ParseState *pstate, List *targetlist,
 	foreach(o_target, targetlist)
 	{
 		ResTarget  *res = (ResTarget *) lfirst(o_target);
+		Node	   *transformed = NULL;
 
 		/*
 		 * Check for "something.*".  Depending on the complexity of the
@@ -162,13 +163,19 @@ transformTargetList(ParseState *pstate, List *targetlist,
 
 				if (IsA(llast(ind->indirection), A_Star))
 				{
-					/* It is something.*, expand into multiple items */
-					p_target = list_concat(p_target,
-										   ExpandIndirectionStar(pstate,
-																 ind,
-																 true,
-																 exprKind));
-					continue;
+					Node	   *columns = ExpandIndirectionStar(pstate,
+																ind,
+																true,
+																exprKind);
+
+					if (IsA(columns, List))
+					{
+						/* It is something.*, expand into multiple items */
+						p_target = list_concat(p_target, (List *) columns);
+						continue;
+					}
+
+					transformed = (Node *) columns;
 				}
 			}
 		}
@@ -180,7 +187,7 @@ transformTargetList(ParseState *pstate, List *targetlist,
 		p_target = lappend(p_target,
 						   transformTargetEntry(pstate,
 												res->val,
-												NULL,
+												transformed,
 												exprKind,
 												res->name,
 												false));
@@ -251,10 +258,15 @@ transformExpressionList(ParseState *pstate, List *exprlist,
 
 			if (IsA(llast(ind->indirection), A_Star))
 			{
-				/* It is something.*, expand into multiple items */
-				result = list_concat(result,
-									 ExpandIndirectionStar(pstate, ind,
-														   false, exprKind));
+				Node	   *cols = ExpandIndirectionStar(pstate, ind,
+														 false, exprKind);
+
+				if (!cols || IsA(cols, List))
+					/* It is something.*, expand into multiple items */
+					result = list_concat(result, (List *) cols);
+				else
+					result = lappend(result, cols);
+
 				continue;
 			}
 		}
@@ -1345,22 +1357,30 @@ ExpandAllTables(ParseState *pstate, int location)
  * For robustness, we use a separate "make_target_entry" flag to control
  * this rather than relying on exprKind.
  */
-static List *
+static Node *
 ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
 					  bool make_target_entry, ParseExprKind exprKind)
 {
 	Node	   *expr;
+	ParseExprKind sv_expr_kind;
+	bool		trailing_star_expansion = false;
+
+	/* Save and restore identity of expression type we're parsing */
+	Assert(exprKind != EXPR_KIND_NONE);
+	sv_expr_kind = pstate->p_expr_kind;
+	pstate->p_expr_kind = exprKind;
 
 	/* Strip off the '*' to create a reference to the rowtype object */
-	ind = copyObject(ind);
-	ind->indirection = list_truncate(ind->indirection,
-									 list_length(ind->indirection) - 1);
+	expr = transformIndirection(pstate, ind, &trailing_star_expansion);
+
+	pstate->p_expr_kind = sv_expr_kind;
 
-	/* And transform that */
-	expr = transformExpr(pstate, (Node *) ind, exprKind);
+	/* '*' was consumed by generic type subscripting */
+	if (!trailing_star_expansion)
+		return expr;
 
 	/* Expand the rowtype expression into individual fields */
-	return ExpandRowReference(pstate, expr, make_target_entry);
+	return (Node *) ExpandRowReference(pstate, expr, make_target_entry);
 }
 
 /*
@@ -1785,13 +1805,18 @@ FigureColnameInternal(Node *node, char **name)
 				char	   *fname = NULL;
 				ListCell   *l;
 
-				/* find last field name, if any, ignoring "*" and subscripts */
+				/*
+				 * find last field name, if any, ignoring subscripts, and use
+				 * '?column?' when there's a trailing '*'.
+				 */
 				foreach(l, ind->indirection)
 				{
 					Node	   *i = lfirst(l);
 
 					if (IsA(i, String))
 						fname = strVal(i);
+					else if (IsA(i, A_Star))
+						fname = "?column?";
 				}
 				if (fname)
 				{
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index cfae8159a76..1bc292c5c45 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -12926,7 +12926,11 @@ printSubscripts(SubscriptingRef *sbsref, deparse_context *context)
 	{
 		Node	   *up = (Node *) lfirst(uplist_item);
 
-		if (IsA(up, String))
+		if (!up)
+		{
+			appendStringInfoString(buf, ".*");
+		}
+		else if (IsA(up, String))
 		{
 			appendStringInfoChar(buf, '.');
 			appendStringInfoString(buf, quote_identifier(strVal(up)));
diff --git a/src/include/parser/parse_expr.h b/src/include/parser/parse_expr.h
index efbaff8e710..c9f6a7724c0 100644
--- a/src/include/parser/parse_expr.h
+++ b/src/include/parser/parse_expr.h
@@ -22,4 +22,7 @@ extern Node *transformExpr(ParseState *pstate, Node *expr, ParseExprKind exprKin
 
 extern const char *ParseExprKindName(ParseExprKind exprKind);
 
+extern Node *transformIndirection(ParseState *pstate, A_Indirection *ind,
+								  bool *trailing_star_expansion);
+
 #endif							/* PARSE_EXPR_H */
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 480ecb44a94..aab69b4722c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5980,12 +5980,172 @@ SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
  
 (1 row)
 
+/* wild card member access */
 SELECT (jb).a.* FROM test_jsonb_dot_notation;
-ERROR:  type jsonb is not composite
+                 ?column?                  
+-------------------------------------------
+ ["c", "d", "f", {"y": "yyy", "z": "zzz"}]
+(1 row)
+
+SELECT (jb).* FROM test_jsonb_dot_notation;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (jb).* FROM test_jsonb_dot_notation t;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (jb).* FROM test_jsonb_dot_notation;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (t.jb).* FROM test_jsonb_dot_notation;
+ERROR:  missing FROM-clause entry for table "t"
+LINE 1: SELECT (t.jb).* FROM test_jsonb_dot_notation;
+                ^
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+                 ?column?                  
+-------------------------------------------
+ ["c", "d", "f", {"y": "yyy", "z": "zzz"}]
+(1 row)
+
+SELECT (jb).a.*.b FROM test_jsonb_dot_notation;
+ b 
+---
+ 
+(1 row)
+
+SELECT (jb).a.*.x FROM test_jsonb_dot_notation;
+ x 
+---
+ 
+(1 row)
+
+SELECT (jb).a.*.y FROM test_jsonb_dot_notation;
+   y   
+-------
+ "yyy"
+(1 row)
+
+SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+    ?column?    
+----------------
+ ["yyy", "zzz"]
+(1 row)
+
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT (jb).*.x FROM test_jsonb_dot_notation t;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+ x 
+---
+ 
+(1 row)
+
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+ x 
+---
+ 
+(1 row)
+
+SELECT ((jb).*)[:].x FROM test_jsonb_dot_notation t;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT (jb).*.x.* FROM test_jsonb_dot_notation;
+           ?column?           
+------------------------------
+ ["yyy", "zzz", "YYY", "ZZZ"]
+(1 row)
+
+SELECT (jb).*.x.y FROM test_jsonb_dot_notation;
+       y        
+----------------
+ ["yyy", "YYY"]
+(1 row)
+
+SELECT (jb).*.x.z FROM test_jsonb_dot_notation;
+       z        
+----------------
+ ["zzz", "ZZZ"]
+(1 row)
+
+SELECT (jb).*.*.y FROM test_jsonb_dot_notation;
+       y        
+----------------
+ ["yyy", "YYY"]
+(1 row)
+
+SELECT (jb).*.*.* FROM test_jsonb_dot_notation;
+           ?column?           
+------------------------------
+ ["yyy", "zzz", "YYY", "ZZZ"]
+(1 row)
+
+SELECT (jb).*.*.*.* FROM test_jsonb_dot_notation;
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT (jb).a.b.c.* FROM test_jsonb_dot_notation;
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT (jb).a.**.x FROM test_jsonb_dot_notation; -- not supported
+ERROR:  syntax error at or near "**"
+LINE 1: SELECT (jb).a.**.x FROM test_jsonb_dot_notation;
+                      ^
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).* FROM test_jsonb_dot_notation;
-ERROR:  type jsonb is not composite
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: jb.*
+(2 rows)
+
 SELECT (jb).* FROM test_jsonb_dot_notation;
-ERROR:  type jsonb is not composite
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
                   QUERY PLAN                  
 ----------------------------------------------
@@ -6012,3 +6172,29 @@ SELECT (jb).a[1] FROM test_jsonb_dot_notation;
  2
 (1 row)
 
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.* FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: jb.a.*
+(2 rows)
+
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+                 ?column?                  
+-------------------------------------------
+ ["c", "d", "f", {"y": "yyy", "z": "zzz"}]
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: jb.a.*[1:2].*.b
+(2 rows)
+
+SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
+ b 
+---
+ 
+(1 row)
+
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 55b6c07f462..7d602bd8dac 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1609,7 +1609,34 @@ SELECT (jb).a.x.y FROM test_jsonb_dot_notation;
 SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
 SELECT (jb).b.x.z FROM test_jsonb_dot_notation;
 SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
+
+/* wild card member access */
 SELECT (jb).a.* FROM test_jsonb_dot_notation;
+SELECT (jb).* FROM test_jsonb_dot_notation;
+SELECT (jb).* FROM test_jsonb_dot_notation t;
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+SELECT (jb).* FROM test_jsonb_dot_notation;
+SELECT (t.jb).* FROM test_jsonb_dot_notation;
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.b FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.x FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.y FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+SELECT (jb).*.x FROM test_jsonb_dot_notation t;
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+SELECT ((jb).*)[:].x FROM test_jsonb_dot_notation t;
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+SELECT (jb).*.x.* FROM test_jsonb_dot_notation;
+SELECT (jb).*.x.y FROM test_jsonb_dot_notation;
+SELECT (jb).*.x.z FROM test_jsonb_dot_notation;
+SELECT (jb).*.*.y FROM test_jsonb_dot_notation;
+SELECT (jb).*.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).*.*.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.b.c.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.**.x FROM test_jsonb_dot_notation; -- not supported
 
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).* FROM test_jsonb_dot_notation;
 SELECT (jb).* FROM test_jsonb_dot_notation;
@@ -1617,3 +1644,7 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
 SELECT (t.jb).a FROM test_jsonb_dot_notation t;
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
 SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
+SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v9-0005-Eanble-String-node-as-field-accessors-in-generic-.patch (7.7K, ../../CAK98qZ1rZaVNy6ViangQom4iinZVH7=ebqhTsPxMQbN5ZtE9XQ@mail.gmail.com/9-v9-0005-Eanble-String-node-as-field-accessors-in-generic-.patch)
  download | inline diff:
From 9e46413c7dfe4508e0ba78efb7733c845789b054 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Sat, 1 Apr 2023 16:21:20 +0300
Subject: [PATCH v9 5/7] Eanble String node as field accessors in generic
 subscripting

Now that we are allowing container generic subscripting to take dot
notation in the list of indirections, and it is transformed as a
String node.

For jsonb, we want to represent field accessors as String nodes in
refupperexprs for distinguishing from ordinary text subscripts which
can be needed for correct EXPLAIN.

Strings node is no longer a valid expression nodes, so added special
handling for them in walkers in nodeFuncs etc.
---
 src/backend/executor/execExpr.c    | 24 +++++++---
 src/backend/nodes/nodeFuncs.c      | 73 ++++++++++++++++++++++++++----
 src/backend/parser/parse_collate.c | 22 +++++++--
 src/backend/utils/adt/ruleutils.c  | 29 ++++++++----
 4 files changed, 121 insertions(+), 27 deletions(-)

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 03566c4d181..be4213455e5 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -3328,9 +3328,15 @@ ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref,
 		{
 			sbsrefstate->upperprovided[i] = true;
 			/* Each subscript is evaluated into appropriate array entry */
-			ExecInitExprRec(e, state,
-							&sbsrefstate->upperindex[i],
-							&sbsrefstate->upperindexnull[i]);
+			if (IsA(e, String))
+			{
+				sbsrefstate->upperindex[i] = CStringGetTextDatum(strVal(e));
+				sbsrefstate->upperindexnull[i] = false;
+			}
+			else
+				ExecInitExprRec(e, state,
+								&sbsrefstate->upperindex[i],
+								&sbsrefstate->upperindexnull[i]);
 		}
 		i++;
 	}
@@ -3351,9 +3357,15 @@ ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref,
 		{
 			sbsrefstate->lowerprovided[i] = true;
 			/* Each subscript is evaluated into appropriate array entry */
-			ExecInitExprRec(e, state,
-							&sbsrefstate->lowerindex[i],
-							&sbsrefstate->lowerindexnull[i]);
+			if (IsA(e, String))
+			{
+				sbsrefstate->lowerindex[i] = CStringGetTextDatum(strVal(e));
+				sbsrefstate->lowerindexnull[i] = false;
+			}
+			else
+				ExecInitExprRec(e, state,
+								&sbsrefstate->lowerindex[i],
+								&sbsrefstate->lowerindexnull[i]);
 		}
 		i++;
 	}
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 7bc823507f1..a9c29ab8f29 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -2182,12 +2182,28 @@ expression_tree_walker_impl(Node *node,
 		case T_SubscriptingRef:
 			{
 				SubscriptingRef *sbsref = (SubscriptingRef *) node;
+				ListCell   *lc;
+
+				/*
+				 * Recurse directly for upper/lower container index lists,
+				 * skipping String subscripts used for dot notation.
+				 */
+				foreach(lc, sbsref->refupperindexpr)
+				{
+					Node	   *expr = lfirst(lc);
+
+					if (expr && !IsA(expr, String) && WALK(expr))
+						return true;
+				}
+
+				foreach(lc, sbsref->reflowerindexpr)
+				{
+					Node	   *expr = lfirst(lc);
+
+					if (expr && !IsA(expr, String) && WALK(expr))
+						return true;
+				}
 
-				/* recurse directly for upper/lower container index lists */
-				if (LIST_WALK(sbsref->refupperindexpr))
-					return true;
-				if (LIST_WALK(sbsref->reflowerindexpr))
-					return true;
 				/* walker must see the refexpr and refassgnexpr, however */
 				if (WALK(sbsref->refexpr))
 					return true;
@@ -3082,12 +3098,51 @@ expression_tree_mutator_impl(Node *node,
 			{
 				SubscriptingRef *sbsref = (SubscriptingRef *) node;
 				SubscriptingRef *newnode;
+				ListCell   *lc;
+				List	   *exprs = NIL;
 
 				FLATCOPY(newnode, sbsref, SubscriptingRef);
-				MUTATE(newnode->refupperindexpr, sbsref->refupperindexpr,
-					   List *);
-				MUTATE(newnode->reflowerindexpr, sbsref->reflowerindexpr,
-					   List *);
+
+				foreach(lc, sbsref->refupperindexpr)
+				{
+					Node	   *expr = lfirst(lc);
+
+					if (expr && IsA(expr, String))
+					{
+						String	   *str;
+
+						FLATCOPY(str, expr, String);
+						expr = (Node *) str;
+					}
+					else
+						expr = mutator(expr, context);
+
+					exprs = lappend(exprs, expr);
+				}
+
+				newnode->refupperindexpr = exprs;
+
+				exprs = NIL;
+
+				foreach(lc, sbsref->reflowerindexpr)
+				{
+					Node	   *expr = lfirst(lc);
+
+					if (expr && IsA(expr, String))
+					{
+						String	   *str;
+
+						FLATCOPY(str, expr, String);
+						expr = (Node *) str;
+					}
+					else
+						expr = mutator(expr, context);
+
+					exprs = lappend(exprs, expr);
+				}
+
+				newnode->reflowerindexpr = exprs;
+
 				MUTATE(newnode->refexpr, sbsref->refexpr,
 					   Expr *);
 				MUTATE(newnode->refassgnexpr, sbsref->refassgnexpr,
diff --git a/src/backend/parser/parse_collate.c b/src/backend/parser/parse_collate.c
index d2e218353f3..be6dea6ffd2 100644
--- a/src/backend/parser/parse_collate.c
+++ b/src/backend/parser/parse_collate.c
@@ -680,11 +680,25 @@ assign_collations_walker(Node *node, assign_collations_context *context)
 							 * contribute anything.)
 							 */
 							SubscriptingRef *sbsref = (SubscriptingRef *) node;
+							ListCell   *lc;
+
+							/* skip String subscripts used for dot notation */
+							foreach(lc, sbsref->refupperindexpr)
+							{
+								Node	   *expr = lfirst(lc);
+
+								if (expr && !IsA(expr, String))
+									assign_expr_collations(context->pstate, expr);
+							}
+
+							foreach(lc, sbsref->reflowerindexpr)
+							{
+								Node	   *expr = lfirst(lc);
+
+								if (expr && !IsA(expr, String))
+									assign_expr_collations(context->pstate, expr);
+							}
 
-							assign_expr_collations(context->pstate,
-												   (Node *) sbsref->refupperindexpr);
-							assign_expr_collations(context->pstate,
-												   (Node *) sbsref->reflowerindexpr);
 							(void) assign_collations_walker((Node *) sbsref->refexpr,
 															&loccontext);
 							(void) assign_collations_walker((Node *) sbsref->refassgnexpr,
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index d11a8a20eea..cfae8159a76 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -47,6 +47,7 @@
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "nodes/pathnodes.h"
+#include "nodes/subscripting.h"
 #include "optimizer/optimizer.h"
 #include "parser/parse_agg.h"
 #include "parser/parse_func.h"
@@ -12923,17 +12924,29 @@ printSubscripts(SubscriptingRef *sbsref, deparse_context *context)
 	lowlist_item = list_head(sbsref->reflowerindexpr);	/* could be NULL */
 	foreach(uplist_item, sbsref->refupperindexpr)
 	{
-		appendStringInfoChar(buf, '[');
-		if (lowlist_item)
+		Node	   *up = (Node *) lfirst(uplist_item);
+
+		if (IsA(up, String))
+		{
+			appendStringInfoChar(buf, '.');
+			appendStringInfoString(buf, quote_identifier(strVal(up)));
+		}
+		else
 		{
+			appendStringInfoChar(buf, '[');
+			if (lowlist_item)
+			{
+				/* If subexpression is NULL, get_rule_expr prints nothing */
+				get_rule_expr((Node *) lfirst(lowlist_item), context, false);
+				appendStringInfoChar(buf, ':');
+			}
 			/* If subexpression is NULL, get_rule_expr prints nothing */
-			get_rule_expr((Node *) lfirst(lowlist_item), context, false);
-			appendStringInfoChar(buf, ':');
-			lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
+			get_rule_expr((Node *) lfirst(uplist_item), context, false);
+			appendStringInfoChar(buf, ']');
 		}
-		/* If subexpression is NULL, get_rule_expr prints nothing */
-		get_rule_expr((Node *) lfirst(uplist_item), context, false);
-		appendStringInfoChar(buf, ']');
+
+		if (lowlist_item)
+			lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
 	}
 }
 
-- 
2.39.5 (Apple Git-154)



^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
@ 2025-03-03 19:16             ` Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  0 siblings, 1 reply; 67+ messages in thread

From: Matheus Alcantara @ 2025-03-03 19:16 UTC (permalink / raw)
  To: Alexandra Wang <[email protected]>; +Cc: Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>; jian he <[email protected]>

Hi Alex,

The code comments and the commit messages help a lot when reviewing! Thanks for
the new version.

The code LGTM and check-world is happy. I've also performed some tests and
everything looks good!

Just some minor points about this new version:

## v9-0005

Typo on commit message title

## v9-0006

> + * The following functions create various types of JsonPathParseItem nodes,
> + * which are used to build JsonPath expressions for jsonb simplified accessor.
>
Just to avoid misinterpretation I think that we can replace "The following
functions" with "The make_jsonpath_item_* functions" since we can have more
functions in the future that are not fully related with these. Does that make
sense?

-- 
Matheus Alcantara






^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
@ 2025-03-03 19:43               ` Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  0 siblings, 1 reply; 67+ messages in thread

From: Matheus Alcantara @ 2025-03-03 19:43 UTC (permalink / raw)
  To: Alexandra Wang <[email protected]>; +Cc: Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>; jian he <[email protected]>

On Mon, Mar 3, 2025 at 4:16 PM Matheus Alcantara
<[email protected]> wrote:
>
> Hi Alex,
>
> The code comments and the commit messages help a lot when reviewing! Thanks for
> the new version.
>
> The code LGTM and check-world is happy. I've also performed some tests and
> everything looks good!
>
> Just some minor points about this new version:
>
> ## v9-0005
>
> Typo on commit message title
>
> ## v9-0006
>
> > + * The following functions create various types of JsonPathParseItem nodes,
> > + * which are used to build JsonPath expressions for jsonb simplified accessor.
> >
> Just to avoid misinterpretation I think that we can replace "The following
> functions" with "The make_jsonpath_item_* functions" since we can have more
> functions in the future that are not fully related with these. Does that make
> sense?
>

Sorry, I've forgotten to include a question. Do you have anything in mind about
documentation changes for this patch?

-- 
Matheus Alcantara






^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
@ 2025-03-03 20:22                 ` Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  0 siblings, 1 reply; 67+ messages in thread

From: Alexandra Wang @ 2025-03-03 20:22 UTC (permalink / raw)
  To: Matheus Alcantara <[email protected]>; +Cc: Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>; jian he <[email protected]>

Hi Matheus,

On Mon, Mar 3, 2025 at 1:43 PM Matheus Alcantara <[email protected]>
wrote:

> On Mon, Mar 3, 2025 at 4:16 PM Matheus Alcantara
> <[email protected]> wrote:
> >
> > Hi Alex,
> >
> > The code comments and the commit messages help a lot when reviewing!
> Thanks for
> > the new version.
> >
> > The code LGTM and check-world is happy. I've also performed some tests
> and
> > everything looks good!

> Just some minor points about this new version:
> >
> > ## v9-0005
> >
> > Typo on commit message title

> ## v9-0006
> >
> > > + * The following functions create various types of JsonPathParseItem
> nodes,
> > > + * which are used to build JsonPath expressions for jsonb simplified
> accessor.
> > >
> > Just to avoid misinterpretation I think that we can replace "The
> following
> > functions" with "The make_jsonpath_item_* functions" since we can have
> more
> > functions in the future that are not fully related with these. Does that
> make
> > sense?
>

Thank you so much for reviewing! I've attached v10, which addresses your
feedback.

On Mon, Mar 3, 2025 at 1:43 PM Matheus Alcantara <[email protected]>
wrote:

> Sorry, I've forgotten to include a question. Do you have anything in mind
> about
> documentation changes for this patch?
>

For the documentation, I’m thinking of adding it under JSON Types [1].
I’d either add a new “Simple Dot-Notation” section after jsonb
subscripting [2] or replace it. Let me know what you think.

[1] https://www.postgresql.org/docs/current/datatype-json.html#DATATYPE-JSON
[2]
https://www.postgresql.org/docs/current/datatype-json.html#JSONB-SUBSCRIPTING

Best,
Alex


Attachments:

  [application/octet-stream] v10-0004-Extract-coerce_jsonpath_subscript.patch (5.5K, ../../CAK98qZ14uKKRVFg4ibzMfReYaZD6Byxq8nYnvNNtXNjCfcd8kQ@mail.gmail.com/3-v10-0004-Extract-coerce_jsonpath_subscript.patch)
  download | inline diff:
From f03fa1630122561006254b958ab9c14e4cb5bc06 Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Wed, 26 Feb 2025 13:03:27 -0600
Subject: [PATCH v10 4/7] Extract coerce_jsonpath_subscript()

This is a preparation step for a future commit that will reuse the
aforementioned function.
---
 src/backend/utils/adt/jsonbsubs.c | 142 ++++++++++++++++--------------
 1 file changed, 78 insertions(+), 64 deletions(-)

diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index a0d38a0fd80..3ffe40cfa40 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -32,6 +32,83 @@ typedef struct JsonbSubWorkspace
 	Datum	   *index;			/* Subscript values in Datum format */
 } JsonbSubWorkspace;
 
+static Oid
+jsonb_subscript_type(Node *expr)
+{
+	if (expr && IsA(expr, String))
+		return TEXTOID;
+
+	return exprType(expr);
+}
+
+static Node *
+coerce_jsonpath_subscript(ParseState *pstate, Node *subExpr, Oid numtype)
+{
+	Oid			subExprType = jsonb_subscript_type(subExpr);
+	Oid			targetType = UNKNOWNOID;
+
+	if (subExprType != UNKNOWNOID)
+	{
+		Oid			targets[2] = {numtype, TEXTOID};
+
+		/*
+		 * Jsonb can handle multiple subscript types, but cases when a
+		 * subscript could be coerced to multiple target types must be
+		 * avoided, similar to overloaded functions. It could be possibly
+		 * extend with jsonpath in the future.
+		 */
+		for (int i = 0; i < 2; i++)
+		{
+			if (can_coerce_type(1, &subExprType, &targets[i], COERCION_IMPLICIT))
+			{
+				/*
+				 * One type has already succeeded, it means there are two
+				 * coercion targets possible, failure.
+				 */
+				if (targetType != UNKNOWNOID)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
+							 errhint("jsonb subscript must be coercible to only one type, integer or text."),
+							 parser_errposition(pstate, exprLocation(subExpr))));
+
+				targetType = targets[i];
+			}
+		}
+
+		/*
+		 * No suitable types were found, failure.
+		 */
+		if (targetType == UNKNOWNOID)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
+					 errhint("jsonb subscript must be coercible to either integer or text."),
+					 parser_errposition(pstate, exprLocation(subExpr))));
+	}
+	else
+		targetType = TEXTOID;
+
+	/*
+	 * We known from can_coerce_type that coercion will succeed, so
+	 * coerce_type could be used. Note the implicit coercion context, which is
+	 * required to handle subscripts of different types, similar to overloaded
+	 * functions.
+	 */
+	subExpr = coerce_type(pstate,
+						  subExpr, subExprType,
+						  targetType, -1,
+						  COERCION_IMPLICIT,
+						  COERCE_IMPLICIT_CAST,
+						  -1);
+	if (subExpr == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("jsonb subscript must have text type"),
+				 parser_errposition(pstate, exprLocation(subExpr))));
+
+	return subExpr;
+}
 
 /*
  * Finish parse analysis of a SubscriptingRef expression for a jsonb.
@@ -75,71 +152,8 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 
 		if (ai->uidx)
 		{
-			Oid			subExprType = InvalidOid,
-						targetType = UNKNOWNOID;
-
 			subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
-			subExprType = exprType(subExpr);
-
-			if (subExprType != UNKNOWNOID)
-			{
-				Oid			targets[2] = {INT4OID, TEXTOID};
-
-				/*
-				 * Jsonb can handle multiple subscript types, but cases when a
-				 * subscript could be coerced to multiple target types must be
-				 * avoided, similar to overloaded functions. It could be
-				 * possibly extend with jsonpath in the future.
-				 */
-				for (int i = 0; i < 2; i++)
-				{
-					if (can_coerce_type(1, &subExprType, &targets[i], COERCION_IMPLICIT))
-					{
-						/*
-						 * One type has already succeeded, it means there are
-						 * two coercion targets possible, failure.
-						 */
-						if (targetType != UNKNOWNOID)
-							ereport(ERROR,
-									(errcode(ERRCODE_DATATYPE_MISMATCH),
-									 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
-									 errhint("jsonb subscript must be coercible to only one type, integer or text."),
-									 parser_errposition(pstate, exprLocation(subExpr))));
-
-						targetType = targets[i];
-					}
-				}
-
-				/*
-				 * No suitable types were found, failure.
-				 */
-				if (targetType == UNKNOWNOID)
-					ereport(ERROR,
-							(errcode(ERRCODE_DATATYPE_MISMATCH),
-							 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
-							 errhint("jsonb subscript must be coercible to either integer or text."),
-							 parser_errposition(pstate, exprLocation(subExpr))));
-			}
-			else
-				targetType = TEXTOID;
-
-			/*
-			 * We known from can_coerce_type that coercion will succeed, so
-			 * coerce_type could be used. Note the implicit coercion context,
-			 * which is required to handle subscripts of different types,
-			 * similar to overloaded functions.
-			 */
-			subExpr = coerce_type(pstate,
-								  subExpr, subExprType,
-								  targetType, -1,
-								  COERCION_IMPLICIT,
-								  COERCE_IMPLICIT_CAST,
-								  -1);
-			if (subExpr == NULL)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("jsonb subscript must have text type"),
-						 parser_errposition(pstate, exprLocation(subExpr))));
+			subExpr = coerce_jsonpath_subscript(pstate, subExpr, INT4OID);
 		}
 		else
 		{
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v10-0002-Allow-Generic-Type-Subscripting-to-Accept-Dot-No.patch (10.3K, ../../CAK98qZ14uKKRVFg4ibzMfReYaZD6Byxq8nYnvNNtXNjCfcd8kQ@mail.gmail.com/4-v10-0002-Allow-Generic-Type-Subscripting-to-Accept-Dot-No.patch)
  download | inline diff:
From 050703ccb4e1786710722b451eabcf1a908cd763 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Sat, 1 Apr 2023 16:21:20 +0300
Subject: [PATCH v10 2/7] Allow Generic Type Subscripting to Accept Dot
 Notation (.) as Input

This change extends generic type subscripting to recognize dot
notation (.) in addition to bracket notation ([]). While this does not
yet provide full support for dot notation, it enables subscripting
containers to process it in the future.

For now, container-specific transform functions only handle
subscripting indices and stop processing when encountering dot
notation. It is up to individual containers to decide how to transform
dot notation in subsequent updates.
---
 src/backend/parser/parse_expr.c   | 66 ++++++++++++++++++++-----------
 src/backend/parser/parse_node.c   | 41 +++++++++++++++++--
 src/backend/parser/parse_target.c |  3 +-
 src/backend/utils/adt/arraysubs.c | 13 ++++--
 src/backend/utils/adt/jsonbsubs.c | 11 +++++-
 src/include/parser/parse_node.h   |  3 +-
 6 files changed, 105 insertions(+), 32 deletions(-)

diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 2c0f4a50b21..8ea51176196 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -442,8 +442,9 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 	ListCell   *i;
 
 	/*
-	 * We have to split any field-selection operations apart from
-	 * subscripting.  Adjacent A_Indices nodes have to be treated as a single
+	 * Combine field names and subscripts into a single indirection list, as
+	 * some subscripting containers, such as jsonb, support field access using
+	 * dot notation. Adjacent A_Indices nodes have to be treated as a single
 	 * multidimensional subscript operation.
 	 */
 	foreach(i, ind->indirection)
@@ -461,19 +462,43 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		}
 		else
 		{
-			Node	   *newresult;
-
 			Assert(IsA(n, String));
+			subscripts = lappend(subscripts, n);
+		}
+	}
+
+	while (subscripts)
+	{
+		/* try processing container subscripts first */
+		Node	   *newresult = (Node *)
+			transformContainerSubscripts(pstate,
+										 result,
+										 exprType(result),
+										 exprTypmod(result),
+										 &subscripts,
+										 false,
+										 true);
+
+		if (!newresult)
+		{
+			/*
+			 * generic subscripting failed; falling back to function call or
+			 * field selection for a composite type.
+			 */
+			Node	   *n;
+
+			Assert(subscripts);
 
-			/* process subscripts before this field selection */
-			while (subscripts)
-				result = (Node *) transformContainerSubscripts(pstate,
-															   result,
-															   exprType(result),
-															   exprTypmod(result),
-															   &subscripts,
-															   false);
+			n = linitial(subscripts);
+
+			if (!IsA(n, String))
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("cannot subscript type %s because it does not support subscripting",
+								format_type_be(exprType(result))),
+						 parser_errposition(pstate, exprLocation(result))));
 
+			/* try to find function for field selection */
 			newresult = ParseFuncOrColumn(pstate,
 										  list_make1(n),
 										  list_make1(result),
@@ -481,19 +506,16 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 										  NULL,
 										  false,
 										  location);
-			if (newresult == NULL)
+
+			if (!newresult)
 				unknown_attribute(pstate, result, strVal(n), location);
-			result = newresult;
+
+			/* consume field select */
+			subscripts = list_delete_first(subscripts);
 		}
+
+		result = newresult;
 	}
-	/* process trailing subscripts, if any */
-	while (subscripts)
-		result = (Node *) transformContainerSubscripts(pstate,
-													   result,
-													   exprType(result),
-													   exprTypmod(result),
-													   &subscripts,
-													   false);
 
 	return result;
 }
diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c
index 19a6b678e67..b3e476eb181 100644
--- a/src/backend/parser/parse_node.c
+++ b/src/backend/parser/parse_node.c
@@ -238,6 +238,8 @@ transformContainerType(Oid *containerType, int32 *containerTypmod)
  * containerTypMod	typmod for the container
  * indirection		Untransformed list of subscripts (must not be NIL)
  * isAssignment		True if this will become a container assignment.
+ * noError			True for return NULL with no error, if the container type
+ * 					is not subscriptable.
  */
 SubscriptingRef *
 transformContainerSubscripts(ParseState *pstate,
@@ -245,13 +247,15 @@ transformContainerSubscripts(ParseState *pstate,
 							 Oid containerType,
 							 int32 containerTypMod,
 							 List **indirection,
-							 bool isAssignment)
+							 bool isAssignment,
+							 bool noError)
 {
 	SubscriptingRef *sbsref;
 	const SubscriptRoutines *sbsroutines;
 	Oid			elementType;
 	bool		isSlice = false;
 	ListCell   *idx;
+	int			indirection_length = list_length(*indirection);
 
 	/*
 	 * Determine the actual container type, smashing any domain.  In the
@@ -267,11 +271,16 @@ transformContainerSubscripts(ParseState *pstate,
 	 */
 	sbsroutines = getSubscriptingRoutines(containerType, &elementType);
 	if (!sbsroutines)
+	{
+		if (noError)
+			return NULL;
+
 		ereport(ERROR,
 				(errcode(ERRCODE_DATATYPE_MISMATCH),
 				 errmsg("cannot subscript type %s because it does not support subscripting",
 						format_type_be(containerType)),
 				 parser_errposition(pstate, exprLocation(containerBase))));
+	}
 
 	/*
 	 * Detect whether any of the indirection items are slice specifiers.
@@ -282,9 +291,9 @@ transformContainerSubscripts(ParseState *pstate,
 	 */
 	foreach(idx, *indirection)
 	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
+		Node	   *ai = lfirst(idx);
 
-		if (ai->is_slice)
+		if (IsA(ai, A_Indices) && castNode(A_Indices, ai)->is_slice)
 		{
 			isSlice = true;
 			break;
@@ -312,6 +321,32 @@ transformContainerSubscripts(ParseState *pstate,
 	sbsroutines->transform(sbsref, indirection, pstate,
 						   isSlice, isAssignment);
 
+	/*
+	 * Error out, if datatype failed to consume any indirection elements.
+	 */
+	if (list_length(*indirection) == indirection_length)
+	{
+		Node	   *ind = linitial(*indirection);
+
+		if (noError)
+			return NULL;
+
+		if (IsA(ind, String))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("type %s does not support dot notation",
+							format_type_be(containerType)),
+					 parser_errposition(pstate, exprLocation(containerBase))));
+		else if (IsA(ind, A_Indices))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("type %s does not support array subscripting",
+							format_type_be(containerType)),
+					 parser_errposition(pstate, exprLocation(containerBase))));
+		else
+			elog(ERROR, "invalid indirection operation: %d", nodeTag(ind));
+	}
+
 	/*
 	 * Verify we got a valid type (this defends, for example, against someone
 	 * using array_subscript_handler as typsubscript without setting typelem).
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 4675a523045..3ef5897f2eb 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -937,7 +937,8 @@ transformAssignmentSubscripts(ParseState *pstate,
 										  containerType,
 										  containerTypMod,
 										  &subscripts,
-										  true);
+										  true,
+										  false);
 
 	typeNeeded = sbsref->refrestype;
 	typmodNeeded = sbsref->reftypmod;
diff --git a/src/backend/utils/adt/arraysubs.c b/src/backend/utils/adt/arraysubs.c
index 234c2c278c1..d03d3519dfd 100644
--- a/src/backend/utils/adt/arraysubs.c
+++ b/src/backend/utils/adt/arraysubs.c
@@ -62,6 +62,7 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	List	   *upperIndexpr = NIL;
 	List	   *lowerIndexpr = NIL;
 	ListCell   *idx;
+	int			ndim;
 
 	/*
 	 * Transform the subscript expressions, and separate upper and lower
@@ -73,9 +74,14 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	 */
 	foreach(idx, *indirection)
 	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
+		A_Indices  *ai;
 		Node	   *subexpr;
 
+		if (!IsA(lfirst(idx), A_Indices))
+			break;
+
+		ai = lfirst_node(A_Indices, idx);
+
 		if (isSlice)
 		{
 			if (ai->lidx)
@@ -145,14 +151,15 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->reflowerindexpr = lowerIndexpr;
 
 	/* Verify subscript list lengths are within implementation limit */
-	if (list_length(upperIndexpr) > MAXDIM)
+	ndim = list_length(upperIndexpr);
+	if (ndim > MAXDIM)
 		ereport(ERROR,
 				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 				 errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
 						list_length(upperIndexpr), MAXDIM)));
 	/* We need not check lowerIndexpr separately */
 
-	*indirection = NIL;
+	*indirection = list_delete_first_n(*indirection, ndim);
 
 	/*
 	 * Determine the result type of the subscripting operation.  It's the same
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index 8ad6aa1ad4f..a0d38a0fd80 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -55,9 +55,14 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	 */
 	foreach(idx, *indirection)
 	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
+		A_Indices  *ai;
 		Node	   *subExpr;
 
+		if (!IsA(lfirst(idx), A_Indices))
+			break;
+
+		ai = lfirst_node(A_Indices, idx);
+
 		if (isSlice)
 		{
 			Node	   *expr = ai->uidx ? ai->uidx : ai->lidx;
@@ -160,7 +165,9 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->refrestype = JSONBOID;
 	sbsref->reftypmod = -1;
 
-	*indirection = NIL;
+	/* Remove processed elements */
+	if (upperIndexpr)
+		*indirection = list_delete_first_n(*indirection, list_length(upperIndexpr));
 }
 
 /*
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 5ae11ccec33..71b04bd503c 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -378,7 +378,8 @@ extern SubscriptingRef *transformContainerSubscripts(ParseState *pstate,
 													 Oid containerType,
 													 int32 containerTypMod,
 													 List **indirection,
-													 bool isAssignment);
+													 bool isAssignment,
+													 bool noError);
 extern Const *make_const(ParseState *pstate, A_Const *aconst);
 
 #endif							/* PARSE_NODE_H */
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v10-0003-Export-jsonPathFromParseResult.patch (2.5K, ../../CAK98qZ14uKKRVFg4ibzMfReYaZD6Byxq8nYnvNNtXNjCfcd8kQ@mail.gmail.com/5-v10-0003-Export-jsonPathFromParseResult.patch)
  download | inline diff:
From 6029a27fcad003f1c6643ddba011fc3660db21e3 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Sat, 1 Apr 2023 23:15:55 +0300
Subject: [PATCH v10 3/7] Export jsonPathFromParseResult()

This is a preparation step for a future commit that will reuse the
aforementioned function.
---
 src/backend/utils/adt/jsonpath.c | 19 +++++++++++++++----
 src/include/utils/jsonpath.h     |  4 ++++
 2 files changed, 19 insertions(+), 4 deletions(-)

diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index 762f7e8a09d..1536797cf23 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -166,15 +166,13 @@ jsonpath_send(PG_FUNCTION_ARGS)
  * Converts C-string to a jsonpath value.
  *
  * Uses jsonpath parser to turn string into an AST, then
- * flattenJsonPathParseItem() does second pass turning AST into binary
+ * jsonPathFromParseResult() does second pass turning AST into binary
  * representation of jsonpath.
  */
 static Datum
 jsonPathFromCstring(char *in, int len, struct Node *escontext)
 {
 	JsonPathParseResult *jsonpath = parsejsonpath(in, len, escontext);
-	JsonPath   *res;
-	StringInfoData buf;
 
 	if (SOFT_ERROR_OCCURRED(escontext))
 		return (Datum) 0;
@@ -185,8 +183,21 @@ jsonPathFromCstring(char *in, int len, struct Node *escontext)
 				 errmsg("invalid input syntax for type %s: \"%s\"", "jsonpath",
 						in)));
 
+	return jsonPathFromParseResult(jsonpath, 4 * len, escontext);
+}
+
+/*
+ * Converts jsonpath AST into jsonpath value in binary.
+ */
+Datum
+jsonPathFromParseResult(JsonPathParseResult *jsonpath, int estimated_len,
+						struct Node *escontext)
+{
+	JsonPath   *res;
+	StringInfoData buf;
+
 	initStringInfo(&buf);
-	enlargeStringInfo(&buf, 4 * len /* estimation */ );
+	enlargeStringInfo(&buf, estimated_len);
 
 	appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
 
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index 23a76d233e9..e05941623e7 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -281,6 +281,10 @@ extern JsonPathParseResult *parsejsonpath(const char *str, int len,
 extern bool jspConvertRegexFlags(uint32 xflags, int *result,
 								 struct Node *escontext);
 
+extern Datum jsonPathFromParseResult(JsonPathParseResult *jsonpath,
+									 int estimated_len,
+									 struct Node *escontext);
+
 /*
  * Struct for details about external variables passed into jsonpath executor
  */
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v10-0005-Enable-String-node-as-field-accessors-in-generic.patch (7.7K, ../../CAK98qZ14uKKRVFg4ibzMfReYaZD6Byxq8nYnvNNtXNjCfcd8kQ@mail.gmail.com/6-v10-0005-Enable-String-node-as-field-accessors-in-generic.patch)
  download | inline diff:
From 6a9eec5d7a0df7deaf2b94c52af0377b6577cca2 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Sat, 1 Apr 2023 16:21:20 +0300
Subject: [PATCH v10 5/7] Enable String node as field accessors in generic
 subscripting

Now that we are allowing container generic subscripting to take dot
notation in the list of indirections, and it is transformed as a
String node.

For jsonb, we want to represent field accessors as String nodes in
refupperexprs for distinguishing from ordinary text subscripts which
can be needed for correct EXPLAIN.

Strings node is no longer a valid expression nodes, so added special
handling for them in walkers in nodeFuncs etc.
---
 src/backend/executor/execExpr.c    | 24 +++++++---
 src/backend/nodes/nodeFuncs.c      | 73 ++++++++++++++++++++++++++----
 src/backend/parser/parse_collate.c | 22 +++++++--
 src/backend/utils/adt/ruleutils.c  | 29 ++++++++----
 4 files changed, 121 insertions(+), 27 deletions(-)

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 03566c4d181..be4213455e5 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -3328,9 +3328,15 @@ ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref,
 		{
 			sbsrefstate->upperprovided[i] = true;
 			/* Each subscript is evaluated into appropriate array entry */
-			ExecInitExprRec(e, state,
-							&sbsrefstate->upperindex[i],
-							&sbsrefstate->upperindexnull[i]);
+			if (IsA(e, String))
+			{
+				sbsrefstate->upperindex[i] = CStringGetTextDatum(strVal(e));
+				sbsrefstate->upperindexnull[i] = false;
+			}
+			else
+				ExecInitExprRec(e, state,
+								&sbsrefstate->upperindex[i],
+								&sbsrefstate->upperindexnull[i]);
 		}
 		i++;
 	}
@@ -3351,9 +3357,15 @@ ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref,
 		{
 			sbsrefstate->lowerprovided[i] = true;
 			/* Each subscript is evaluated into appropriate array entry */
-			ExecInitExprRec(e, state,
-							&sbsrefstate->lowerindex[i],
-							&sbsrefstate->lowerindexnull[i]);
+			if (IsA(e, String))
+			{
+				sbsrefstate->lowerindex[i] = CStringGetTextDatum(strVal(e));
+				sbsrefstate->lowerindexnull[i] = false;
+			}
+			else
+				ExecInitExprRec(e, state,
+								&sbsrefstate->lowerindex[i],
+								&sbsrefstate->lowerindexnull[i]);
 		}
 		i++;
 	}
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 7bc823507f1..a9c29ab8f29 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -2182,12 +2182,28 @@ expression_tree_walker_impl(Node *node,
 		case T_SubscriptingRef:
 			{
 				SubscriptingRef *sbsref = (SubscriptingRef *) node;
+				ListCell   *lc;
+
+				/*
+				 * Recurse directly for upper/lower container index lists,
+				 * skipping String subscripts used for dot notation.
+				 */
+				foreach(lc, sbsref->refupperindexpr)
+				{
+					Node	   *expr = lfirst(lc);
+
+					if (expr && !IsA(expr, String) && WALK(expr))
+						return true;
+				}
+
+				foreach(lc, sbsref->reflowerindexpr)
+				{
+					Node	   *expr = lfirst(lc);
+
+					if (expr && !IsA(expr, String) && WALK(expr))
+						return true;
+				}
 
-				/* recurse directly for upper/lower container index lists */
-				if (LIST_WALK(sbsref->refupperindexpr))
-					return true;
-				if (LIST_WALK(sbsref->reflowerindexpr))
-					return true;
 				/* walker must see the refexpr and refassgnexpr, however */
 				if (WALK(sbsref->refexpr))
 					return true;
@@ -3082,12 +3098,51 @@ expression_tree_mutator_impl(Node *node,
 			{
 				SubscriptingRef *sbsref = (SubscriptingRef *) node;
 				SubscriptingRef *newnode;
+				ListCell   *lc;
+				List	   *exprs = NIL;
 
 				FLATCOPY(newnode, sbsref, SubscriptingRef);
-				MUTATE(newnode->refupperindexpr, sbsref->refupperindexpr,
-					   List *);
-				MUTATE(newnode->reflowerindexpr, sbsref->reflowerindexpr,
-					   List *);
+
+				foreach(lc, sbsref->refupperindexpr)
+				{
+					Node	   *expr = lfirst(lc);
+
+					if (expr && IsA(expr, String))
+					{
+						String	   *str;
+
+						FLATCOPY(str, expr, String);
+						expr = (Node *) str;
+					}
+					else
+						expr = mutator(expr, context);
+
+					exprs = lappend(exprs, expr);
+				}
+
+				newnode->refupperindexpr = exprs;
+
+				exprs = NIL;
+
+				foreach(lc, sbsref->reflowerindexpr)
+				{
+					Node	   *expr = lfirst(lc);
+
+					if (expr && IsA(expr, String))
+					{
+						String	   *str;
+
+						FLATCOPY(str, expr, String);
+						expr = (Node *) str;
+					}
+					else
+						expr = mutator(expr, context);
+
+					exprs = lappend(exprs, expr);
+				}
+
+				newnode->reflowerindexpr = exprs;
+
 				MUTATE(newnode->refexpr, sbsref->refexpr,
 					   Expr *);
 				MUTATE(newnode->refassgnexpr, sbsref->refassgnexpr,
diff --git a/src/backend/parser/parse_collate.c b/src/backend/parser/parse_collate.c
index d2e218353f3..be6dea6ffd2 100644
--- a/src/backend/parser/parse_collate.c
+++ b/src/backend/parser/parse_collate.c
@@ -680,11 +680,25 @@ assign_collations_walker(Node *node, assign_collations_context *context)
 							 * contribute anything.)
 							 */
 							SubscriptingRef *sbsref = (SubscriptingRef *) node;
+							ListCell   *lc;
+
+							/* skip String subscripts used for dot notation */
+							foreach(lc, sbsref->refupperindexpr)
+							{
+								Node	   *expr = lfirst(lc);
+
+								if (expr && !IsA(expr, String))
+									assign_expr_collations(context->pstate, expr);
+							}
+
+							foreach(lc, sbsref->reflowerindexpr)
+							{
+								Node	   *expr = lfirst(lc);
+
+								if (expr && !IsA(expr, String))
+									assign_expr_collations(context->pstate, expr);
+							}
 
-							assign_expr_collations(context->pstate,
-												   (Node *) sbsref->refupperindexpr);
-							assign_expr_collations(context->pstate,
-												   (Node *) sbsref->reflowerindexpr);
 							(void) assign_collations_walker((Node *) sbsref->refexpr,
 															&loccontext);
 							(void) assign_collations_walker((Node *) sbsref->refassgnexpr,
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index d11a8a20eea..cfae8159a76 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -47,6 +47,7 @@
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "nodes/pathnodes.h"
+#include "nodes/subscripting.h"
 #include "optimizer/optimizer.h"
 #include "parser/parse_agg.h"
 #include "parser/parse_func.h"
@@ -12923,17 +12924,29 @@ printSubscripts(SubscriptingRef *sbsref, deparse_context *context)
 	lowlist_item = list_head(sbsref->reflowerindexpr);	/* could be NULL */
 	foreach(uplist_item, sbsref->refupperindexpr)
 	{
-		appendStringInfoChar(buf, '[');
-		if (lowlist_item)
+		Node	   *up = (Node *) lfirst(uplist_item);
+
+		if (IsA(up, String))
+		{
+			appendStringInfoChar(buf, '.');
+			appendStringInfoString(buf, quote_identifier(strVal(up)));
+		}
+		else
 		{
+			appendStringInfoChar(buf, '[');
+			if (lowlist_item)
+			{
+				/* If subexpression is NULL, get_rule_expr prints nothing */
+				get_rule_expr((Node *) lfirst(lowlist_item), context, false);
+				appendStringInfoChar(buf, ':');
+			}
 			/* If subexpression is NULL, get_rule_expr prints nothing */
-			get_rule_expr((Node *) lfirst(lowlist_item), context, false);
-			appendStringInfoChar(buf, ':');
-			lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
+			get_rule_expr((Node *) lfirst(uplist_item), context, false);
+			appendStringInfoChar(buf, ']');
 		}
-		/* If subexpression is NULL, get_rule_expr prints nothing */
-		get_rule_expr((Node *) lfirst(uplist_item), context, false);
-		appendStringInfoChar(buf, ']');
+
+		if (lowlist_item)
+			lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
 	}
 }
 
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v10-0001-Allow-transformation-of-only-a-sublist-of-subscr.patch (8.8K, ../../CAK98qZ14uKKRVFg4ibzMfReYaZD6Byxq8nYnvNNtXNjCfcd8kQ@mail.gmail.com/7-v10-0001-Allow-transformation-of-only-a-sublist-of-subscr.patch)
  download | inline diff:
From bd946db88d0f149f073f76eaa2501e4845b9b2b5 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Fri, 14 Oct 2022 15:35:22 +0300
Subject: [PATCH v10 1/7] Allow transformation of only a sublist of subscripts

This is a preparation step for allowing subscripting containers to
transform only a prefix of an indirection list and modify the list
in-place by removing the processed elements. Currently, all elements
are consumed, and the list is set to NIL after transformation.

In the following commit, subscripting containers will gain the
flexibility to stop transformation when encountering an unsupported
indirection and return the remaining indirections to the caller.
---
 contrib/hstore/hstore_subs.c      | 10 ++++++----
 src/backend/parser/parse_expr.c   |  9 ++++-----
 src/backend/parser/parse_node.c   |  4 ++--
 src/backend/parser/parse_target.c |  2 +-
 src/backend/utils/adt/arraysubs.c |  6 ++++--
 src/backend/utils/adt/jsonbsubs.c |  6 ++++--
 src/include/nodes/subscripting.h  |  7 ++++++-
 src/include/parser/parse_node.h   |  2 +-
 8 files changed, 28 insertions(+), 18 deletions(-)

diff --git a/contrib/hstore/hstore_subs.c b/contrib/hstore/hstore_subs.c
index 3d03f66fa0d..1b29543ab67 100644
--- a/contrib/hstore/hstore_subs.c
+++ b/contrib/hstore/hstore_subs.c
@@ -40,7 +40,7 @@
  */
 static void
 hstore_subscript_transform(SubscriptingRef *sbsref,
-						   List *indirection,
+						   List **indirection,
 						   ParseState *pstate,
 						   bool isSlice,
 						   bool isAssignment)
@@ -49,15 +49,15 @@ hstore_subscript_transform(SubscriptingRef *sbsref,
 	Node	   *subexpr;
 
 	/* We support only single-subscript, non-slice cases */
-	if (isSlice || list_length(indirection) != 1)
+	if (isSlice || list_length(*indirection) != 1)
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("hstore allows only one subscript"),
 				 parser_errposition(pstate,
-									exprLocation((Node *) indirection))));
+									exprLocation((Node *) *indirection))));
 
 	/* Transform the subscript expression to type text */
-	ai = linitial_node(A_Indices, indirection);
+	ai = linitial_node(A_Indices, *indirection);
 	Assert(ai->uidx != NULL && ai->lidx == NULL && !ai->is_slice);
 
 	subexpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
@@ -81,6 +81,8 @@ hstore_subscript_transform(SubscriptingRef *sbsref,
 	/* Determine the result type of the subscripting operation; always text */
 	sbsref->refrestype = TEXTOID;
 	sbsref->reftypmod = -1;
+
+	*indirection = NIL;
 }
 
 /*
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index bad1df732ea..2c0f4a50b21 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -466,14 +466,13 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 			Assert(IsA(n, String));
 
 			/* process subscripts before this field selection */
-			if (subscripts)
+			while (subscripts)
 				result = (Node *) transformContainerSubscripts(pstate,
 															   result,
 															   exprType(result),
 															   exprTypmod(result),
-															   subscripts,
+															   &subscripts,
 															   false);
-			subscripts = NIL;
 
 			newresult = ParseFuncOrColumn(pstate,
 										  list_make1(n),
@@ -488,12 +487,12 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		}
 	}
 	/* process trailing subscripts, if any */
-	if (subscripts)
+	while (subscripts)
 		result = (Node *) transformContainerSubscripts(pstate,
 													   result,
 													   exprType(result),
 													   exprTypmod(result),
-													   subscripts,
+													   &subscripts,
 													   false);
 
 	return result;
diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c
index d6feb16aef3..19a6b678e67 100644
--- a/src/backend/parser/parse_node.c
+++ b/src/backend/parser/parse_node.c
@@ -244,7 +244,7 @@ transformContainerSubscripts(ParseState *pstate,
 							 Node *containerBase,
 							 Oid containerType,
 							 int32 containerTypMod,
-							 List *indirection,
+							 List **indirection,
 							 bool isAssignment)
 {
 	SubscriptingRef *sbsref;
@@ -280,7 +280,7 @@ transformContainerSubscripts(ParseState *pstate,
 	 * element.  If any of the items are slice specifiers (lower:upper), then
 	 * the subscript expression means a container slice operation.
 	 */
-	foreach(idx, indirection)
+	foreach(idx, *indirection)
 	{
 		A_Indices  *ai = lfirst_node(A_Indices, idx);
 
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 4aba0d9d4d5..4675a523045 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -936,7 +936,7 @@ transformAssignmentSubscripts(ParseState *pstate,
 										  basenode,
 										  containerType,
 										  containerTypMod,
-										  subscripts,
+										  &subscripts,
 										  true);
 
 	typeNeeded = sbsref->refrestype;
diff --git a/src/backend/utils/adt/arraysubs.c b/src/backend/utils/adt/arraysubs.c
index 2940fb8e8d7..234c2c278c1 100644
--- a/src/backend/utils/adt/arraysubs.c
+++ b/src/backend/utils/adt/arraysubs.c
@@ -54,7 +54,7 @@ typedef struct ArraySubWorkspace
  */
 static void
 array_subscript_transform(SubscriptingRef *sbsref,
-						  List *indirection,
+						  List **indirection,
 						  ParseState *pstate,
 						  bool isSlice,
 						  bool isAssignment)
@@ -71,7 +71,7 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	 * indirection items to slices by treating the single subscript as the
 	 * upper bound and supplying an assumed lower bound of 1.
 	 */
-	foreach(idx, indirection)
+	foreach(idx, *indirection)
 	{
 		A_Indices  *ai = lfirst_node(A_Indices, idx);
 		Node	   *subexpr;
@@ -152,6 +152,8 @@ array_subscript_transform(SubscriptingRef *sbsref,
 						list_length(upperIndexpr), MAXDIM)));
 	/* We need not check lowerIndexpr separately */
 
+	*indirection = NIL;
+
 	/*
 	 * Determine the result type of the subscripting operation.  It's the same
 	 * as the array type if we're slicing, else it's the element type.  In
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index de64d498512..8ad6aa1ad4f 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -41,7 +41,7 @@ typedef struct JsonbSubWorkspace
  */
 static void
 jsonb_subscript_transform(SubscriptingRef *sbsref,
-						  List *indirection,
+						  List **indirection,
 						  ParseState *pstate,
 						  bool isSlice,
 						  bool isAssignment)
@@ -53,7 +53,7 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	 * Transform and convert the subscript expressions. Jsonb subscripting
 	 * does not support slices, look only and the upper index.
 	 */
-	foreach(idx, indirection)
+	foreach(idx, *indirection)
 	{
 		A_Indices  *ai = lfirst_node(A_Indices, idx);
 		Node	   *subExpr;
@@ -159,6 +159,8 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	/* Determine the result type of the subscripting operation; always jsonb */
 	sbsref->refrestype = JSONBOID;
 	sbsref->reftypmod = -1;
+
+	*indirection = NIL;
 }
 
 /*
diff --git a/src/include/nodes/subscripting.h b/src/include/nodes/subscripting.h
index 234e8ad8012..5d576af346f 100644
--- a/src/include/nodes/subscripting.h
+++ b/src/include/nodes/subscripting.h
@@ -71,6 +71,11 @@ struct SubscriptExecSteps;
  * does not care to support slicing, it can just throw an error if isSlice.)
  * See array_subscript_transform() for sample code.
  *
+ * The transform method receives a pointer to a list of raw indirections.
+ * This allows the method to parse a sublist of the indirections (typically
+ * the prefix) and modify the original list in place, enabling the caller to
+ * either process the remaining indirections differently or raise an error.
+ *
  * The transform method is also responsible for identifying the result type
  * of the subscripting operation.  At call, refcontainertype and reftypmod
  * describe the container type (this will be a base type not a domain), and
@@ -93,7 +98,7 @@ struct SubscriptExecSteps;
  * assignment must return.
  */
 typedef void (*SubscriptTransform) (SubscriptingRef *sbsref,
-									List *indirection,
+									List **indirection,
 									struct ParseState *pstate,
 									bool isSlice,
 									bool isAssignment);
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 994284019fb..5ae11ccec33 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -377,7 +377,7 @@ extern SubscriptingRef *transformContainerSubscripts(ParseState *pstate,
 													 Node *containerBase,
 													 Oid containerType,
 													 int32 containerTypMod,
-													 List *indirection,
+													 List **indirection,
 													 bool isAssignment);
 extern Const *make_const(ParseState *pstate, A_Const *aconst);
 
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v10-0006-Implement-read-only-dot-notation-for-jsonb.patch (26.8K, ../../CAK98qZ14uKKRVFg4ibzMfReYaZD6Byxq8nYnvNNtXNjCfcd8kQ@mail.gmail.com/8-v10-0006-Implement-read-only-dot-notation-for-jsonb.patch)
  download | inline diff:
From 809e88954658cca633a56566521071188070db53 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Sat, 1 Apr 2023 23:17:53 +0300
Subject: [PATCH v10 6/7] Implement read-only dot notation for jsonb

This patch introduces JSONB member access using dot notation, wildcard
access, and array subscripting with slicing, aligning with the JSON
simplified accessor specified in SQL:2023. Specifically, the following
syntax enhancements are added:

1. Simple dot-notation access to JSONB object fields
2. Wildcard dot-notation access to JSONB object fields
2. Subscripting for index range access to JSONB array elements

Examples:

-- Setup
create table t(x int, y jsonb);
insert into t select 1, '{"a": 1, "b": 42}'::jsonb;
insert into t select 1, '{"a": 2, "b": {"c": 42}}'::jsonb;
insert into t select 1, '{"a": 3, "b": {"c": "42"}, "d":[11, 12]}'::jsonb;

-- Existing syntax predates the SQL standard:
select (t.y)->'b' from t;
select (t.y)->'b'->'c' from t;
select (t.y)->'d'->0 from t;

-- JSON simplified accessor specified by the SQL standard:
select (t.y).b from t;
select (t.y).b.c from t;
select (t.y).d[0] from t;

The SQL standard states that simplified access is equivalent to:
JSON_QUERY (VEP, 'lax $.JC' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR)

where:
  VEP = <value expression primary>
  JC = <JSON simplified accessor op chain>

For example, the JSON_QUERY equivalents of the above queries are:

select json_query(y, 'lax $.b' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t;
select json_query(y, 'lax $.b.c' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t;
select json_query(y, 'lax $.d[0]' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t;

Implementation details:

Extends the existing container subscripting interface to support
container-specific information, specifically a JSONPath expression for
jsonb.

During query transformation, detects dot-notation, wildcard access,
and sliced subscripting. If any of these accessors are present,
constructs a JSONPath expression representing the access chain.

During execution, if a JSONPath expression is present in
JsonbSubWorkspace, executes it via JsonPathQuery().

Does not transform accessors directly into JSON_QUERY during
transformation to preserve the original query structure for EXPLAIN
and CREATE VIEW.
---
 src/backend/utils/adt/jsonbsubs.c   | 294 ++++++++++++++++++++++++++--
 src/include/nodes/primnodes.h       |   7 +
 src/test/regress/expected/jsonb.out | 249 ++++++++++++++++++++++-
 src/test/regress/sql/jsonb.sql      |  45 +++++
 4 files changed, 569 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index 3ffe40cfa40..edada16025b 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -15,21 +15,30 @@
 #include "postgres.h"
 
 #include "executor/execExpr.h"
+#include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "nodes/subscripting.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_expr.h"
 #include "utils/builtins.h"
 #include "utils/jsonb.h"
+#include "utils/jsonpath.h"
 
 
-/* SubscriptingRefState.workspace for jsonb subscripting execution */
+/*
+ * SubscriptingRefState.workspace for generic jsonb subscripting execution.
+ *
+ * Stores state for both jsonb simple subscripting and dot notation access.
+ * Dot notation additionally uses `jsonpath` for JsonPath evaluation.
+ */
 typedef struct JsonbSubWorkspace
 {
 	bool		expectArray;	/* jsonb root is expected to be an array */
 	Oid		   *indexOid;		/* OID of coerced subscript expression, could
 								 * be only integer or text */
 	Datum	   *index;			/* Subscript values in Datum format */
+	JsonPath   *jsonpath;		/* JsonPath for dot notation execution via
+								 * JsonPathQuery() */
 } JsonbSubWorkspace;
 
 static Oid
@@ -110,6 +119,229 @@ coerce_jsonpath_subscript(ParseState *pstate, Node *subExpr, Oid numtype)
 	return subExpr;
 }
 
+/*
+ * During transformation, determine whether to build a JsonPath
+ * for JsonPathQuery() execution.
+ *
+ * JsonPath is needed if the indirection list includes:
+ * - String-based access (dot notation)
+ * - Wildcard (`*`)
+ * - Slice-based subscripting
+ *
+ * Otherwise, simple jsonb subscripting is sufficient.
+ */
+static bool
+jsonb_check_jsonpath_needed(List *indirection)
+{
+	ListCell   *lc;
+
+	foreach(lc, indirection)
+	{
+		Node	   *accessor = lfirst(lc);
+
+		if (IsA(accessor, String) ||
+			IsA(accessor, A_Star))
+			return true;
+		else
+		{
+			A_Indices  *ai;
+
+			Assert(IsA(accessor, A_Indices));
+			ai = castNode(A_Indices, accessor);
+
+			if (!ai->uidx || ai->lidx)
+			{
+				Assert(ai->is_slice);
+				return true;
+			}
+		}
+	}
+
+	return false;
+}
+
+/*
+ * Helper functions for constructing JsonPath expressions.
+ *
+ * The make_jsonpath_item_* functions create various types of JsonPathParseItem
+ * nodes, which are used to build JsonPath expressions for jsonb simplified
+ * accessor.
+ */
+
+static JsonPathParseItem *
+make_jsonpath_item(JsonPathItemType type)
+{
+	JsonPathParseItem *v = palloc(sizeof(*v));
+
+	v->type = type;
+	v->next = NULL;
+
+	return v;
+}
+
+static JsonPathParseItem *
+make_jsonpath_item_int(int32 val, List **exprs)
+{
+	JsonPathParseItem *jpi = make_jsonpath_item(jpiNumeric);
+
+	jpi->value.numeric =
+		DatumGetNumeric(DirectFunctionCall1(int4_numeric, Int32GetDatum(val)));
+
+	*exprs = lappend(*exprs, makeConst(INT4OID, -1, InvalidOid, 4,
+									   Int32GetDatum(val), false, true));
+
+	return jpi;
+}
+
+/*
+ * Convert an expression into a JsonPathParseItem.
+ * If the expression is a constant integer, create a direct numeric item.
+ * Otherwise, create a variable reference and add it to the expression list.
+ */
+static JsonPathParseItem *
+make_jsonpath_item_expr(ParseState *pstate, Node *expr, List **exprs)
+{
+	Const	   *cnst;
+
+	expr = transformExpr(pstate, expr, pstate->p_expr_kind);
+
+	if (!IsA(expr, Const))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("jsonb simplified accessor supports subscripting in const int4, got type: %s",
+						format_type_be(exprType(expr))),
+				 parser_errposition(pstate, exprLocation(expr))));
+
+	cnst = (Const *) expr;
+
+	if (cnst->consttype == INT4OID && !cnst->constisnull)
+	{
+		int32		val = DatumGetInt32(cnst->constvalue);
+
+		return make_jsonpath_item_int(val, exprs);
+	}
+
+	ereport(ERROR,
+			(errcode(ERRCODE_DATATYPE_MISMATCH),
+			 errmsg("jsonb simplified accessor supports subscripting in type: INT4, got type: %s",
+					format_type_be(cnst->consttype)),
+			 parser_errposition(pstate, exprLocation(expr))));
+}
+
+/*
+ * jsonb_subscript_make_jsonpath
+ *
+ * Constructs a JsonPath expression from a list of indirections.
+ * This function is used when jsonb subscripting involves dot notation,
+ * wildcards (*), or slice-based subscripting, requiring JsonPath-based
+ * evaluation.
+ *
+ * The function modifies the indirection list in place, removing processed
+ * elements as it converts them into JsonPath components, as follows:
+ * - String keys (dot notation) -> jpiKey items.
+ * - Wildcard (*) -> jpiAnyKey item.
+ * - Array indices and slices -> jpiIndexArray items.
+ *
+ * Parameters:
+ * - pstate: Parse state context.
+ * - indirection: List of subscripting expressions (modified in-place).
+ * - uexprs: Upper-bound expressions extracted from subscripts.
+ * - lexprs: Lower-bound expressions extracted from subscripts.
+ * Returns:
+ * - a Const node containing the transformed JsonPath expression.
+ */
+static Node *
+jsonb_subscript_make_jsonpath(ParseState *pstate, List **indirection,
+							  List **uexprs, List **lexprs)
+{
+	JsonPathParseResult jpres;
+	JsonPathParseItem *path = make_jsonpath_item(jpiRoot);
+	ListCell   *lc;
+	Datum		jsp;
+	int			pathlen = 0;
+
+	*uexprs = NIL;
+	*lexprs = NIL;
+
+	jpres.expr = path;
+	jpres.lax = true;
+
+	foreach(lc, *indirection)
+	{
+		Node	   *accessor = lfirst(lc);
+		JsonPathParseItem *jpi;
+
+		if (IsA(accessor, String))
+		{
+			char	   *field = strVal(accessor);
+
+			jpi = make_jsonpath_item(jpiKey);
+			jpi->value.string.val = field;
+			jpi->value.string.len = strlen(field);
+
+			*uexprs = lappend(*uexprs, accessor);
+		}
+		else if (IsA(accessor, A_Star))
+		{
+			jpi = make_jsonpath_item(jpiAnyKey);
+
+			*uexprs = lappend(*uexprs, NULL);
+		}
+		else if (IsA(accessor, A_Indices))
+		{
+			A_Indices  *ai = castNode(A_Indices, accessor);
+
+			jpi = make_jsonpath_item(jpiIndexArray);
+			jpi->value.array.nelems = 1;
+			jpi->value.array.elems = palloc(sizeof(jpi->value.array.elems[0]));
+
+			if (ai->is_slice)
+			{
+				while (list_length(*lexprs) < list_length(*uexprs))
+					*lexprs = lappend(*lexprs, NULL);
+
+				if (ai->lidx)
+					jpi->value.array.elems[0].from = make_jsonpath_item_expr(pstate, ai->lidx, lexprs);
+				else
+					jpi->value.array.elems[0].from = make_jsonpath_item_int(0, lexprs);
+
+				if (ai->uidx)
+					jpi->value.array.elems[0].to = make_jsonpath_item_expr(pstate, ai->uidx, uexprs);
+				else
+				{
+					jpi->value.array.elems[0].to = make_jsonpath_item(jpiLast);
+					*uexprs = lappend(*uexprs, NULL);
+				}
+			}
+			else
+			{
+				Assert(ai->uidx && !ai->lidx);
+				jpi->value.array.elems[0].from = make_jsonpath_item_expr(pstate, ai->uidx, uexprs);
+				jpi->value.array.elems[0].to = NULL;
+			}
+		}
+		else
+			break;
+
+		/* append path item */
+		path->next = jpi;
+		path = jpi;
+		pathlen++;
+	}
+
+	if (*lexprs)
+	{
+		while (list_length(*lexprs) < list_length(*uexprs))
+			*lexprs = lappend(*lexprs, NULL);
+	}
+
+	*indirection = list_delete_first_n(*indirection, pathlen);
+
+	jsp = jsonPathFromParseResult(&jpres, 0, NULL);
+
+	return (Node *) makeConst(JSONPATHOID, -1, InvalidOid, -1, jsp, false, false);
+}
+
 /*
  * Finish parse analysis of a SubscriptingRef expression for a jsonb.
  *
@@ -126,19 +358,32 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	List	   *upperIndexpr = NIL;
 	ListCell   *idx;
 
+	/* Determine the result type of the subscripting operation; always jsonb */
+	sbsref->refrestype = JSONBOID;
+	sbsref->reftypmod = -1;
+
+	if (jsonb_check_jsonpath_needed(*indirection))
+	{
+		sbsref->refjsonbpath =
+			jsonb_subscript_make_jsonpath(pstate, indirection,
+										  &sbsref->refupperindexpr,
+										  &sbsref->reflowerindexpr);
+		return;
+	}
+
 	/*
 	 * Transform and convert the subscript expressions. Jsonb subscripting
 	 * does not support slices, look only and the upper index.
 	 */
 	foreach(idx, *indirection)
 	{
+		Node	   *i = lfirst(idx);
 		A_Indices  *ai;
 		Node	   *subExpr;
 
-		if (!IsA(lfirst(idx), A_Indices))
-			break;
+		Assert(IsA(i, A_Indices));
 
-		ai = lfirst_node(A_Indices, idx);
+		ai = castNode(A_Indices, i);
 
 		if (isSlice)
 		{
@@ -175,10 +420,6 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->refupperindexpr = upperIndexpr;
 	sbsref->reflowerindexpr = NIL;
 
-	/* Determine the result type of the subscripting operation; always jsonb */
-	sbsref->refrestype = JSONBOID;
-	sbsref->reftypmod = -1;
-
 	/* Remove processed elements */
 	if (upperIndexpr)
 		*indirection = list_delete_first_n(*indirection, list_length(upperIndexpr));
@@ -233,7 +474,7 @@ jsonb_subscript_check_subscripts(ExprState *state,
 			 * For jsonb fetch and assign functions we need to provide path in
 			 * text format. Convert if it's not already text.
 			 */
-			if (workspace->indexOid[i] == INT4OID)
+			if (!workspace->jsonpath && workspace->indexOid[i] == INT4OID)
 			{
 				Datum		datum = sbsrefstate->upperindex[i];
 				char	   *cs = DatumGetCString(DirectFunctionCall1(int4out, datum));
@@ -261,17 +502,32 @@ jsonb_subscript_fetch(ExprState *state,
 {
 	SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
 	JsonbSubWorkspace *workspace = (JsonbSubWorkspace *) sbsrefstate->workspace;
-	Jsonb	   *jsonbSource;
 
 	/* Should not get here if source jsonb (or any subscript) is null */
 	Assert(!(*op->resnull));
 
-	jsonbSource = DatumGetJsonbP(*op->resvalue);
-	*op->resvalue = jsonb_get_element(jsonbSource,
-									  workspace->index,
-									  sbsrefstate->numupper,
-									  op->resnull,
-									  false);
+	if (workspace->jsonpath)
+	{
+		bool		empty = false;
+		bool		error = false;
+
+		*op->resvalue = JsonPathQuery(*op->resvalue, workspace->jsonpath,
+									  JSW_CONDITIONAL,
+									  &empty, &error, NULL,
+									  NULL);
+
+		*op->resnull = empty || error;
+	}
+	else
+	{
+		Jsonb	   *jsonbSource = DatumGetJsonbP(*op->resvalue);
+
+		*op->resvalue = jsonb_get_element(jsonbSource,
+										  workspace->index,
+										  sbsrefstate->numupper,
+										  op->resnull,
+										  false);
+	}
 }
 
 /*
@@ -381,6 +637,7 @@ jsonb_exec_setup(const SubscriptingRef *sbsref,
 	ListCell   *lc;
 	int			nupper = sbsref->refupperindexpr->length;
 	char	   *ptr;
+	bool		useJsonpath = sbsref->refjsonbpath != NULL;
 
 	/* Allocate type-specific workspace with space for per-subscript data */
 	workspace = palloc0(MAXALIGN(sizeof(JsonbSubWorkspace)) +
@@ -388,6 +645,9 @@ jsonb_exec_setup(const SubscriptingRef *sbsref,
 	workspace->expectArray = false;
 	ptr = ((char *) workspace) + MAXALIGN(sizeof(JsonbSubWorkspace));
 
+	if (useJsonpath)
+		workspace->jsonpath = DatumGetJsonPathP(castNode(Const, sbsref->refjsonbpath)->constvalue);
+
 	/*
 	 * This coding assumes sizeof(Datum) >= sizeof(Oid), else we might
 	 * misalign the indexOid pointer
@@ -404,7 +664,7 @@ jsonb_exec_setup(const SubscriptingRef *sbsref,
 		Node	   *expr = lfirst(lc);
 		int			i = foreach_current_index(lc);
 
-		workspace->indexOid[i] = exprType(expr);
+		workspace->indexOid[i] = jsonb_subscript_type(expr);
 	}
 
 	/*
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index d0576da3e25..9d380ed60d6 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -718,6 +718,13 @@ typedef struct SubscriptingRef
 	Expr	   *refexpr;
 	/* expression for the source value, or NULL if fetch */
 	Expr	   *refassgnexpr;
+
+	/*
+	 * container-specific extra information, currently used only by jsonb.
+	 * stores a JsonPath expression when jsonb dot notation is used. NULL for
+	 * simple subscripting.
+	 */
+	Node	   *refjsonbpath;
 } SubscriptingRef;
 
 /*
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 2baff931bf2..480ecb44a94 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -4939,6 +4939,12 @@ select ('123'::jsonb)['a'];
  
 (1 row)
 
+select ('123'::jsonb).a;
+ a 
+---
+ 
+(1 row)
+
 select ('123'::jsonb)[0];
  jsonb 
 -------
@@ -4951,12 +4957,24 @@ select ('123'::jsonb)[NULL];
  
 (1 row)
 
+select ('123'::jsonb).NULL;
+ null 
+------
+ 
+(1 row)
+
 select ('{"a": 1}'::jsonb)['a'];
  jsonb 
 -------
  1
 (1 row)
 
+select ('{"a": 1}'::jsonb).a;
+ a 
+---
+ 1
+(1 row)
+
 select ('{"a": 1}'::jsonb)[0];
  jsonb 
 -------
@@ -4969,6 +4987,12 @@ select ('{"a": 1}'::jsonb)['not_exist'];
  
 (1 row)
 
+select ('{"a": 1}'::jsonb)."not_exist";
+ not_exist 
+-----------
+ 
+(1 row)
+
 select ('{"a": 1}'::jsonb)[NULL];
  jsonb 
 -------
@@ -4981,6 +5005,12 @@ select ('[1, "2", null]'::jsonb)['a'];
  
 (1 row)
 
+select ('[1, "2", null]'::jsonb).a;
+ a 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[0];
  jsonb 
 -------
@@ -4993,6 +5023,12 @@ select ('[1, "2", null]'::jsonb)['1'];
  "2"
 (1 row)
 
+select ('[1, "2", null]'::jsonb)."1";
+ 1 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[1.0];
 ERROR:  subscript type numeric is not supported
 LINE 1: select ('[1, "2", null]'::jsonb)[1.0];
@@ -5022,6 +5058,12 @@ select ('[1, "2", null]'::jsonb)[1]['a'];
  
 (1 row)
 
+select ('[1, "2", null]'::jsonb)[1].a;
+ a 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[1][0];
  jsonb 
 -------
@@ -5034,73 +5076,139 @@ select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['b'];
  "c"
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).b;
+  b  
+-----
+ "c"
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'];
    jsonb   
 -----------
  [1, 2, 3]
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d;
+     d     
+-----------
+ [1, 2, 3]
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'][1];
  jsonb 
 -------
  2
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d[1];
+ d 
+---
+ 2
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d']['a'];
  jsonb 
 -------
  
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['a'];
+ERROR:  jsonb simplified accessor supports subscripting in type: INT4, got type: unknown
+LINE 1: select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['a'];
+                                                               ^
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d.a;
+ a 
+---
+ 
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1'];
      jsonb     
 ---------------
  {"a2": "aaa"}
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1;
+      a1       
+---------------
+ {"a2": "aaa"}
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2'];
  jsonb 
 -------
  "aaa"
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2;
+  a2   
+-------
+ "aaa"
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']['a3'];
  jsonb 
 -------
  
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2.a3;
+ a3 
+----
+ 
+(1 row)
+
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'];
          jsonb         
 -----------------------
  ["aaa", "bbb", "ccc"]
 (1 row)
 
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1;
+          b1           
+-----------------------
+ ["aaa", "bbb", "ccc"]
+(1 row)
+
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'][2];
  jsonb 
 -------
  "ccc"
 (1 row)
 
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1[2];
+  b1   
+-------
+ "ccc"
+(1 row)
+
 -- slices are not supported
 select ('{"a": 1}'::jsonb)['a':'b'];
-ERROR:  jsonb subscript does not support slices
+ERROR:  jsonb simplified accessor supports subscripting in type: INT4, got type: unknown
 LINE 1: select ('{"a": 1}'::jsonb)['a':'b'];
-                                       ^
+                                   ^
 select ('[1, "2", null]'::jsonb)[1:2];
-ERROR:  jsonb subscript does not support slices
-LINE 1: select ('[1, "2", null]'::jsonb)[1:2];
-                                           ^
+    jsonb    
+-------------
+ ["2", null]
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[:2];
 ERROR:  jsonb subscript does not support slices
 LINE 1: select ('[1, "2", null]'::jsonb)[:2];
                                           ^
 select ('[1, "2", null]'::jsonb)[1:];
-ERROR:  jsonb subscript does not support slices
-LINE 1: select ('[1, "2", null]'::jsonb)[1:];
-                                         ^
+    jsonb    
+-------------
+ ["2", null]
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[:];
-ERROR:  jsonb subscript does not support slices
+     jsonb      
+----------------
+ [1, "2", null]
+(1 row)
+
 create TEMP TABLE test_jsonb_subscript (
        id int,
        test_json jsonb
@@ -5781,3 +5889,126 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
  12345
 (1 row)
 
+-- dot notation
+CREATE TABLE test_jsonb_dot_notation AS
+SELECT '{"a": [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], "b": [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]}'::jsonb jb;
+SELECT (jb).a FROM test_jsonb_dot_notation;
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+SELECT (jb)."a" FROM test_jsonb_dot_notation; -- double quote should work
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+SELECT (jb).'a' FROM test_jsonb_dot_notation; -- single quote should not work
+ERROR:  syntax error at or near "'a'"
+LINE 1: SELECT (jb).'a' FROM test_jsonb_dot_notation;
+                    ^
+SELECT (jb).b FROM test_jsonb_dot_notation;
+                         b                         
+---------------------------------------------------
+ [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]
+(1 row)
+
+SELECT (jb).c FROM test_jsonb_dot_notation;
+ c 
+---
+ 
+(1 row)
+
+SELECT (jb).a.b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT (jb).a[2].b FROM test_jsonb_dot_notation;
+  b  
+-----
+ "c"
+(1 row)
+
+SELECT (jb).a[2:3].b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT (jb).a[2:].b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT (jb).a[:2].b FROM test_jsonb_dot_notation;
+  b  
+-----
+ "c"
+(1 row)
+
+SELECT (jb).a[:].b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT (jb).a.x.y FROM test_jsonb_dot_notation;
+   y   
+-------
+ "yyy"
+(1 row)
+
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
+            x             
+--------------------------
+ {"y": "YYY", "z": "ZZZ"}
+(1 row)
+
+SELECT (jb).b.x.z FROM test_jsonb_dot_notation;
+   z   
+-------
+ "ZZZ"
+(1 row)
+
+SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
+ c 
+---
+ 
+(1 row)
+
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+ERROR:  type jsonb is not composite
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).* FROM test_jsonb_dot_notation;
+ERROR:  type jsonb is not composite
+SELECT (jb).* FROM test_jsonb_dot_notation;
+ERROR:  type jsonb is not composite
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+                  QUERY PLAN                  
+----------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation t
+   Output: jb.a
+(2 rows)
+
+SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: jb.a[1]
+(2 rows)
+
+SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+ a 
+---
+ 2
+(1 row)
+
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 544bb610e2d..55b6c07f462 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1286,30 +1286,47 @@ select jsonb_insert('{"a": {"b": "value"}}', '{a, b}', '"new_value"', true);
 
 -- jsonb subscript
 select ('123'::jsonb)['a'];
+select ('123'::jsonb).a;
 select ('123'::jsonb)[0];
 select ('123'::jsonb)[NULL];
+select ('123'::jsonb).NULL;
 select ('{"a": 1}'::jsonb)['a'];
+select ('{"a": 1}'::jsonb).a;
 select ('{"a": 1}'::jsonb)[0];
 select ('{"a": 1}'::jsonb)['not_exist'];
+select ('{"a": 1}'::jsonb)."not_exist";
 select ('{"a": 1}'::jsonb)[NULL];
 select ('[1, "2", null]'::jsonb)['a'];
+select ('[1, "2", null]'::jsonb).a;
 select ('[1, "2", null]'::jsonb)[0];
 select ('[1, "2", null]'::jsonb)['1'];
+select ('[1, "2", null]'::jsonb)."1";
 select ('[1, "2", null]'::jsonb)[1.0];
 select ('[1, "2", null]'::jsonb)[2];
 select ('[1, "2", null]'::jsonb)[3];
 select ('[1, "2", null]'::jsonb)[-2];
 select ('[1, "2", null]'::jsonb)[1]['a'];
+select ('[1, "2", null]'::jsonb)[1].a;
 select ('[1, "2", null]'::jsonb)[1][0];
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['b'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).b;
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d;
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'][1];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d[1];
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d']['a'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['a'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d.a;
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1;
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2;
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']['a3'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2.a3;
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'];
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1;
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'][2];
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1[2];
 
 -- slices are not supported
 select ('{"a": 1}'::jsonb)['a':'b'];
@@ -1572,3 +1589,31 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::float8;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int2;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int4;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
+
+-- dot notation
+CREATE TABLE test_jsonb_dot_notation AS
+SELECT '{"a": [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], "b": [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]}'::jsonb jb;
+
+SELECT (jb).a FROM test_jsonb_dot_notation;
+SELECT (jb)."a" FROM test_jsonb_dot_notation; -- double quote should work
+SELECT (jb).'a' FROM test_jsonb_dot_notation; -- single quote should not work
+SELECT (jb).b FROM test_jsonb_dot_notation;
+SELECT (jb).c FROM test_jsonb_dot_notation;
+SELECT (jb).a.b FROM test_jsonb_dot_notation;
+SELECT (jb).a[2].b FROM test_jsonb_dot_notation;
+SELECT (jb).a[2:3].b FROM test_jsonb_dot_notation;
+SELECT (jb).a[2:].b FROM test_jsonb_dot_notation;
+SELECT (jb).a[:2].b FROM test_jsonb_dot_notation;
+SELECT (jb).a[:].b FROM test_jsonb_dot_notation;
+SELECT (jb).a.x.y FROM test_jsonb_dot_notation;
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
+SELECT (jb).b.x.z FROM test_jsonb_dot_notation;
+SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).* FROM test_jsonb_dot_notation;
+SELECT (jb).* FROM test_jsonb_dot_notation;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+SELECT (jb).a[1] FROM test_jsonb_dot_notation;
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v10-0007-Allow-wild-card-member-access-for-jsonb.patch (18.9K, ../../CAK98qZ14uKKRVFg4ibzMfReYaZD6Byxq8nYnvNNtXNjCfcd8kQ@mail.gmail.com/9-v10-0007-Allow-wild-card-member-access-for-jsonb.patch)
  download | inline diff:
From a0e2790c6d724aa27d78f8d3b7abea189d3440cf Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Sat, 1 Apr 2023 23:15:26 +0300
Subject: [PATCH v10 7/7] Allow wild card member access for jsonb

---
 src/backend/parser/gram.y           |   2 +
 src/backend/parser/parse_expr.c     |  34 +++--
 src/backend/parser/parse_target.c   |  67 +++++++---
 src/backend/utils/adt/ruleutils.c   |   6 +-
 src/include/parser/parse_expr.h     |   3 +
 src/test/regress/expected/jsonb.out | 192 +++++++++++++++++++++++++++-
 src/test/regress/sql/jsonb.sql      |  31 +++++
 7 files changed, 299 insertions(+), 36 deletions(-)

diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7d99c9355c6..041968c35db 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -18968,6 +18968,7 @@ check_func_name(List *names, core_yyscan_t yyscanner)
 static List *
 check_indirection(List *indirection, core_yyscan_t yyscanner)
 {
+#if 0
 	ListCell   *l;
 
 	foreach(l, indirection)
@@ -18978,6 +18979,7 @@ check_indirection(List *indirection, core_yyscan_t yyscanner)
 				parser_yyerror("improper use of \"*\"");
 		}
 	}
+#endif
 	return indirection;
 }
 
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 8ea51176196..512ac5b4970 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -74,7 +74,6 @@ static Node *transformColumnRef(ParseState *pstate, ColumnRef *cref);
 static Node *transformWholeRowRef(ParseState *pstate,
 								  ParseNamespaceItem *nsitem,
 								  int sublevels_up, int location);
-static Node *transformIndirection(ParseState *pstate, A_Indirection *ind);
 static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
 static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
 static Node *transformJsonObjectConstructor(ParseState *pstate,
@@ -158,7 +157,7 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 			break;
 
 		case T_A_Indirection:
-			result = transformIndirection(pstate, (A_Indirection *) expr);
+			result = transformIndirection(pstate, (A_Indirection *) expr, NULL);
 			break;
 
 		case T_A_ArrayExpr:
@@ -432,8 +431,9 @@ unknown_attribute(ParseState *pstate, Node *relref, const char *attname,
 	}
 }
 
-static Node *
-transformIndirection(ParseState *pstate, A_Indirection *ind)
+Node *
+transformIndirection(ParseState *pstate, A_Indirection *ind,
+					 bool *trailing_star_expansion)
 {
 	Node	   *last_srf = pstate->p_last_srf;
 	Node	   *result = transformExprRecurse(pstate, ind->arg);
@@ -454,12 +454,7 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		if (IsA(n, A_Indices))
 			subscripts = lappend(subscripts, n);
 		else if (IsA(n, A_Star))
-		{
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("row expansion via \"*\" is not supported here"),
-					 parser_errposition(pstate, location)));
-		}
+			subscripts = lappend(subscripts, n);
 		else
 		{
 			Assert(IsA(n, String));
@@ -491,7 +486,21 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 
 			n = linitial(subscripts);
 
-			if (!IsA(n, String))
+			if (IsA(n, A_Star))
+			{
+				/* Success, if trailing star expansion is allowed */
+				if (trailing_star_expansion && list_length(subscripts) == 1)
+				{
+					*trailing_star_expansion = true;
+					return result;
+				}
+
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("row expansion via \"*\" is not supported here"),
+						 parser_errposition(pstate, location)));
+			}
+			else if (!IsA(n, String))
 				ereport(ERROR,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
 						 errmsg("cannot subscript type %s because it does not support subscripting",
@@ -517,6 +526,9 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		result = newresult;
 	}
 
+	if (trailing_star_expansion)
+		*trailing_star_expansion = false;
+
 	return result;
 }
 
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 3ef5897f2eb..141fc1dfeb1 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -48,7 +48,7 @@ static Node *transformAssignmentSubscripts(ParseState *pstate,
 static List *ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
 								 bool make_target_entry);
 static List *ExpandAllTables(ParseState *pstate, int location);
-static List *ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
+static Node *ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
 								   bool make_target_entry, ParseExprKind exprKind);
 static List *ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 							   int sublevels_up, int location,
@@ -134,6 +134,7 @@ transformTargetList(ParseState *pstate, List *targetlist,
 	foreach(o_target, targetlist)
 	{
 		ResTarget  *res = (ResTarget *) lfirst(o_target);
+		Node	   *transformed = NULL;
 
 		/*
 		 * Check for "something.*".  Depending on the complexity of the
@@ -162,13 +163,19 @@ transformTargetList(ParseState *pstate, List *targetlist,
 
 				if (IsA(llast(ind->indirection), A_Star))
 				{
-					/* It is something.*, expand into multiple items */
-					p_target = list_concat(p_target,
-										   ExpandIndirectionStar(pstate,
-																 ind,
-																 true,
-																 exprKind));
-					continue;
+					Node	   *columns = ExpandIndirectionStar(pstate,
+																ind,
+																true,
+																exprKind);
+
+					if (IsA(columns, List))
+					{
+						/* It is something.*, expand into multiple items */
+						p_target = list_concat(p_target, (List *) columns);
+						continue;
+					}
+
+					transformed = (Node *) columns;
 				}
 			}
 		}
@@ -180,7 +187,7 @@ transformTargetList(ParseState *pstate, List *targetlist,
 		p_target = lappend(p_target,
 						   transformTargetEntry(pstate,
 												res->val,
-												NULL,
+												transformed,
 												exprKind,
 												res->name,
 												false));
@@ -251,10 +258,15 @@ transformExpressionList(ParseState *pstate, List *exprlist,
 
 			if (IsA(llast(ind->indirection), A_Star))
 			{
-				/* It is something.*, expand into multiple items */
-				result = list_concat(result,
-									 ExpandIndirectionStar(pstate, ind,
-														   false, exprKind));
+				Node	   *cols = ExpandIndirectionStar(pstate, ind,
+														 false, exprKind);
+
+				if (!cols || IsA(cols, List))
+					/* It is something.*, expand into multiple items */
+					result = list_concat(result, (List *) cols);
+				else
+					result = lappend(result, cols);
+
 				continue;
 			}
 		}
@@ -1345,22 +1357,30 @@ ExpandAllTables(ParseState *pstate, int location)
  * For robustness, we use a separate "make_target_entry" flag to control
  * this rather than relying on exprKind.
  */
-static List *
+static Node *
 ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
 					  bool make_target_entry, ParseExprKind exprKind)
 {
 	Node	   *expr;
+	ParseExprKind sv_expr_kind;
+	bool		trailing_star_expansion = false;
+
+	/* Save and restore identity of expression type we're parsing */
+	Assert(exprKind != EXPR_KIND_NONE);
+	sv_expr_kind = pstate->p_expr_kind;
+	pstate->p_expr_kind = exprKind;
 
 	/* Strip off the '*' to create a reference to the rowtype object */
-	ind = copyObject(ind);
-	ind->indirection = list_truncate(ind->indirection,
-									 list_length(ind->indirection) - 1);
+	expr = transformIndirection(pstate, ind, &trailing_star_expansion);
+
+	pstate->p_expr_kind = sv_expr_kind;
 
-	/* And transform that */
-	expr = transformExpr(pstate, (Node *) ind, exprKind);
+	/* '*' was consumed by generic type subscripting */
+	if (!trailing_star_expansion)
+		return expr;
 
 	/* Expand the rowtype expression into individual fields */
-	return ExpandRowReference(pstate, expr, make_target_entry);
+	return (Node *) ExpandRowReference(pstate, expr, make_target_entry);
 }
 
 /*
@@ -1785,13 +1805,18 @@ FigureColnameInternal(Node *node, char **name)
 				char	   *fname = NULL;
 				ListCell   *l;
 
-				/* find last field name, if any, ignoring "*" and subscripts */
+				/*
+				 * find last field name, if any, ignoring subscripts, and use
+				 * '?column?' when there's a trailing '*'.
+				 */
 				foreach(l, ind->indirection)
 				{
 					Node	   *i = lfirst(l);
 
 					if (IsA(i, String))
 						fname = strVal(i);
+					else if (IsA(i, A_Star))
+						fname = "?column?";
 				}
 				if (fname)
 				{
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index cfae8159a76..1bc292c5c45 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -12926,7 +12926,11 @@ printSubscripts(SubscriptingRef *sbsref, deparse_context *context)
 	{
 		Node	   *up = (Node *) lfirst(uplist_item);
 
-		if (IsA(up, String))
+		if (!up)
+		{
+			appendStringInfoString(buf, ".*");
+		}
+		else if (IsA(up, String))
 		{
 			appendStringInfoChar(buf, '.');
 			appendStringInfoString(buf, quote_identifier(strVal(up)));
diff --git a/src/include/parser/parse_expr.h b/src/include/parser/parse_expr.h
index efbaff8e710..c9f6a7724c0 100644
--- a/src/include/parser/parse_expr.h
+++ b/src/include/parser/parse_expr.h
@@ -22,4 +22,7 @@ extern Node *transformExpr(ParseState *pstate, Node *expr, ParseExprKind exprKin
 
 extern const char *ParseExprKindName(ParseExprKind exprKind);
 
+extern Node *transformIndirection(ParseState *pstate, A_Indirection *ind,
+								  bool *trailing_star_expansion);
+
 #endif							/* PARSE_EXPR_H */
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 480ecb44a94..aab69b4722c 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5980,12 +5980,172 @@ SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
  
 (1 row)
 
+/* wild card member access */
 SELECT (jb).a.* FROM test_jsonb_dot_notation;
-ERROR:  type jsonb is not composite
+                 ?column?                  
+-------------------------------------------
+ ["c", "d", "f", {"y": "yyy", "z": "zzz"}]
+(1 row)
+
+SELECT (jb).* FROM test_jsonb_dot_notation;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (jb).* FROM test_jsonb_dot_notation t;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (jb).* FROM test_jsonb_dot_notation;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (t.jb).* FROM test_jsonb_dot_notation;
+ERROR:  missing FROM-clause entry for table "t"
+LINE 1: SELECT (t.jb).* FROM test_jsonb_dot_notation;
+                ^
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+                 ?column?                  
+-------------------------------------------
+ ["c", "d", "f", {"y": "yyy", "z": "zzz"}]
+(1 row)
+
+SELECT (jb).a.*.b FROM test_jsonb_dot_notation;
+ b 
+---
+ 
+(1 row)
+
+SELECT (jb).a.*.x FROM test_jsonb_dot_notation;
+ x 
+---
+ 
+(1 row)
+
+SELECT (jb).a.*.y FROM test_jsonb_dot_notation;
+   y   
+-------
+ "yyy"
+(1 row)
+
+SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+    ?column?    
+----------------
+ ["yyy", "zzz"]
+(1 row)
+
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT (jb).*.x FROM test_jsonb_dot_notation t;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+ x 
+---
+ 
+(1 row)
+
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+ x 
+---
+ 
+(1 row)
+
+SELECT ((jb).*)[:].x FROM test_jsonb_dot_notation t;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT (jb).*.x.* FROM test_jsonb_dot_notation;
+           ?column?           
+------------------------------
+ ["yyy", "zzz", "YYY", "ZZZ"]
+(1 row)
+
+SELECT (jb).*.x.y FROM test_jsonb_dot_notation;
+       y        
+----------------
+ ["yyy", "YYY"]
+(1 row)
+
+SELECT (jb).*.x.z FROM test_jsonb_dot_notation;
+       z        
+----------------
+ ["zzz", "ZZZ"]
+(1 row)
+
+SELECT (jb).*.*.y FROM test_jsonb_dot_notation;
+       y        
+----------------
+ ["yyy", "YYY"]
+(1 row)
+
+SELECT (jb).*.*.* FROM test_jsonb_dot_notation;
+           ?column?           
+------------------------------
+ ["yyy", "zzz", "YYY", "ZZZ"]
+(1 row)
+
+SELECT (jb).*.*.*.* FROM test_jsonb_dot_notation;
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT (jb).a.b.c.* FROM test_jsonb_dot_notation;
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT (jb).a.**.x FROM test_jsonb_dot_notation; -- not supported
+ERROR:  syntax error at or near "**"
+LINE 1: SELECT (jb).a.**.x FROM test_jsonb_dot_notation;
+                      ^
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).* FROM test_jsonb_dot_notation;
-ERROR:  type jsonb is not composite
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: jb.*
+(2 rows)
+
 SELECT (jb).* FROM test_jsonb_dot_notation;
-ERROR:  type jsonb is not composite
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
                   QUERY PLAN                  
 ----------------------------------------------
@@ -6012,3 +6172,29 @@ SELECT (jb).a[1] FROM test_jsonb_dot_notation;
  2
 (1 row)
 
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.* FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: jb.a.*
+(2 rows)
+
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+                 ?column?                  
+-------------------------------------------
+ ["c", "d", "f", {"y": "yyy", "z": "zzz"}]
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: jb.a.*[1:2].*.b
+(2 rows)
+
+SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
+ b 
+---
+ 
+(1 row)
+
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 55b6c07f462..7d602bd8dac 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1609,7 +1609,34 @@ SELECT (jb).a.x.y FROM test_jsonb_dot_notation;
 SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
 SELECT (jb).b.x.z FROM test_jsonb_dot_notation;
 SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
+
+/* wild card member access */
 SELECT (jb).a.* FROM test_jsonb_dot_notation;
+SELECT (jb).* FROM test_jsonb_dot_notation;
+SELECT (jb).* FROM test_jsonb_dot_notation t;
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+SELECT (jb).* FROM test_jsonb_dot_notation;
+SELECT (t.jb).* FROM test_jsonb_dot_notation;
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.b FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.x FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.y FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+SELECT (jb).*.x FROM test_jsonb_dot_notation t;
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+SELECT ((jb).*)[:].x FROM test_jsonb_dot_notation t;
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+SELECT (jb).*.x.* FROM test_jsonb_dot_notation;
+SELECT (jb).*.x.y FROM test_jsonb_dot_notation;
+SELECT (jb).*.x.z FROM test_jsonb_dot_notation;
+SELECT (jb).*.*.y FROM test_jsonb_dot_notation;
+SELECT (jb).*.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).*.*.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.b.c.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.**.x FROM test_jsonb_dot_notation; -- not supported
 
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).* FROM test_jsonb_dot_notation;
 SELECT (jb).* FROM test_jsonb_dot_notation;
@@ -1617,3 +1644,7 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
 SELECT (t.jb).a FROM test_jsonb_dot_notation t;
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
 SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
+SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
-- 
2.39.5 (Apple Git-154)



^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
@ 2025-03-04 14:05                   ` Mark Dilger <[email protected]>
  2025-03-04 15:34                     ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  0 siblings, 2 replies; 67+ messages in thread

From: Mark Dilger @ 2025-03-04 14:05 UTC (permalink / raw)
  To: Alexandra Wang <[email protected]>; +Cc: Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>; jian he <[email protected]>

On Mon, Mar 3, 2025 at 12:23 PM Alexandra Wang <[email protected]>
wrote:

>  I've attached v10, which addresses your feedback.
>
>
Hi Alex!  Thanks for the patches.

In src/test/regress/sql/jsonb.sql, the section marked with "-- slices are
not supported" should be relabeled.  That comment predates these patches,
and is now misleading.

A bit further down in expected/jsonb.out, there is an expected failure, but
no SQL comment to indicate that it is expected:

+SELECT (t.jb).* FROM test_jsonb_dot_notation;
+ERROR:  missing FROM-clause entry for table "t"
+LINE 1: SELECT (t.jb).* FROM test_jsonb_dot_notation;

Perhaps a "-- fails" comment would clarify?  Then, further down,

+SELECT (jb).a.**.x FROM test_jsonb_dot_notation; -- not supported
+ERROR:  syntax error at or near "**"
+LINE 1: SELECT (jb).a.**.x FROM test_jsonb_dot_notation;

I wonder if it would be better to have the parser handle this case and
raise a ERRCODE_FEATURE_NOT_SUPPORTED instead?

I got curious about the support for this new dot notation in the plpgsql
parser and tried:

+DO $$
+DECLARE
+  a jsonb := '[1,2,3,4,5,6,7]'::jsonb;
+BEGIN
+  WHILE a IS NOT NULL
+  LOOP
+    RAISE NOTICE '%', a;
+    a := a[2:];
+  END LOOP;
+END
+$$ LANGUAGE plpgsql;
+NOTICE:  [1, 2, 3, 4, 5, 6, 7]
+NOTICE:  [3, 4, 5, 6, 7]
+NOTICE:  [5, 6, 7]
+NOTICE:  7

which looks good!  But then I tried:

+DO $$
+DECLARE
+  a jsonb := '{"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6,
-8]}'::jsonb;
+BEGIN
+  WHILE a IS NOT NULL
+  LOOP
+    RAISE NOTICE '%', a;
+    a := COALESCE(a."NU", a[2]);
+  END LOOP;
+END
+$$ LANGUAGE plpgsql;
+NOTICE:  {"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}
+ERROR:  missing FROM-clause entry for table "a"
+LINE 1: a := COALESCE(a."NU", a[2])
+                      ^
+QUERY:  a := COALESCE(a."NU", a[2])
+CONTEXT:  PL/pgSQL function inline_code_block line 8 at assignment

which suggests the plpgsql parser does not recognize a."NU" as we'd
expect.  Any thoughts on this?

I notice there are no changes in src/interfaces/ecpg/test, which concerns
me.  The sqljson.pgc and sqljson_jsontable.pgc files are already testing
json handling in ecpg; perhaps just extend those a bit?

—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
@ 2025-03-04 15:34                     ` Mark Dilger <[email protected]>
  2025-03-04 18:13                       ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  1 sibling, 1 reply; 67+ messages in thread

From: Mark Dilger @ 2025-03-04 15:34 UTC (permalink / raw)
  To: Alexandra Wang <[email protected]>; +Cc: Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>; jian he <[email protected]>

On Tue, Mar 4, 2025 at 6:05 AM Mark Dilger <[email protected]>
wrote:

> But then I tried:
>
> +DO $$
> +DECLARE
> +  a jsonb := '{"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6,
> -8]}'::jsonb;
> +BEGIN
> +  WHILE a IS NOT NULL
> +  LOOP
> +    RAISE NOTICE '%', a;
> +    a := COALESCE(a."NU", a[2]);
> +  END LOOP;
> +END
> +$$ LANGUAGE plpgsql;
> +NOTICE:  {"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}
> +ERROR:  missing FROM-clause entry for table "a"
> +LINE 1: a := COALESCE(a."NU", a[2])
> +                      ^
> +QUERY:  a := COALESCE(a."NU", a[2])
> +CONTEXT:  PL/pgSQL function inline_code_block line 8 at assignment
>
> which suggests the plpgsql parser does not recognize a."NU" as we'd
> expect.  Any thoughts on this?
>

I should mention that

+DO $$
+DECLARE
+  a jsonb := '{"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6,
-8]}'::jsonb;
+BEGIN
+  WHILE a IS NOT NULL
+  LOOP
+    RAISE NOTICE '%', a;
+    a := COALESCE((a)."NU", (a)[2]);
+  END LOOP;
+END
+$$ LANGUAGE plpgsql;
+NOTICE:  {"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}
+NOTICE:  [{"": [[3]]}, [6], [2], "bCi"]
+NOTICE:  [2]

works fine.  I guess that is good enough.  Should we add these to the
sql/jsonb.sql to document the expected behavior, both with the error when
using plain "a" and with the correct output when using "(a)"?  The reason I
mention this is that the plpgsql parser might get changed at some point,
and without a test case, we might not notice if this breaks.

—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-04 15:34                     ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
@ 2025-03-04 18:13                       ` Andrew Dunstan <[email protected]>
  0 siblings, 0 replies; 67+ messages in thread

From: Andrew Dunstan @ 2025-03-04 18:13 UTC (permalink / raw)
  To: Mark Dilger <[email protected]>; Alexandra Wang <[email protected]>; +Cc: Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>; jian he <[email protected]>


On 2025-03-04 Tu 10:34 AM, Mark Dilger wrote:
>
>
> On Tue, Mar 4, 2025 at 6:05 AM Mark Dilger 
> <[email protected]> wrote:
>
>     But then I tried:
>
>     +DO $$
>     +DECLARE
>     +  a jsonb := '{"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"],
>     "aaf": [-6, -8]}'::jsonb;
>     +BEGIN
>     +  WHILE a IS NOT NULL
>     +  LOOP
>     +    RAISE NOTICE '%', a;
>     +    a := COALESCE(a."NU", a[2]);
>     +  END LOOP;
>     +END
>     +$$ LANGUAGE plpgsql;
>     +NOTICE:  {"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf":
>     [-6, -8]}
>     +ERROR:  missing FROM-clause entry for table "a"
>     +LINE 1: a := COALESCE(a."NU", a[2])
>     +                      ^
>     +QUERY:  a := COALESCE(a."NU", a[2])
>     +CONTEXT:  PL/pgSQL function inline_code_block line 8 at assignment
>
>     which suggests the plpgsql parser does not recognize a."NU" as
>     we'd expect.  Any thoughts on this?
>
>
> I should mention that
>
> +DO $$
> +DECLARE
> +  a jsonb := '{"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": 
> [-6, -8]}'::jsonb;
> +BEGIN
> +  WHILE a IS NOT NULL
> +  LOOP
> +    RAISE NOTICE '%', a;
> +    a := COALESCE((a)."NU", (a)[2]);
> +  END LOOP;
> +END
> +$$ LANGUAGE plpgsql;
> +NOTICE:  {"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}
> +NOTICE:  [{"": [[3]]}, [6], [2], "bCi"]
> +NOTICE:  [2]
> works fine.  I guess that is good enough.  Should we add these to the 
> sql/jsonb.sql to document the expected behavior, both with the error 
> when using plain "a" and with the correct output when using "(a)"?  
> The reason I mention this is that the plpgsql parser might get changed 
> at some point, and without a test case, we might not notice if this 
> breaks.
>

Yes, I think so.


cheers


andrew



--
Andrew Dunstan
EDB:https://www.enterprisedb.com


^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
@ 2025-03-13 14:02                     ` Alexandra Wang <[email protected]>
  2025-03-20 14:18                       ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-06-28 23:52                       ` Re: SQL:2023 JSON simplified accessor support Jelte Fennema-Nio <[email protected]>
  1 sibling, 3 replies; 67+ messages in thread

From: Alexandra Wang @ 2025-03-13 14:02 UTC (permalink / raw)
  To: Mark Dilger <[email protected]>; +Cc: Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>; jian he <[email protected]>

Hi Mark,

Thank you so much for reviewing! I have attached the new patches.

On Tue, Mar 4, 2025 at 8:05 AM Mark Dilger <[email protected]>
wrote:

>
> On Mon, Mar 3, 2025 at 12:23 PM Alexandra Wang <
> [email protected]> wrote:
>
>>  I've attached v10, which addresses your feedback.
>>
>>
> Hi Alex!  Thanks for the patches.
>
> In src/test/regress/sql/jsonb.sql, the section marked with "-- slices are
> not supported" should be relabeled.  That comment predates these patches,
> and is now misleading.
>
> A bit further down in expected/jsonb.out, there is an expected failure,
> but no SQL comment to indicate that it is expected:
>
> +SELECT (t.jb).* FROM test_jsonb_dot_notation;
> +ERROR:  missing FROM-clause entry for table "t"
> +LINE 1: SELECT (t.jb).* FROM test_jsonb_dot_notation;
>
> Perhaps a "-- fails" comment would clarify?  Then, further down,
>

Fixed.


>
>
+SELECT (jb).a.**.x FROM test_jsonb_dot_notation; -- not supported
> +ERROR:  syntax error at or near "**"
> +LINE 1: SELECT (jb).a.**.x FROM test_jsonb_dot_notation;
>
> I wonder if it would be better to have the parser handle this case and
> raise a ERRCODE_FEATURE_NOT_SUPPORTED instead?
>

In 0008 I added a new token named "DOUBLE_ASTERISK" to the lexers to
represent "**". Hope this helps!


> I got curious about the support for this new dot notation in the plpgsql
> parser and tried:
>
> +DO $$
> +DECLARE
> +  a jsonb := '[1,2,3,4,5,6,7]'::jsonb;
> +BEGIN
> +  WHILE a IS NOT NULL
> +  LOOP
> +    RAISE NOTICE '%', a;
> +    a := a[2:];
> +  END LOOP;
> +END
> +$$ LANGUAGE plpgsql;
> +NOTICE:  [1, 2, 3, 4, 5, 6, 7]
> +NOTICE:  [3, 4, 5, 6, 7]
> +NOTICE:  [5, 6, 7]
> +NOTICE:  7
>
> which looks good!  But then I tried:
>
> +DO $$
> +DECLARE
> +  a jsonb := '{"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6,
> -8]}'::jsonb;
> +BEGIN
> +  WHILE a IS NOT NULL
> +  LOOP
> +    RAISE NOTICE '%', a;
> +    a := COALESCE(a."NU", a[2]);
> +  END LOOP;
> +END
> +$$ LANGUAGE plpgsql;
> +NOTICE:  {"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}
> +ERROR:  missing FROM-clause entry for table "a"
> +LINE 1: a := COALESCE(a."NU", a[2])
> +                      ^
> +QUERY:  a := COALESCE(a."NU", a[2])
> +CONTEXT:  PL/pgSQL function inline_code_block line 8 at assignment
>
> which suggests the plpgsql parser does not recognize a."NU" as we'd
> expect.  Any thoughts on this?
>

Thanks for the tests! I added them to the "jsonb" regress test.


> I notice there are no changes in src/interfaces/ecpg/test, which concerns
> me.  The sqljson.pgc and sqljson_jsontable.pgc files are already testing
> json handling in ecpg; perhaps just extend those a bit?
>
Thanks for bringing this up! I have added new tests in
src/interfaces/ecpg/test/sql/sqljson.pgc.

Best,
Alex


Attachments:

  [application/x-patch] v11-0002-Allow-Generic-Type-Subscripting-to-Accept-Dot-No.patch (10.3K, ../../CAK98qZ1nz6ZZhQqTOCNwRguZE5GsBLW5BQT_k=s7AA6gc2CN_g@mail.gmail.com/3-v11-0002-Allow-Generic-Type-Subscripting-to-Accept-Dot-No.patch)
  download | inline diff:
From a9f64ad839d1ea76585833729294fa4936297308 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Sat, 1 Apr 2023 16:21:20 +0300
Subject: [PATCH v11 2/8] Allow Generic Type Subscripting to Accept Dot
 Notation (.) as Input

This change extends generic type subscripting to recognize dot
notation (.) in addition to bracket notation ([]). While this does not
yet provide full support for dot notation, it enables subscripting
containers to process it in the future.

For now, container-specific transform functions only handle
subscripting indices and stop processing when encountering dot
notation. It is up to individual containers to decide how to transform
dot notation in subsequent updates.
---
 src/backend/parser/parse_expr.c   | 66 ++++++++++++++++++++-----------
 src/backend/parser/parse_node.c   | 41 +++++++++++++++++--
 src/backend/parser/parse_target.c |  3 +-
 src/backend/utils/adt/arraysubs.c | 13 ++++--
 src/backend/utils/adt/jsonbsubs.c | 11 +++++-
 src/include/parser/parse_node.h   |  3 +-
 6 files changed, 105 insertions(+), 32 deletions(-)

diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 2c0f4a50b21..8ea51176196 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -442,8 +442,9 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 	ListCell   *i;
 
 	/*
-	 * We have to split any field-selection operations apart from
-	 * subscripting.  Adjacent A_Indices nodes have to be treated as a single
+	 * Combine field names and subscripts into a single indirection list, as
+	 * some subscripting containers, such as jsonb, support field access using
+	 * dot notation. Adjacent A_Indices nodes have to be treated as a single
 	 * multidimensional subscript operation.
 	 */
 	foreach(i, ind->indirection)
@@ -461,19 +462,43 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		}
 		else
 		{
-			Node	   *newresult;
-
 			Assert(IsA(n, String));
+			subscripts = lappend(subscripts, n);
+		}
+	}
+
+	while (subscripts)
+	{
+		/* try processing container subscripts first */
+		Node	   *newresult = (Node *)
+			transformContainerSubscripts(pstate,
+										 result,
+										 exprType(result),
+										 exprTypmod(result),
+										 &subscripts,
+										 false,
+										 true);
+
+		if (!newresult)
+		{
+			/*
+			 * generic subscripting failed; falling back to function call or
+			 * field selection for a composite type.
+			 */
+			Node	   *n;
+
+			Assert(subscripts);
 
-			/* process subscripts before this field selection */
-			while (subscripts)
-				result = (Node *) transformContainerSubscripts(pstate,
-															   result,
-															   exprType(result),
-															   exprTypmod(result),
-															   &subscripts,
-															   false);
+			n = linitial(subscripts);
+
+			if (!IsA(n, String))
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("cannot subscript type %s because it does not support subscripting",
+								format_type_be(exprType(result))),
+						 parser_errposition(pstate, exprLocation(result))));
 
+			/* try to find function for field selection */
 			newresult = ParseFuncOrColumn(pstate,
 										  list_make1(n),
 										  list_make1(result),
@@ -481,19 +506,16 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 										  NULL,
 										  false,
 										  location);
-			if (newresult == NULL)
+
+			if (!newresult)
 				unknown_attribute(pstate, result, strVal(n), location);
-			result = newresult;
+
+			/* consume field select */
+			subscripts = list_delete_first(subscripts);
 		}
+
+		result = newresult;
 	}
-	/* process trailing subscripts, if any */
-	while (subscripts)
-		result = (Node *) transformContainerSubscripts(pstate,
-													   result,
-													   exprType(result),
-													   exprTypmod(result),
-													   &subscripts,
-													   false);
 
 	return result;
 }
diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c
index 19a6b678e67..b3e476eb181 100644
--- a/src/backend/parser/parse_node.c
+++ b/src/backend/parser/parse_node.c
@@ -238,6 +238,8 @@ transformContainerType(Oid *containerType, int32 *containerTypmod)
  * containerTypMod	typmod for the container
  * indirection		Untransformed list of subscripts (must not be NIL)
  * isAssignment		True if this will become a container assignment.
+ * noError			True for return NULL with no error, if the container type
+ * 					is not subscriptable.
  */
 SubscriptingRef *
 transformContainerSubscripts(ParseState *pstate,
@@ -245,13 +247,15 @@ transformContainerSubscripts(ParseState *pstate,
 							 Oid containerType,
 							 int32 containerTypMod,
 							 List **indirection,
-							 bool isAssignment)
+							 bool isAssignment,
+							 bool noError)
 {
 	SubscriptingRef *sbsref;
 	const SubscriptRoutines *sbsroutines;
 	Oid			elementType;
 	bool		isSlice = false;
 	ListCell   *idx;
+	int			indirection_length = list_length(*indirection);
 
 	/*
 	 * Determine the actual container type, smashing any domain.  In the
@@ -267,11 +271,16 @@ transformContainerSubscripts(ParseState *pstate,
 	 */
 	sbsroutines = getSubscriptingRoutines(containerType, &elementType);
 	if (!sbsroutines)
+	{
+		if (noError)
+			return NULL;
+
 		ereport(ERROR,
 				(errcode(ERRCODE_DATATYPE_MISMATCH),
 				 errmsg("cannot subscript type %s because it does not support subscripting",
 						format_type_be(containerType)),
 				 parser_errposition(pstate, exprLocation(containerBase))));
+	}
 
 	/*
 	 * Detect whether any of the indirection items are slice specifiers.
@@ -282,9 +291,9 @@ transformContainerSubscripts(ParseState *pstate,
 	 */
 	foreach(idx, *indirection)
 	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
+		Node	   *ai = lfirst(idx);
 
-		if (ai->is_slice)
+		if (IsA(ai, A_Indices) && castNode(A_Indices, ai)->is_slice)
 		{
 			isSlice = true;
 			break;
@@ -312,6 +321,32 @@ transformContainerSubscripts(ParseState *pstate,
 	sbsroutines->transform(sbsref, indirection, pstate,
 						   isSlice, isAssignment);
 
+	/*
+	 * Error out, if datatype failed to consume any indirection elements.
+	 */
+	if (list_length(*indirection) == indirection_length)
+	{
+		Node	   *ind = linitial(*indirection);
+
+		if (noError)
+			return NULL;
+
+		if (IsA(ind, String))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("type %s does not support dot notation",
+							format_type_be(containerType)),
+					 parser_errposition(pstate, exprLocation(containerBase))));
+		else if (IsA(ind, A_Indices))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("type %s does not support array subscripting",
+							format_type_be(containerType)),
+					 parser_errposition(pstate, exprLocation(containerBase))));
+		else
+			elog(ERROR, "invalid indirection operation: %d", nodeTag(ind));
+	}
+
 	/*
 	 * Verify we got a valid type (this defends, for example, against someone
 	 * using array_subscript_handler as typsubscript without setting typelem).
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 4675a523045..3ef5897f2eb 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -937,7 +937,8 @@ transformAssignmentSubscripts(ParseState *pstate,
 										  containerType,
 										  containerTypMod,
 										  &subscripts,
-										  true);
+										  true,
+										  false);
 
 	typeNeeded = sbsref->refrestype;
 	typmodNeeded = sbsref->reftypmod;
diff --git a/src/backend/utils/adt/arraysubs.c b/src/backend/utils/adt/arraysubs.c
index 234c2c278c1..d03d3519dfd 100644
--- a/src/backend/utils/adt/arraysubs.c
+++ b/src/backend/utils/adt/arraysubs.c
@@ -62,6 +62,7 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	List	   *upperIndexpr = NIL;
 	List	   *lowerIndexpr = NIL;
 	ListCell   *idx;
+	int			ndim;
 
 	/*
 	 * Transform the subscript expressions, and separate upper and lower
@@ -73,9 +74,14 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	 */
 	foreach(idx, *indirection)
 	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
+		A_Indices  *ai;
 		Node	   *subexpr;
 
+		if (!IsA(lfirst(idx), A_Indices))
+			break;
+
+		ai = lfirst_node(A_Indices, idx);
+
 		if (isSlice)
 		{
 			if (ai->lidx)
@@ -145,14 +151,15 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->reflowerindexpr = lowerIndexpr;
 
 	/* Verify subscript list lengths are within implementation limit */
-	if (list_length(upperIndexpr) > MAXDIM)
+	ndim = list_length(upperIndexpr);
+	if (ndim > MAXDIM)
 		ereport(ERROR,
 				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 				 errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
 						list_length(upperIndexpr), MAXDIM)));
 	/* We need not check lowerIndexpr separately */
 
-	*indirection = NIL;
+	*indirection = list_delete_first_n(*indirection, ndim);
 
 	/*
 	 * Determine the result type of the subscripting operation.  It's the same
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index 8ad6aa1ad4f..a0d38a0fd80 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -55,9 +55,14 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	 */
 	foreach(idx, *indirection)
 	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
+		A_Indices  *ai;
 		Node	   *subExpr;
 
+		if (!IsA(lfirst(idx), A_Indices))
+			break;
+
+		ai = lfirst_node(A_Indices, idx);
+
 		if (isSlice)
 		{
 			Node	   *expr = ai->uidx ? ai->uidx : ai->lidx;
@@ -160,7 +165,9 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->refrestype = JSONBOID;
 	sbsref->reftypmod = -1;
 
-	*indirection = NIL;
+	/* Remove processed elements */
+	if (upperIndexpr)
+		*indirection = list_delete_first_n(*indirection, list_length(upperIndexpr));
 }
 
 /*
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 5ae11ccec33..71b04bd503c 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -378,7 +378,8 @@ extern SubscriptingRef *transformContainerSubscripts(ParseState *pstate,
 													 Oid containerType,
 													 int32 containerTypMod,
 													 List **indirection,
-													 bool isAssignment);
+													 bool isAssignment,
+													 bool noError);
 extern Const *make_const(ParseState *pstate, A_Const *aconst);
 
 #endif							/* PARSE_NODE_H */
-- 
2.39.5 (Apple Git-154)



  [application/x-patch] v11-0003-Export-jsonPathFromParseResult.patch (2.5K, ../../CAK98qZ1nz6ZZhQqTOCNwRguZE5GsBLW5BQT_k=s7AA6gc2CN_g@mail.gmail.com/4-v11-0003-Export-jsonPathFromParseResult.patch)
  download | inline diff:
From 5bfc2d23e8520d8b15ef8c51ae50e35e5be4abd5 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Sat, 1 Apr 2023 23:15:55 +0300
Subject: [PATCH v11 3/8] Export jsonPathFromParseResult()

This is a preparation step for a future commit that will reuse the
aforementioned function.
---
 src/backend/utils/adt/jsonpath.c | 19 +++++++++++++++----
 src/include/utils/jsonpath.h     |  4 ++++
 2 files changed, 19 insertions(+), 4 deletions(-)

diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index 762f7e8a09d..1536797cf23 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -166,15 +166,13 @@ jsonpath_send(PG_FUNCTION_ARGS)
  * Converts C-string to a jsonpath value.
  *
  * Uses jsonpath parser to turn string into an AST, then
- * flattenJsonPathParseItem() does second pass turning AST into binary
+ * jsonPathFromParseResult() does second pass turning AST into binary
  * representation of jsonpath.
  */
 static Datum
 jsonPathFromCstring(char *in, int len, struct Node *escontext)
 {
 	JsonPathParseResult *jsonpath = parsejsonpath(in, len, escontext);
-	JsonPath   *res;
-	StringInfoData buf;
 
 	if (SOFT_ERROR_OCCURRED(escontext))
 		return (Datum) 0;
@@ -185,8 +183,21 @@ jsonPathFromCstring(char *in, int len, struct Node *escontext)
 				 errmsg("invalid input syntax for type %s: \"%s\"", "jsonpath",
 						in)));
 
+	return jsonPathFromParseResult(jsonpath, 4 * len, escontext);
+}
+
+/*
+ * Converts jsonpath AST into jsonpath value in binary.
+ */
+Datum
+jsonPathFromParseResult(JsonPathParseResult *jsonpath, int estimated_len,
+						struct Node *escontext)
+{
+	JsonPath   *res;
+	StringInfoData buf;
+
 	initStringInfo(&buf);
-	enlargeStringInfo(&buf, 4 * len /* estimation */ );
+	enlargeStringInfo(&buf, estimated_len);
 
 	appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
 
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index 23a76d233e9..e05941623e7 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -281,6 +281,10 @@ extern JsonPathParseResult *parsejsonpath(const char *str, int len,
 extern bool jspConvertRegexFlags(uint32 xflags, int *result,
 								 struct Node *escontext);
 
+extern Datum jsonPathFromParseResult(JsonPathParseResult *jsonpath,
+									 int estimated_len,
+									 struct Node *escontext);
+
 /*
  * Struct for details about external variables passed into jsonpath executor
  */
-- 
2.39.5 (Apple Git-154)



  [application/x-patch] v11-0004-Extract-coerce_jsonpath_subscript.patch (5.5K, ../../CAK98qZ1nz6ZZhQqTOCNwRguZE5GsBLW5BQT_k=s7AA6gc2CN_g@mail.gmail.com/5-v11-0004-Extract-coerce_jsonpath_subscript.patch)
  download | inline diff:
From a72c7ab3b37589ada2ffea19a29aaad673be59af Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Wed, 26 Feb 2025 13:03:27 -0600
Subject: [PATCH v11 4/8] Extract coerce_jsonpath_subscript()

This is a preparation step for a future commit that will reuse the
aforementioned function.
---
 src/backend/utils/adt/jsonbsubs.c | 142 ++++++++++++++++--------------
 1 file changed, 78 insertions(+), 64 deletions(-)

diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index a0d38a0fd80..3ffe40cfa40 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -32,6 +32,83 @@ typedef struct JsonbSubWorkspace
 	Datum	   *index;			/* Subscript values in Datum format */
 } JsonbSubWorkspace;
 
+static Oid
+jsonb_subscript_type(Node *expr)
+{
+	if (expr && IsA(expr, String))
+		return TEXTOID;
+
+	return exprType(expr);
+}
+
+static Node *
+coerce_jsonpath_subscript(ParseState *pstate, Node *subExpr, Oid numtype)
+{
+	Oid			subExprType = jsonb_subscript_type(subExpr);
+	Oid			targetType = UNKNOWNOID;
+
+	if (subExprType != UNKNOWNOID)
+	{
+		Oid			targets[2] = {numtype, TEXTOID};
+
+		/*
+		 * Jsonb can handle multiple subscript types, but cases when a
+		 * subscript could be coerced to multiple target types must be
+		 * avoided, similar to overloaded functions. It could be possibly
+		 * extend with jsonpath in the future.
+		 */
+		for (int i = 0; i < 2; i++)
+		{
+			if (can_coerce_type(1, &subExprType, &targets[i], COERCION_IMPLICIT))
+			{
+				/*
+				 * One type has already succeeded, it means there are two
+				 * coercion targets possible, failure.
+				 */
+				if (targetType != UNKNOWNOID)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
+							 errhint("jsonb subscript must be coercible to only one type, integer or text."),
+							 parser_errposition(pstate, exprLocation(subExpr))));
+
+				targetType = targets[i];
+			}
+		}
+
+		/*
+		 * No suitable types were found, failure.
+		 */
+		if (targetType == UNKNOWNOID)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
+					 errhint("jsonb subscript must be coercible to either integer or text."),
+					 parser_errposition(pstate, exprLocation(subExpr))));
+	}
+	else
+		targetType = TEXTOID;
+
+	/*
+	 * We known from can_coerce_type that coercion will succeed, so
+	 * coerce_type could be used. Note the implicit coercion context, which is
+	 * required to handle subscripts of different types, similar to overloaded
+	 * functions.
+	 */
+	subExpr = coerce_type(pstate,
+						  subExpr, subExprType,
+						  targetType, -1,
+						  COERCION_IMPLICIT,
+						  COERCE_IMPLICIT_CAST,
+						  -1);
+	if (subExpr == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("jsonb subscript must have text type"),
+				 parser_errposition(pstate, exprLocation(subExpr))));
+
+	return subExpr;
+}
 
 /*
  * Finish parse analysis of a SubscriptingRef expression for a jsonb.
@@ -75,71 +152,8 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 
 		if (ai->uidx)
 		{
-			Oid			subExprType = InvalidOid,
-						targetType = UNKNOWNOID;
-
 			subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
-			subExprType = exprType(subExpr);
-
-			if (subExprType != UNKNOWNOID)
-			{
-				Oid			targets[2] = {INT4OID, TEXTOID};
-
-				/*
-				 * Jsonb can handle multiple subscript types, but cases when a
-				 * subscript could be coerced to multiple target types must be
-				 * avoided, similar to overloaded functions. It could be
-				 * possibly extend with jsonpath in the future.
-				 */
-				for (int i = 0; i < 2; i++)
-				{
-					if (can_coerce_type(1, &subExprType, &targets[i], COERCION_IMPLICIT))
-					{
-						/*
-						 * One type has already succeeded, it means there are
-						 * two coercion targets possible, failure.
-						 */
-						if (targetType != UNKNOWNOID)
-							ereport(ERROR,
-									(errcode(ERRCODE_DATATYPE_MISMATCH),
-									 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
-									 errhint("jsonb subscript must be coercible to only one type, integer or text."),
-									 parser_errposition(pstate, exprLocation(subExpr))));
-
-						targetType = targets[i];
-					}
-				}
-
-				/*
-				 * No suitable types were found, failure.
-				 */
-				if (targetType == UNKNOWNOID)
-					ereport(ERROR,
-							(errcode(ERRCODE_DATATYPE_MISMATCH),
-							 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
-							 errhint("jsonb subscript must be coercible to either integer or text."),
-							 parser_errposition(pstate, exprLocation(subExpr))));
-			}
-			else
-				targetType = TEXTOID;
-
-			/*
-			 * We known from can_coerce_type that coercion will succeed, so
-			 * coerce_type could be used. Note the implicit coercion context,
-			 * which is required to handle subscripts of different types,
-			 * similar to overloaded functions.
-			 */
-			subExpr = coerce_type(pstate,
-								  subExpr, subExprType,
-								  targetType, -1,
-								  COERCION_IMPLICIT,
-								  COERCE_IMPLICIT_CAST,
-								  -1);
-			if (subExpr == NULL)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("jsonb subscript must have text type"),
-						 parser_errposition(pstate, exprLocation(subExpr))));
+			subExpr = coerce_jsonpath_subscript(pstate, subExpr, INT4OID);
 		}
 		else
 		{
-- 
2.39.5 (Apple Git-154)



  [application/x-patch] v11-0005-Enable-String-node-as-field-accessors-in-generic.patch (7.7K, ../../CAK98qZ1nz6ZZhQqTOCNwRguZE5GsBLW5BQT_k=s7AA6gc2CN_g@mail.gmail.com/6-v11-0005-Enable-String-node-as-field-accessors-in-generic.patch)
  download | inline diff:
From f0d425619ef76e7855808be0908a154228d6d8f3 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Sat, 1 Apr 2023 16:21:20 +0300
Subject: [PATCH v11 5/8] Enable String node as field accessors in generic
 subscripting

Now that we are allowing container generic subscripting to take dot
notation in the list of indirections, and it is transformed as a
String node.

For jsonb, we want to represent field accessors as String nodes in
refupperexprs for distinguishing from ordinary text subscripts which
can be needed for correct EXPLAIN.

Strings node is no longer a valid expression nodes, so added special
handling for them in walkers in nodeFuncs etc.
---
 src/backend/executor/execExpr.c    | 24 +++++++---
 src/backend/nodes/nodeFuncs.c      | 73 ++++++++++++++++++++++++++----
 src/backend/parser/parse_collate.c | 22 +++++++--
 src/backend/utils/adt/ruleutils.c  | 29 ++++++++----
 4 files changed, 121 insertions(+), 27 deletions(-)

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index f1569879b52..b0459011639 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -3336,9 +3336,15 @@ ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref,
 		{
 			sbsrefstate->upperprovided[i] = true;
 			/* Each subscript is evaluated into appropriate array entry */
-			ExecInitExprRec(e, state,
-							&sbsrefstate->upperindex[i],
-							&sbsrefstate->upperindexnull[i]);
+			if (IsA(e, String))
+			{
+				sbsrefstate->upperindex[i] = CStringGetTextDatum(strVal(e));
+				sbsrefstate->upperindexnull[i] = false;
+			}
+			else
+				ExecInitExprRec(e, state,
+								&sbsrefstate->upperindex[i],
+								&sbsrefstate->upperindexnull[i]);
 		}
 		i++;
 	}
@@ -3359,9 +3365,15 @@ ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref,
 		{
 			sbsrefstate->lowerprovided[i] = true;
 			/* Each subscript is evaluated into appropriate array entry */
-			ExecInitExprRec(e, state,
-							&sbsrefstate->lowerindex[i],
-							&sbsrefstate->lowerindexnull[i]);
+			if (IsA(e, String))
+			{
+				sbsrefstate->lowerindex[i] = CStringGetTextDatum(strVal(e));
+				sbsrefstate->lowerindexnull[i] = false;
+			}
+			else
+				ExecInitExprRec(e, state,
+								&sbsrefstate->lowerindex[i],
+								&sbsrefstate->lowerindexnull[i]);
 		}
 		i++;
 	}
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 7bc823507f1..a9c29ab8f29 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -2182,12 +2182,28 @@ expression_tree_walker_impl(Node *node,
 		case T_SubscriptingRef:
 			{
 				SubscriptingRef *sbsref = (SubscriptingRef *) node;
+				ListCell   *lc;
+
+				/*
+				 * Recurse directly for upper/lower container index lists,
+				 * skipping String subscripts used for dot notation.
+				 */
+				foreach(lc, sbsref->refupperindexpr)
+				{
+					Node	   *expr = lfirst(lc);
+
+					if (expr && !IsA(expr, String) && WALK(expr))
+						return true;
+				}
+
+				foreach(lc, sbsref->reflowerindexpr)
+				{
+					Node	   *expr = lfirst(lc);
+
+					if (expr && !IsA(expr, String) && WALK(expr))
+						return true;
+				}
 
-				/* recurse directly for upper/lower container index lists */
-				if (LIST_WALK(sbsref->refupperindexpr))
-					return true;
-				if (LIST_WALK(sbsref->reflowerindexpr))
-					return true;
 				/* walker must see the refexpr and refassgnexpr, however */
 				if (WALK(sbsref->refexpr))
 					return true;
@@ -3082,12 +3098,51 @@ expression_tree_mutator_impl(Node *node,
 			{
 				SubscriptingRef *sbsref = (SubscriptingRef *) node;
 				SubscriptingRef *newnode;
+				ListCell   *lc;
+				List	   *exprs = NIL;
 
 				FLATCOPY(newnode, sbsref, SubscriptingRef);
-				MUTATE(newnode->refupperindexpr, sbsref->refupperindexpr,
-					   List *);
-				MUTATE(newnode->reflowerindexpr, sbsref->reflowerindexpr,
-					   List *);
+
+				foreach(lc, sbsref->refupperindexpr)
+				{
+					Node	   *expr = lfirst(lc);
+
+					if (expr && IsA(expr, String))
+					{
+						String	   *str;
+
+						FLATCOPY(str, expr, String);
+						expr = (Node *) str;
+					}
+					else
+						expr = mutator(expr, context);
+
+					exprs = lappend(exprs, expr);
+				}
+
+				newnode->refupperindexpr = exprs;
+
+				exprs = NIL;
+
+				foreach(lc, sbsref->reflowerindexpr)
+				{
+					Node	   *expr = lfirst(lc);
+
+					if (expr && IsA(expr, String))
+					{
+						String	   *str;
+
+						FLATCOPY(str, expr, String);
+						expr = (Node *) str;
+					}
+					else
+						expr = mutator(expr, context);
+
+					exprs = lappend(exprs, expr);
+				}
+
+				newnode->reflowerindexpr = exprs;
+
 				MUTATE(newnode->refexpr, sbsref->refexpr,
 					   Expr *);
 				MUTATE(newnode->refassgnexpr, sbsref->refassgnexpr,
diff --git a/src/backend/parser/parse_collate.c b/src/backend/parser/parse_collate.c
index d2e218353f3..be6dea6ffd2 100644
--- a/src/backend/parser/parse_collate.c
+++ b/src/backend/parser/parse_collate.c
@@ -680,11 +680,25 @@ assign_collations_walker(Node *node, assign_collations_context *context)
 							 * contribute anything.)
 							 */
 							SubscriptingRef *sbsref = (SubscriptingRef *) node;
+							ListCell   *lc;
+
+							/* skip String subscripts used for dot notation */
+							foreach(lc, sbsref->refupperindexpr)
+							{
+								Node	   *expr = lfirst(lc);
+
+								if (expr && !IsA(expr, String))
+									assign_expr_collations(context->pstate, expr);
+							}
+
+							foreach(lc, sbsref->reflowerindexpr)
+							{
+								Node	   *expr = lfirst(lc);
+
+								if (expr && !IsA(expr, String))
+									assign_expr_collations(context->pstate, expr);
+							}
 
-							assign_expr_collations(context->pstate,
-												   (Node *) sbsref->refupperindexpr);
-							assign_expr_collations(context->pstate,
-												   (Node *) sbsref->reflowerindexpr);
 							(void) assign_collations_walker((Node *) sbsref->refexpr,
 															&loccontext);
 							(void) assign_collations_walker((Node *) sbsref->refassgnexpr,
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 9e90acedb91..d8d9305520e 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -47,6 +47,7 @@
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "nodes/pathnodes.h"
+#include "nodes/subscripting.h"
 #include "optimizer/optimizer.h"
 #include "parser/parse_agg.h"
 #include "parser/parse_func.h"
@@ -12995,17 +12996,29 @@ printSubscripts(SubscriptingRef *sbsref, deparse_context *context)
 	lowlist_item = list_head(sbsref->reflowerindexpr);	/* could be NULL */
 	foreach(uplist_item, sbsref->refupperindexpr)
 	{
-		appendStringInfoChar(buf, '[');
-		if (lowlist_item)
+		Node	   *up = (Node *) lfirst(uplist_item);
+
+		if (IsA(up, String))
+		{
+			appendStringInfoChar(buf, '.');
+			appendStringInfoString(buf, quote_identifier(strVal(up)));
+		}
+		else
 		{
+			appendStringInfoChar(buf, '[');
+			if (lowlist_item)
+			{
+				/* If subexpression is NULL, get_rule_expr prints nothing */
+				get_rule_expr((Node *) lfirst(lowlist_item), context, false);
+				appendStringInfoChar(buf, ':');
+			}
 			/* If subexpression is NULL, get_rule_expr prints nothing */
-			get_rule_expr((Node *) lfirst(lowlist_item), context, false);
-			appendStringInfoChar(buf, ':');
-			lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
+			get_rule_expr((Node *) lfirst(uplist_item), context, false);
+			appendStringInfoChar(buf, ']');
 		}
-		/* If subexpression is NULL, get_rule_expr prints nothing */
-		get_rule_expr((Node *) lfirst(uplist_item), context, false);
-		appendStringInfoChar(buf, ']');
+
+		if (lowlist_item)
+			lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
 	}
 }
 
-- 
2.39.5 (Apple Git-154)



  [application/x-patch] v11-0001-Allow-transformation-of-only-a-sublist-of-subscr.patch (8.8K, ../../CAK98qZ1nz6ZZhQqTOCNwRguZE5GsBLW5BQT_k=s7AA6gc2CN_g@mail.gmail.com/7-v11-0001-Allow-transformation-of-only-a-sublist-of-subscr.patch)
  download | inline diff:
From 3fce9de051391cb99fde3c04f52dcd6464a922e6 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Fri, 14 Oct 2022 15:35:22 +0300
Subject: [PATCH v11 1/8] Allow transformation of only a sublist of subscripts

This is a preparation step for allowing subscripting containers to
transform only a prefix of an indirection list and modify the list
in-place by removing the processed elements. Currently, all elements
are consumed, and the list is set to NIL after transformation.

In the following commit, subscripting containers will gain the
flexibility to stop transformation when encountering an unsupported
indirection and return the remaining indirections to the caller.
---
 contrib/hstore/hstore_subs.c      | 10 ++++++----
 src/backend/parser/parse_expr.c   |  9 ++++-----
 src/backend/parser/parse_node.c   |  4 ++--
 src/backend/parser/parse_target.c |  2 +-
 src/backend/utils/adt/arraysubs.c |  6 ++++--
 src/backend/utils/adt/jsonbsubs.c |  6 ++++--
 src/include/nodes/subscripting.h  |  7 ++++++-
 src/include/parser/parse_node.h   |  2 +-
 8 files changed, 28 insertions(+), 18 deletions(-)

diff --git a/contrib/hstore/hstore_subs.c b/contrib/hstore/hstore_subs.c
index 3d03f66fa0d..1b29543ab67 100644
--- a/contrib/hstore/hstore_subs.c
+++ b/contrib/hstore/hstore_subs.c
@@ -40,7 +40,7 @@
  */
 static void
 hstore_subscript_transform(SubscriptingRef *sbsref,
-						   List *indirection,
+						   List **indirection,
 						   ParseState *pstate,
 						   bool isSlice,
 						   bool isAssignment)
@@ -49,15 +49,15 @@ hstore_subscript_transform(SubscriptingRef *sbsref,
 	Node	   *subexpr;
 
 	/* We support only single-subscript, non-slice cases */
-	if (isSlice || list_length(indirection) != 1)
+	if (isSlice || list_length(*indirection) != 1)
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("hstore allows only one subscript"),
 				 parser_errposition(pstate,
-									exprLocation((Node *) indirection))));
+									exprLocation((Node *) *indirection))));
 
 	/* Transform the subscript expression to type text */
-	ai = linitial_node(A_Indices, indirection);
+	ai = linitial_node(A_Indices, *indirection);
 	Assert(ai->uidx != NULL && ai->lidx == NULL && !ai->is_slice);
 
 	subexpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
@@ -81,6 +81,8 @@ hstore_subscript_transform(SubscriptingRef *sbsref,
 	/* Determine the result type of the subscripting operation; always text */
 	sbsref->refrestype = TEXTOID;
 	sbsref->reftypmod = -1;
+
+	*indirection = NIL;
 }
 
 /*
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index bad1df732ea..2c0f4a50b21 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -466,14 +466,13 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 			Assert(IsA(n, String));
 
 			/* process subscripts before this field selection */
-			if (subscripts)
+			while (subscripts)
 				result = (Node *) transformContainerSubscripts(pstate,
 															   result,
 															   exprType(result),
 															   exprTypmod(result),
-															   subscripts,
+															   &subscripts,
 															   false);
-			subscripts = NIL;
 
 			newresult = ParseFuncOrColumn(pstate,
 										  list_make1(n),
@@ -488,12 +487,12 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		}
 	}
 	/* process trailing subscripts, if any */
-	if (subscripts)
+	while (subscripts)
 		result = (Node *) transformContainerSubscripts(pstate,
 													   result,
 													   exprType(result),
 													   exprTypmod(result),
-													   subscripts,
+													   &subscripts,
 													   false);
 
 	return result;
diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c
index d6feb16aef3..19a6b678e67 100644
--- a/src/backend/parser/parse_node.c
+++ b/src/backend/parser/parse_node.c
@@ -244,7 +244,7 @@ transformContainerSubscripts(ParseState *pstate,
 							 Node *containerBase,
 							 Oid containerType,
 							 int32 containerTypMod,
-							 List *indirection,
+							 List **indirection,
 							 bool isAssignment)
 {
 	SubscriptingRef *sbsref;
@@ -280,7 +280,7 @@ transformContainerSubscripts(ParseState *pstate,
 	 * element.  If any of the items are slice specifiers (lower:upper), then
 	 * the subscript expression means a container slice operation.
 	 */
-	foreach(idx, indirection)
+	foreach(idx, *indirection)
 	{
 		A_Indices  *ai = lfirst_node(A_Indices, idx);
 
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 4aba0d9d4d5..4675a523045 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -936,7 +936,7 @@ transformAssignmentSubscripts(ParseState *pstate,
 										  basenode,
 										  containerType,
 										  containerTypMod,
-										  subscripts,
+										  &subscripts,
 										  true);
 
 	typeNeeded = sbsref->refrestype;
diff --git a/src/backend/utils/adt/arraysubs.c b/src/backend/utils/adt/arraysubs.c
index 2940fb8e8d7..234c2c278c1 100644
--- a/src/backend/utils/adt/arraysubs.c
+++ b/src/backend/utils/adt/arraysubs.c
@@ -54,7 +54,7 @@ typedef struct ArraySubWorkspace
  */
 static void
 array_subscript_transform(SubscriptingRef *sbsref,
-						  List *indirection,
+						  List **indirection,
 						  ParseState *pstate,
 						  bool isSlice,
 						  bool isAssignment)
@@ -71,7 +71,7 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	 * indirection items to slices by treating the single subscript as the
 	 * upper bound and supplying an assumed lower bound of 1.
 	 */
-	foreach(idx, indirection)
+	foreach(idx, *indirection)
 	{
 		A_Indices  *ai = lfirst_node(A_Indices, idx);
 		Node	   *subexpr;
@@ -152,6 +152,8 @@ array_subscript_transform(SubscriptingRef *sbsref,
 						list_length(upperIndexpr), MAXDIM)));
 	/* We need not check lowerIndexpr separately */
 
+	*indirection = NIL;
+
 	/*
 	 * Determine the result type of the subscripting operation.  It's the same
 	 * as the array type if we're slicing, else it's the element type.  In
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index de64d498512..8ad6aa1ad4f 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -41,7 +41,7 @@ typedef struct JsonbSubWorkspace
  */
 static void
 jsonb_subscript_transform(SubscriptingRef *sbsref,
-						  List *indirection,
+						  List **indirection,
 						  ParseState *pstate,
 						  bool isSlice,
 						  bool isAssignment)
@@ -53,7 +53,7 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	 * Transform and convert the subscript expressions. Jsonb subscripting
 	 * does not support slices, look only and the upper index.
 	 */
-	foreach(idx, indirection)
+	foreach(idx, *indirection)
 	{
 		A_Indices  *ai = lfirst_node(A_Indices, idx);
 		Node	   *subExpr;
@@ -159,6 +159,8 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	/* Determine the result type of the subscripting operation; always jsonb */
 	sbsref->refrestype = JSONBOID;
 	sbsref->reftypmod = -1;
+
+	*indirection = NIL;
 }
 
 /*
diff --git a/src/include/nodes/subscripting.h b/src/include/nodes/subscripting.h
index 234e8ad8012..5d576af346f 100644
--- a/src/include/nodes/subscripting.h
+++ b/src/include/nodes/subscripting.h
@@ -71,6 +71,11 @@ struct SubscriptExecSteps;
  * does not care to support slicing, it can just throw an error if isSlice.)
  * See array_subscript_transform() for sample code.
  *
+ * The transform method receives a pointer to a list of raw indirections.
+ * This allows the method to parse a sublist of the indirections (typically
+ * the prefix) and modify the original list in place, enabling the caller to
+ * either process the remaining indirections differently or raise an error.
+ *
  * The transform method is also responsible for identifying the result type
  * of the subscripting operation.  At call, refcontainertype and reftypmod
  * describe the container type (this will be a base type not a domain), and
@@ -93,7 +98,7 @@ struct SubscriptExecSteps;
  * assignment must return.
  */
 typedef void (*SubscriptTransform) (SubscriptingRef *sbsref,
-									List *indirection,
+									List **indirection,
 									struct ParseState *pstate,
 									bool isSlice,
 									bool isAssignment);
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 994284019fb..5ae11ccec33 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -377,7 +377,7 @@ extern SubscriptingRef *transformContainerSubscripts(ParseState *pstate,
 													 Node *containerBase,
 													 Oid containerType,
 													 int32 containerTypMod,
-													 List *indirection,
+													 List **indirection,
 													 bool isAssignment);
 extern Const *make_const(ParseState *pstate, A_Const *aconst);
 
-- 
2.39.5 (Apple Git-154)



  [application/x-patch] v11-0007-Allow-wild-card-member-access-for-jsonb.patch (22.4K, ../../CAK98qZ1nz6ZZhQqTOCNwRguZE5GsBLW5BQT_k=s7AA6gc2CN_g@mail.gmail.com/8-v11-0007-Allow-wild-card-member-access-for-jsonb.patch)
  download | inline diff:
From 7680782c792d7dbaf062c04295ee9861c0565d36 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Sat, 1 Apr 2023 23:15:26 +0300
Subject: [PATCH v11 7/8] Allow wild card member access for jsonb

---
 src/backend/parser/gram.y                     |   2 +
 src/backend/parser/parse_expr.c               |  34 ++--
 src/backend/parser/parse_target.c             |  67 +++++--
 src/backend/utils/adt/ruleutils.c             |   6 +-
 src/include/parser/parse_expr.h               |   3 +
 .../ecpg/test/expected/sql-sqljson.c          |  14 +-
 .../ecpg/test/expected/sql-sqljson.stderr     |  17 +-
 .../ecpg/test/expected/sql-sqljson.stdout     |   2 +
 src/interfaces/ecpg/test/sql/sqljson.pgc      |   5 +-
 src/test/regress/expected/jsonb.out           | 179 +++++++++++++++++-
 src/test/regress/sql/jsonb.sql                |  31 +++
 11 files changed, 317 insertions(+), 43 deletions(-)

diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 271ae26cbaf..44840bc1b25 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -18996,6 +18996,7 @@ check_func_name(List *names, core_yyscan_t yyscanner)
 static List *
 check_indirection(List *indirection, core_yyscan_t yyscanner)
 {
+#if 0
 	ListCell   *l;
 
 	foreach(l, indirection)
@@ -19006,6 +19007,7 @@ check_indirection(List *indirection, core_yyscan_t yyscanner)
 				parser_yyerror("improper use of \"*\"");
 		}
 	}
+#endif
 	return indirection;
 }
 
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 8ea51176196..512ac5b4970 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -74,7 +74,6 @@ static Node *transformColumnRef(ParseState *pstate, ColumnRef *cref);
 static Node *transformWholeRowRef(ParseState *pstate,
 								  ParseNamespaceItem *nsitem,
 								  int sublevels_up, int location);
-static Node *transformIndirection(ParseState *pstate, A_Indirection *ind);
 static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
 static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
 static Node *transformJsonObjectConstructor(ParseState *pstate,
@@ -158,7 +157,7 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 			break;
 
 		case T_A_Indirection:
-			result = transformIndirection(pstate, (A_Indirection *) expr);
+			result = transformIndirection(pstate, (A_Indirection *) expr, NULL);
 			break;
 
 		case T_A_ArrayExpr:
@@ -432,8 +431,9 @@ unknown_attribute(ParseState *pstate, Node *relref, const char *attname,
 	}
 }
 
-static Node *
-transformIndirection(ParseState *pstate, A_Indirection *ind)
+Node *
+transformIndirection(ParseState *pstate, A_Indirection *ind,
+					 bool *trailing_star_expansion)
 {
 	Node	   *last_srf = pstate->p_last_srf;
 	Node	   *result = transformExprRecurse(pstate, ind->arg);
@@ -454,12 +454,7 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		if (IsA(n, A_Indices))
 			subscripts = lappend(subscripts, n);
 		else if (IsA(n, A_Star))
-		{
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("row expansion via \"*\" is not supported here"),
-					 parser_errposition(pstate, location)));
-		}
+			subscripts = lappend(subscripts, n);
 		else
 		{
 			Assert(IsA(n, String));
@@ -491,7 +486,21 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 
 			n = linitial(subscripts);
 
-			if (!IsA(n, String))
+			if (IsA(n, A_Star))
+			{
+				/* Success, if trailing star expansion is allowed */
+				if (trailing_star_expansion && list_length(subscripts) == 1)
+				{
+					*trailing_star_expansion = true;
+					return result;
+				}
+
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("row expansion via \"*\" is not supported here"),
+						 parser_errposition(pstate, location)));
+			}
+			else if (!IsA(n, String))
 				ereport(ERROR,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
 						 errmsg("cannot subscript type %s because it does not support subscripting",
@@ -517,6 +526,9 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		result = newresult;
 	}
 
+	if (trailing_star_expansion)
+		*trailing_star_expansion = false;
+
 	return result;
 }
 
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 3ef5897f2eb..141fc1dfeb1 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -48,7 +48,7 @@ static Node *transformAssignmentSubscripts(ParseState *pstate,
 static List *ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
 								 bool make_target_entry);
 static List *ExpandAllTables(ParseState *pstate, int location);
-static List *ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
+static Node *ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
 								   bool make_target_entry, ParseExprKind exprKind);
 static List *ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 							   int sublevels_up, int location,
@@ -134,6 +134,7 @@ transformTargetList(ParseState *pstate, List *targetlist,
 	foreach(o_target, targetlist)
 	{
 		ResTarget  *res = (ResTarget *) lfirst(o_target);
+		Node	   *transformed = NULL;
 
 		/*
 		 * Check for "something.*".  Depending on the complexity of the
@@ -162,13 +163,19 @@ transformTargetList(ParseState *pstate, List *targetlist,
 
 				if (IsA(llast(ind->indirection), A_Star))
 				{
-					/* It is something.*, expand into multiple items */
-					p_target = list_concat(p_target,
-										   ExpandIndirectionStar(pstate,
-																 ind,
-																 true,
-																 exprKind));
-					continue;
+					Node	   *columns = ExpandIndirectionStar(pstate,
+																ind,
+																true,
+																exprKind);
+
+					if (IsA(columns, List))
+					{
+						/* It is something.*, expand into multiple items */
+						p_target = list_concat(p_target, (List *) columns);
+						continue;
+					}
+
+					transformed = (Node *) columns;
 				}
 			}
 		}
@@ -180,7 +187,7 @@ transformTargetList(ParseState *pstate, List *targetlist,
 		p_target = lappend(p_target,
 						   transformTargetEntry(pstate,
 												res->val,
-												NULL,
+												transformed,
 												exprKind,
 												res->name,
 												false));
@@ -251,10 +258,15 @@ transformExpressionList(ParseState *pstate, List *exprlist,
 
 			if (IsA(llast(ind->indirection), A_Star))
 			{
-				/* It is something.*, expand into multiple items */
-				result = list_concat(result,
-									 ExpandIndirectionStar(pstate, ind,
-														   false, exprKind));
+				Node	   *cols = ExpandIndirectionStar(pstate, ind,
+														 false, exprKind);
+
+				if (!cols || IsA(cols, List))
+					/* It is something.*, expand into multiple items */
+					result = list_concat(result, (List *) cols);
+				else
+					result = lappend(result, cols);
+
 				continue;
 			}
 		}
@@ -1345,22 +1357,30 @@ ExpandAllTables(ParseState *pstate, int location)
  * For robustness, we use a separate "make_target_entry" flag to control
  * this rather than relying on exprKind.
  */
-static List *
+static Node *
 ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
 					  bool make_target_entry, ParseExprKind exprKind)
 {
 	Node	   *expr;
+	ParseExprKind sv_expr_kind;
+	bool		trailing_star_expansion = false;
+
+	/* Save and restore identity of expression type we're parsing */
+	Assert(exprKind != EXPR_KIND_NONE);
+	sv_expr_kind = pstate->p_expr_kind;
+	pstate->p_expr_kind = exprKind;
 
 	/* Strip off the '*' to create a reference to the rowtype object */
-	ind = copyObject(ind);
-	ind->indirection = list_truncate(ind->indirection,
-									 list_length(ind->indirection) - 1);
+	expr = transformIndirection(pstate, ind, &trailing_star_expansion);
+
+	pstate->p_expr_kind = sv_expr_kind;
 
-	/* And transform that */
-	expr = transformExpr(pstate, (Node *) ind, exprKind);
+	/* '*' was consumed by generic type subscripting */
+	if (!trailing_star_expansion)
+		return expr;
 
 	/* Expand the rowtype expression into individual fields */
-	return ExpandRowReference(pstate, expr, make_target_entry);
+	return (Node *) ExpandRowReference(pstate, expr, make_target_entry);
 }
 
 /*
@@ -1785,13 +1805,18 @@ FigureColnameInternal(Node *node, char **name)
 				char	   *fname = NULL;
 				ListCell   *l;
 
-				/* find last field name, if any, ignoring "*" and subscripts */
+				/*
+				 * find last field name, if any, ignoring subscripts, and use
+				 * '?column?' when there's a trailing '*'.
+				 */
 				foreach(l, ind->indirection)
 				{
 					Node	   *i = lfirst(l);
 
 					if (IsA(i, String))
 						fname = strVal(i);
+					else if (IsA(i, A_Star))
+						fname = "?column?";
 				}
 				if (fname)
 				{
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index d8d9305520e..6a207d58be2 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -12998,7 +12998,11 @@ printSubscripts(SubscriptingRef *sbsref, deparse_context *context)
 	{
 		Node	   *up = (Node *) lfirst(uplist_item);
 
-		if (IsA(up, String))
+		if (!up)
+		{
+			appendStringInfoString(buf, ".*");
+		}
+		else if (IsA(up, String))
 		{
 			appendStringInfoChar(buf, '.');
 			appendStringInfoString(buf, quote_identifier(strVal(up)));
diff --git a/src/include/parser/parse_expr.h b/src/include/parser/parse_expr.h
index efbaff8e710..c9f6a7724c0 100644
--- a/src/include/parser/parse_expr.h
+++ b/src/include/parser/parse_expr.h
@@ -22,4 +22,7 @@ extern Node *transformExpr(ParseState *pstate, Node *expr, ParseExprKind exprKin
 
 extern const char *ParseExprKindName(ParseExprKind exprKind);
 
+extern Node *transformIndirection(ParseState *pstate, A_Indirection *ind,
+								  bool *trailing_star_expansion);
+
 #endif							/* PARSE_EXPR_H */
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.c b/src/interfaces/ecpg/test/expected/sql-sqljson.c
index e1e2b1e03f0..748b2e2bee6 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.c
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.c
@@ -515,14 +515,24 @@ if (sqlca.sqlcode < 0) sqlprint();}
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 145 "sqljson.pgc"
 
-	// error
+	printf("Found json=%s\n", json);
 
-  { ECPGdisconnect(__LINE__, "CURRENT");
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . * . x )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 148 "sqljson.pgc"
 
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 148 "sqljson.pgc"
 
+	printf("Found json=%s\n", json);
+
+  { ECPGdisconnect(__LINE__, "CURRENT");
+#line 151 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 151 "sqljson.pgc"
+
 
   return 0;
 }
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
index e532a8f44fa..92c5d1520c4 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
@@ -347,12 +347,17 @@ SQL error: schema "jsonb" does not exist on line 121
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 145: using PQexec
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_check_PQresult on line 145: bad response - ERROR:  row expansion via "*" is not supported here
-LINE 1: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x...
-                        ^
+[NO_PID]: ecpg_process_output on line 145: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 145: RESULT: [{"b": 1, "c": 2}, [{"x": 1}, {"x": [12, {"y": 1}]}]] offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 148: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . * . x ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 148: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 148: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 148: RESULT: [1, [12, {"y": 1}]] offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: raising sqlstate 0A000 (sqlcode -400): row expansion via "*" is not supported here on line 145
-[NO_PID]: sqlca: code: -400, state: 0A000
-SQL error: row expansion via "*" is not supported here on line 145
 [NO_PID]: ecpg_finish: connection ecpg1_regression closed
 [NO_PID]: sqlca: code: 0, state: 00000
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
index bfa93e86d00..96af113bda8 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
@@ -36,3 +36,5 @@ Found json={"x": 1}
 Found json=[12, {"y": 1}]
 Found json=[{"x": 1}, {"x": [12, {"y": 1}]}]
 Found json=[1, [12, {"y": 1}]]
+Found json=[{"b": 1, "c": 2}, [{"x": 1}, {"x": [12, {"y": 1}]}]]
+Found json=[1, [12, {"y": 1}]]
diff --git a/src/interfaces/ecpg/test/sql/sqljson.pgc b/src/interfaces/ecpg/test/sql/sqljson.pgc
index 96be3919928..4e7427d237d 100644
--- a/src/interfaces/ecpg/test/sql/sqljson.pgc
+++ b/src/interfaces/ecpg/test/sql/sqljson.pgc
@@ -143,7 +143,10 @@ EXEC SQL END DECLARE SECTION;
 	printf("Found json=%s\n", json);
 
 	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).*) INTO :json;
-	// error
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).*.x) INTO :json;
+	printf("Found json=%s\n", json);
 
   EXEC SQL DISCONNECT;
 
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 91a7b825764..1a9452937d5 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -6031,8 +6031,159 @@ SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
  
 (1 row)
 
+/* wild card member access */
 SELECT (jb).a.* FROM test_jsonb_dot_notation;
-ERROR:  type jsonb is not composite
+                 ?column?                  
+-------------------------------------------
+ ["c", "d", "f", {"y": "yyy", "z": "zzz"}]
+(1 row)
+
+SELECT (jb).* FROM test_jsonb_dot_notation;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (jb).* FROM test_jsonb_dot_notation t;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (jb).* FROM test_jsonb_dot_notation;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (t.jb).* FROM test_jsonb_dot_notation;
+ERROR:  missing FROM-clause entry for table "t"
+LINE 1: SELECT (t.jb).* FROM test_jsonb_dot_notation;
+                ^
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+                 ?column?                  
+-------------------------------------------
+ ["c", "d", "f", {"y": "yyy", "z": "zzz"}]
+(1 row)
+
+SELECT (jb).a.*.b FROM test_jsonb_dot_notation;
+ b 
+---
+ 
+(1 row)
+
+SELECT (jb).a.*.x FROM test_jsonb_dot_notation;
+ x 
+---
+ 
+(1 row)
+
+SELECT (jb).a.*.y FROM test_jsonb_dot_notation;
+   y   
+-------
+ "yyy"
+(1 row)
+
+SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+    ?column?    
+----------------
+ ["yyy", "zzz"]
+(1 row)
+
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT (jb).*.x FROM test_jsonb_dot_notation t;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+ x 
+---
+ 
+(1 row)
+
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+ x 
+---
+ 
+(1 row)
+
+SELECT ((jb).*)[:].x FROM test_jsonb_dot_notation t;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT (jb).*.x.* FROM test_jsonb_dot_notation;
+           ?column?           
+------------------------------
+ ["yyy", "zzz", "YYY", "ZZZ"]
+(1 row)
+
+SELECT (jb).*.x.y FROM test_jsonb_dot_notation;
+       y        
+----------------
+ ["yyy", "YYY"]
+(1 row)
+
+SELECT (jb).*.x.z FROM test_jsonb_dot_notation;
+       z        
+----------------
+ ["zzz", "ZZZ"]
+(1 row)
+
+SELECT (jb).*.*.y FROM test_jsonb_dot_notation;
+       y        
+----------------
+ ["yyy", "YYY"]
+(1 row)
+
+SELECT (jb).*.*.* FROM test_jsonb_dot_notation;
+           ?column?           
+------------------------------
+ ["yyy", "zzz", "YYY", "ZZZ"]
+(1 row)
+
+SELECT (jb).*.*.*.* FROM test_jsonb_dot_notation;
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT (jb).a.b.c.* FROM test_jsonb_dot_notation;
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT (jb).a.**.x FROM test_jsonb_dot_notation; -- not supported
+ERROR:  syntax error at or near "**"
+LINE 1: SELECT (jb).a.**.x FROM test_jsonb_dot_notation;
+                      ^
 -- explains should work
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
                   QUERY PLAN                  
@@ -6060,6 +6211,32 @@ SELECT (jb).a[1] FROM test_jsonb_dot_notation;
  2
 (1 row)
 
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.* FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: jb.a.*
+(2 rows)
+
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+                 ?column?                  
+-------------------------------------------
+ ["c", "d", "f", {"y": "yyy", "z": "zzz"}]
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: jb.a.*[1:2].*.b
+(2 rows)
+
+SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
+ b 
+---
+ 
+(1 row)
+
 -- jsonb array access in plpgsql
 DO $$
 DECLARE
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 4bd3990fb55..b48deed7dbd 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1626,13 +1626,44 @@ SELECT (jb).a.x.y FROM test_jsonb_dot_notation;
 SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
 SELECT (jb).b.x.z FROM test_jsonb_dot_notation;
 SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
+
+/* wild card member access */
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+SELECT (jb).* FROM test_jsonb_dot_notation;
+SELECT (jb).* FROM test_jsonb_dot_notation t;
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+SELECT (jb).* FROM test_jsonb_dot_notation;
+SELECT (t.jb).* FROM test_jsonb_dot_notation;
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
 SELECT (jb).a.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.b FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.x FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.y FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+SELECT (jb).*.x FROM test_jsonb_dot_notation t;
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+SELECT ((jb).*)[:].x FROM test_jsonb_dot_notation t;
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+SELECT (jb).*.x.* FROM test_jsonb_dot_notation;
+SELECT (jb).*.x.y FROM test_jsonb_dot_notation;
+SELECT (jb).*.x.z FROM test_jsonb_dot_notation;
+SELECT (jb).*.*.y FROM test_jsonb_dot_notation;
+SELECT (jb).*.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).*.*.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.b.c.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.**.x FROM test_jsonb_dot_notation; -- not supported
 
 -- explains should work
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
 SELECT (t.jb).a FROM test_jsonb_dot_notation t;
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
 SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
+SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
 
 -- jsonb array access in plpgsql
 DO $$
-- 
2.39.5 (Apple Git-154)



  [application/x-patch] v11-0006-Implement-read-only-dot-notation-for-jsonb.patch (42.3K, ../../CAK98qZ1nz6ZZhQqTOCNwRguZE5GsBLW5BQT_k=s7AA6gc2CN_g@mail.gmail.com/9-v11-0006-Implement-read-only-dot-notation-for-jsonb.patch)
  download | inline diff:
From b775394875ac683cb4cbce36863555a72a84db12 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Sat, 1 Apr 2023 23:17:53 +0300
Subject: [PATCH v11 6/8] Implement read-only dot notation for jsonb

This patch introduces JSONB member access using dot notation, wildcard
access, and array subscripting with slicing, aligning with the JSON
simplified accessor specified in SQL:2023. Specifically, the following
syntax enhancements are added:

1. Simple dot-notation access to JSONB object fields
2. Wildcard dot-notation access to JSONB object fields
2. Subscripting for index range access to JSONB array elements

Examples:

-- Setup
create table t(x int, y jsonb);
insert into t select 1, '{"a": 1, "b": 42}'::jsonb;
insert into t select 1, '{"a": 2, "b": {"c": 42}}'::jsonb;
insert into t select 1, '{"a": 3, "b": {"c": "42"}, "d":[11, 12]}'::jsonb;

-- Existing syntax predates the SQL standard:
select (t.y)->'b' from t;
select (t.y)->'b'->'c' from t;
select (t.y)->'d'->0 from t;

-- JSON simplified accessor specified by the SQL standard:
select (t.y).b from t;
select (t.y).b.c from t;
select (t.y).d[0] from t;

The SQL standard states that simplified access is equivalent to:
JSON_QUERY (VEP, 'lax $.JC' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR)

where:
  VEP = <value expression primary>
  JC = <JSON simplified accessor op chain>

For example, the JSON_QUERY equivalents of the above queries are:

select json_query(y, 'lax $.b' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t;
select json_query(y, 'lax $.b.c' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t;
select json_query(y, 'lax $.d[0]' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t;

Implementation details:

Extends the existing container subscripting interface to support
container-specific information, specifically a JSONPath expression for
jsonb.

During query transformation, detects dot-notation, wildcard access,
and sliced subscripting. If any of these accessors are present,
constructs a JSONPath expression representing the access chain.

During execution, if a JSONPath expression is present in
JsonbSubWorkspace, executes it via JsonPathQuery().

Does not transform accessors directly into JSON_QUERY during
transformation to preserve the original query structure for EXPLAIN
and CREATE VIEW.
---
 src/backend/utils/adt/jsonbsubs.c             | 288 ++++++++++++++++-
 src/include/nodes/primnodes.h                 |   7 +
 .../ecpg/test/expected/sql-sqljson.c          | 102 +++++-
 .../ecpg/test/expected/sql-sqljson.stderr     |  86 +++++
 .../ecpg/test/expected/sql-sqljson.stdout     |   8 +
 src/interfaces/ecpg/test/sql/sqljson.pgc      |  30 ++
 src/test/regress/expected/jsonb.out           | 305 +++++++++++++++++-
 src/test/regress/sql/jsonb.sql                |  85 ++++-
 8 files changed, 877 insertions(+), 34 deletions(-)

diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index 3ffe40cfa40..3588a1d062f 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -15,21 +15,30 @@
 #include "postgres.h"
 
 #include "executor/execExpr.h"
+#include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "nodes/subscripting.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_expr.h"
 #include "utils/builtins.h"
 #include "utils/jsonb.h"
+#include "utils/jsonpath.h"
 
 
-/* SubscriptingRefState.workspace for jsonb subscripting execution */
+/*
+ * SubscriptingRefState.workspace for generic jsonb subscripting execution.
+ *
+ * Stores state for both jsonb simple subscripting and dot notation access.
+ * Dot notation additionally uses `jsonpath` for JsonPath evaluation.
+ */
 typedef struct JsonbSubWorkspace
 {
 	bool		expectArray;	/* jsonb root is expected to be an array */
 	Oid		   *indexOid;		/* OID of coerced subscript expression, could
 								 * be only integer or text */
 	Datum	   *index;			/* Subscript values in Datum format */
+	JsonPath   *jsonpath;		/* JsonPath for dot notation execution via
+								 * JsonPathQuery() */
 } JsonbSubWorkspace;
 
 static Oid
@@ -110,6 +119,223 @@ coerce_jsonpath_subscript(ParseState *pstate, Node *subExpr, Oid numtype)
 	return subExpr;
 }
 
+/*
+ * During transformation, determine whether to build a JsonPath
+ * for JsonPathQuery() execution.
+ *
+ * JsonPath is needed if the indirection list includes:
+ * - String-based access (dot notation)
+ * - Wildcard (`*`)
+ * - Slice-based subscripting
+ *
+ * Otherwise, simple jsonb subscripting is sufficient.
+ */
+static bool
+jsonb_check_jsonpath_needed(List *indirection)
+{
+	ListCell   *lc;
+
+	foreach(lc, indirection)
+	{
+		Node	   *accessor = lfirst(lc);
+
+		if (IsA(accessor, String) ||
+			IsA(accessor, A_Star))
+			return true;
+		else
+		{
+			Assert(IsA(accessor, A_Indices));
+
+			if (castNode(A_Indices, accessor)->is_slice)
+				return true;
+		}
+	}
+
+	return false;
+}
+
+/*
+ * Helper functions for constructing JsonPath expressions.
+ *
+ * The make_jsonpath_item_* functions create various types of JsonPathParseItem
+ * nodes, which are used to build JsonPath expressions for jsonb simplified
+ * accessor.
+ */
+
+static JsonPathParseItem *
+make_jsonpath_item(JsonPathItemType type)
+{
+	JsonPathParseItem *v = palloc(sizeof(*v));
+
+	v->type = type;
+	v->next = NULL;
+
+	return v;
+}
+
+static JsonPathParseItem *
+make_jsonpath_item_int(int32 val, List **exprs)
+{
+	JsonPathParseItem *jpi = make_jsonpath_item(jpiNumeric);
+
+	jpi->value.numeric =
+		DatumGetNumeric(DirectFunctionCall1(int4_numeric, Int32GetDatum(val)));
+
+	*exprs = lappend(*exprs, makeConst(INT4OID, -1, InvalidOid, 4,
+									   Int32GetDatum(val), false, true));
+
+	return jpi;
+}
+
+/*
+ * Convert an expression into a JsonPathParseItem.
+ * If the expression is a constant integer, create a direct numeric item.
+ * Otherwise, create a variable reference and add it to the expression list.
+ */
+static JsonPathParseItem *
+make_jsonpath_item_expr(ParseState *pstate, Node *expr, List **exprs)
+{
+	Const	   *cnst;
+
+	expr = transformExpr(pstate, expr, pstate->p_expr_kind);
+
+	if (!IsA(expr, Const))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("jsonb simplified accessor supports subscripting in const int4, got type: %s",
+						format_type_be(exprType(expr))),
+				 parser_errposition(pstate, exprLocation(expr))));
+
+	cnst = (Const *) expr;
+
+	if (cnst->consttype == INT4OID && !cnst->constisnull)
+	{
+		int32		val = DatumGetInt32(cnst->constvalue);
+
+		return make_jsonpath_item_int(val, exprs);
+	}
+
+	ereport(ERROR,
+			(errcode(ERRCODE_DATATYPE_MISMATCH),
+			 errmsg("jsonb simplified accessor supports subscripting in type: INT4, got type: %s",
+					format_type_be(cnst->consttype)),
+			 parser_errposition(pstate, exprLocation(expr))));
+}
+
+/*
+ * jsonb_subscript_make_jsonpath
+ *
+ * Constructs a JsonPath expression from a list of indirections.
+ * This function is used when jsonb subscripting involves dot notation,
+ * wildcards (*), or slice-based subscripting, requiring JsonPath-based
+ * evaluation.
+ *
+ * The function modifies the indirection list in place, removing processed
+ * elements as it converts them into JsonPath components, as follows:
+ * - String keys (dot notation) -> jpiKey items.
+ * - Wildcard (*) -> jpiAnyKey item.
+ * - Array indices and slices -> jpiIndexArray items.
+ *
+ * Parameters:
+ * - pstate: Parse state context.
+ * - indirection: List of subscripting expressions (modified in-place).
+ * - uexprs: Upper-bound expressions extracted from subscripts.
+ * - lexprs: Lower-bound expressions extracted from subscripts.
+ * Returns:
+ * - a Const node containing the transformed JsonPath expression.
+ */
+static Node *
+jsonb_subscript_make_jsonpath(ParseState *pstate, List **indirection,
+							  List **uexprs, List **lexprs)
+{
+	JsonPathParseResult jpres;
+	JsonPathParseItem *path = make_jsonpath_item(jpiRoot);
+	ListCell   *lc;
+	Datum		jsp;
+	int			pathlen = 0;
+
+	*uexprs = NIL;
+	*lexprs = NIL;
+
+	jpres.expr = path;
+	jpres.lax = true;
+
+	foreach(lc, *indirection)
+	{
+		Node	   *accessor = lfirst(lc);
+		JsonPathParseItem *jpi;
+
+		if (IsA(accessor, String))
+		{
+			char	   *field = strVal(accessor);
+
+			jpi = make_jsonpath_item(jpiKey);
+			jpi->value.string.val = field;
+			jpi->value.string.len = strlen(field);
+
+			*uexprs = lappend(*uexprs, accessor);
+		}
+		else if (IsA(accessor, A_Star))
+		{
+			jpi = make_jsonpath_item(jpiAnyKey);
+
+			*uexprs = lappend(*uexprs, NULL);
+		}
+		else if (IsA(accessor, A_Indices))
+		{
+			A_Indices  *ai = castNode(A_Indices, accessor);
+
+			jpi = make_jsonpath_item(jpiIndexArray);
+			jpi->value.array.nelems = 1;
+			jpi->value.array.elems = palloc(sizeof(jpi->value.array.elems[0]));
+
+			if (ai->is_slice)
+			{
+				while (list_length(*lexprs) < list_length(*uexprs))
+					*lexprs = lappend(*lexprs, NULL);
+
+				if (ai->lidx)
+					jpi->value.array.elems[0].from = make_jsonpath_item_expr(pstate, ai->lidx, lexprs);
+				else
+					jpi->value.array.elems[0].from = make_jsonpath_item_int(0, lexprs);
+
+				if (ai->uidx)
+					jpi->value.array.elems[0].to = make_jsonpath_item_expr(pstate, ai->uidx, uexprs);
+				else
+				{
+					jpi->value.array.elems[0].to = make_jsonpath_item(jpiLast);
+					*uexprs = lappend(*uexprs, NULL);
+				}
+			}
+			else
+			{
+				Assert(ai->uidx && !ai->lidx);
+				jpi->value.array.elems[0].from = make_jsonpath_item_expr(pstate, ai->uidx, uexprs);
+				jpi->value.array.elems[0].to = NULL;
+			}
+		}
+		else
+			break;
+
+		/* append path item */
+		path->next = jpi;
+		path = jpi;
+		pathlen++;
+	}
+
+	if (*lexprs)
+	{
+		while (list_length(*lexprs) < list_length(*uexprs))
+			*lexprs = lappend(*lexprs, NULL);
+	}
+
+	*indirection = list_delete_first_n(*indirection, pathlen);
+
+	jsp = jsonPathFromParseResult(&jpres, 0, NULL);
+
+	return (Node *) makeConst(JSONPATHOID, -1, InvalidOid, -1, jsp, false, false);
+}
+
 /*
  * Finish parse analysis of a SubscriptingRef expression for a jsonb.
  *
@@ -126,19 +352,32 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	List	   *upperIndexpr = NIL;
 	ListCell   *idx;
 
+	/* Determine the result type of the subscripting operation; always jsonb */
+	sbsref->refrestype = JSONBOID;
+	sbsref->reftypmod = -1;
+
+	if (jsonb_check_jsonpath_needed(*indirection))
+	{
+		sbsref->refjsonbpath =
+			jsonb_subscript_make_jsonpath(pstate, indirection,
+										  &sbsref->refupperindexpr,
+										  &sbsref->reflowerindexpr);
+		return;
+	}
+
 	/*
 	 * Transform and convert the subscript expressions. Jsonb subscripting
 	 * does not support slices, look only and the upper index.
 	 */
 	foreach(idx, *indirection)
 	{
+		Node	   *i = lfirst(idx);
 		A_Indices  *ai;
 		Node	   *subExpr;
 
-		if (!IsA(lfirst(idx), A_Indices))
-			break;
+		Assert(IsA(i, A_Indices));
 
-		ai = lfirst_node(A_Indices, idx);
+		ai = castNode(A_Indices, i);
 
 		if (isSlice)
 		{
@@ -175,10 +414,6 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->refupperindexpr = upperIndexpr;
 	sbsref->reflowerindexpr = NIL;
 
-	/* Determine the result type of the subscripting operation; always jsonb */
-	sbsref->refrestype = JSONBOID;
-	sbsref->reftypmod = -1;
-
 	/* Remove processed elements */
 	if (upperIndexpr)
 		*indirection = list_delete_first_n(*indirection, list_length(upperIndexpr));
@@ -233,7 +468,7 @@ jsonb_subscript_check_subscripts(ExprState *state,
 			 * For jsonb fetch and assign functions we need to provide path in
 			 * text format. Convert if it's not already text.
 			 */
-			if (workspace->indexOid[i] == INT4OID)
+			if (!workspace->jsonpath && workspace->indexOid[i] == INT4OID)
 			{
 				Datum		datum = sbsrefstate->upperindex[i];
 				char	   *cs = DatumGetCString(DirectFunctionCall1(int4out, datum));
@@ -261,17 +496,32 @@ jsonb_subscript_fetch(ExprState *state,
 {
 	SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
 	JsonbSubWorkspace *workspace = (JsonbSubWorkspace *) sbsrefstate->workspace;
-	Jsonb	   *jsonbSource;
 
 	/* Should not get here if source jsonb (or any subscript) is null */
 	Assert(!(*op->resnull));
 
-	jsonbSource = DatumGetJsonbP(*op->resvalue);
-	*op->resvalue = jsonb_get_element(jsonbSource,
-									  workspace->index,
-									  sbsrefstate->numupper,
-									  op->resnull,
-									  false);
+	if (workspace->jsonpath)
+	{
+		bool		empty = false;
+		bool		error = false;
+
+		*op->resvalue = JsonPathQuery(*op->resvalue, workspace->jsonpath,
+									  JSW_CONDITIONAL,
+									  &empty, &error, NULL,
+									  NULL);
+
+		*op->resnull = empty || error;
+	}
+	else
+	{
+		Jsonb	   *jsonbSource = DatumGetJsonbP(*op->resvalue);
+
+		*op->resvalue = jsonb_get_element(jsonbSource,
+										  workspace->index,
+										  sbsrefstate->numupper,
+										  op->resnull,
+										  false);
+	}
 }
 
 /*
@@ -381,6 +631,7 @@ jsonb_exec_setup(const SubscriptingRef *sbsref,
 	ListCell   *lc;
 	int			nupper = sbsref->refupperindexpr->length;
 	char	   *ptr;
+	bool		useJsonpath = sbsref->refjsonbpath != NULL;
 
 	/* Allocate type-specific workspace with space for per-subscript data */
 	workspace = palloc0(MAXALIGN(sizeof(JsonbSubWorkspace)) +
@@ -388,6 +639,9 @@ jsonb_exec_setup(const SubscriptingRef *sbsref,
 	workspace->expectArray = false;
 	ptr = ((char *) workspace) + MAXALIGN(sizeof(JsonbSubWorkspace));
 
+	if (useJsonpath)
+		workspace->jsonpath = DatumGetJsonPathP(castNode(Const, sbsref->refjsonbpath)->constvalue);
+
 	/*
 	 * This coding assumes sizeof(Datum) >= sizeof(Oid), else we might
 	 * misalign the indexOid pointer
@@ -404,7 +658,7 @@ jsonb_exec_setup(const SubscriptingRef *sbsref,
 		Node	   *expr = lfirst(lc);
 		int			i = foreach_current_index(lc);
 
-		workspace->indexOid[i] = exprType(expr);
+		workspace->indexOid[i] = jsonb_subscript_type(expr);
 	}
 
 	/*
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index d0576da3e25..9d380ed60d6 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -718,6 +718,13 @@ typedef struct SubscriptingRef
 	Expr	   *refexpr;
 	/* expression for the source value, or NULL if fetch */
 	Expr	   *refassgnexpr;
+
+	/*
+	 * container-specific extra information, currently used only by jsonb.
+	 * stores a JsonPath expression when jsonb dot notation is used. NULL for
+	 * simple subscripting.
+	 */
+	Node	   *refjsonbpath;
 } SubscriptingRef;
 
 /*
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.c b/src/interfaces/ecpg/test/expected/sql-sqljson.c
index 39221f9ea5d..e1e2b1e03f0 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.c
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.c
@@ -417,12 +417,112 @@ if (sqlca.sqlcode < 0) sqlprint();}
 	  for (int i = 0; i < sizeof(is_json); i++)
 		  printf("Found is_json[%d]: %s\n", i, is_json[i] ? "true" : "false");
 
-  { ECPGdisconnect(__LINE__, "CURRENT");
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . \"a\" )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 118 "sqljson.pgc"
 
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 118 "sqljson.pgc"
 
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb . \"a\" )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 121 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 121 "sqljson.pgc"
+
+	// error
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . a )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 124 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 124 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . a . b )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 127 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 127 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( coalesce ( json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . c ) , 'null' ) )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 130 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 130 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ 0 ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 133 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 133 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 136 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 136 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ : ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 139 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 139 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b . x )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 142 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 142 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . * )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 145 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 145 "sqljson.pgc"
+
+	// error
+
+  { ECPGdisconnect(__LINE__, "CURRENT");
+#line 148 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 148 "sqljson.pgc"
+
 
   return 0;
 }
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
index e55a95dd711..e532a8f44fa 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
@@ -268,5 +268,91 @@ SQL error: cannot use type jsonb in RETURNING clause of JSON_SERIALIZE() on line
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_get_data on line 102: RESULT: f offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 118: query: select json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . "a" ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 118: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 118: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 118: RESULT: {"b": 1, "c": 2} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 121: query: select json ( '{"a": {"b": 1, "c": 2}}' :: jsonb . "a" ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 121: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 121: bad response - ERROR:  schema "jsonb" does not exist
+LINE 1: select json ( '{"a": {"b": 1, "c": 2}}' :: jsonb . "a" )
+                                                   ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 3F000 (sqlcode -400): schema "jsonb" does not exist on line 121
+[NO_PID]: sqlca: code: -400, state: 3F000
+SQL error: schema "jsonb" does not exist on line 121
+[NO_PID]: ecpg_execute on line 124: query: select json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . a ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 124: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 124: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 124: RESULT: {"b": 1, "c": 2} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 127: query: select json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . a . b ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 127: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 127: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 127: RESULT: 1 offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 130: query: select json ( coalesce ( json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . c ) , 'null' ) ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 130: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 130: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 130: RESULT: null offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 133: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 0 ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 133: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 133: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 133: RESULT: {"x": 1} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 136: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 136: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 136: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 136: RESULT: [12, {"y": 1}] offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 139: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 139: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 139: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 139: RESULT: [{"x": 1}, {"x": [12, {"y": 1}]}] offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 142: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b . x ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 142: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 142: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 142: RESULT: [1, [12, {"y": 1}]] offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 145: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . * ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 145: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 145: bad response - ERROR:  row expansion via "*" is not supported here
+LINE 1: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x...
+                        ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 0A000 (sqlcode -400): row expansion via "*" is not supported here on line 145
+[NO_PID]: sqlca: code: -400, state: 0A000
+SQL error: row expansion via "*" is not supported here on line 145
 [NO_PID]: ecpg_finish: connection ecpg1_regression closed
 [NO_PID]: sqlca: code: 0, state: 00000
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
index 83f8df13e5a..bfa93e86d00 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
@@ -28,3 +28,11 @@ Found is_json[4]: false
 Found is_json[5]: false
 Found is_json[6]: true
 Found is_json[7]: false
+Found json={"b": 1, "c": 2}
+Found json={"b": 1, "c": 2}
+Found json=1
+Found json=null
+Found json={"x": 1}
+Found json=[12, {"y": 1}]
+Found json=[{"x": 1}, {"x": [12, {"y": 1}]}]
+Found json=[1, [12, {"y": 1}]]
diff --git a/src/interfaces/ecpg/test/sql/sqljson.pgc b/src/interfaces/ecpg/test/sql/sqljson.pgc
index ddcbcc3b3cb..96be3919928 100644
--- a/src/interfaces/ecpg/test/sql/sqljson.pgc
+++ b/src/interfaces/ecpg/test/sql/sqljson.pgc
@@ -115,6 +115,36 @@ EXEC SQL END DECLARE SECTION;
 	  for (int i = 0; i < sizeof(is_json); i++)
 		  printf("Found is_json[%d]: %s\n", i, is_json[i] ? "true" : "false");
 
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}}'::jsonb)."a") INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON('{"a": {"b": 1, "c": 2}}'::jsonb."a") INTO :json;
+	// error
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}}'::jsonb).a) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}}'::jsonb).a.b) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(COALESCE(JSON(('{"a": {"b": 1, "c": 2}}'::jsonb).c), 'null')) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b[0]) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b[1].x[0:]) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b[:]) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b.x) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).*) INTO :json;
+	// error
+
   EXEC SQL DISCONNECT;
 
   return 0;
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5a1eb18aba2..91a7b825764 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -4989,6 +4989,12 @@ select ('123'::jsonb)['a'];
  
 (1 row)
 
+select ('123'::jsonb).a;
+ a 
+---
+ 
+(1 row)
+
 select ('123'::jsonb)[0];
  jsonb 
 -------
@@ -5001,12 +5007,24 @@ select ('123'::jsonb)[NULL];
  
 (1 row)
 
+select ('123'::jsonb).NULL;
+ null 
+------
+ 
+(1 row)
+
 select ('{"a": 1}'::jsonb)['a'];
  jsonb 
 -------
  1
 (1 row)
 
+select ('{"a": 1}'::jsonb).a;
+ a 
+---
+ 1
+(1 row)
+
 select ('{"a": 1}'::jsonb)[0];
  jsonb 
 -------
@@ -5019,6 +5037,12 @@ select ('{"a": 1}'::jsonb)['not_exist'];
  
 (1 row)
 
+select ('{"a": 1}'::jsonb)."not_exist";
+ not_exist 
+-----------
+ 
+(1 row)
+
 select ('{"a": 1}'::jsonb)[NULL];
  jsonb 
 -------
@@ -5031,6 +5055,12 @@ select ('[1, "2", null]'::jsonb)['a'];
  
 (1 row)
 
+select ('[1, "2", null]'::jsonb).a;
+ a 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[0];
  jsonb 
 -------
@@ -5043,6 +5073,12 @@ select ('[1, "2", null]'::jsonb)['1'];
  "2"
 (1 row)
 
+select ('[1, "2", null]'::jsonb)."1";
+ 1 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[1.0];
 ERROR:  subscript type numeric is not supported
 LINE 1: select ('[1, "2", null]'::jsonb)[1.0];
@@ -5072,6 +5108,12 @@ select ('[1, "2", null]'::jsonb)[1]['a'];
  
 (1 row)
 
+select ('[1, "2", null]'::jsonb)[1].a;
+ a 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[1][0];
  jsonb 
 -------
@@ -5084,73 +5126,140 @@ select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['b'];
  "c"
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).b;
+  b  
+-----
+ "c"
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'];
    jsonb   
 -----------
  [1, 2, 3]
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d;
+     d     
+-----------
+ [1, 2, 3]
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'][1];
  jsonb 
 -------
  2
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d[1];
+ d 
+---
+ 2
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d']['a'];
  jsonb 
 -------
  
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['a'];
+ERROR:  jsonb simplified accessor supports subscripting in type: INT4, got type: unknown
+LINE 1: select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['a'];
+                                                               ^
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d.a;
+ a 
+---
+ 
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1'];
      jsonb     
 ---------------
  {"a2": "aaa"}
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1;
+      a1       
+---------------
+ {"a2": "aaa"}
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2'];
  jsonb 
 -------
  "aaa"
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2;
+  a2   
+-------
+ "aaa"
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']['a3'];
  jsonb 
 -------
  
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2.a3;
+ a3 
+----
+ 
+(1 row)
+
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'];
          jsonb         
 -----------------------
  ["aaa", "bbb", "ccc"]
 (1 row)
 
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1;
+          b1           
+-----------------------
+ ["aaa", "bbb", "ccc"]
+(1 row)
+
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'][2];
  jsonb 
 -------
  "ccc"
 (1 row)
 
--- slices are not supported
-select ('{"a": 1}'::jsonb)['a':'b'];
-ERROR:  jsonb subscript does not support slices
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1[2];
+  b1   
+-------
+ "ccc"
+(1 row)
+
+select ('{"a": 1}'::jsonb)['a':'b']; -- fails
+ERROR:  jsonb simplified accessor supports subscripting in type: INT4, got type: unknown
 LINE 1: select ('{"a": 1}'::jsonb)['a':'b'];
-                                       ^
+                                   ^
 select ('[1, "2", null]'::jsonb)[1:2];
-ERROR:  jsonb subscript does not support slices
-LINE 1: select ('[1, "2", null]'::jsonb)[1:2];
-                                           ^
+    jsonb    
+-------------
+ ["2", null]
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[:2];
-ERROR:  jsonb subscript does not support slices
-LINE 1: select ('[1, "2", null]'::jsonb)[:2];
-                                          ^
+     jsonb      
+----------------
+ [1, "2", null]
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[1:];
-ERROR:  jsonb subscript does not support slices
-LINE 1: select ('[1, "2", null]'::jsonb)[1:];
-                                         ^
+    jsonb    
+-------------
+ ["2", null]
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[:];
-ERROR:  jsonb subscript does not support slices
+     jsonb      
+----------------
+ [1, "2", null]
+(1 row)
+
 create TEMP TABLE test_jsonb_subscript (
        id int,
        test_json jsonb
@@ -5831,3 +5940,171 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
  12345
 (1 row)
 
+-- dot notation
+CREATE TABLE test_jsonb_dot_notation AS
+SELECT '{"a": [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], "b": [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]}'::jsonb jb;
+SELECT (jb).a FROM test_jsonb_dot_notation;
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+SELECT (jb)."a" FROM test_jsonb_dot_notation; -- double quote should work
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+SELECT (jb).'a' FROM test_jsonb_dot_notation; -- single quote should not work
+ERROR:  syntax error at or near "'a'"
+LINE 1: SELECT (jb).'a' FROM test_jsonb_dot_notation;
+                    ^
+SELECT (jb).b FROM test_jsonb_dot_notation;
+                         b                         
+---------------------------------------------------
+ [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]
+(1 row)
+
+SELECT (jb).c FROM test_jsonb_dot_notation;
+ c 
+---
+ 
+(1 row)
+
+SELECT (jb).a.b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT (jb).a[2].b FROM test_jsonb_dot_notation;
+  b  
+-----
+ "c"
+(1 row)
+
+SELECT (jb).a[2:3].b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT (jb).a[2:].b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT (jb).a[:2].b FROM test_jsonb_dot_notation;
+  b  
+-----
+ "c"
+(1 row)
+
+SELECT (jb).a[:].b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT (jb).a.x.y FROM test_jsonb_dot_notation;
+   y   
+-------
+ "yyy"
+(1 row)
+
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
+            x             
+--------------------------
+ {"y": "YYY", "z": "ZZZ"}
+(1 row)
+
+SELECT (jb).b.x.z FROM test_jsonb_dot_notation;
+   z   
+-------
+ "ZZZ"
+(1 row)
+
+SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
+ c 
+---
+ 
+(1 row)
+
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+ERROR:  type jsonb is not composite
+-- explains should work
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+                  QUERY PLAN                  
+----------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation t
+   Output: jb.a
+(2 rows)
+
+SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: jb.a[1]
+(2 rows)
+
+SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+ a 
+---
+ 2
+(1 row)
+
+-- jsonb array access in plpgsql
+DO $$
+DECLARE
+  a jsonb := '[1,2,3,4,5,6,7]'::jsonb;
+BEGIN
+  WHILE a IS NOT NULL
+  LOOP
+    RAISE NOTICE '%', a;
+    a := a[2:];
+  END LOOP;
+END
+$$ LANGUAGE plpgsql;
+NOTICE:  [1, 2, 3, 4, 5, 6, 7]
+NOTICE:  [3, 4, 5, 6, 7]
+NOTICE:  [5, 6, 7]
+NOTICE:  7
+-- jsonb dot access in plpgsql
+DO $$
+DECLARE
+  a jsonb := '{"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}'::jsonb;
+BEGIN
+  WHILE a IS NOT NULL
+  LOOP
+    RAISE NOTICE '%', a;
+    a := COALESCE(a."NU", a[2]); -- fails
+  END LOOP;
+END
+$$ LANGUAGE plpgsql;
+NOTICE:  {"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}
+ERROR:  missing FROM-clause entry for table "a"
+LINE 1: a := COALESCE(a."NU", a[2])
+                      ^
+QUERY:  a := COALESCE(a."NU", a[2])
+CONTEXT:  PL/pgSQL function inline_code_block line 8 at assignment
+DO $$
+    DECLARE
+        a jsonb := '{"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := COALESCE((a)."NU", a[2]); -- succeeds
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+NOTICE:  {"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}
+NOTICE:  [{"": [[3]]}, [6], [2], "bCi"]
+NOTICE:  [2]
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 57c11acddfe..4bd3990fb55 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1304,33 +1304,49 @@ select jsonb_insert('{"a": {"b": "value"}}', '{a, b}', '"new_value"', true);
 
 -- jsonb subscript
 select ('123'::jsonb)['a'];
+select ('123'::jsonb).a;
 select ('123'::jsonb)[0];
 select ('123'::jsonb)[NULL];
+select ('123'::jsonb).NULL;
 select ('{"a": 1}'::jsonb)['a'];
+select ('{"a": 1}'::jsonb).a;
 select ('{"a": 1}'::jsonb)[0];
 select ('{"a": 1}'::jsonb)['not_exist'];
+select ('{"a": 1}'::jsonb)."not_exist";
 select ('{"a": 1}'::jsonb)[NULL];
 select ('[1, "2", null]'::jsonb)['a'];
+select ('[1, "2", null]'::jsonb).a;
 select ('[1, "2", null]'::jsonb)[0];
 select ('[1, "2", null]'::jsonb)['1'];
+select ('[1, "2", null]'::jsonb)."1";
 select ('[1, "2", null]'::jsonb)[1.0];
 select ('[1, "2", null]'::jsonb)[2];
 select ('[1, "2", null]'::jsonb)[3];
 select ('[1, "2", null]'::jsonb)[-2];
 select ('[1, "2", null]'::jsonb)[1]['a'];
+select ('[1, "2", null]'::jsonb)[1].a;
 select ('[1, "2", null]'::jsonb)[1][0];
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['b'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).b;
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d;
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'][1];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d[1];
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d']['a'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['a'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d.a;
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1;
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2;
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']['a3'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2.a3;
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'];
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1;
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'][2];
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1[2];
 
--- slices are not supported
-select ('{"a": 1}'::jsonb)['a':'b'];
+select ('{"a": 1}'::jsonb)['a':'b']; -- fails
 select ('[1, "2", null]'::jsonb)[1:2];
 select ('[1, "2", null]'::jsonb)[:2];
 select ('[1, "2", null]'::jsonb)[1:];
@@ -1590,3 +1606,68 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::float8;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int2;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int4;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
+
+-- dot notation
+CREATE TABLE test_jsonb_dot_notation AS
+SELECT '{"a": [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], "b": [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]}'::jsonb jb;
+
+SELECT (jb).a FROM test_jsonb_dot_notation;
+SELECT (jb)."a" FROM test_jsonb_dot_notation; -- double quote should work
+SELECT (jb).'a' FROM test_jsonb_dot_notation; -- single quote should not work
+SELECT (jb).b FROM test_jsonb_dot_notation;
+SELECT (jb).c FROM test_jsonb_dot_notation;
+SELECT (jb).a.b FROM test_jsonb_dot_notation;
+SELECT (jb).a[2].b FROM test_jsonb_dot_notation;
+SELECT (jb).a[2:3].b FROM test_jsonb_dot_notation;
+SELECT (jb).a[2:].b FROM test_jsonb_dot_notation;
+SELECT (jb).a[:2].b FROM test_jsonb_dot_notation;
+SELECT (jb).a[:].b FROM test_jsonb_dot_notation;
+SELECT (jb).a.x.y FROM test_jsonb_dot_notation;
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
+SELECT (jb).b.x.z FROM test_jsonb_dot_notation;
+SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+
+-- explains should work
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+
+-- jsonb array access in plpgsql
+DO $$
+DECLARE
+  a jsonb := '[1,2,3,4,5,6,7]'::jsonb;
+BEGIN
+  WHILE a IS NOT NULL
+  LOOP
+    RAISE NOTICE '%', a;
+    a := a[2:];
+  END LOOP;
+END
+$$ LANGUAGE plpgsql;
+
+-- jsonb dot access in plpgsql
+DO $$
+DECLARE
+  a jsonb := '{"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}'::jsonb;
+BEGIN
+  WHILE a IS NOT NULL
+  LOOP
+    RAISE NOTICE '%', a;
+    a := COALESCE(a."NU", a[2]); -- fails
+  END LOOP;
+END
+$$ LANGUAGE plpgsql;
+
+DO $$
+    DECLARE
+        a jsonb := '{"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := COALESCE((a)."NU", a[2]); -- succeeds
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
-- 
2.39.5 (Apple Git-154)



  [application/x-patch] v11-0008-Add-as-a-new-token-in-scanners.patch (8.1K, ../../CAK98qZ1nz6ZZhQqTOCNwRguZE5GsBLW5BQT_k=s7AA6gc2CN_g@mail.gmail.com/10-v11-0008-Add-as-a-new-token-in-scanners.patch)
  download | inline diff:
From c88047f7fb45363e836aef0eb2422099106e544b Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Mon, 10 Mar 2025 07:00:03 -0500
Subject: [PATCH v11 8/8] Add "**" as a new token in scanners

DOUBLE_ASTERISK is now recognized but not yet implemented.

This change improves error message readability, as "**" is supported
in jsonpath_scan.l as ANY_P.  However, the SQL standard for simplified
JSON accessors does not specify "**", so it is not supported in that
context.
---
 src/backend/parser/gram.y                            |  9 ++++++++-
 src/backend/parser/scan.l                            |  6 ++++++
 src/include/parser/scanner.h                         |  2 +-
 src/interfaces/ecpg/preproc/pgc.l                    |  5 +++++
 src/interfaces/ecpg/test/expected/sql-sqljson.c      | 12 +++++++++++-
 src/interfaces/ecpg/test/expected/sql-sqljson.stderr | 11 +++++++++++
 src/interfaces/ecpg/test/sql/sqljson.pgc             |  3 +++
 src/pl/plpgsql/src/pl_gram.y                         |  2 +-
 src/test/regress/expected/jsonb.out                  |  2 +-
 9 files changed, 47 insertions(+), 5 deletions(-)

diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 44840bc1b25..4635616d93a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -687,7 +687,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
  */
 %token <str>	IDENT UIDENT FCONST SCONST USCONST BCONST XCONST Op
 %token <ival>	ICONST PARAM
-%token			TYPECAST DOT_DOT COLON_EQUALS EQUALS_GREATER
+%token			TYPECAST DOT_DOT COLON_EQUALS EQUALS_GREATER DOUBLE_ASTERISK
 %token			LESS_EQUALS GREATER_EQUALS NOT_EQUALS
 
 /*
@@ -16969,6 +16969,13 @@ indirection_el:
 				{
 					$$ = (Node *) makeNode(A_Star);
 				}
+			| '.' DOUBLE_ASTERISK
+				{
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					errmsg("arbitrary depth wild card in simple json accessor not supported"),
+					parser_errposition(@2)));
+				}
 			| '[' a_expr ']'
 				{
 					A_Indices *ai = makeNode(A_Indices);
diff --git a/src/backend/parser/scan.l b/src/backend/parser/scan.l
index 08990831fe8..c58ba233153 100644
--- a/src/backend/parser/scan.l
+++ b/src/backend/parser/scan.l
@@ -338,6 +338,7 @@ identifier		{ident_start}{ident_cont}*
 typecast		"::"
 dot_dot			\.\.
 colon_equals	":="
+double_asterisk	"**"
 
 /*
  * These operator-like tokens (unlike the above ones) also match the {operator}
@@ -851,6 +852,11 @@ other			.
 					return COLON_EQUALS;
 				}
 
+{double_asterisk}	{
+						SET_YYLLOC();
+						return DOUBLE_ASTERISK;
+					}
+
 {equals_greater} {
 					SET_YYLLOC();
 					return EQUALS_GREATER;
diff --git a/src/include/parser/scanner.h b/src/include/parser/scanner.h
index 74ad86698ac..2f9b7baa1a9 100644
--- a/src/include/parser/scanner.h
+++ b/src/include/parser/scanner.h
@@ -50,7 +50,7 @@ typedef union core_YYSTYPE
  * the ASCII characters plus these:
  *	%token <str>	IDENT UIDENT FCONST SCONST USCONST BCONST XCONST Op
  *	%token <ival>	ICONST PARAM
- *	%token			TYPECAST DOT_DOT COLON_EQUALS EQUALS_GREATER
+ *	%token			TYPECAST DOT_DOT COLON_EQUALS EQUALS_GREATER DOUBLE_ASTERISK
  *	%token			LESS_EQUALS GREATER_EQUALS NOT_EQUALS
  * The above token definitions *must* be the first ones declared in any
  * bison parser built atop this scanner, so that they will have consistent
diff --git a/src/interfaces/ecpg/preproc/pgc.l b/src/interfaces/ecpg/preproc/pgc.l
index 63283a4a1e5..1415cbe2808 100644
--- a/src/interfaces/ecpg/preproc/pgc.l
+++ b/src/interfaces/ecpg/preproc/pgc.l
@@ -321,6 +321,7 @@ array			({ident_cont}|{whitespace}|[\[\]\+\-\*\%\/\(\)\>\.])*
 typecast		"::"
 dot_dot			\.\.
 colon_equals	":="
+double_asterisk	"**"
 
 /*
  * These operator-like tokens (unlike the above ones) also match the {operator}
@@ -832,6 +833,10 @@ cppline			{space}*#([^i][A-Za-z]*|{if}|{ifdef}|{ifndef}|{import})((\/\*[^*/]*\*+
 					return COLON_EQUALS;
 				}
 
+{double_asterisk}	{
+						return DOUBLE_ASTERISK;
+					}
+
 {equals_greater} {
 					return EQUALS_GREATER;
 				}
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.c b/src/interfaces/ecpg/test/expected/sql-sqljson.c
index 748b2e2bee6..f772305c209 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.c
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.c
@@ -527,12 +527,22 @@ if (sqlca.sqlcode < 0) sqlprint();}
 
 	printf("Found json=%s\n", json);
 
-  { ECPGdisconnect(__LINE__, "CURRENT");
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . ** . b )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 151 "sqljson.pgc"
 
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 151 "sqljson.pgc"
 
+	// error
+
+  { ECPGdisconnect(__LINE__, "CURRENT");
+#line 154 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 154 "sqljson.pgc"
+
 
   return 0;
 }
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
index 92c5d1520c4..d9a2fe21915 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
@@ -359,5 +359,16 @@ SQL error: schema "jsonb" does not exist on line 121
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_get_data on line 148: RESULT: [1, [12, {"y": 1}]] offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 151: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . ** . b ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 151: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 151: bad response - ERROR:  arbitrary depth wild card in simple json accessor not supported
+LINE 1: ...b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . ** . b )
+                                                               ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 0A000 (sqlcode -400): arbitrary depth wild card in simple json accessor not supported on line 151
+[NO_PID]: sqlca: code: -400, state: 0A000
+SQL error: arbitrary depth wild card in simple json accessor not supported on line 151
 [NO_PID]: ecpg_finish: connection ecpg1_regression closed
 [NO_PID]: sqlca: code: 0, state: 00000
diff --git a/src/interfaces/ecpg/test/sql/sqljson.pgc b/src/interfaces/ecpg/test/sql/sqljson.pgc
index 4e7427d237d..65443d30055 100644
--- a/src/interfaces/ecpg/test/sql/sqljson.pgc
+++ b/src/interfaces/ecpg/test/sql/sqljson.pgc
@@ -148,6 +148,9 @@ EXEC SQL END DECLARE SECTION;
 	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).*.x) INTO :json;
 	printf("Found json=%s\n", json);
 
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).**.b) INTO :json;
+	// error
+
   EXEC SQL DISCONNECT;
 
   return 0;
diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index 5612e66d023..13e06ad5b0b 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -245,7 +245,7 @@ static	void			check_raise_parameters(PLpgSQL_stmt_raise *stmt);
  */
 %token <str>	IDENT UIDENT FCONST SCONST USCONST BCONST XCONST Op
 %token <ival>	ICONST PARAM
-%token			TYPECAST DOT_DOT COLON_EQUALS EQUALS_GREATER
+%token			TYPECAST DOT_DOT COLON_EQUALS EQUALS_GREATER DOUBLE_ASTERISK
 %token			LESS_EQUALS GREATER_EQUALS NOT_EQUALS
 
 /*
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 1a9452937d5..0729d7251c6 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -6181,7 +6181,7 @@ SELECT (jb).a.b.c.* FROM test_jsonb_dot_notation;
 (1 row)
 
 SELECT (jb).a.**.x FROM test_jsonb_dot_notation; -- not supported
-ERROR:  syntax error at or near "**"
+ERROR:  arbitrary depth wild card in simple json accessor not supported
 LINE 1: SELECT (jb).a.**.x FROM test_jsonb_dot_notation;
                       ^
 -- explains should work
-- 
2.39.5 (Apple Git-154)



^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
@ 2025-03-20 14:18                       ` Peter Eisentraut <[email protected]>
  2 siblings, 0 replies; 67+ messages in thread

From: Peter Eisentraut @ 2025-03-20 14:18 UTC (permalink / raw)
  To: Alexandra Wang <[email protected]>; Mark Dilger <[email protected]>; +Cc: Matheus Alcantara <[email protected]>; Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>; jian he <[email protected]>

This patch set has expanded significantly in scope recently, which is 
probably the right thing, but that means there won't be enough time to 
review and finish it for PG18.  So I'm moving this to the next 
commitfest now.

On 13.03.25 15:02, Alexandra Wang wrote:
> Hi Mark,
> 
> Thank you so much for reviewing! I have attached the new patches.
> 
> On Tue, Mar 4, 2025 at 8:05 AM Mark Dilger <[email protected] 
> <mailto:[email protected]>> wrote:
> 
> 
>     On Mon, Mar 3, 2025 at 12:23 PM Alexandra Wang
>     <[email protected] <mailto:[email protected]>>
>     wrote:
> 
>           I've attached v10, which addresses your feedback.
> 
> 
>     Hi Alex!  Thanks for the patches.
> 
>     In src/test/regress/sql/jsonb.sql, the section marked with "--
>     slices are not supported" should be relabeled.  That comment
>     predates these patches, and is now misleading.
> 
>     A bit further down in expected/jsonb.out, there is an expected
>     failure, but no SQL comment to indicate that it is expected:
> 
>     +SELECT (t.jb).* FROM test_jsonb_dot_notation;
>     +ERROR:  missing FROM-clause entry for table "t"
>     +LINE 1: SELECT (t.jb).* FROM test_jsonb_dot_notation;
> 
>     Perhaps a "-- fails" comment would clarify?  Then, further down,
> 
> 
> Fixed.
> 
>     +SELECT (jb).a.**.x FROM test_jsonb_dot_notation; -- not supported
>     +ERROR:  syntax error at or near "**"
>     +LINE 1: SELECT (jb).a.**.x FROM test_jsonb_dot_notation;
> 
>     I wonder if it would be better to have the parser handle this case
>     and raise a ERRCODE_FEATURE_NOT_SUPPORTED instead?
> 
> 
> In 0008 I added a new token named "DOUBLE_ASTERISK" to the lexers to
> represent "**". Hope this helps!
> 
>     I got curious about the support for this new dot notation in the
>     plpgsql parser and tried:
> 
>     +DO $$
>     +DECLARE
>     +  a jsonb := '[1,2,3,4,5,6,7]'::jsonb;
>     +BEGIN
>     +  WHILE a IS NOT NULL
>     +  LOOP
>     +    RAISE NOTICE '%', a;
>     +    a := a[2:];
>     +  END LOOP;
>     +END
>     +$$ LANGUAGE plpgsql;
>     +NOTICE:  [1, 2, 3, 4, 5, 6, 7]
>     +NOTICE:  [3, 4, 5, 6, 7]
>     +NOTICE:  [5, 6, 7]
>     +NOTICE:  7
> 
>     which looks good!  But then I tried:
> 
>     +DO $$
>     +DECLARE
>     +  a jsonb := '{"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf":
>     [-6, -8]}'::jsonb;
>     +BEGIN
>     +  WHILE a IS NOT NULL
>     +  LOOP
>     +    RAISE NOTICE '%', a;
>     +    a := COALESCE(a."NU", a[2]);
>     +  END LOOP;
>     +END
>     +$$ LANGUAGE plpgsql;
>     +NOTICE:  {"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}
>     +ERROR:  missing FROM-clause entry for table "a"
>     +LINE 1: a := COALESCE(a."NU", a[2])
>     +                      ^
>     +QUERY:  a := COALESCE(a."NU", a[2])
>     +CONTEXT:  PL/pgSQL function inline_code_block line 8 at assignment
> 
>     which suggests the plpgsql parser does not recognize a."NU" as we'd
>     expect.  Any thoughts on this?
> 
> 
> Thanks for the tests! I added them to the "jsonb" regress test.
> 
>     I notice there are no changes in src/interfaces/ecpg/test, which
>     concerns me.  The sqljson.pgc and sqljson_jsontable.pgc files are
>     already testing json handling in ecpg; perhaps just extend those a bit?
> 
> Thanks for bringing this up! I have added new tests in src/interfaces/ 
> ecpg/test/sql/sqljson.pgc.
> 
> 
> Best,
> Alex






^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
@ 2025-03-27 14:27                       ` Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:22                         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2 siblings, 2 replies; 67+ messages in thread

From: Vik Fearing @ 2025-03-27 14:27 UTC (permalink / raw)
  To: Alexandra Wang <[email protected]>; Mark Dilger <[email protected]>; +Cc: Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>; jian he <[email protected]>


On 13/03/2025 15:02, Alexandra Wang wrote:
> Hi Mark,
>
> Thank you so much for reviewing! I have attached the new patches.
>

Hi Alex,


I am reviewing this from a feature perspective and not from a code 
perspective.  On the whole, this looks good to me from a standards point 
of view.


There are two things that stand out to me about this feature:


1) I am not seeing the ** syntax in the standard, so does it need to be 
signaled as not supported?  Perhaps I am just overlooking something.


2) There is no support for <JSON item method>. Since this appears to be 
constructing a jsonpath query, could that not be added to the syntax and 
allow jsonpath to throw the error if the function doesn't exist?


-- 

Vik Fearing






^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
@ 2025-04-23 16:54                         ` Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  1 sibling, 1 reply; 67+ messages in thread

From: Nikita Malakhov @ 2025-04-23 16:54 UTC (permalink / raw)
  To: Vik Fearing <[email protected]>; +Cc: Alexandra Wang <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>; jian he <[email protected]>

Hi Alex!

Glad you made so much effort to develop this patch set!
I think this is an important part of Json functionality.

I've looked into you patch and noticed change in behavior
in new test results:

postgres@postgres=# create table t(x int, y jsonb);
insert into t select 1, '{"a": 1, "b": 42}'::jsonb;
insert into t select 1, '{"a": 2, "b": {"c": 42}}'::jsonb;
insert into t select 1, '{"a": 3, "b": {"c": "42"}, "d":[11, 12]}'::jsonb;
CREATE TABLE
Time: 6.373 ms
INSERT 0 1
Time: 3.299 ms
INSERT 0 1
Time: 2.532 ms
INSERT 0 1
Time: 2.453 ms

Original master:
postgres@postgres=# select (t.y).b.c.d.e from t;
ERROR:  column notation .b applied to type jsonb, which is not a composite
type
LINE 1: select (t.y).b.c.d.e from t;
                ^
Time: 0.553 ms

Patched (with v11):
postgres@postgres=# select (t.y).b.c.d.e from t;
 e
---



(3 rows)

Is this correct?

--
Regards,
Nikita Malakhov
Postgres Professional
The Russian Postgres Company
https://postgrespro.ru/


^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
@ 2025-06-23 14:34                           ` Alexandra Wang <[email protected]>
  2025-06-24 13:29                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  0 siblings, 1 reply; 67+ messages in thread

From: Alexandra Wang @ 2025-06-23 14:34 UTC (permalink / raw)
  To: Nikita Malakhov <[email protected]>; +Cc: Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>; jian he <[email protected]>

Hi Nikita,

Thank you so much for reviewing!

On Wed, Apr 23, 2025 at 6:54 PM Nikita Malakhov <[email protected]> wrote:

> Hi Alex!
>
> Glad you made so much effort to develop this patch set!
> I think this is an important part of Json functionality.
>
> I've looked into you patch and noticed change in behavior
> in new test results:
>
> postgres@postgres=# create table t(x int, y jsonb);
> insert into t select 1, '{"a": 1, "b": 42}'::jsonb;
> insert into t select 1, '{"a": 2, "b": {"c": 42}}'::jsonb;
> insert into t select 1, '{"a": 3, "b": {"c": "42"}, "d":[11, 12]}'::jsonb;
> CREATE TABLE
> Time: 6.373 ms
> INSERT 0 1
> Time: 3.299 ms
> INSERT 0 1
> Time: 2.532 ms
> INSERT 0 1
> Time: 2.453 ms
>
> Original master:
> postgres@postgres=# select (t.y).b.c.d.e from t;
> ERROR:  column notation .b applied to type jsonb, which is not a composite
> type
> LINE 1: select (t.y).b.c.d.e from t;
>                 ^
> Time: 0.553 ms
>
> Patched (with v11):
> postgres@postgres=# select (t.y).b.c.d.e from t;
>  e
> ---
>
>
>
> (3 rows)
>
> Is this correct?
>

This is correct.

With this patch, the query should return 3 empty rows. We expect
dot notation to behave the same as the json_query() below in lax mode
with NULL ON EMPTY.

postgres=# select json_query(y, 'lax $.b.c.d.e' WITH CONDITIONAL ARRAY
WRAPPER NULL ON EMPTY NULL ON ERROR) from t;
 json_query
------------



(3 rows)

Best,
Alex


^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
@ 2025-06-24 13:29                             ` jian he <[email protected]>
  2025-06-25 05:56                               ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-07-09 08:01                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  0 siblings, 2 replies; 67+ messages in thread

From: jian he @ 2025-06-24 13:29 UTC (permalink / raw)
  To: Alexandra Wang <[email protected]>; +Cc: Nikita Malakhov <[email protected]>; Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>

hi.

I have applied for 0001 to 0006.

static void
jsonb_subscript_transform(SubscriptingRef *sbsref,
                          List **indirection,
                          ParseState *pstate,
                          bool isSlice,
                          bool isAssignment)
{
    List       *upperIndexpr = NIL;
    ListCell   *idx;
    sbsref->refrestype = JSONBOID;
    sbsref->reftypmod = -1;
    if (jsonb_check_jsonpath_needed(*indirection))
    {
        sbsref->refjsonbpath =
            jsonb_subscript_make_jsonpath(pstate, indirection,
                                          &sbsref->refupperindexpr,
                                          &sbsref->reflowerindexpr);
        return;
    }
    foreach(idx, *indirection)
    {
        Node       *i = lfirst(idx);
        A_Indices  *ai;
        Node       *subExpr;
        Assert(IsA(i, A_Indices));
        ai = castNode(A_Indices, i);
        if (isSlice)
        {
            Node       *expr = ai->uidx ? ai->uidx : ai->lidx;
            ereport(ERROR,
                    (errcode(ERRCODE_DATATYPE_MISMATCH),
                     errmsg("jsonb subscript does not support slices"),
                     parser_errposition(pstate, exprLocation(expr))));
}

I am confused by the above error handling:
errmsg("jsonb subscript does not support slices").

we can do
select (jsonb '[1,2,3]')[0:1];
or
SELECT (jb).a[2:3].b FROM test_jsonb_dot_notation;

this is by definition, "slices"?
Anyway, I doubt this error handling will ever be reachable.

jsonb_check_jsonpath_needed checks whether the indirection contains is_slice,
but jsonb_subscript_transform already takes isSlice as an argument.
Maybe we can refactor it somehow.


T_String is a primitive node type with no subnodes.
typedef struct String
{
    pg_node_attr(special_read_write)
    NodeTag        type;
    char       *sval;
} String;
then in src/backend/nodes/nodeFuncs.c:
                    if (expr && !IsA(expr, String) && WALK(expr))
                        return true;
we can change it to
                    if (WALK(expr))
                        return true;
but in function expression_tree_walker_impl
we have to change it as

        case T_MergeSupportFunc:
        case T_String:
            /* primitive node types with no expression subnodes */
            break;





^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-06-24 13:29                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
@ 2025-06-25 05:56                               ` jian he <[email protected]>
  2025-06-25 07:40                                 ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  1 sibling, 1 reply; 67+ messages in thread

From: jian he @ 2025-06-25 05:56 UTC (permalink / raw)
  To: Alexandra Wang <[email protected]>; +Cc: Nikita Malakhov <[email protected]>; Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>

hi.

in src/backend/catalog/sql_features.txt
should we mark any T860, T861, T862, T863, T864
items as YES?


typedef struct SubscriptingRef
{
    /* expressions that evaluate to upper container indexes */
    List       *refupperindexpr;
}
SubscriptingRef.refupperindexpr meaning changed,
So the above comments also need to be changed?


v11-0004-Extract-coerce_jsonpath_subscript.patch
+ /*
+ * We known from can_coerce_type that coercion will succeed, so
+ * coerce_type could be used. Note the implicit coercion context, which is
+ * required to handle subscripts of different types, similar to overloaded
+ * functions.
+ */
+ subExpr = coerce_type(pstate,
+  subExpr, subExprType,
+  targetType, -1,
+  COERCION_IMPLICIT,
+  COERCE_IMPLICIT_CAST,
+  -1);
+ if (subExpr == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("jsonb subscript must have text type"),

the targetType can be "integer", then the error message
errmsg("jsonb subscript must have text type") would be wrong?
Also this error handling is not necessary.
since we can_coerce_type already tell us that coercion will succeed.
Also, do we really put v11-0004 as a separate patch?


in gram.y we have:
                    if (!IsA($5, A_Const) ||
                        castNode(A_Const, $5)->val.node.type != T_String)
                        ereport(ERROR,
                                errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                                errmsg("only string constants are
supported in JSON_TABLE path specification"),
so simply, in make_jsonpath_item_expr we can

    expr = transformExpr(pstate, expr, pstate->p_expr_kind);
    if (!IsA(expr, Const) || ((Const *) expr)->consttype != INT4OID)
        ereport(ERROR,
                errcode(ERRCODE_DATATYPE_MISMATCH),
                errmsg("only integer constants are supported in jsonb
simplified accessor subscripting"),
                parser_errposition(pstate, exprLocation(expr)));

because I think the current error message "got type: unknown" is not good.
select ('123'::jsonb).a['1'];
ERROR:  jsonb simplified accessor supports subscripting in type: INT4,
got type: unknown
then we don't need two "ereport(ERROR" within make_jsonpath_item_expr
we can also Assert (cnst->constisnull) is false.
see gram.y:16976


I saw you introduce the word "AST", for example
"Converts jsonpath AST into jsonpath value in binary."
I am not sure that is fine.


in jsonb_subscript_make_jsonpath we have:
+ foreach(lc, *indirection)
+ {
+
+        if (IsA(accessor, String))
+ ....
+        else if (IsA(accessor, A_Star))
+ ....
+        else if (IsA(accessor, A_Indices))
+ ....
+        else
+              break;

Is the last else branch unreliable? since indirection only support for
String, A_Star, A_Indices, we already have Assert in jsonb_check_jsonpath_needed
to ensure that.
+ *indirection = list_delete_first_n(*indirection, pathlen);
also this is not necessary,
because pathlen will be the same length as list *indirection in current design.


Please check the attached minor refactoring based on v11-0001 to v11-0006


Attachments:

  [application/octet-stream] v11-0001-misc-refactoring-based-on-v11_01_to_06.no-cfbot (3.7K, ../../CACJufxFAaJ3=X7wnGmS0857ia8+-iwDzxhua9X8Qnh_CVB1V1A@mail.gmail.com/2-v11-0001-misc-refactoring-based-on-v11_01_to_06.no-cfbot)
  download

^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-06-24 13:29                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-06-25 05:56                               ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
@ 2025-06-25 07:40                                 ` jian he <[email protected]>
  2025-06-27 02:09                                   ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  0 siblings, 1 reply; 67+ messages in thread

From: jian he @ 2025-06-25 07:40 UTC (permalink / raw)
  To: Alexandra Wang <[email protected]>; +Cc: Nikita Malakhov <[email protected]>; Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>

On Wed, Jun 25, 2025 at 1:56 PM jian he <[email protected]> wrote:
>
> hi.

CREATE TABLE test_jsonb_dot_notation AS
SELECT '{"a": [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y":
"yyy", "z": "zzz"}}], "b": [3, 4, {"b": "g", "x": {"y": "YYY", "z":
"ZZZ"}}]}'::jsonb jb;
CREATE VIEW v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
CREATE VIEW v2 AS SELECT (jb).a[3:].x.y[:-1] FROM test_jsonb_dot_notation;
CREATE VIEW v3 AS SELECT (jb).a[:3].x.y[-1:] FROM test_jsonb_dot_notation;

\sv v2
\sv v3
will trigger segment fault.

also do we need ban subscript slicing, when upper bound is less than
lower bound,
for example:
SELECT (jb).a[3:].x.y[0:'-1'::integer] AS y FROM test_jsonb_dot_notation;

please check the attached minor refactoring, which addresses the above issue
based on patches v11-0001 through v11-0006.


Attachments:

  [application/octet-stream] v11-0001-misc-refactoring-SubscriptingRef-deparsing.no-cfbot (3.6K, ../../CACJufxGPUKL39f7GX9PDRsUAHGZvtOGTuiUWVrMSP63FAgVVGA@mail.gmail.com/2-v11-0001-misc-refactoring-SubscriptingRef-deparsing.no-cfbot)
  download

^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-06-24 13:29                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-06-25 05:56                               ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-06-25 07:40                                 ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
@ 2025-06-27 02:09                                   ` jian he <[email protected]>
  0 siblings, 0 replies; 67+ messages in thread

From: jian he @ 2025-06-27 02:09 UTC (permalink / raw)
  To: Alexandra Wang <[email protected]>; +Cc: Nikita Malakhov <[email protected]>; Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>

hi.

in gram.y we have:
indirection_el:
            '.' attr_name
                {
                    $$ = (Node *) makeString($2);
                }

we can be sure that dot notation, following dot is a plain string.
then in jsonb_subscript_transform, we can transform the String Node to
a TEXTOID Const.
with that, most of the
v11-0005-Enable-String-node-as-field-accessors-in-generic.patch
would be unnecessary.
Also in v11-0006-Implement-read-only-dot-notation-for-jsonb.patch
all these with pattern
``if (IsA(expr, String)``
can be removed.


in transformContainerSubscripts we have:
    sbsref = makeNode(SubscriptingRef);
    sbsref->refcontainertype = containerType;
    sbsref->refelemtype = elementType;
    sbsref->reftypmod = containerTypMod;
    sbsref->refexpr = (Expr *) containerBase;
    sbsref->refassgnexpr = NULL;    /* caller will fill if it's an assignment */
    sbsroutines->transform(sbsref, indirection, pstate,
                           isSlice, isAssignment);

then jsonb_subscript_transform we have
        sbsref->refjsonbpath =
            jsonb_subscript_make_jsonpath(pstate, indirection,
                                          &sbsref->refupperindexpr,
                                          &sbsref->reflowerindexpr);
of course sbsref->refupperindexpr, sbsref->reflowerindexpr is NIL
since we first called jsonb_subscript_make_jsonpath.

so we can simplify the function signature as
static void jsonb_subscript_make_jsonpath(pstate, indirection, sbsref)

Within jsonb_subscript_make_jsonpath we are going to populate
refupperindexpr, reflowerindexpr, refjsonbpath.


The attached patch addresses both of these issues, along with additional related
refactoring.  It consolidates all the changes I think are appropriate, based on
patches v1-0001 to v1-0006. This will include patches previously I sent in
the earlier thread.


Attachments:

  [application/octet-stream] v11-0001-refactoring-v11_01-to-v11_06.no-cfbot (17.9K, ../../CACJufxFk7POVLuoJAzjsqywxy-4AJ6j2a0+A9fdo2eH5PM8EkQ@mail.gmail.com/2-v11-0001-refactoring-v11_01-to-v11_06.no-cfbot)
  download

^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-06-24 13:29                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
@ 2025-07-09 08:01                               ` Alexandra Wang <[email protected]>
  2025-07-10 08:53                                 ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  1 sibling, 1 reply; 67+ messages in thread

From: Alexandra Wang @ 2025-07-09 08:01 UTC (permalink / raw)
  To: jian he <[email protected]>; +Cc: Nikita Malakhov <[email protected]>; Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>

Hi Jian,

On Tue, Jun 24, 2025 at 3:30 PM jian he <[email protected]> wrote:

> hi.
>
> I have applied for 0001 to 0006.
>

Thank you so much for the very detailed and thorough review, and for
the patches!

I've attached v12 that addresses your feedback:

v12-0001 to v12-0004 are refactoring patches that prepare for the
implementation.

v12-0005 - implements jsonb dot-notation (e.g., (jb).a)
Changes from v11-0006 include:
a. subscripting with slicing and wildcard-related code has been
removed and will be added in the following commit
b. introduced a dedicated FieldAccessorExpr node to transform Strings
in dot-notation.
c. mixed syntax (such as (jb).a['b'].c is now allowed, with warnings.
d. miscellaneous bug fixes and code cleanup based on Jian’s feedback.

v12-0006 - implements jsonb subscripting with slicing.
This was split out from v11-0006 into a separate commit.

v12-0007 - implements wildcard member accessor.
I added the Star node that Jelte suggested for transforming the
wildcard member accessor.



Please find more detailed replies below:

On Tue, Jun 24, 2025 at 3:30 PM jian he <[email protected]> wrote:

> static void
> jsonb_subscript_transform(SubscriptingRef *sbsref,
>                           List **indirection,
>                           ParseState *pstate,
>                           bool isSlice,
>                           bool isAssignment)
> {
>     List       *upperIndexpr = NIL;
>     ListCell   *idx;
>     sbsref->refrestype = JSONBOID;
>     sbsref->reftypmod = -1;
>     if (jsonb_check_jsonpath_needed(*indirection))
>     {
>         sbsref->refjsonbpath =
>             jsonb_subscript_make_jsonpath(pstate, indirection,
>                                           &sbsref->refupperindexpr,
>                                           &sbsref->reflowerindexpr);
>         return;
>     }
>     foreach(idx, *indirection)
>     {
>         Node       *i = lfirst(idx);
>         A_Indices  *ai;
>         Node       *subExpr;
>         Assert(IsA(i, A_Indices));
>         ai = castNode(A_Indices, i);
>         if (isSlice)
>         {
>             Node       *expr = ai->uidx ? ai->uidx : ai->lidx;
>             ereport(ERROR,
>                     (errcode(ERRCODE_DATATYPE_MISMATCH),
>                      errmsg("jsonb subscript does not support slices"),
>                      parser_errposition(pstate, exprLocation(expr))));
> }
>
> I am confused by the above error handling:
> errmsg("jsonb subscript does not support slices").


> we can do
> select (jsonb '[1,2,3]')[0:1];
> or
> SELECT (jb).a[2:3].b FROM test_jsonb_dot_notation;
>
> this is by definition, "slices"?
> Anyway, I doubt this error handling will ever be reachable.


> jsonb_check_jsonpath_needed checks whether the indirection contains
> is_slice,
> but jsonb_subscript_transform already takes isSlice as an argument.
> Maybe we can refactor it somehow.
>

Good catch! I believe this error handling was originally copied
because the existing JSONB subscripting in Postgres doesn’t support
slices.

In v12, I’ve separated the slicing implementation into its own commit,
distinct from the one that implements dot-notation. Hope that helps!

On Tue, Jun 24, 2025 at 3:30 PM jian he <[email protected]> wrote:

> T_String is a primitive node type with no subnodes.
> typedef struct String
> {
>     pg_node_attr(special_read_write)
>     NodeTag        type;
>     char       *sval;
> } String;
> then in src/backend/nodes/nodeFuncs.c:
>                     if (expr && !IsA(expr, String) && WALK(expr))
>                         return true;
> we can change it to
>                     if (WALK(expr))
>                         return true;
> but in function expression_tree_walker_impl
> we have to change it as
>
>         case T_MergeSupportFunc:
>         case T_String:
>             /* primitive node types with no expression subnodes */
>             break;
>

You’re right, the changes to the walker-related code weren’t very
clean. In v12, I introduced a new node, FieldAccessorExpr, to serve as
the expression node for dot-notation. This helped minimize changes to
the walker code and, in my opinion, results in a cleaner design than
using a more general Const(text) node.

On Tue, Jun 24, 2025 at 10:57 PM jian he <[email protected]>
wrote:

> in src/backend/catalog/sql_features.txt
> should we mark any T860, T861, T862, T863, T864
> items as YES?
>

I’ve updated T860 and T861. I’m not entirely sure about the rest so
left them unchanged.

On Tue, Jun 24, 2025 at 10:57 PM jian he <[email protected]>
wrote:

> typedef struct SubscriptingRef
> {
>     /* expressions that evaluate to upper container indexes */
>     List       *refupperindexpr;
> }
> SubscriptingRef.refupperindexpr meaning changed,
> So the above comments also need to be changed?
>

Thanks. Done.

On Tue, Jun 24, 2025 at 10:57 PM jian he <[email protected]>
wrote:

> v11-0004-Extract-coerce_jsonpath_subscript.patch
> + /*
> + * We known from can_coerce_type that coercion will succeed, so
> + * coerce_type could be used. Note the implicit coercion context, which is
> + * required to handle subscripts of different types, similar to overloaded
> + * functions.
> + */
> + subExpr = coerce_type(pstate,
> +  subExpr, subExprType,
> +  targetType, -1,
> +  COERCION_IMPLICIT,
> +  COERCE_IMPLICIT_CAST,
> +  -1);
> + if (subExpr == NULL)
> + ereport(ERROR,
> + (errcode(ERRCODE_DATATYPE_MISMATCH),
> + errmsg("jsonb subscript must have text type"),
>
> the targetType can be "integer", then the error message
> errmsg("jsonb subscript must have text type") would be wrong?
> Also this error handling is not necessary.
> since we can_coerce_type already tell us that coercion will succeed.
> Also, do we really put v11-0004 as a separate patch?
>

Thanks for pointing this out! I’ve removed the unnecessary error
handling as you suggested. I kept v12-0004 as a separate patch to make
the review easier, but I’ll leave it up to the committer to decide
whether to squash it with the other commits.

On Tue, Jun 24, 2025 at 10:57 PM jian he <[email protected]>
wrote:

> in gram.y we have:
>                     if (!IsA($5, A_Const) ||
>                         castNode(A_Const, $5)->val.node.type != T_String)
>                         ereport(ERROR,
>                                 errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
>                                 errmsg("only string constants are
> supported in JSON_TABLE path specification"),
> so simply, in make_jsonpath_item_expr we can
>
>     expr = transformExpr(pstate, expr, pstate->p_expr_kind);
>     if (!IsA(expr, Const) || ((Const *) expr)->consttype != INT4OID)
>         ereport(ERROR,
>                 errcode(ERRCODE_DATATYPE_MISMATCH),
>                 errmsg("only integer constants are supported in jsonb
> simplified accessor subscripting"),
>                 parser_errposition(pstate, exprLocation(expr)));
>
> because I think the current error message "got type: unknown" is not good.
> select ('123'::jsonb).a['1'];
> ERROR:  jsonb simplified accessor supports subscripting in type: INT4,
> got type: unknown
> then we don't need two "ereport(ERROR" within make_jsonpath_item_expr
> we can also Assert (cnst->constisnull) is false.
> see gram.y:16976
>

Thanks for pointing this out! In v12, I added support for mixed
subscripting syntax, and updated the ERROR (or WARNING) messages as
follows:

postgres=# SELECT (jb)['a'].b FROM test_jsonb_dot_notation; -- returns an
array due to lax mode
     b
------------
 ["c", "d"]
(1 row)

postgres=# SELECT (jb).a['b'] FROM test_jsonb_dot_notation; -- returns NULL
due to strict mode with warnings
WARNING:  01000: mixed usage of jsonb simplified accessor syntax and jsonb
subscripting.
LINE 1: SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
                      ^
HINT:  use dot-notation for member access, or use non-null integer
constants subscripting for array access.
LOCATION:  jsonb_subscript_make_jsonpath, jsonbsubs.c:366
 a
---

(1 row)

postgres=# select ('{"a": 1}'::jsonb)['a':'b']; -- fails
ERROR:  42804: only non-null integer constants are supported for jsonb
simplified accessor subscripting
LINE 1: select ('{"a": 1}'::jsonb)['a':'b'];
                                   ^
HINT:  use int data type for subscripting with slicing.
LOCATION:  make_jsonpath_item_expr, jsonbsubs.c:218

So some of the ERRORs now disappear or downgrade to WARNINGs.

On Tue, Jun 24, 2025 at 10:57 PM jian he <[email protected]>
wrote:

> I saw you introduce the word "AST", for example
> "Converts jsonpath AST into jsonpath value in binary."
> I am not sure that is fine.
>
Fixed.

On Tue, Jun 24, 2025 at 10:57 PM jian he <[email protected]>
wrote:

> in jsonb_subscript_make_jsonpath we have:
> + foreach(lc, *indirection)
> + {
> +
> +        if (IsA(accessor, String))
> + ....
> +        else if (IsA(accessor, A_Star))
> + ....
> +        else if (IsA(accessor, A_Indices))
> + ....
> +        else
> +              break;
>
> Is the last else branch unreliable? since indirection only support for
> String, A_Star, A_Indices, we already have Assert in
> jsonb_check_jsonpath_needed
> to ensure that.
> + *indirection = list_delete_first_n(*indirection, pathlen);
> also this is not necessary,
> because pathlen will be the same length as list *indirection in current
> design.
>

I kept this logic in v12, because in order to support the mixed usage
of dot-notation and existing jsonb subscripting (e.g., (jb).a['b'].c),
we need to switch between making jsonpath and transforming the upper
indexes for evaluation. So now, *indirection =
list_delete_first_n(*indirection, pathlen); is necessary, and pathlen
can differ.

The else break; can be removed, but I choose to keep it for now and
added comments to clarify the behavior.

On Wed, Jun 25, 2025 at 12:41 AM jian he <[email protected]>
wrote:

> On Wed, Jun 25, 2025 at 1:56 PM jian he <[email protected]>
> wrote:
> >
> > hi.
>
> CREATE TABLE test_jsonb_dot_notation AS
> SELECT '{"a": [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y":
> "yyy", "z": "zzz"}}], "b": [3, 4, {"b": "g", "x": {"y": "YYY", "z":
> "ZZZ"}}]}'::jsonb jb;
> CREATE VIEW v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
> CREATE VIEW v2 AS SELECT (jb).a[3:].x.y[:-1] FROM test_jsonb_dot_notation;
> CREATE VIEW v3 AS SELECT (jb).a[:3].x.y[-1:] FROM test_jsonb_dot_notation;
>
> \sv v2
> \sv v3
> will trigger segment fault.
>

Great catch, thanks! Fixed and added your tests.

On Wed, Jun 25, 2025 at 12:41 AM jian he <[email protected]>
wrote:

> also do we need ban subscript slicing, when upper bound is less than
> lower bound,
> for example:
> SELECT (jb).a[3:].x.y[0:'-1'::integer] AS y FROM test_jsonb_dot_notation;
>

I don't think we need to ban this case, since the SQL standard doesn't
ban it, and the (empty) result seems reasonable.


On Thu, Jun 26, 2025 at 7:10 PM jian he <[email protected]> wrote:

> in gram.y we have:
> indirection_el:
>             '.' attr_name
>                 {
>                     $$ = (Node *) makeString($2);
>                 }
>
> we can be sure that dot notation, following dot is a plain string.
> then in jsonb_subscript_transform, we can transform the String Node to
> a TEXTOID Const.
> with that, most of the
> v11-0005-Enable-String-node-as-field-accessors-in-generic.patch
> would be unnecessary.
> Also in v11-0006-Implement-read-only-dot-notation-for-jsonb.patch
> all these with pattern
> ``if (IsA(expr, String)``
> can be removed.
>

Thanks for giving so much thought into this, I really appreciate it!
As I mentioned earlier, instead of using the Const(text) node, I added
a dedicated FieldAccessorExpr node for dot-notation. I think this
addresses the same concern.

On Thu, Jun 26, 2025 at 7:10 PM jian he <[email protected]> wrote:

> in transformContainerSubscripts we have:
>     sbsref = makeNode(SubscriptingRef);
>     sbsref->refcontainertype = containerType;
>     sbsref->refelemtype = elementType;
>     sbsref->reftypmod = containerTypMod;
>     sbsref->refexpr = (Expr *) containerBase;
>     sbsref->refassgnexpr = NULL;    /* caller will fill if it's an
> assignment */
>     sbsroutines->transform(sbsref, indirection, pstate,
>                            isSlice, isAssignment);
>
> then jsonb_subscript_transform we have
>         sbsref->refjsonbpath =
>             jsonb_subscript_make_jsonpath(pstate, indirection,
>                                           &sbsref->refupperindexpr,
>                                           &sbsref->reflowerindexpr);
> of course sbsref->refupperindexpr, sbsref->reflowerindexpr is NIL
> since we first called jsonb_subscript_make_jsonpath.
>
> so we can simplify the function signature as
> static void jsonb_subscript_make_jsonpath(pstate, indirection, sbsref)
>
> Within jsonb_subscript_make_jsonpath we are going to populate
> refupperindexpr, reflowerindexpr, refjsonbpath.
>

You are right. I applied your suggestion.

On Thu, Jun 26, 2025 at 7:10 PM jian he <[email protected]> wrote:

> The attached patch addresses both of these issues, along with additional
> related
> refactoring.  It consolidates all the changes I think are appropriate,
> based on
> patches v1-0001 to v1-0006. This will include patches previously I sent in
> the earlier thread.
>

Thanks again for the patch! It was really helpful! I didn't directly
apply it as I made a few different choices, but I think I have
addressed all the points you covered in it.

Let me know your thoughts!

Best,
Alex


Attachments:

  [application/octet-stream] v12-0006-Implement-Jsonb-subscripting-with-slicing.patch (17.4K, ../../CAK98qZ0whQ=c+JGXbGSEBxCtLgy6sf-YGYqsKTAGsS-wt0wj+A@mail.gmail.com/3-v12-0006-Implement-Jsonb-subscripting-with-slicing.patch)
  download | inline diff:
From e84373f02b26351d1df8b66d272e50d1d8d6aefa Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v12 6/7] Implement Jsonb subscripting with slicing

Previously, slicing was not supported for jsonb subscripting. This commit
implements subscripting with slicing as part of the JSON simplified accessor
syntax specified in SQL:2023.

Co-authored-by: Nikita Glukhov <[email protected]>
Co-authored-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Mark Dilger <[email protected]>
Reviewed-by: Jian He <[email protected]>
Reviewed-by: Vik Fearing <[email protected]>
Reviewed-by: Nikita Malakhov <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Tested-by: Mark Dilger <[email protected]>
Tested-by: Jian He <[email protected]>
---
 src/backend/utils/adt/jsonbsubs.c             |  33 +++-
 .../ecpg/test/expected/sql-sqljson.c          |  16 +-
 .../ecpg/test/expected/sql-sqljson.stderr     |  26 ++--
 .../ecpg/test/expected/sql-sqljson.stdout     |   3 +
 src/interfaces/ecpg/test/sql/sqljson.pgc      |   7 +-
 src/test/regress/expected/jsonb.out           | 145 ++++++++++++++++--
 src/test/regress/sql/jsonb.sql                |  53 ++++++-
 7 files changed, 243 insertions(+), 40 deletions(-)

diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index 82cf60df822..c35d20933ea 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -192,9 +192,10 @@ make_jsonpath_item_int(int32 val, List **exprs)
  * - pstate: parse state context
  * - expr: input expression node
  * - exprs: list of expression nodes (updated in place)
+ * - no_error: returns NULL when the data type doesn't match. Otherwise, emits an ERROR.
  */
 static JsonPathParseItem *
-make_jsonpath_item_expr(ParseState *pstate, Node *expr, List **exprs)
+make_jsonpath_item_expr(ParseState *pstate, Node *expr, List **exprs, bool no_error)
 {
 	Const	   *cnst;
 
@@ -207,7 +208,14 @@ make_jsonpath_item_expr(ParseState *pstate, Node *expr, List **exprs)
 			return make_jsonpath_item_int(DatumGetInt32(cnst->constvalue), exprs);
 	}
 
-	return NULL;
+	if (no_error)
+		return NULL;
+	else
+		ereport(ERROR,
+				errcode(ERRCODE_DATATYPE_MISMATCH),
+				errmsg("only non-null integer constants are supported for jsonb simplified accessor subscripting"),
+				errhint("use int data type for subscripting with slicing."),
+				parser_errposition(pstate, exprLocation(expr)));
 }
 
 /*
@@ -279,19 +287,28 @@ jsonb_subscript_make_jsonpath(ParseState *pstate, List **indirection, Subscripti
 
 			if (ai->is_slice)
 			{
-				Node	   *expr = ai->uidx ? ai->uidx : ai->lidx;
+				while (list_length(sbsref->reflowerindexpr) < list_length(sbsref->refupperindexpr))
+					sbsref->reflowerindexpr = lappend(sbsref->reflowerindexpr, NULL);
+
+				if (ai->lidx)
+					jpi->value.array.elems[0].from = make_jsonpath_item_expr(pstate, ai->lidx, &sbsref->reflowerindexpr, false);
+				else
+					jpi->value.array.elems[0].from = make_jsonpath_item_int(0, &sbsref->reflowerindexpr);
 
-				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("jsonb subscript does not support slices"),
-						 parser_errposition(pstate, exprLocation(expr))));
+				if (ai->uidx)
+					jpi->value.array.elems[0].to = make_jsonpath_item_expr(pstate, ai->uidx, &sbsref->refupperindexpr, false);
+				else
+				{
+					jpi->value.array.elems[0].to = make_jsonpath_item(jpiLast);
+					sbsref->refupperindexpr = lappend(sbsref->refupperindexpr, NULL);
+				}
 			}
 			else
 			{
 				JsonPathParseItem *jpi_from = NULL;
 
 				Assert(ai->uidx && !ai->lidx);
-				jpi_from = make_jsonpath_item_expr(pstate, ai->uidx, &sbsref->refupperindexpr);
+				jpi_from = make_jsonpath_item_expr(pstate, ai->uidx, &sbsref->refupperindexpr, true);
 				if (jpi_from == NULL)
 				{
 					/*
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.c b/src/interfaces/ecpg/test/expected/sql-sqljson.c
index e6a7ece6dab..935b47a3b9a 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.c
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.c
@@ -505,7 +505,7 @@ if (sqlca.sqlcode < 0) sqlprint();}
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 142 "sqljson.pgc"
 
-	// error
+	printf("Found json=%s\n", json);
 
 	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . * )", ECPGt_EOIT, 
 	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
@@ -525,14 +525,24 @@ if (sqlca.sqlcode < 0) sqlprint();}
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 148 "sqljson.pgc"
 
-	// error
+	printf("Found json=%s\n", json);
 
-  { ECPGdisconnect(__LINE__, "CURRENT");
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ : ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 151 "sqljson.pgc"
 
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 151 "sqljson.pgc"
 
+	printf("Found json=%s\n", json);
+
+  { ECPGdisconnect(__LINE__, "CURRENT");
+#line 154 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 154 "sqljson.pgc"
+
 
   return 0;
 }
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
index 19f8c58af06..f3f899c6d87 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
@@ -339,13 +339,10 @@ SQL error: schema "jsonb" does not exist on line 121
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 142: using PQexec
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_check_PQresult on line 142: bad response - ERROR:  jsonb subscript does not support slices
-LINE 1: ..., {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] )
-                                                                ^
+[NO_PID]: ecpg_process_output on line 142: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 142: RESULT: [12, {"y": 1}] offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: raising sqlstate 42804 (sqlcode -400): jsonb subscript does not support slices on line 142
-[NO_PID]: sqlca: code: -400, state: 42804
-SQL error: jsonb subscript does not support slices on line 142
 [NO_PID]: ecpg_execute on line 145: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . * ); with 0 parameter(s) on connection ecpg1_regression
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 145: using PQexec
@@ -361,12 +358,17 @@ SQL error: row expansion via "*" is not supported here on line 145
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 148: using PQexec
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_check_PQresult on line 148: bad response - ERROR:  jsonb subscript does not support slices
-LINE 1: ...x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )
-                                                                ^
+[NO_PID]: ecpg_process_output on line 148: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 148: RESULT: [{"x": 1}, {"x": [12, {"y": 1}]}] offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 151: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 151: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 151: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 151: RESULT: [{"x": 1}, {"x": [12, {"y": 1}]}] offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: raising sqlstate 42804 (sqlcode -400): jsonb subscript does not support slices on line 148
-[NO_PID]: sqlca: code: -400, state: 42804
-SQL error: jsonb subscript does not support slices on line 148
 [NO_PID]: ecpg_finish: connection ecpg1_regression closed
 [NO_PID]: sqlca: code: 0, state: 00000
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
index 442d36931f1..d01a8457f01 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
@@ -35,3 +35,6 @@ Found json=null
 Found json={"x": 1}
 Found json=[1, [12, {"y": 1}]]
 Found json={"x": 1}
+Found json=[12, {"y": 1}]
+Found json=[{"x": 1}, {"x": [12, {"y": 1}]}]
+Found json=[{"x": 1}, {"x": [12, {"y": 1}]}]
diff --git a/src/interfaces/ecpg/test/sql/sqljson.pgc b/src/interfaces/ecpg/test/sql/sqljson.pgc
index 57a9bff424d..9423d25fd0b 100644
--- a/src/interfaces/ecpg/test/sql/sqljson.pgc
+++ b/src/interfaces/ecpg/test/sql/sqljson.pgc
@@ -140,13 +140,16 @@ EXEC SQL END DECLARE SECTION;
 	printf("Found json=%s\n", json);
 
 	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b[1].x[0:]) INTO :json;
-	// error
+	printf("Found json=%s\n", json);
 
 	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).*) INTO :json;
 	// error
 
 	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb)['b'][0:]) INTO :json;
-	// error
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b[:]) INTO :json;
+	printf("Found json=%s\n", json);
 
   EXEC SQL DISCONNECT;
 
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 30a7023513d..4b3a596e697 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5255,23 +5255,34 @@ select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b
 (1 row)
 
 select ('{"a": 1}'::jsonb)['a':'b']; -- fails
-ERROR:  jsonb subscript does not support slices
+ERROR:  only non-null integer constants are supported for jsonb simplified accessor subscripting
 LINE 1: select ('{"a": 1}'::jsonb)['a':'b'];
-                                       ^
+                                   ^
+HINT:  use int data type for subscripting with slicing.
 select ('[1, "2", null]'::jsonb)[1:2];
-ERROR:  jsonb subscript does not support slices
-LINE 1: select ('[1, "2", null]'::jsonb)[1:2];
-                                           ^
+    jsonb    
+-------------
+ ["2", null]
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[:2];
-ERROR:  jsonb subscript does not support slices
-LINE 1: select ('[1, "2", null]'::jsonb)[:2];
-                                          ^
+     jsonb      
+----------------
+ [1, "2", null]
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[1:];
-ERROR:  jsonb subscript does not support slices
-LINE 1: select ('[1, "2", null]'::jsonb)[1:];
-                                         ^
+    jsonb    
+-------------
+ ["2", null]
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[:];
-ERROR:  jsonb subscript does not support slices
+     jsonb      
+----------------
+ [1, "2", null]
+(1 row)
+
 create TEMP TABLE test_jsonb_subscript (
        id int,
        test_json jsonb
@@ -6013,8 +6024,42 @@ SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
  
 (1 row)
 
-SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t; -- fails
-ERROR:  jsonb subscript does not support slices
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
+            x             
+--------------------------
+ {"y": "YYY", "z": "ZZZ"}
+(1 row)
+
+SELECT (jb).a[2:3].b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT (jb).a[2:].b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT (jb).a[:2].b FROM test_jsonb_dot_notation;
+  b  
+-----
+ "c"
+(1 row)
+
+SELECT (jb).a[:].b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
+            x             
+--------------------------
+ {"y": "YYY", "z": "ZZZ"}
+(1 row)
+
 SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
 ERROR:  type jsonb is not composite
 -- explains should work
@@ -6050,6 +6095,28 @@ CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_d
 CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
  SELECT (jb).a[3].x.y AS y
    FROM test_jsonb_dot_notation
+CREATE VIEW v2 AS SELECT (jb).a[3:].x.y[:-1] FROM test_jsonb_dot_notation;
+\sv v2
+CREATE OR REPLACE VIEW public.v2 AS
+ SELECT (jb).a[3:].x.y[0:'-1'::integer] AS y
+   FROM test_jsonb_dot_notation
+SELECT * from v2;
+ y 
+---
+ 
+(1 row)
+
+CREATE VIEW v3 AS SELECT (jb).a[:3].x.y[-1:] FROM test_jsonb_dot_notation;
+\sv v3
+CREATE OR REPLACE VIEW public.v3 AS
+ SELECT (jb).a[0:3].x.y['-1'::integer:] AS y
+   FROM test_jsonb_dot_notation
+SELECT * from v3;
+   y   
+-------
+ "yyy"
+(1 row)
+
 -- mixed syntax
 DROP VIEW test_jsonb_dot_notation_v1;
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
@@ -6294,5 +6361,55 @@ SELECT * from test_jsonb_dot_notation_v1;
 (1 row)
 
 DROP VIEW public.test_jsonb_dot_notation_v1;
+-- jsonb array access in plpgsql
+DO $$
+    DECLARE
+        a jsonb := '[1,2,3,4,5,6,7]'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := a[2:];
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+NOTICE:  [1, 2, 3, 4, 5, 6, 7]
+NOTICE:  [3, 4, 5, 6, 7]
+NOTICE:  [5, 6, 7]
+NOTICE:  7
+-- jsonb dot access in plpgsql
+DO $$
+    DECLARE
+        a jsonb := '{"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := COALESCE(a."NU", a[2]); -- fails
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+NOTICE:  {"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}
+ERROR:  missing FROM-clause entry for table "a"
+LINE 1: a := COALESCE(a."NU", a[2])
+                      ^
+QUERY:  a := COALESCE(a."NU", a[2])
+CONTEXT:  PL/pgSQL function inline_code_block line 8 at assignment
+DO $$
+    DECLARE
+        a jsonb := '{"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := COALESCE((a)."NU", a[2]); -- succeeds
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+NOTICE:  {"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}
+NOTICE:  [{"": [[3]]}, [6], [2], "bCi"]
+NOTICE:  [2]
 -- clean up
+DROP VIEW v2;
+DROP VIEW v3;
 DROP TABLE test_jsonb_dot_notation;
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index a0fad90a9d5..b8ecefda04d 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1623,7 +1623,12 @@ SELECT (jb).a[2].b FROM test_jsonb_dot_notation;
 SELECT (jb).a.x.y FROM test_jsonb_dot_notation;
 SELECT (jb).b.x.z FROM test_jsonb_dot_notation;
 SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
-SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t; -- fails
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
+SELECT (jb).a[2:3].b FROM test_jsonb_dot_notation;
+SELECT (jb).a[2:].b FROM test_jsonb_dot_notation;
+SELECT (jb).a[:2].b FROM test_jsonb_dot_notation;
+SELECT (jb).a[:].b FROM test_jsonb_dot_notation;
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
 SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
 
 -- explains should work
@@ -1635,6 +1640,12 @@ SELECT (jb).a[1] FROM test_jsonb_dot_notation;
 -- views should work
 CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
 \sv test_jsonb_dot_notation_v1
+CREATE VIEW v2 AS SELECT (jb).a[3:].x.y[:-1] FROM test_jsonb_dot_notation;
+\sv v2
+SELECT * from v2;
+CREATE VIEW v3 AS SELECT (jb).a[:3].x.y[-1:] FROM test_jsonb_dot_notation;
+\sv v3
+SELECT * from v3;
 
 -- mixed syntax
 DROP VIEW test_jsonb_dot_notation_v1;
@@ -1695,5 +1706,45 @@ SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
 SELECT * from test_jsonb_dot_notation_v1;
 DROP VIEW public.test_jsonb_dot_notation_v1;
 
+-- jsonb array access in plpgsql
+DO $$
+    DECLARE
+        a jsonb := '[1,2,3,4,5,6,7]'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := a[2:];
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+
+-- jsonb dot access in plpgsql
+DO $$
+    DECLARE
+        a jsonb := '{"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := COALESCE(a."NU", a[2]); -- fails
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+
+DO $$
+    DECLARE
+        a jsonb := '{"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := COALESCE((a)."NU", a[2]); -- succeeds
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+
 -- clean up
+DROP VIEW v2;
+DROP VIEW v3;
 DROP TABLE test_jsonb_dot_notation;
\ No newline at end of file
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v12-0004-Extract-coerce_jsonpath_subscript.patch (5.7K, ../../CAK98qZ0whQ=c+JGXbGSEBxCtLgy6sf-YGYqsKTAGsS-wt0wj+A@mail.gmail.com/4-v12-0004-Extract-coerce_jsonpath_subscript.patch)
  download | inline diff:
From dfaa519306c528cc1ce0ef67da1cc8f1b1e0593e Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v12 4/7] Extract coerce_jsonpath_subscript()

This is a preparation step for a future commit that will reuse the
aforementioned function.

Co-authored-by: Nikita Glukhov <[email protected]>
Co-authored-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Jian He <[email protected]>
---
 src/backend/utils/adt/jsonbsubs.c | 130 +++++++++++++++---------------
 1 file changed, 65 insertions(+), 65 deletions(-)

diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index a0d38a0fd80..5d0ec6bf2fa 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -32,6 +32,69 @@ typedef struct JsonbSubWorkspace
 	Datum	   *index;			/* Subscript values in Datum format */
 } JsonbSubWorkspace;
 
+static Node *
+coerce_jsonpath_subscript(ParseState *pstate, Node *subExpr, Oid numtype)
+{
+	Oid			subExprType = exprType(subExpr);
+	Oid			targetType = UNKNOWNOID;
+
+	if (subExprType != UNKNOWNOID)
+	{
+		Oid			targets[2] = {numtype, TEXTOID};
+
+		/*
+		 * Jsonb can handle multiple subscript types, but cases when a
+		 * subscript could be coerced to multiple target types must be
+		 * avoided, similar to overloaded functions. It could be possibly
+		 * extend with jsonpath in the future.
+		 */
+		for (int i = 0; i < 2; i++)
+		{
+			if (can_coerce_type(1, &subExprType, &targets[i], COERCION_IMPLICIT))
+			{
+				/*
+				 * One type has already succeeded, it means there are two
+				 * coercion targets possible, failure.
+				 */
+				if (targetType != UNKNOWNOID)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
+							 errhint("jsonb subscript must be coercible to only one type, integer or text."),
+							 parser_errposition(pstate, exprLocation(subExpr))));
+
+				targetType = targets[i];
+			}
+		}
+
+		/*
+		 * No suitable types were found, failure.
+		 */
+		if (targetType == UNKNOWNOID)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
+					 errhint("jsonb subscript must be coercible to either integer or text."),
+					 parser_errposition(pstate, exprLocation(subExpr))));
+	}
+	else
+		targetType = TEXTOID;
+
+	/*
+	 * We known from can_coerce_type that coercion will succeed, so
+	 * coerce_type could be used. Note the implicit coercion context, which is
+	 * required to handle subscripts of different types, similar to overloaded
+	 * functions.
+	 */
+	subExpr = coerce_type(pstate,
+						  subExpr, subExprType,
+						  targetType, -1,
+						  COERCION_IMPLICIT,
+						  COERCE_IMPLICIT_CAST,
+						  -1);
+
+	return subExpr;
+}
 
 /*
  * Finish parse analysis of a SubscriptingRef expression for a jsonb.
@@ -51,7 +114,7 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 
 	/*
 	 * Transform and convert the subscript expressions. Jsonb subscripting
-	 * does not support slices, look only and the upper index.
+	 * does not support slices, look only at the upper index.
 	 */
 	foreach(idx, *indirection)
 	{
@@ -75,71 +138,8 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 
 		if (ai->uidx)
 		{
-			Oid			subExprType = InvalidOid,
-						targetType = UNKNOWNOID;
-
 			subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
-			subExprType = exprType(subExpr);
-
-			if (subExprType != UNKNOWNOID)
-			{
-				Oid			targets[2] = {INT4OID, TEXTOID};
-
-				/*
-				 * Jsonb can handle multiple subscript types, but cases when a
-				 * subscript could be coerced to multiple target types must be
-				 * avoided, similar to overloaded functions. It could be
-				 * possibly extend with jsonpath in the future.
-				 */
-				for (int i = 0; i < 2; i++)
-				{
-					if (can_coerce_type(1, &subExprType, &targets[i], COERCION_IMPLICIT))
-					{
-						/*
-						 * One type has already succeeded, it means there are
-						 * two coercion targets possible, failure.
-						 */
-						if (targetType != UNKNOWNOID)
-							ereport(ERROR,
-									(errcode(ERRCODE_DATATYPE_MISMATCH),
-									 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
-									 errhint("jsonb subscript must be coercible to only one type, integer or text."),
-									 parser_errposition(pstate, exprLocation(subExpr))));
-
-						targetType = targets[i];
-					}
-				}
-
-				/*
-				 * No suitable types were found, failure.
-				 */
-				if (targetType == UNKNOWNOID)
-					ereport(ERROR,
-							(errcode(ERRCODE_DATATYPE_MISMATCH),
-							 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
-							 errhint("jsonb subscript must be coercible to either integer or text."),
-							 parser_errposition(pstate, exprLocation(subExpr))));
-			}
-			else
-				targetType = TEXTOID;
-
-			/*
-			 * We known from can_coerce_type that coercion will succeed, so
-			 * coerce_type could be used. Note the implicit coercion context,
-			 * which is required to handle subscripts of different types,
-			 * similar to overloaded functions.
-			 */
-			subExpr = coerce_type(pstate,
-								  subExpr, subExprType,
-								  targetType, -1,
-								  COERCION_IMPLICIT,
-								  COERCE_IMPLICIT_CAST,
-								  -1);
-			if (subExpr == NULL)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("jsonb subscript must have text type"),
-						 parser_errposition(pstate, exprLocation(subExpr))));
+			subExpr = coerce_jsonpath_subscript(pstate, subExpr, INT4OID);
 		}
 		else
 		{
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v12-0001-Allow-transformation-of-only-a-sublist-of-subscr.patch (9.0K, ../../CAK98qZ0whQ=c+JGXbGSEBxCtLgy6sf-YGYqsKTAGsS-wt0wj+A@mail.gmail.com/5-v12-0001-Allow-transformation-of-only-a-sublist-of-subscr.patch)
  download | inline diff:
From 18f0f086d55eb44ad6d2853bc3998a43ddc73abb Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v12 1/7] Allow transformation of only a sublist of subscripts

This is a preparation step for allowing subscripting containers to
transform only a prefix of an indirection list and modify the list
in-place by removing the processed elements. Currently, all elements
are consumed, and the list is set to NIL after transformation.

In the following commit, subscripting containers will gain the
flexibility to stop transformation when encountering an unsupported
indirection and return the remaining indirections to the caller.

Reviewed-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Jian He <[email protected]>
---
 contrib/hstore/hstore_subs.c      | 10 ++++++----
 src/backend/parser/parse_expr.c   |  9 ++++-----
 src/backend/parser/parse_node.c   |  4 ++--
 src/backend/parser/parse_target.c |  2 +-
 src/backend/utils/adt/arraysubs.c |  6 ++++--
 src/backend/utils/adt/jsonbsubs.c |  6 ++++--
 src/include/nodes/subscripting.h  |  7 ++++++-
 src/include/parser/parse_node.h   |  2 +-
 8 files changed, 28 insertions(+), 18 deletions(-)

diff --git a/contrib/hstore/hstore_subs.c b/contrib/hstore/hstore_subs.c
index 3d03f66fa0d..1b29543ab67 100644
--- a/contrib/hstore/hstore_subs.c
+++ b/contrib/hstore/hstore_subs.c
@@ -40,7 +40,7 @@
  */
 static void
 hstore_subscript_transform(SubscriptingRef *sbsref,
-						   List *indirection,
+						   List **indirection,
 						   ParseState *pstate,
 						   bool isSlice,
 						   bool isAssignment)
@@ -49,15 +49,15 @@ hstore_subscript_transform(SubscriptingRef *sbsref,
 	Node	   *subexpr;
 
 	/* We support only single-subscript, non-slice cases */
-	if (isSlice || list_length(indirection) != 1)
+	if (isSlice || list_length(*indirection) != 1)
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("hstore allows only one subscript"),
 				 parser_errposition(pstate,
-									exprLocation((Node *) indirection))));
+									exprLocation((Node *) *indirection))));
 
 	/* Transform the subscript expression to type text */
-	ai = linitial_node(A_Indices, indirection);
+	ai = linitial_node(A_Indices, *indirection);
 	Assert(ai->uidx != NULL && ai->lidx == NULL && !ai->is_slice);
 
 	subexpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
@@ -81,6 +81,8 @@ hstore_subscript_transform(SubscriptingRef *sbsref,
 	/* Determine the result type of the subscripting operation; always text */
 	sbsref->refrestype = TEXTOID;
 	sbsref->reftypmod = -1;
+
+	*indirection = NIL;
 }
 
 /*
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index d66276801c6..e1565e11d09 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -466,14 +466,13 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 			Assert(IsA(n, String));
 
 			/* process subscripts before this field selection */
-			if (subscripts)
+			while (subscripts)
 				result = (Node *) transformContainerSubscripts(pstate,
 															   result,
 															   exprType(result),
 															   exprTypmod(result),
-															   subscripts,
+															   &subscripts,
 															   false);
-			subscripts = NIL;
 
 			newresult = ParseFuncOrColumn(pstate,
 										  list_make1(n),
@@ -488,12 +487,12 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		}
 	}
 	/* process trailing subscripts, if any */
-	if (subscripts)
+	while (subscripts)
 		result = (Node *) transformContainerSubscripts(pstate,
 													   result,
 													   exprType(result),
 													   exprTypmod(result),
-													   subscripts,
+													   &subscripts,
 													   false);
 
 	return result;
diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c
index d6feb16aef3..19a6b678e67 100644
--- a/src/backend/parser/parse_node.c
+++ b/src/backend/parser/parse_node.c
@@ -244,7 +244,7 @@ transformContainerSubscripts(ParseState *pstate,
 							 Node *containerBase,
 							 Oid containerType,
 							 int32 containerTypMod,
-							 List *indirection,
+							 List **indirection,
 							 bool isAssignment)
 {
 	SubscriptingRef *sbsref;
@@ -280,7 +280,7 @@ transformContainerSubscripts(ParseState *pstate,
 	 * element.  If any of the items are slice specifiers (lower:upper), then
 	 * the subscript expression means a container slice operation.
 	 */
-	foreach(idx, indirection)
+	foreach(idx, *indirection)
 	{
 		A_Indices  *ai = lfirst_node(A_Indices, idx);
 
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 4aba0d9d4d5..4675a523045 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -936,7 +936,7 @@ transformAssignmentSubscripts(ParseState *pstate,
 										  basenode,
 										  containerType,
 										  containerTypMod,
-										  subscripts,
+										  &subscripts,
 										  true);
 
 	typeNeeded = sbsref->refrestype;
diff --git a/src/backend/utils/adt/arraysubs.c b/src/backend/utils/adt/arraysubs.c
index 2940fb8e8d7..234c2c278c1 100644
--- a/src/backend/utils/adt/arraysubs.c
+++ b/src/backend/utils/adt/arraysubs.c
@@ -54,7 +54,7 @@ typedef struct ArraySubWorkspace
  */
 static void
 array_subscript_transform(SubscriptingRef *sbsref,
-						  List *indirection,
+						  List **indirection,
 						  ParseState *pstate,
 						  bool isSlice,
 						  bool isAssignment)
@@ -71,7 +71,7 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	 * indirection items to slices by treating the single subscript as the
 	 * upper bound and supplying an assumed lower bound of 1.
 	 */
-	foreach(idx, indirection)
+	foreach(idx, *indirection)
 	{
 		A_Indices  *ai = lfirst_node(A_Indices, idx);
 		Node	   *subexpr;
@@ -152,6 +152,8 @@ array_subscript_transform(SubscriptingRef *sbsref,
 						list_length(upperIndexpr), MAXDIM)));
 	/* We need not check lowerIndexpr separately */
 
+	*indirection = NIL;
+
 	/*
 	 * Determine the result type of the subscripting operation.  It's the same
 	 * as the array type if we're slicing, else it's the element type.  In
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index de64d498512..8ad6aa1ad4f 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -41,7 +41,7 @@ typedef struct JsonbSubWorkspace
  */
 static void
 jsonb_subscript_transform(SubscriptingRef *sbsref,
-						  List *indirection,
+						  List **indirection,
 						  ParseState *pstate,
 						  bool isSlice,
 						  bool isAssignment)
@@ -53,7 +53,7 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	 * Transform and convert the subscript expressions. Jsonb subscripting
 	 * does not support slices, look only and the upper index.
 	 */
-	foreach(idx, indirection)
+	foreach(idx, *indirection)
 	{
 		A_Indices  *ai = lfirst_node(A_Indices, idx);
 		Node	   *subExpr;
@@ -159,6 +159,8 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	/* Determine the result type of the subscripting operation; always jsonb */
 	sbsref->refrestype = JSONBOID;
 	sbsref->reftypmod = -1;
+
+	*indirection = NIL;
 }
 
 /*
diff --git a/src/include/nodes/subscripting.h b/src/include/nodes/subscripting.h
index 234e8ad8012..5d576af346f 100644
--- a/src/include/nodes/subscripting.h
+++ b/src/include/nodes/subscripting.h
@@ -71,6 +71,11 @@ struct SubscriptExecSteps;
  * does not care to support slicing, it can just throw an error if isSlice.)
  * See array_subscript_transform() for sample code.
  *
+ * The transform method receives a pointer to a list of raw indirections.
+ * This allows the method to parse a sublist of the indirections (typically
+ * the prefix) and modify the original list in place, enabling the caller to
+ * either process the remaining indirections differently or raise an error.
+ *
  * The transform method is also responsible for identifying the result type
  * of the subscripting operation.  At call, refcontainertype and reftypmod
  * describe the container type (this will be a base type not a domain), and
@@ -93,7 +98,7 @@ struct SubscriptExecSteps;
  * assignment must return.
  */
 typedef void (*SubscriptTransform) (SubscriptingRef *sbsref,
-									List *indirection,
+									List **indirection,
 									struct ParseState *pstate,
 									bool isSlice,
 									bool isAssignment);
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index f7d07c84542..58a4b9df157 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -361,7 +361,7 @@ extern SubscriptingRef *transformContainerSubscripts(ParseState *pstate,
 													 Node *containerBase,
 													 Oid containerType,
 													 int32 containerTypMod,
-													 List *indirection,
+													 List **indirection,
 													 bool isAssignment);
 extern Const *make_const(ParseState *pstate, A_Const *aconst);
 
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v12-0007-Implement-jsonb-wildcard-member-accessor.patch (31.4K, ../../CAK98qZ0whQ=c+JGXbGSEBxCtLgy6sf-YGYqsKTAGsS-wt0wj+A@mail.gmail.com/6-v12-0007-Implement-jsonb-wildcard-member-accessor.patch)
  download | inline diff:
From 40199a1ca6ce4fb975190df99b746205b1ebb5b8 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v12 7/7] Implement jsonb wildcard member accessor

This commit adds support for wildcard member access in jsonb, as
specified by the JSON simplified accessor syntax in SQL:2023.

Co-authored-by: Nikita Glukhov <[email protected]>
Co-authored-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Mark Dilger <[email protected]>
Reviewed-by: Jian He <[email protected]>
Reviewed-by: Vik Fearing <[email protected]>
Reviewed-by: Nikita Malakhov <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Jelte Fennema-Nio <[email protected]>
Tested-by: Jelte Fennema-Nio <[email protected]>
---
 src/backend/nodes/nodeFuncs.c                 |   8 +
 src/backend/parser/gram.y                     |   2 +
 src/backend/parser/parse_expr.c               |  34 ++-
 src/backend/parser/parse_target.c             |  67 ++++--
 src/backend/utils/adt/jsonbsubs.c             |  12 +-
 src/backend/utils/adt/ruleutils.c             |   6 +-
 src/include/nodes/primnodes.h                 |  12 +
 src/include/parser/parse_expr.h               |   3 +
 .../ecpg/test/expected/sql-sqljson.c          |  18 +-
 .../ecpg/test/expected/sql-sqljson.stderr     |  23 +-
 .../ecpg/test/expected/sql-sqljson.stdout     |   2 +
 src/interfaces/ecpg/test/sql/sqljson.pgc      |   5 +-
 src/test/regress/expected/jsonb.out           | 222 +++++++++++++++++-
 src/test/regress/sql/jsonb.sql                |  42 +++-
 14 files changed, 405 insertions(+), 51 deletions(-)

diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index d1bd575d9bd..5f3038a1c26 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -291,6 +291,9 @@ exprType(const Node *expr)
 			 */
 			type = TEXTOID;
 			break;
+		case T_Star:
+			type = UNKNOWNOID;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			type = InvalidOid;	/* keep compiler quiet */
@@ -1156,6 +1159,9 @@ exprSetCollation(Node *expr, Oid collation)
 		case T_FieldAccessorExpr:
 			((FieldAccessorExpr *) expr)->faecollid = collation;
 			break;
+		case T_Star:
+			Assert(!OidIsValid(collation));
+			break;
 		case T_SubscriptingRef:
 			((SubscriptingRef *) expr)->refcollid = collation;
 			break;
@@ -2140,6 +2146,7 @@ expression_tree_walker_impl(Node *node,
 		case T_CTESearchClause:
 		case T_MergeSupportFunc:
 		case T_FieldAccessorExpr:
+		case T_Star:
 			/* primitive node types with no expression subnodes */
 			break;
 		case T_WithCheckOption:
@@ -3020,6 +3027,7 @@ expression_tree_mutator_impl(Node *node,
 		case T_CTESearchClause:
 		case T_MergeSupportFunc:
 		case T_FieldAccessorExpr:
+		case T_Star:
 			return copyObject(node);
 		case T_WithCheckOption:
 			{
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 70a0d832a11..532217425b6 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -18984,6 +18984,7 @@ check_func_name(List *names, core_yyscan_t yyscanner)
 static List *
 check_indirection(List *indirection, core_yyscan_t yyscanner)
 {
+#if 0
 	ListCell   *l;
 
 	foreach(l, indirection)
@@ -18994,6 +18995,7 @@ check_indirection(List *indirection, core_yyscan_t yyscanner)
 				parser_yyerror("improper use of \"*\"");
 		}
 	}
+#endif
 	return indirection;
 }
 
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 07d46747811..d91700ebf39 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -74,7 +74,6 @@ static Node *transformColumnRef(ParseState *pstate, ColumnRef *cref);
 static Node *transformWholeRowRef(ParseState *pstate,
 								  ParseNamespaceItem *nsitem,
 								  int sublevels_up, int location);
-static Node *transformIndirection(ParseState *pstate, A_Indirection *ind);
 static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
 static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
 static Node *transformJsonObjectConstructor(ParseState *pstate,
@@ -158,7 +157,7 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 			break;
 
 		case T_A_Indirection:
-			result = transformIndirection(pstate, (A_Indirection *) expr);
+			result = transformIndirection(pstate, (A_Indirection *) expr, NULL);
 			break;
 
 		case T_A_ArrayExpr:
@@ -432,8 +431,9 @@ unknown_attribute(ParseState *pstate, Node *relref, const char *attname,
 	}
 }
 
-static Node *
-transformIndirection(ParseState *pstate, A_Indirection *ind)
+Node *
+transformIndirection(ParseState *pstate, A_Indirection *ind,
+					 bool *trailing_star_expansion)
 {
 	Node	   *last_srf = pstate->p_last_srf;
 	Node	   *result = transformExprRecurse(pstate, ind->arg);
@@ -454,12 +454,7 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		if (IsA(n, A_Indices))
 			subscripts = lappend(subscripts, n);
 		else if (IsA(n, A_Star))
-		{
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("row expansion via \"*\" is not supported here"),
-					 parser_errposition(pstate, location)));
-		}
+			subscripts = lappend(subscripts, n);
 		else
 		{
 			Assert(IsA(n, String));
@@ -491,7 +486,21 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 
 			n = linitial(subscripts);
 
-			if (!IsA(n, String))
+			if (IsA(n, A_Star))
+			{
+				/* Success, if trailing star expansion is allowed */
+				if (trailing_star_expansion && list_length(subscripts) == 1)
+				{
+					*trailing_star_expansion = true;
+					return result;
+				}
+
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("row expansion via \"*\" is not supported here"),
+						 parser_errposition(pstate, location)));
+			}
+			else if (!IsA(n, String))
 				ereport(ERROR,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
 						 errmsg("cannot subscript type %s because it does not support subscripting",
@@ -517,6 +526,9 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		result = newresult;
 	}
 
+	if (trailing_star_expansion)
+		*trailing_star_expansion = false;
+
 	return result;
 }
 
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 3ef5897f2eb..141fc1dfeb1 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -48,7 +48,7 @@ static Node *transformAssignmentSubscripts(ParseState *pstate,
 static List *ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
 								 bool make_target_entry);
 static List *ExpandAllTables(ParseState *pstate, int location);
-static List *ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
+static Node *ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
 								   bool make_target_entry, ParseExprKind exprKind);
 static List *ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 							   int sublevels_up, int location,
@@ -134,6 +134,7 @@ transformTargetList(ParseState *pstate, List *targetlist,
 	foreach(o_target, targetlist)
 	{
 		ResTarget  *res = (ResTarget *) lfirst(o_target);
+		Node	   *transformed = NULL;
 
 		/*
 		 * Check for "something.*".  Depending on the complexity of the
@@ -162,13 +163,19 @@ transformTargetList(ParseState *pstate, List *targetlist,
 
 				if (IsA(llast(ind->indirection), A_Star))
 				{
-					/* It is something.*, expand into multiple items */
-					p_target = list_concat(p_target,
-										   ExpandIndirectionStar(pstate,
-																 ind,
-																 true,
-																 exprKind));
-					continue;
+					Node	   *columns = ExpandIndirectionStar(pstate,
+																ind,
+																true,
+																exprKind);
+
+					if (IsA(columns, List))
+					{
+						/* It is something.*, expand into multiple items */
+						p_target = list_concat(p_target, (List *) columns);
+						continue;
+					}
+
+					transformed = (Node *) columns;
 				}
 			}
 		}
@@ -180,7 +187,7 @@ transformTargetList(ParseState *pstate, List *targetlist,
 		p_target = lappend(p_target,
 						   transformTargetEntry(pstate,
 												res->val,
-												NULL,
+												transformed,
 												exprKind,
 												res->name,
 												false));
@@ -251,10 +258,15 @@ transformExpressionList(ParseState *pstate, List *exprlist,
 
 			if (IsA(llast(ind->indirection), A_Star))
 			{
-				/* It is something.*, expand into multiple items */
-				result = list_concat(result,
-									 ExpandIndirectionStar(pstate, ind,
-														   false, exprKind));
+				Node	   *cols = ExpandIndirectionStar(pstate, ind,
+														 false, exprKind);
+
+				if (!cols || IsA(cols, List))
+					/* It is something.*, expand into multiple items */
+					result = list_concat(result, (List *) cols);
+				else
+					result = lappend(result, cols);
+
 				continue;
 			}
 		}
@@ -1345,22 +1357,30 @@ ExpandAllTables(ParseState *pstate, int location)
  * For robustness, we use a separate "make_target_entry" flag to control
  * this rather than relying on exprKind.
  */
-static List *
+static Node *
 ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
 					  bool make_target_entry, ParseExprKind exprKind)
 {
 	Node	   *expr;
+	ParseExprKind sv_expr_kind;
+	bool		trailing_star_expansion = false;
+
+	/* Save and restore identity of expression type we're parsing */
+	Assert(exprKind != EXPR_KIND_NONE);
+	sv_expr_kind = pstate->p_expr_kind;
+	pstate->p_expr_kind = exprKind;
 
 	/* Strip off the '*' to create a reference to the rowtype object */
-	ind = copyObject(ind);
-	ind->indirection = list_truncate(ind->indirection,
-									 list_length(ind->indirection) - 1);
+	expr = transformIndirection(pstate, ind, &trailing_star_expansion);
+
+	pstate->p_expr_kind = sv_expr_kind;
 
-	/* And transform that */
-	expr = transformExpr(pstate, (Node *) ind, exprKind);
+	/* '*' was consumed by generic type subscripting */
+	if (!trailing_star_expansion)
+		return expr;
 
 	/* Expand the rowtype expression into individual fields */
-	return ExpandRowReference(pstate, expr, make_target_entry);
+	return (Node *) ExpandRowReference(pstate, expr, make_target_entry);
 }
 
 /*
@@ -1785,13 +1805,18 @@ FigureColnameInternal(Node *node, char **name)
 				char	   *fname = NULL;
 				ListCell   *l;
 
-				/* find last field name, if any, ignoring "*" and subscripts */
+				/*
+				 * find last field name, if any, ignoring subscripts, and use
+				 * '?column?' when there's a trailing '*'.
+				 */
 				foreach(l, ind->indirection)
 				{
 					Node	   *i = lfirst(l);
 
 					if (IsA(i, String))
 						fname = strVal(i);
+					else if (IsA(i, A_Star))
+						fname = "?column?";
 				}
 				if (fname)
 				{
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index c35d20933ea..a29808b6e76 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -124,7 +124,7 @@ jsonb_check_jsonpath_needed(List *indirection)
 	{
 		Node	   *accessor = lfirst(lc);
 
-		if (IsA(accessor, String))
+		if (IsA(accessor, String) || IsA(accessor, A_Star))
 			return true;
 		else
 		{
@@ -325,6 +325,16 @@ jsonb_subscript_make_jsonpath(ParseState *pstate, List **indirection, Subscripti
 				jpi->value.array.elems[0].to = NULL;
 			}
 		}
+		else if (IsA(accessor, A_Star))
+		{
+			Star *star_node;
+			jpi = make_jsonpath_item(jpiAnyKey);
+
+			star_node = makeNode(Star);
+			star_node->type = T_Star;
+
+			sbsref->refupperindexpr = lappend(sbsref->refupperindexpr, star_node);
+		}
 		else
 
 			/*
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index baa3ae97d57..ace0eff52e2 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -13010,7 +13010,11 @@ printSubscripts(SubscriptingRef *sbsref, deparse_context *context)
 	{
 		Node *upper = (Node *) lfirst(uplist_item);
 
-		if (upper && IsA(upper, FieldAccessorExpr))
+		if (upper && IsA(upper, Star))
+		{
+			appendStringInfoString(buf, ".*");
+		}
+		else if (upper && IsA(upper, FieldAccessorExpr))
 		{
 			FieldAccessorExpr *fae = (FieldAccessorExpr *) upper;
 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index b208d7949b1..63446edaad7 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -2420,4 +2420,16 @@ typedef struct FieldAccessorExpr
 	int			location;
 }			FieldAccessorExpr;
 
+/*
+ * Star - represents a wildcard member accessor (e.g., ".*") used in JSONB simplified accessor.
+ *
+ * This node serves as a syntactic placeholder in the expression tree and does not get evaluated, hence it
+ * has no associated type or collation.
+ */
+typedef struct Star
+{
+	NodeTag		type;
+}			Star;
+
+
 #endif							/* PRIMNODES_H */
diff --git a/src/include/parser/parse_expr.h b/src/include/parser/parse_expr.h
index efbaff8e710..c9f6a7724c0 100644
--- a/src/include/parser/parse_expr.h
+++ b/src/include/parser/parse_expr.h
@@ -22,4 +22,7 @@ extern Node *transformExpr(ParseState *pstate, Node *expr, ParseExprKind exprKin
 
 extern const char *ParseExprKindName(ParseExprKind exprKind);
 
+extern Node *transformIndirection(ParseState *pstate, A_Indirection *ind,
+								  bool *trailing_star_expansion);
+
 #endif							/* PARSE_EXPR_H */
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.c b/src/interfaces/ecpg/test/expected/sql-sqljson.c
index 935b47a3b9a..585d9b14445 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.c
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.c
@@ -515,9 +515,9 @@ if (sqlca.sqlcode < 0) sqlprint();}
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 145 "sqljson.pgc"
 
-	// error
+	printf("Found json=%s\n", json);
 
-	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )", ECPGt_EOIT, 
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . * . x )", ECPGt_EOIT, 
 	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
 	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 148 "sqljson.pgc"
@@ -527,7 +527,7 @@ if (sqlca.sqlcode < 0) sqlprint();}
 
 	printf("Found json=%s\n", json);
 
-	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ : ] )", ECPGt_EOIT, 
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )", ECPGt_EOIT, 
 	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
 	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 151 "sqljson.pgc"
@@ -537,12 +537,22 @@ if (sqlca.sqlcode < 0) sqlprint();}
 
 	printf("Found json=%s\n", json);
 
-  { ECPGdisconnect(__LINE__, "CURRENT");
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ : ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 154 "sqljson.pgc"
 
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 154 "sqljson.pgc"
 
+	printf("Found json=%s\n", json);
+
+  { ECPGdisconnect(__LINE__, "CURRENT");
+#line 157 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 157 "sqljson.pgc"
+
 
   return 0;
 }
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
index f3f899c6d87..4b9088545d6 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
@@ -347,22 +347,19 @@ SQL error: schema "jsonb" does not exist on line 121
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 145: using PQexec
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_check_PQresult on line 145: bad response - ERROR:  row expansion via "*" is not supported here
-LINE 1: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x...
-                        ^
+[NO_PID]: ecpg_process_output on line 145: correctly got 1 tuples with 1 fields
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: raising sqlstate 0A000 (sqlcode -400): row expansion via "*" is not supported here on line 145
-[NO_PID]: sqlca: code: -400, state: 0A000
-SQL error: row expansion via "*" is not supported here on line 145
-[NO_PID]: ecpg_execute on line 148: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: ecpg_get_data on line 145: RESULT: [{"b": 1, "c": 2}, [{"x": 1}, {"x": [12, {"y": 1}]}]] offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 148: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . * . x ); with 0 parameter(s) on connection ecpg1_regression
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 148: using PQexec
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_process_output on line 148: correctly got 1 tuples with 1 fields
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_get_data on line 148: RESULT: [{"x": 1}, {"x": [12, {"y": 1}]}] offset: -1; array: no
+[NO_PID]: ecpg_get_data on line 148: RESULT: [1, [12, {"y": 1}]] offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_execute on line 151: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: ecpg_execute on line 151: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] ); with 0 parameter(s) on connection ecpg1_regression
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 151: using PQexec
 [NO_PID]: sqlca: code: 0, state: 00000
@@ -370,5 +367,13 @@ SQL error: row expansion via "*" is not supported here on line 145
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_get_data on line 151: RESULT: [{"x": 1}, {"x": [12, {"y": 1}]}] offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 154: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 154: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 154: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 154: RESULT: [{"x": 1}, {"x": [12, {"y": 1}]}] offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_finish: connection ecpg1_regression closed
 [NO_PID]: sqlca: code: 0, state: 00000
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
index d01a8457f01..145dc95d430 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
@@ -36,5 +36,7 @@ Found json={"x": 1}
 Found json=[1, [12, {"y": 1}]]
 Found json={"x": 1}
 Found json=[12, {"y": 1}]
+Found json=[{"b": 1, "c": 2}, [{"x": 1}, {"x": [12, {"y": 1}]}]]
+Found json=[1, [12, {"y": 1}]]
 Found json=[{"x": 1}, {"x": [12, {"y": 1}]}]
 Found json=[{"x": 1}, {"x": [12, {"y": 1}]}]
diff --git a/src/interfaces/ecpg/test/sql/sqljson.pgc b/src/interfaces/ecpg/test/sql/sqljson.pgc
index 9423d25fd0b..2af50b5da4b 100644
--- a/src/interfaces/ecpg/test/sql/sqljson.pgc
+++ b/src/interfaces/ecpg/test/sql/sqljson.pgc
@@ -143,7 +143,10 @@ EXEC SQL END DECLARE SECTION;
 	printf("Found json=%s\n", json);
 
 	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).*) INTO :json;
-	// error
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).*.x) INTO :json;
+	printf("Found json=%s\n", json);
 
 	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb)['b'][0:]) INTO :json;
 	printf("Found json=%s\n", json);
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 4b3a596e697..741505d3b6d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -6060,8 +6060,175 @@ SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
  {"y": "YYY", "z": "ZZZ"}
 (1 row)
 
-SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
-ERROR:  type jsonb is not composite
+/* wild card member access */
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+                 ?column?                  
+-------------------------------------------
+ ["c", "d", "f", {"y": "yyy", "z": "zzz"}]
+(1 row)
+
+SELECT (jb).* FROM test_jsonb_dot_notation;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (jb).* FROM test_jsonb_dot_notation t;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (jb).* FROM test_jsonb_dot_notation;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (t.jb).* FROM test_jsonb_dot_notation;
+ERROR:  missing FROM-clause entry for table "t"
+LINE 1: SELECT (t.jb).* FROM test_jsonb_dot_notation;
+                ^
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+                 ?column?                  
+-------------------------------------------
+ ["c", "d", "f", {"y": "yyy", "z": "zzz"}]
+(1 row)
+
+SELECT (jb).a.*.b FROM test_jsonb_dot_notation;
+ b 
+---
+ 
+(1 row)
+
+SELECT (jb).a.*.x FROM test_jsonb_dot_notation;
+ x 
+---
+ 
+(1 row)
+
+SELECT (jb).a.*.y FROM test_jsonb_dot_notation;
+   y   
+-------
+ "yyy"
+(1 row)
+
+SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+    ?column?    
+----------------
+ ["yyy", "zzz"]
+(1 row)
+
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT (jb).*.x FROM test_jsonb_dot_notation t;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+ x 
+---
+ 
+(1 row)
+
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+ x 
+---
+ 
+(1 row)
+
+SELECT ((jb).*)[:].x FROM test_jsonb_dot_notation t;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT (jb).*.x.* FROM test_jsonb_dot_notation;
+           ?column?           
+------------------------------
+ ["yyy", "zzz", "YYY", "ZZZ"]
+(1 row)
+
+SELECT (jb).*.x.y FROM test_jsonb_dot_notation;
+       y        
+----------------
+ ["yyy", "YYY"]
+(1 row)
+
+SELECT (jb).*.x.z FROM test_jsonb_dot_notation;
+       z        
+----------------
+ ["zzz", "ZZZ"]
+(1 row)
+
+SELECT (jb).*.*.y FROM test_jsonb_dot_notation;
+       y        
+----------------
+ ["yyy", "YYY"]
+(1 row)
+
+SELECT (jb).*.*.* FROM test_jsonb_dot_notation;
+           ?column?           
+------------------------------
+ ["yyy", "zzz", "YYY", "ZZZ"]
+(1 row)
+
+SELECT (jb).*.*.*.* FROM test_jsonb_dot_notation;
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT (jb).a.b.c.* FROM test_jsonb_dot_notation;
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+    ?column?    
+----------------
+ ["yyy", "zzz"]
+(1 row)
+
+SELECT (jb).a.*[:].* FROM test_jsonb_dot_notation;
+    ?column?    
+----------------
+ ["yyy", "zzz"]
+(1 row)
+
+SELECT (jb).a.*[*].* FROM test_jsonb_dot_notation; -- not supported
+ERROR:  syntax error at or near "*"
+LINE 1: SELECT (jb).a.*[*].* FROM test_jsonb_dot_notation;
+                        ^
+SELECT (jb).a.**.x FROM test_jsonb_dot_notation; -- not supported
+ERROR:  syntax error at or near "**"
+LINE 1: SELECT (jb).a.**.x FROM test_jsonb_dot_notation;
+                      ^
 -- explains should work
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
                   QUERY PLAN                  
@@ -6089,6 +6256,45 @@ SELECT (jb).a[1] FROM test_jsonb_dot_notation;
  2
 (1 row)
 
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.* FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb).a.*
+(2 rows)
+
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+                 ?column?                  
+-------------------------------------------
+ ["c", "d", "f", {"y": "yyy", "z": "zzz"}]
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:].* FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb).a.*[:].*
+(2 rows)
+
+SELECT (jb).a.*[1:].* FROM test_jsonb_dot_notation;
+ ?column? 
+----------
+ 
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb).a.*[:2].*.b
+(2 rows)
+
+SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
+ b 
+---
+ 
+(1 row)
+
 -- views should work
 CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
 \sv test_jsonb_dot_notation_v1
@@ -6117,6 +6323,17 @@ SELECT * from v3;
  "yyy"
 (1 row)
 
+CREATE VIEW v4 AS SELECT (jb).a.*[:].* FROM test_jsonb_dot_notation;
+\sv v4
+CREATE OR REPLACE VIEW public.v4 AS
+ SELECT (jb).a.*[:].* AS "?column?"
+   FROM test_jsonb_dot_notation
+SELECT * from v4;
+    ?column?    
+----------------
+ ["yyy", "zzz"]
+(1 row)
+
 -- mixed syntax
 DROP VIEW test_jsonb_dot_notation_v1;
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
@@ -6412,4 +6629,5 @@ NOTICE:  [2]
 -- clean up
 DROP VIEW v2;
 DROP VIEW v3;
+DROP VIEW v4;
 DROP TABLE test_jsonb_dot_notation;
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index b8ecefda04d..440ea1c6549 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1629,13 +1629,49 @@ SELECT (jb).a[2:].b FROM test_jsonb_dot_notation;
 SELECT (jb).a[:2].b FROM test_jsonb_dot_notation;
 SELECT (jb).a[:].b FROM test_jsonb_dot_notation;
 SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
-SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
+
+/* wild card member access */
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+SELECT (jb).* FROM test_jsonb_dot_notation;
+SELECT (jb).* FROM test_jsonb_dot_notation t;
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+SELECT (jb).* FROM test_jsonb_dot_notation;
+SELECT (t.jb).* FROM test_jsonb_dot_notation;
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.b FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.x FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.y FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+SELECT (jb).*.x FROM test_jsonb_dot_notation t;
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+SELECT ((jb).*)[:].x FROM test_jsonb_dot_notation t;
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+SELECT (jb).*.x.* FROM test_jsonb_dot_notation;
+SELECT (jb).*.x.y FROM test_jsonb_dot_notation;
+SELECT (jb).*.x.z FROM test_jsonb_dot_notation;
+SELECT (jb).*.*.y FROM test_jsonb_dot_notation;
+SELECT (jb).*.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).*.*.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.b.c.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*[:].* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*[*].* FROM test_jsonb_dot_notation; -- not supported
+SELECT (jb).a.**.x FROM test_jsonb_dot_notation; -- not supported
 
 -- explains should work
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
 SELECT (t.jb).a FROM test_jsonb_dot_notation t;
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
 SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:].* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*[1:].* FROM test_jsonb_dot_notation;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
+SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
 
 -- views should work
 CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
@@ -1646,6 +1682,9 @@ SELECT * from v2;
 CREATE VIEW v3 AS SELECT (jb).a[:3].x.y[-1:] FROM test_jsonb_dot_notation;
 \sv v3
 SELECT * from v3;
+CREATE VIEW v4 AS SELECT (jb).a.*[:].* FROM test_jsonb_dot_notation;
+\sv v4
+SELECT * from v4;
 
 -- mixed syntax
 DROP VIEW test_jsonb_dot_notation_v1;
@@ -1747,4 +1786,5 @@ $$ LANGUAGE plpgsql;
 -- clean up
 DROP VIEW v2;
 DROP VIEW v3;
+DROP VIEW v4;
 DROP TABLE test_jsonb_dot_notation;
\ No newline at end of file
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v12-0005-Implement-read-only-dot-notation-for-jsonb.patch (66.3K, ../../CAK98qZ0whQ=c+JGXbGSEBxCtLgy6sf-YGYqsKTAGsS-wt0wj+A@mail.gmail.com/7-v12-0005-Implement-read-only-dot-notation-for-jsonb.patch)
  download | inline diff:
From 12b7b98a3dc5a784a7178846b54915ea1be6e4e5 Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v12 5/7] Implement read-only dot notation for jsonb

This patch introduces JSONB member access using dot notation that
aligns with the JSON simplified accessor specified in SQL:2023.

Examples:

-- Setup
create table t(x int, y jsonb);
insert into t select 1, '{"a": 1, "b": 42}'::jsonb;
insert into t select 1, '{"a": 2, "b": {"c": 42}}'::jsonb;
insert into t select 1, '{"a": 3, "b": {"c": "42"}, "d":[11, 12]}'::jsonb;

-- Existing syntax in PostgreSQL that predates the SQL standard:
select (t.y)->'b' from t;
select (t.y)->'b'->'c' from t;
select (t.y)->'d'->0 from t;

-- JSON simplified accessor specified by the SQL standard:
select (t.y).b from t;
select (t.y).b.c from t;
select (t.y).d[0] from t;

The SQL standard states that simplified access is equivalent to:
JSON_QUERY (VEP, 'lax $.JC' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR)

where:
  VEP = <value expression primary>
  JC = <JSON simplified accessor op chain>

For example, the JSON_QUERY equivalents of the above queries are:

select json_query(y, 'lax $.b' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t;
select json_query(y, 'lax $.b.c' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t;
select json_query(y, 'lax $.d[0]' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t;

Implementation details:

Extends the existing container subscripting interface to support
container-specific information, specifically a JSONPath expression for
jsonb.

During query transformation, if dot-notation is present, constructs a
JSONPath expression representing the access chain.

During execution, if a JSONPath expression is present in
JsonbSubWorkspace, executes it via JsonPathQuery().

Does not transform accessors directly into JSON_QUERY during
transformation to preserve the original query structure for EXPLAIN
and CREATE VIEW.

Co-authored-by: Nikita Glukhov <[email protected]>
Co-authored-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Mark Dilger <[email protected]>
Reviewed-by: Jian He <[email protected]>
Reviewed-by: Vik Fearing <[email protected]>
Reviewed-by: Nikita Malakhov <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Tested-by: Jelte Fennema-Nio <[email protected]>
---
 src/backend/catalog/sql_features.txt          |   4 +-
 src/backend/executor/execExpr.c               |  76 +--
 src/backend/nodes/nodeFuncs.c                 |  12 +
 src/backend/utils/adt/jsonbsubs.c             | 341 +++++++++++--
 src/backend/utils/adt/ruleutils.c             |  43 +-
 src/include/nodes/primnodes.h                 |  55 +-
 .../ecpg/test/expected/sql-sqljson.c          | 112 ++++-
 .../ecpg/test/expected/sql-sqljson.stderr     | 100 ++++
 .../ecpg/test/expected/sql-sqljson.stdout     |   7 +
 src/interfaces/ecpg/test/sql/sqljson.pgc      |  33 ++
 src/test/regress/expected/jsonb.out           | 469 +++++++++++++++++-
 src/test/regress/sql/jsonb.sql                | 111 ++++-
 12 files changed, 1263 insertions(+), 100 deletions(-)

diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_features.txt
index ebe85337c28..457e993305e 100644
--- a/src/backend/catalog/sql_features.txt
+++ b/src/backend/catalog/sql_features.txt
@@ -568,8 +568,8 @@ T838	JSON_TABLE: PLAN DEFAULT clause			NO
 T839	Formatted cast of datetimes to/from character strings			NO	
 T840	Hex integer literals in SQL/JSON path language			YES	
 T851	SQL/JSON: optional keywords for default syntax			YES	
-T860	SQL/JSON simplified accessor: column reference only			NO	
-T861	SQL/JSON simplified accessor: case-sensitive JSON member accessor			NO	
+T860	SQL/JSON simplified accessor: column reference only			YES	
+T861	SQL/JSON simplified accessor: case-sensitive JSON member accessor			YES	
 T862	SQL/JSON simplified accessor: wildcard member accessor			NO	
 T863	SQL/JSON simplified accessor: single-quoted string literal as member accessor			NO	
 T864	SQL/JSON simplified accessor			NO	
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index f1569879b52..75e57ca0c79 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -3320,50 +3320,52 @@ ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref,
 								   state->steps_len - 1);
 	}
 
-	/* Evaluate upper subscripts */
-	i = 0;
-	foreach(lc, sbsref->refupperindexpr)
+	/* Evaluate upper subscripts, unless refjsonbpath is used for execution */
+	if (!sbsref->refjsonbpath)
 	{
-		Expr	   *e = (Expr *) lfirst(lc);
-
-		/* When slicing, individual subscript bounds can be omitted */
-		if (!e)
-		{
-			sbsrefstate->upperprovided[i] = false;
-			sbsrefstate->upperindexnull[i] = true;
-		}
-		else
-		{
-			sbsrefstate->upperprovided[i] = true;
-			/* Each subscript is evaluated into appropriate array entry */
-			ExecInitExprRec(e, state,
-							&sbsrefstate->upperindex[i],
-							&sbsrefstate->upperindexnull[i]);
+		i = 0;
+		foreach(lc, sbsref->refupperindexpr) {
+			Expr *e = (Expr *) lfirst(lc);
+
+			/* When slicing, individual subscript bounds can be omitted */
+			if (!e) {
+				sbsrefstate->upperprovided[i] = false;
+				sbsrefstate->upperindexnull[i] = true;
+			} else {
+				sbsrefstate->upperprovided[i] = true;
+				/* Each subscript is evaluated into appropriate array entry */
+				ExecInitExprRec(e, state,
+								&sbsrefstate->upperindex[i],
+								&sbsrefstate->upperindexnull[i]);
+			}
+			i++;
 		}
-		i++;
 	}
 
-	/* Evaluate lower subscripts similarly */
-	i = 0;
-	foreach(lc, sbsref->reflowerindexpr)
+	/* Evaluate lower subscripts similarly, unless refjsonbpath is used for execution  */
+	if (!sbsref->refjsonbpath)
 	{
-		Expr	   *e = (Expr *) lfirst(lc);
-
-		/* When slicing, individual subscript bounds can be omitted */
-		if (!e)
+		i = 0;
+		foreach(lc, sbsref->reflowerindexpr)
 		{
-			sbsrefstate->lowerprovided[i] = false;
-			sbsrefstate->lowerindexnull[i] = true;
-		}
-		else
-		{
-			sbsrefstate->lowerprovided[i] = true;
-			/* Each subscript is evaluated into appropriate array entry */
-			ExecInitExprRec(e, state,
-							&sbsrefstate->lowerindex[i],
-							&sbsrefstate->lowerindexnull[i]);
+			Expr	   *e = (Expr *) lfirst(lc);
+
+			/* When slicing, individual subscript bounds can be omitted */
+			if (!e)
+			{
+				sbsrefstate->lowerprovided[i] = false;
+				sbsrefstate->lowerindexnull[i] = true;
+			}
+			else
+			{
+				sbsrefstate->lowerprovided[i] = true;
+				/* Each subscript is evaluated into appropriate array entry */
+				ExecInitExprRec(e, state,
+								&sbsrefstate->lowerindex[i],
+								&sbsrefstate->lowerindexnull[i]);
+			}
+			i++;
 		}
-		i++;
 	}
 
 	/* SBSREF_SUBSCRIPTS checks and converts all the subscripts at once */
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 7bc823507f1..d1bd575d9bd 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -284,6 +284,13 @@ exprType(const Node *expr)
 		case T_PlaceHolderVar:
 			type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_FieldAccessorExpr:
+			/*
+			 * FieldAccessorExpr is not evaluable. Treat it as TEXT for collation,
+			 * deparsing, and similar purposes, since it represents a JSON field name.
+			 */
+			type = TEXTOID;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			type = InvalidOid;	/* keep compiler quiet */
@@ -1146,6 +1153,9 @@ exprSetCollation(Node *expr, Oid collation)
 		case T_MergeSupportFunc:
 			((MergeSupportFunc *) expr)->msfcollid = collation;
 			break;
+		case T_FieldAccessorExpr:
+			((FieldAccessorExpr *) expr)->faecollid = collation;
+			break;
 		case T_SubscriptingRef:
 			((SubscriptingRef *) expr)->refcollid = collation;
 			break;
@@ -2129,6 +2139,7 @@ expression_tree_walker_impl(Node *node,
 		case T_SortGroupClause:
 		case T_CTESearchClause:
 		case T_MergeSupportFunc:
+		case T_FieldAccessorExpr:
 			/* primitive node types with no expression subnodes */
 			break;
 		case T_WithCheckOption:
@@ -3008,6 +3019,7 @@ expression_tree_mutator_impl(Node *node,
 		case T_SortGroupClause:
 		case T_CTESearchClause:
 		case T_MergeSupportFunc:
+		case T_FieldAccessorExpr:
 			return copyObject(node);
 		case T_WithCheckOption:
 			{
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index 5d0ec6bf2fa..82cf60df822 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -15,21 +15,30 @@
 #include "postgres.h"
 
 #include "executor/execExpr.h"
+#include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "nodes/subscripting.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_expr.h"
 #include "utils/builtins.h"
 #include "utils/jsonb.h"
+#include "utils/jsonpath.h"
 
 
-/* SubscriptingRefState.workspace for jsonb subscripting execution */
+/*
+ * SubscriptingRefState.workspace for generic jsonb subscripting execution.
+ *
+ * Stores state for both jsonb simple subscripting and dot notation access.
+ * Dot notation additionally uses `jsonpath` for JsonPath evaluation.
+ */
 typedef struct JsonbSubWorkspace
 {
 	bool		expectArray;	/* jsonb root is expected to be an array */
 	Oid		   *indexOid;		/* OID of coerced subscript expression, could
 								 * be only integer or text */
 	Datum	   *index;			/* Subscript values in Datum format */
+	JsonPath   *jsonpath;		/* JsonPath for dot notation execution via
+								 * JsonPathQuery() */
 } JsonbSubWorkspace;
 
 static Node *
@@ -96,6 +105,244 @@ coerce_jsonpath_subscript(ParseState *pstate, Node *subExpr, Oid numtype)
 	return subExpr;
 }
 
+/*
+ * During transformation, determine whether to build a JsonPath
+ * for JsonPathQuery() execution.
+ *
+ * JsonPath is needed if the indirection list includes:
+ * - String-based access (dot notation)
+ * - Slice-based subscripting
+ *
+ * Otherwise, simple jsonb subscripting is enough.
+ */
+static bool
+jsonb_check_jsonpath_needed(List *indirection)
+{
+	ListCell   *lc;
+
+	foreach(lc, indirection)
+	{
+		Node	   *accessor = lfirst(lc);
+
+		if (IsA(accessor, String))
+			return true;
+		else
+		{
+			Assert(IsA(accessor, A_Indices));
+
+			if (castNode(A_Indices, accessor)->is_slice)
+				return true;
+		}
+	}
+
+	return false;
+}
+
+/*
+ * Helper functions for constructing JsonPath expressions.
+ *
+ * The make_jsonpath_item_* functions create various types of JsonPathParseItem
+ * nodes, which are used to build JsonPath expressions for jsonb simplified
+ * accessor.
+ */
+
+static JsonPathParseItem *
+make_jsonpath_item(JsonPathItemType type)
+{
+	JsonPathParseItem *v = palloc(sizeof(*v));
+
+	v->type = type;
+	v->next = NULL;
+
+	return v;
+}
+
+/*
+ * Build a JsonPathParseItem for a constant integer value.
+ *
+ * This function constructs a jpiNumeric item for use in JsonPath,
+ * and appends a matching Const(INT4) node to the given expression list
+ * for use in EXPLAIN, views, etc.
+ *
+ * Parameters:
+ * - val: integer constant value
+ * - exprs: list of expression nodes (updated in place)
+ */
+static JsonPathParseItem *
+make_jsonpath_item_int(int32 val, List **exprs)
+{
+	JsonPathParseItem *jpi = make_jsonpath_item(jpiNumeric);
+
+	jpi->value.numeric =
+		DatumGetNumeric(DirectFunctionCall1(int4_numeric, Int32GetDatum(val)));
+
+	*exprs = lappend(*exprs, makeConst(INT4OID, -1, InvalidOid, 4,
+									   Int32GetDatum(val), false, true));
+
+	return jpi;
+}
+
+/*
+ * Convert a constant integer expression into a JsonPathParseItem.
+ *
+ * The input expression must be a non-null constant of type INT4. Returns NULL otherwise.
+ * The function extracts the value and delegates it to make_jsonpath_item_int().
+ *
+ * Parameters:
+ * - pstate: parse state context
+ * - expr: input expression node
+ * - exprs: list of expression nodes (updated in place)
+ */
+static JsonPathParseItem *
+make_jsonpath_item_expr(ParseState *pstate, Node *expr, List **exprs)
+{
+	Const	   *cnst;
+
+	expr = transformExpr(pstate, expr, pstate->p_expr_kind);
+
+	if (IsA(expr, Const))
+	{
+		cnst = (Const *) expr;
+		if (cnst->consttype == INT4OID && !(cnst->constisnull))
+			return make_jsonpath_item_int(DatumGetInt32(cnst->constvalue), exprs);
+	}
+
+	return NULL;
+}
+
+/*
+ * Constructs a JsonPath expression from a list of indirections.
+ * This function is used when jsonb subscripting involves dot notation,
+ * requiring JsonPath-based evaluation.
+ *
+ * The function modifies the indirection list in place, removing processed
+ * elements as it converts them into JsonPath components, as follows:
+ * - String keys (dot notation) -> jpiKey items.
+ * - Array indices -> jpiIndexArray items.
+ *
+ * In addition to building the JsonPath expression, this function populates
+ * the following fields of the given SubscriptingRef:
+ * - refjsonbpath: the generated JsonPath
+ * - refupperindexpr: upper index expressions (object keys or array indexes)
+ * - reflowerindexpr: lower index expressions, remains NIL as slices are not yet supported.
+ *
+ * Parameters:
+ * - pstate: Parse state context.
+ * - indirection: List of subscripting expressions (modified in-place).
+ * - sbsref: SubscriptingRef node to update
+ */
+static void
+jsonb_subscript_make_jsonpath(ParseState *pstate, List **indirection, SubscriptingRef *sbsref)
+{
+	JsonPathParseResult jpres;
+	JsonPathParseItem *path = make_jsonpath_item(jpiRoot);
+	ListCell   *lc;
+	Datum		jsp;
+	int			pathlen = 0;
+	int			warning_location = -1;
+
+	sbsref->refupperindexpr = NIL;
+	sbsref->reflowerindexpr = NIL;
+	sbsref->refjsonbpath = NULL;
+
+	jpres.expr = path;
+	jpres.lax = true;
+
+	foreach(lc, *indirection)
+	{
+		Node	   *accessor = lfirst(lc);
+		JsonPathParseItem *jpi;
+
+		if (IsA(accessor, String))
+		{
+			char	   *field = strVal(accessor);
+			FieldAccessorExpr *accessor_expr;
+
+			jpi = make_jsonpath_item(jpiKey);
+			jpi->value.string.val = field;
+			jpi->value.string.len = strlen(field);
+
+			accessor_expr = makeNode(FieldAccessorExpr);
+			accessor_expr->xpr.type = T_FieldAccessorExpr;
+			accessor_expr->fieldname = field;
+			accessor_expr->location = -1;
+
+			sbsref->refupperindexpr = lappend(sbsref->refupperindexpr, accessor_expr);
+		}
+		else if (IsA(accessor, A_Indices))
+		{
+			A_Indices  *ai = castNode(A_Indices, accessor);
+
+			jpi = make_jsonpath_item(jpiIndexArray);
+			jpi->value.array.nelems = 1;
+			jpi->value.array.elems = palloc(sizeof(jpi->value.array.elems[0]));
+
+			if (ai->is_slice)
+			{
+				Node	   *expr = ai->uidx ? ai->uidx : ai->lidx;
+
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("jsonb subscript does not support slices"),
+						 parser_errposition(pstate, exprLocation(expr))));
+			}
+			else
+			{
+				JsonPathParseItem *jpi_from = NULL;
+
+				Assert(ai->uidx && !ai->lidx);
+				jpi_from = make_jsonpath_item_expr(pstate, ai->uidx, &sbsref->refupperindexpr);
+				if (jpi_from == NULL)
+				{
+					/*
+					 * postpone emitting the warning until the end of this
+					 * function
+					 */
+					warning_location = exprLocation(ai->uidx);
+					pfree(jpi->value.array.elems);
+					pfree(jpi);
+					break;
+				}
+
+				jpi->value.array.elems[0].from = jpi_from;
+				jpi->value.array.elems[0].to = NULL;
+			}
+		}
+		else
+
+			/*
+			 * Unsupported node type for creating jsonpath. Instead of
+			 * throwing an ERROR, break here so that we create a jsonpath from
+			 * as many indirection elements as we can and let
+			 * transformIndirection() fallback to alternative logic to handle
+			 * the remaining indirection elements.
+			 */
+			break;
+
+		/* append path item */
+		path->next = jpi;
+		path = jpi;
+		pathlen++;
+	}
+
+	if (pathlen == 0)
+		return;
+
+	*indirection = list_delete_first_n(*indirection, pathlen);
+
+	/* emit warning conditionally to minimize duplicate warnings */
+	if (list_length(*indirection) > 0)
+		ereport(WARNING,
+				errcode(ERRCODE_WARNING),
+				errmsg("mixed usage of jsonb simplified accessor syntax and jsonb subscripting."),
+				errhint("use dot-notation for member access, or use non-null integer constants subscripting for array access."),
+				parser_errposition(pstate, warning_location));
+
+	jsp = jsonPathFromParseResult(&jpres, 0, NULL);
+
+	sbsref->refjsonbpath = (Node *) makeConst(JSONPATHOID, -1, InvalidOid, -1, jsp, false, false);
+}
+
 /*
  * Finish parse analysis of a SubscriptingRef expression for a jsonb.
  *
@@ -112,47 +359,43 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	List	   *upperIndexpr = NIL;
 	ListCell   *idx;
 
+	/* Determine the result type of the subscripting operation; always jsonb */
+	sbsref->refrestype = JSONBOID;
+	sbsref->reftypmod = -1;
+
+	if (jsonb_check_jsonpath_needed(*indirection))
+	{
+		jsonb_subscript_make_jsonpath(pstate, indirection, sbsref);
+		if (sbsref->refjsonbpath)
+			return;
+	}
+
 	/*
-	 * Transform and convert the subscript expressions. Jsonb subscripting
-	 * does not support slices, look only at the upper index.
+	 * We would only reach here if json simplified accessor is not needed, or
+	 * if jsonb_subscript_make_jsonpath() didn't consume any indirection
+	 * element — either way, the first indirection element could not be
+	 * converted into a JsonPath component. This happens when it's a non-slice
+	 * A_Indices with a non-integer upper index. The code below falls back to
+	 * traditional jsonb subscripting for such cases.
 	 */
 	foreach(idx, *indirection)
 	{
+		Node	   *i = lfirst(idx);
 		A_Indices  *ai;
 		Node	   *subExpr;
 
 		if (!IsA(lfirst(idx), A_Indices))
 			break;
 
-		ai = lfirst_node(A_Indices, idx);
+		ai = castNode(A_Indices, i);
 
-		if (isSlice)
-		{
-			Node	   *expr = ai->uidx ? ai->uidx : ai->lidx;
+		if (ai->is_slice)
+			break;
 
-			ereport(ERROR,
-					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("jsonb subscript does not support slices"),
-					 parser_errposition(pstate, exprLocation(expr))));
-		}
+		Assert(!ai->lidx && ai->uidx);
 
-		if (ai->uidx)
-		{
-			subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
-			subExpr = coerce_jsonpath_subscript(pstate, subExpr, INT4OID);
-		}
-		else
-		{
-			/*
-			 * Slice with omitted upper bound. Should not happen as we already
-			 * errored out on slice earlier, but handle this just in case.
-			 */
-			Assert(isSlice && ai->is_slice);
-			ereport(ERROR,
-					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("jsonb subscript does not support slices"),
-					 parser_errposition(pstate, exprLocation(ai->uidx))));
-		}
+		subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
+		subExpr = coerce_jsonpath_subscript(pstate, subExpr, INT4OID);
 
 		upperIndexpr = lappend(upperIndexpr, subExpr);
 	}
@@ -161,10 +404,6 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->refupperindexpr = upperIndexpr;
 	sbsref->reflowerindexpr = NIL;
 
-	/* Determine the result type of the subscripting operation; always jsonb */
-	sbsref->refrestype = JSONBOID;
-	sbsref->reftypmod = -1;
-
 	/* Remove processed elements */
 	if (upperIndexpr)
 		*indirection = list_delete_first_n(*indirection, list_length(upperIndexpr));
@@ -219,7 +458,7 @@ jsonb_subscript_check_subscripts(ExprState *state,
 			 * For jsonb fetch and assign functions we need to provide path in
 			 * text format. Convert if it's not already text.
 			 */
-			if (workspace->indexOid[i] == INT4OID)
+			if (!workspace->jsonpath && workspace->indexOid[i] == INT4OID)
 			{
 				Datum		datum = sbsrefstate->upperindex[i];
 				char	   *cs = DatumGetCString(DirectFunctionCall1(int4out, datum));
@@ -247,17 +486,32 @@ jsonb_subscript_fetch(ExprState *state,
 {
 	SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
 	JsonbSubWorkspace *workspace = (JsonbSubWorkspace *) sbsrefstate->workspace;
-	Jsonb	   *jsonbSource;
 
 	/* Should not get here if source jsonb (or any subscript) is null */
 	Assert(!(*op->resnull));
 
-	jsonbSource = DatumGetJsonbP(*op->resvalue);
-	*op->resvalue = jsonb_get_element(jsonbSource,
-									  workspace->index,
-									  sbsrefstate->numupper,
-									  op->resnull,
-									  false);
+	if (workspace->jsonpath)
+	{
+		bool		empty = false;
+		bool		error = false;
+
+		*op->resvalue = JsonPathQuery(*op->resvalue, workspace->jsonpath,
+									  JSW_CONDITIONAL,
+									  &empty, &error, NULL,
+									  NULL);
+
+		*op->resnull = empty || error;
+	}
+	else
+	{
+		Jsonb	   *jsonbSource = DatumGetJsonbP(*op->resvalue);
+
+		*op->resvalue = jsonb_get_element(jsonbSource,
+										  workspace->index,
+										  sbsrefstate->numupper,
+										  op->resnull,
+										  false);
+	}
 }
 
 /*
@@ -365,7 +619,7 @@ jsonb_exec_setup(const SubscriptingRef *sbsref,
 {
 	JsonbSubWorkspace *workspace;
 	ListCell   *lc;
-	int			nupper = sbsref->refupperindexpr->length;
+	int			nupper = list_length(sbsref->refupperindexpr);
 	char	   *ptr;
 
 	/* Allocate type-specific workspace with space for per-subscript data */
@@ -374,6 +628,9 @@ jsonb_exec_setup(const SubscriptingRef *sbsref,
 	workspace->expectArray = false;
 	ptr = ((char *) workspace) + MAXALIGN(sizeof(JsonbSubWorkspace));
 
+	if (sbsref->refjsonbpath)
+		workspace->jsonpath = DatumGetJsonPathP(castNode(Const, sbsref->refjsonbpath)->constvalue);
+
 	/*
 	 * This coding assumes sizeof(Datum) >= sizeof(Oid), else we might
 	 * misalign the indexOid pointer
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 3d6e6bdbfd2..baa3ae97d57 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9329,10 +9329,13 @@ get_rule_expr(Node *node, deparse_context *context,
 				 * Parenthesize the argument unless it's a simple Var or a
 				 * FieldSelect.  (In particular, if it's another
 				 * SubscriptingRef, we *must* parenthesize to avoid
-				 * confusion.)
+				 * confusion.) Always add parenthesis if JSON simplified
+				 * accessor is used, for now.
 				 */
-				need_parens = !IsA(sbsref->refexpr, Var) &&
-					!IsA(sbsref->refexpr, FieldSelect);
+				need_parens = (!IsA(sbsref->refexpr, Var) &&
+					!IsA(sbsref->refexpr, FieldSelect)) ||
+						sbsref->refjsonbpath;
+
 				if (need_parens)
 					appendStringInfoChar(buf, '(');
 				get_rule_expr((Node *) sbsref->refexpr, context, showimplicit);
@@ -13005,17 +13008,35 @@ printSubscripts(SubscriptingRef *sbsref, deparse_context *context)
 	lowlist_item = list_head(sbsref->reflowerindexpr);	/* could be NULL */
 	foreach(uplist_item, sbsref->refupperindexpr)
 	{
-		appendStringInfoChar(buf, '[');
-		if (lowlist_item)
+		Node *upper = (Node *) lfirst(uplist_item);
+
+		if (upper && IsA(upper, FieldAccessorExpr))
 		{
+			FieldAccessorExpr *fae = (FieldAccessorExpr *) upper;
+
+			/* Use dot-notation for field access */
+			appendStringInfoChar(buf, '.');
+			appendStringInfoString(buf, quote_identifier(fae->fieldname));
+
+			/* Skip matching low index — field access doesn't use slices */
+			if (lowlist_item)
+				lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
+		}
+		else
+		{
+			/* Use JSONB array subscripting */
+			appendStringInfoChar(buf, '[');
+			if (lowlist_item)
+			{
+				/* If subexpression is NULL, get_rule_expr prints nothing */
+				get_rule_expr((Node *) lfirst(lowlist_item), context, false);
+				appendStringInfoChar(buf, ':');
+				lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
+			}
 			/* If subexpression is NULL, get_rule_expr prints nothing */
-			get_rule_expr((Node *) lfirst(lowlist_item), context, false);
-			appendStringInfoChar(buf, ':');
-			lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
+			get_rule_expr(upper, context, false);
+			appendStringInfoChar(buf, ']');
 		}
-		/* If subexpression is NULL, get_rule_expr prints nothing */
-		get_rule_expr((Node *) lfirst(uplist_item), context, false);
-		appendStringInfoChar(buf, ']');
 	}
 }
 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 6dfca3cb35b..b208d7949b1 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -708,18 +708,30 @@ typedef struct SubscriptingRef
 	int32		reftypmod pg_node_attr(query_jumble_ignore);
 	/* collation of result, or InvalidOid if none */
 	Oid			refcollid pg_node_attr(query_jumble_ignore);
-	/* expressions that evaluate to upper container indexes */
+
+	/*
+	 * expressions that evaluate to upper container indexes or expressions
+	 * that are collected but not evaluated when refjsonbpath is set.
+	 */
 	List	   *refupperindexpr;
 
 	/*
-	 * expressions that evaluate to lower container indexes, or NIL for single
-	 * container element.
+	 * expressions that evaluate to lower container indexes, or NIL for a
+	 * single container element, or expressions that are collected but not
+	 * evaluated when refjsonbpath is set.
 	 */
 	List	   *reflowerindexpr;
 	/* the expression that evaluates to a container value */
 	Expr	   *refexpr;
 	/* expression for the source value, or NULL if fetch */
 	Expr	   *refassgnexpr;
+
+	/*
+	 * container-specific extra information, currently used only by jsonb.
+	 * stores a JsonPath expression when jsonb dot notation is used. NULL for
+	 * simple subscripting.
+	 */
+	Node	   *refjsonbpath;
 } SubscriptingRef;
 
 /*
@@ -2371,4 +2383,41 @@ typedef struct OnConflictExpr
 	List	   *exclRelTlist;	/* tlist of the EXCLUDED pseudo relation */
 } OnConflictExpr;
 
+/*
+ * FieldAccessorExpr - represents a single object member access using dot-notation
+ *		in JSON simplified accessor syntax (e.g., jsonb_col.a).
+ *
+ * These nodes appear as list elements in SubscriptingRef.refupperindexpr to
+ * indicate JSON object key access. They are not evaluable expressions by
+ * themselves but serve as placeholders to preserve source-level syntax for
+ * rule rewriting and deparsing (e.g., in EXPLAIN and view definitions).
+ * Execution is handled by the enclosing SubscriptingRef.
+ *
+ * If dot-notation is used in a SubscriptingRef, the JSON path is represented
+ * as a flat list of FieldAccessorExpr nodes (for object field access), Const
+ * nodes (for array indexes), and NULLs (for omitted slice bounds), rather than
+ * through nested expression trees.
+ *
+ * Note: The flat representation avoids nested FieldAccessorExpr chains,
+ * simplifying evaluation and enabling standard-compliant behavior such as
+ * conditional array wrapping. This avoids the need for position-aware
+ * wrapping/unwrapping logic during execution.
+ *
+ * For example, in the expression:
+ *		('{"a": [{"b": 1}]}'::jsonb).a[0].b
+ * the SubscriptingRef will contain:
+ *		- refexpr: the base expression (the jsonb value)
+ *		- refupperindexpr: [FieldAccessorExpr("a"), Const(0),
+ *			FieldAccessorExpr("b")]
+ *		- reflowerindexpr: [NULL, NULL, NULL] (slice lower bounds not used here)
+ */
+typedef struct FieldAccessorExpr
+{
+	Expr		xpr;
+	char	   *fieldname;		/* name of the JSONB object field accessed via
+								 * dot notation */
+	Oid			faecollid pg_node_attr(query_jumble_ignore);
+	int			location;
+}			FieldAccessorExpr;
+
 #endif							/* PRIMNODES_H */
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.c b/src/interfaces/ecpg/test/expected/sql-sqljson.c
index 39221f9ea5d..e6a7ece6dab 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.c
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.c
@@ -417,12 +417,122 @@ if (sqlca.sqlcode < 0) sqlprint();}
 	  for (int i = 0; i < sizeof(is_json); i++)
 		  printf("Found is_json[%d]: %s\n", i, is_json[i] ? "true" : "false");
 
-  { ECPGdisconnect(__LINE__, "CURRENT");
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . \"a\" )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 118 "sqljson.pgc"
 
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 118 "sqljson.pgc"
 
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb . \"a\" )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 121 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 121 "sqljson.pgc"
+
+	// error
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . a )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 124 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 124 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . a . b )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 127 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 127 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( coalesce ( json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . c ) , 'null' ) )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 130 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 130 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ 0 ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 133 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 133 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b . x )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 136 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 136 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) [ 'b' ] [ 0 ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 139 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 139 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 142 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 142 "sqljson.pgc"
+
+	// error
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . * )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 145 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 145 "sqljson.pgc"
+
+	// error
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 148 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 148 "sqljson.pgc"
+
+	// error
+
+  { ECPGdisconnect(__LINE__, "CURRENT");
+#line 151 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 151 "sqljson.pgc"
+
 
   return 0;
 }
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
index e55a95dd711..19f8c58af06 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
@@ -268,5 +268,105 @@ SQL error: cannot use type jsonb in RETURNING clause of JSON_SERIALIZE() on line
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_get_data on line 102: RESULT: f offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 118: query: select json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . "a" ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 118: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 118: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 118: RESULT: {"b": 1, "c": 2} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 121: query: select json ( '{"a": {"b": 1, "c": 2}}' :: jsonb . "a" ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 121: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 121: bad response - ERROR:  schema "jsonb" does not exist
+LINE 1: select json ( '{"a": {"b": 1, "c": 2}}' :: jsonb . "a" )
+                                                   ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 3F000 (sqlcode -400): schema "jsonb" does not exist on line 121
+[NO_PID]: sqlca: code: -400, state: 3F000
+SQL error: schema "jsonb" does not exist on line 121
+[NO_PID]: ecpg_execute on line 124: query: select json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . a ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 124: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 124: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 124: RESULT: {"b": 1, "c": 2} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 127: query: select json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . a . b ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 127: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 127: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 127: RESULT: 1 offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 130: query: select json ( coalesce ( json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . c ) , 'null' ) ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 130: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 130: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 130: RESULT: null offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 133: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 0 ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 133: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 133: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 133: RESULT: {"x": 1} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 136: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b . x ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 136: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 136: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 136: RESULT: [1, [12, {"y": 1}]] offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 139: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 139: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 139: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 139: RESULT: {"x": 1} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 142: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 142: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 142: bad response - ERROR:  jsonb subscript does not support slices
+LINE 1: ..., {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] )
+                                                                ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 42804 (sqlcode -400): jsonb subscript does not support slices on line 142
+[NO_PID]: sqlca: code: -400, state: 42804
+SQL error: jsonb subscript does not support slices on line 142
+[NO_PID]: ecpg_execute on line 145: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . * ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 145: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 145: bad response - ERROR:  row expansion via "*" is not supported here
+LINE 1: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x...
+                        ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 0A000 (sqlcode -400): row expansion via "*" is not supported here on line 145
+[NO_PID]: sqlca: code: -400, state: 0A000
+SQL error: row expansion via "*" is not supported here on line 145
+[NO_PID]: ecpg_execute on line 148: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 148: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 148: bad response - ERROR:  jsonb subscript does not support slices
+LINE 1: ...x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )
+                                                                ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 42804 (sqlcode -400): jsonb subscript does not support slices on line 148
+[NO_PID]: sqlca: code: -400, state: 42804
+SQL error: jsonb subscript does not support slices on line 148
 [NO_PID]: ecpg_finish: connection ecpg1_regression closed
 [NO_PID]: sqlca: code: 0, state: 00000
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
index 83f8df13e5a..442d36931f1 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
@@ -28,3 +28,10 @@ Found is_json[4]: false
 Found is_json[5]: false
 Found is_json[6]: true
 Found is_json[7]: false
+Found json={"b": 1, "c": 2}
+Found json={"b": 1, "c": 2}
+Found json=1
+Found json=null
+Found json={"x": 1}
+Found json=[1, [12, {"y": 1}]]
+Found json={"x": 1}
diff --git a/src/interfaces/ecpg/test/sql/sqljson.pgc b/src/interfaces/ecpg/test/sql/sqljson.pgc
index ddcbcc3b3cb..57a9bff424d 100644
--- a/src/interfaces/ecpg/test/sql/sqljson.pgc
+++ b/src/interfaces/ecpg/test/sql/sqljson.pgc
@@ -115,6 +115,39 @@ EXEC SQL END DECLARE SECTION;
 	  for (int i = 0; i < sizeof(is_json); i++)
 		  printf("Found is_json[%d]: %s\n", i, is_json[i] ? "true" : "false");
 
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}}'::jsonb)."a") INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON('{"a": {"b": 1, "c": 2}}'::jsonb."a") INTO :json;
+	// error
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}}'::jsonb).a) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}}'::jsonb).a.b) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(COALESCE(JSON(('{"a": {"b": 1, "c": 2}}'::jsonb).c), 'null')) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b[0]) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b.x) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb)['b'][0]) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b[1].x[0:]) INTO :json;
+	// error
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).*) INTO :json;
+	// error
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb)['b'][0:]) INTO :json;
+	// error
+
   EXEC SQL DISCONNECT;
 
   return 0;
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5a1eb18aba2..30a7023513d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -4989,6 +4989,12 @@ select ('123'::jsonb)['a'];
  
 (1 row)
 
+select ('123'::jsonb).a;
+ a 
+---
+ 
+(1 row)
+
 select ('123'::jsonb)[0];
  jsonb 
 -------
@@ -5001,12 +5007,24 @@ select ('123'::jsonb)[NULL];
  
 (1 row)
 
+select ('123'::jsonb).NULL;
+ null 
+------
+ 
+(1 row)
+
 select ('{"a": 1}'::jsonb)['a'];
  jsonb 
 -------
  1
 (1 row)
 
+select ('{"a": 1}'::jsonb).a;
+ a 
+---
+ 1
+(1 row)
+
 select ('{"a": 1}'::jsonb)[0];
  jsonb 
 -------
@@ -5019,6 +5037,12 @@ select ('{"a": 1}'::jsonb)['not_exist'];
  
 (1 row)
 
+select ('{"a": 1}'::jsonb)."not_exist";
+ not_exist 
+-----------
+ 
+(1 row)
+
 select ('{"a": 1}'::jsonb)[NULL];
  jsonb 
 -------
@@ -5031,6 +5055,12 @@ select ('[1, "2", null]'::jsonb)['a'];
  
 (1 row)
 
+select ('[1, "2", null]'::jsonb).a;
+ a 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[0];
  jsonb 
 -------
@@ -5043,6 +5073,12 @@ select ('[1, "2", null]'::jsonb)['1'];
  "2"
 (1 row)
 
+select ('[1, "2", null]'::jsonb)."1";
+ 1 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[1.0];
 ERROR:  subscript type numeric is not supported
 LINE 1: select ('[1, "2", null]'::jsonb)[1.0];
@@ -5072,6 +5108,12 @@ select ('[1, "2", null]'::jsonb)[1]['a'];
  
 (1 row)
 
+select ('[1, "2", null]'::jsonb)[1].a;
+ a 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[1][0];
  jsonb 
 -------
@@ -5084,56 +5126,135 @@ select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['b'];
  "c"
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).b;
+  b  
+-----
+ "c"
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'];
    jsonb   
 -----------
  [1, 2, 3]
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d;
+     d     
+-----------
+ [1, 2, 3]
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'][1];
  jsonb 
 -------
  2
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d[1];
+ d 
+---
+ 2
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d']['a'];
  jsonb 
 -------
  
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['a'];
+WARNING:  mixed usage of jsonb simplified accessor syntax and jsonb subscripting.
+LINE 1: select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['a'];
+                                                               ^
+HINT:  use dot-notation for member access, or use non-null integer constants subscripting for array access.
+ d 
+---
+ 
+(1 row)
+
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d.a;
+ a 
+---
+ 
+(1 row)
+
+select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d['a'][0];
+WARNING:  mixed usage of jsonb simplified accessor syntax and jsonb subscripting.
+LINE 1: ..."a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d['a'][0];
+                                                               ^
+HINT:  use dot-notation for member access, or use non-null integer constants subscripting for array access.
+ d 
+---
+ 1
+(1 row)
+
+select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d.a[0];
+ a 
+---
+ 1
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1'];
      jsonb     
 ---------------
  {"a2": "aaa"}
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1;
+      a1       
+---------------
+ {"a2": "aaa"}
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2'];
  jsonb 
 -------
  "aaa"
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2;
+  a2   
+-------
+ "aaa"
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']['a3'];
  jsonb 
 -------
  
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2.a3;
+ a3 
+----
+ 
+(1 row)
+
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'];
          jsonb         
 -----------------------
  ["aaa", "bbb", "ccc"]
 (1 row)
 
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1;
+          b1           
+-----------------------
+ ["aaa", "bbb", "ccc"]
+(1 row)
+
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'][2];
  jsonb 
 -------
  "ccc"
 (1 row)
 
--- slices are not supported
-select ('{"a": 1}'::jsonb)['a':'b'];
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1[2];
+  b1   
+-------
+ "ccc"
+(1 row)
+
+select ('{"a": 1}'::jsonb)['a':'b']; -- fails
 ERROR:  jsonb subscript does not support slices
 LINE 1: select ('{"a": 1}'::jsonb)['a':'b'];
                                        ^
@@ -5831,3 +5952,347 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
  12345
 (1 row)
 
+-- dot notation
+CREATE TABLE test_jsonb_dot_notation AS
+SELECT '{"a": [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], "b": [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]}'::jsonb jb;
+SELECT (jb).a FROM test_jsonb_dot_notation;
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+SELECT (jb)."a" FROM test_jsonb_dot_notation; -- double quote should work
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+SELECT (jb).'a' FROM test_jsonb_dot_notation; -- single quote should not work
+ERROR:  syntax error at or near "'a'"
+LINE 1: SELECT (jb).'a' FROM test_jsonb_dot_notation;
+                    ^
+SELECT (jb).b FROM test_jsonb_dot_notation;
+                         b                         
+---------------------------------------------------
+ [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]
+(1 row)
+
+SELECT (jb).c FROM test_jsonb_dot_notation;
+ c 
+---
+ 
+(1 row)
+
+SELECT (jb).a.b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT (jb).a[2].b FROM test_jsonb_dot_notation;
+  b  
+-----
+ "c"
+(1 row)
+
+SELECT (jb).a.x.y FROM test_jsonb_dot_notation;
+   y   
+-------
+ "yyy"
+(1 row)
+
+SELECT (jb).b.x.z FROM test_jsonb_dot_notation;
+   z   
+-------
+ "ZZZ"
+(1 row)
+
+SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
+ c 
+---
+ 
+(1 row)
+
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t; -- fails
+ERROR:  jsonb subscript does not support slices
+SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
+ERROR:  type jsonb is not composite
+-- explains should work
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+                  QUERY PLAN                  
+----------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation t
+   Output: (jb).a
+(2 rows)
+
+SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb).a[1]
+(2 rows)
+
+SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+ a 
+---
+ 2
+(1 row)
+
+-- views should work
+CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT (jb).a[3].x.y AS y
+   FROM test_jsonb_dot_notation
+-- mixed syntax
+DROP VIEW test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb['a'::text]).b
+(2 rows)
+
+SELECT (jb)['a'].b FROM test_jsonb_dot_notation; -- returns an array due to lax mode
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT (jb['a'::text]).b AS b
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+WARNING:  mixed usage of jsonb simplified accessor syntax and jsonb subscripting.
+LINE 1: EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a['b'] FROM test_js...
+                                                   ^
+HINT:  use dot-notation for member access, or use non-null integer constants subscripting for array access.
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: ((jb).a)['b'::text]
+(2 rows)
+
+SELECT (jb).a['b'] FROM test_jsonb_dot_notation; -- returns NULL due to strict mode with warnings
+WARNING:  mixed usage of jsonb simplified accessor syntax and jsonb subscripting.
+LINE 1: SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+                      ^
+HINT:  use dot-notation for member access, or use non-null integer constants subscripting for array access.
+ a 
+---
+ 
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+WARNING:  mixed usage of jsonb simplified accessor syntax and jsonb subscripting.
+LINE 2: SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+                      ^
+HINT:  use dot-notation for member access, or use non-null integer constants subscripting for array access.
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT ((jb).a)['b'::text] AS a
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+ a 
+---
+ 
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+WARNING:  mixed usage of jsonb simplified accessor syntax and jsonb subscripting.
+LINE 1: EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b.x['z'] FROM test_...
+                                                     ^
+HINT:  use dot-notation for member access, or use non-null integer constants subscripting for array access.
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: ((jb).b.x)['z'::text]
+(2 rows)
+
+SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation; -- warnings
+WARNING:  mixed usage of jsonb simplified accessor syntax and jsonb subscripting.
+LINE 1: SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+                        ^
+HINT:  use dot-notation for member access, or use non-null integer constants subscripting for array access.
+   x   
+-------
+ "ZZZ"
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+WARNING:  mixed usage of jsonb simplified accessor syntax and jsonb subscripting.
+LINE 2: SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+                        ^
+HINT:  use dot-notation for member access, or use non-null integer constants subscripting for array access.
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT ((jb).b.x)['z'::text] AS x
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+   x   
+-------
+ "ZZZ"
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+WARNING:  mixed usage of jsonb simplified accessor syntax and jsonb subscripting.
+LINE 1: EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b'].x['z'] FROM te...
+                                                        ^
+HINT:  use dot-notation for member access, or use non-null integer constants subscripting for array access.
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: ((jb['b'::text]).x)['z'::text]
+(2 rows)
+
+SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;  -- warnings
+WARNING:  mixed usage of jsonb simplified accessor syntax and jsonb subscripting.
+LINE 1: SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+                           ^
+HINT:  use dot-notation for member access, or use non-null integer constants subscripting for array access.
+   x   
+-------
+ "ZZZ"
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+WARNING:  mixed usage of jsonb simplified accessor syntax and jsonb subscripting.
+LINE 2: SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+                           ^
+HINT:  use dot-notation for member access, or use non-null integer constants subscripting for array access.
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT ((jb['b'::text]).x)['z'::text] AS x
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+   x   
+-------
+ "ZZZ"
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+WARNING:  mixed usage of jsonb simplified accessor syntax and jsonb subscripting.
+LINE 1: EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x'].z FROM test_...
+                                                   ^
+HINT:  use dot-notation for member access, or use non-null integer constants subscripting for array access.
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (((jb).b)['x'::text]).z
+(2 rows)
+
+SELECT (jb).b['x'].z FROM test_jsonb_dot_notation; -- returns NULL with warnings
+WARNING:  mixed usage of jsonb simplified accessor syntax and jsonb subscripting.
+LINE 1: SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+                      ^
+HINT:  use dot-notation for member access, or use non-null integer constants subscripting for array access.
+ z 
+---
+ 
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+WARNING:  mixed usage of jsonb simplified accessor syntax and jsonb subscripting.
+LINE 2: SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+                      ^
+HINT:  use dot-notation for member access, or use non-null integer constants subscripting for array access.
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT (((jb).b)['x'::text]).z AS z
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+ z 
+---
+ 
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+WARNING:  mixed usage of jsonb simplified accessor syntax and jsonb subscripting.
+LINE 1: EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x']['z'] FROM te...
+                                                   ^
+HINT:  use dot-notation for member access, or use non-null integer constants subscripting for array access.
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: ((jb).b)['x'::text]['z'::text]
+(2 rows)
+
+SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation; -- returns NULL with warnings
+WARNING:  mixed usage of jsonb simplified accessor syntax and jsonb subscripting.
+LINE 1: SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+                      ^
+HINT:  use dot-notation for member access, or use non-null integer constants subscripting for array access.
+ b 
+---
+ 
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+WARNING:  mixed usage of jsonb simplified accessor syntax and jsonb subscripting.
+LINE 2: SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+                      ^
+HINT:  use dot-notation for member access, or use non-null integer constants subscripting for array access.
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT ((jb).b)['x'::text]['z'::text] AS b
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+ b 
+---
+ 
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb['b'::text]['x'::text]).z
+(2 rows)
+
+SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation; -- returns NULL with warnings
+ z 
+---
+ 
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT (jb['b'::text]['x'::text]).z AS z
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+ z 
+---
+ 
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+-- clean up
+DROP TABLE test_jsonb_dot_notation;
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 57c11acddfe..a0fad90a9d5 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1304,33 +1304,51 @@ select jsonb_insert('{"a": {"b": "value"}}', '{a, b}', '"new_value"', true);
 
 -- jsonb subscript
 select ('123'::jsonb)['a'];
+select ('123'::jsonb).a;
 select ('123'::jsonb)[0];
 select ('123'::jsonb)[NULL];
+select ('123'::jsonb).NULL;
 select ('{"a": 1}'::jsonb)['a'];
+select ('{"a": 1}'::jsonb).a;
 select ('{"a": 1}'::jsonb)[0];
 select ('{"a": 1}'::jsonb)['not_exist'];
+select ('{"a": 1}'::jsonb)."not_exist";
 select ('{"a": 1}'::jsonb)[NULL];
 select ('[1, "2", null]'::jsonb)['a'];
+select ('[1, "2", null]'::jsonb).a;
 select ('[1, "2", null]'::jsonb)[0];
 select ('[1, "2", null]'::jsonb)['1'];
+select ('[1, "2", null]'::jsonb)."1";
 select ('[1, "2", null]'::jsonb)[1.0];
 select ('[1, "2", null]'::jsonb)[2];
 select ('[1, "2", null]'::jsonb)[3];
 select ('[1, "2", null]'::jsonb)[-2];
 select ('[1, "2", null]'::jsonb)[1]['a'];
+select ('[1, "2", null]'::jsonb)[1].a;
 select ('[1, "2", null]'::jsonb)[1][0];
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['b'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).b;
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d;
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'][1];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d[1];
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d']['a'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['a'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d.a;
+select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d['a'][0];
+select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d.a[0];
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1;
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2;
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']['a3'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2.a3;
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'];
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1;
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'][2];
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1[2];
 
--- slices are not supported
-select ('{"a": 1}'::jsonb)['a':'b'];
+select ('{"a": 1}'::jsonb)['a':'b']; -- fails
 select ('[1, "2", null]'::jsonb)[1:2];
 select ('[1, "2", null]'::jsonb)[:2];
 select ('[1, "2", null]'::jsonb)[1:];
@@ -1590,3 +1608,92 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::float8;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int2;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int4;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
+
+-- dot notation
+CREATE TABLE test_jsonb_dot_notation AS
+SELECT '{"a": [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], "b": [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]}'::jsonb jb;
+
+SELECT (jb).a FROM test_jsonb_dot_notation;
+SELECT (jb)."a" FROM test_jsonb_dot_notation; -- double quote should work
+SELECT (jb).'a' FROM test_jsonb_dot_notation; -- single quote should not work
+SELECT (jb).b FROM test_jsonb_dot_notation;
+SELECT (jb).c FROM test_jsonb_dot_notation;
+SELECT (jb).a.b FROM test_jsonb_dot_notation;
+SELECT (jb).a[2].b FROM test_jsonb_dot_notation;
+SELECT (jb).a.x.y FROM test_jsonb_dot_notation;
+SELECT (jb).b.x.z FROM test_jsonb_dot_notation;
+SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t; -- fails
+SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
+
+-- explains should work
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+
+-- views should work
+CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+
+-- mixed syntax
+DROP VIEW test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
+SELECT (jb)['a'].b FROM test_jsonb_dot_notation; -- returns an array due to lax mode
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+SELECT (jb).a['b'] FROM test_jsonb_dot_notation; -- returns NULL due to strict mode with warnings
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation; -- warnings
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;  -- warnings
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+SELECT (jb).b['x'].z FROM test_jsonb_dot_notation; -- returns NULL with warnings
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation; -- returns NULL with warnings
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
+SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation; -- returns NULL with warnings
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+-- clean up
+DROP TABLE test_jsonb_dot_notation;
\ No newline at end of file
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v12-0003-Export-jsonPathFromParseResult.patch (2.7K, ../../CAK98qZ0whQ=c+JGXbGSEBxCtLgy6sf-YGYqsKTAGsS-wt0wj+A@mail.gmail.com/8-v12-0003-Export-jsonPathFromParseResult.patch)
  download | inline diff:
From ba112213dfab562ea790c825c857fe0df3241927 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v12 3/7] Export jsonPathFromParseResult()

This is a preparation step for a future commit that will reuse the
aforementioned function.

Authored-by: Nikita Glukhov <[email protected]>
Reviewed-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Jian He <[email protected]>
---
 src/backend/utils/adt/jsonpath.c | 19 +++++++++++++++----
 src/include/utils/jsonpath.h     |  4 ++++
 2 files changed, 19 insertions(+), 4 deletions(-)

diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index 762f7e8a09d..976aee84cf2 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -166,15 +166,13 @@ jsonpath_send(PG_FUNCTION_ARGS)
  * Converts C-string to a jsonpath value.
  *
  * Uses jsonpath parser to turn string into an AST, then
- * flattenJsonPathParseItem() does second pass turning AST into binary
+ * jsonPathFromParseResult() does second pass turning AST into binary
  * representation of jsonpath.
  */
 static Datum
 jsonPathFromCstring(char *in, int len, struct Node *escontext)
 {
 	JsonPathParseResult *jsonpath = parsejsonpath(in, len, escontext);
-	JsonPath   *res;
-	StringInfoData buf;
 
 	if (SOFT_ERROR_OCCURRED(escontext))
 		return (Datum) 0;
@@ -185,8 +183,21 @@ jsonPathFromCstring(char *in, int len, struct Node *escontext)
 				 errmsg("invalid input syntax for type %s: \"%s\"", "jsonpath",
 						in)));
 
+	return jsonPathFromParseResult(jsonpath, 4 * len, escontext);
+}
+
+/*
+ * Converts jsonpath Abstract Syntax Tree (AST) into jsonpath value in binary.
+ */
+Datum
+jsonPathFromParseResult(JsonPathParseResult *jsonpath, int estimated_len,
+						struct Node *escontext)
+{
+	JsonPath   *res;
+	StringInfoData buf;
+
 	initStringInfo(&buf);
-	enlargeStringInfo(&buf, 4 * len /* estimation */ );
+	enlargeStringInfo(&buf, estimated_len);
 
 	appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
 
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index 23a76d233e9..e05941623e7 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -281,6 +281,10 @@ extern JsonPathParseResult *parsejsonpath(const char *str, int len,
 extern bool jspConvertRegexFlags(uint32 xflags, int *result,
 								 struct Node *escontext);
 
+extern Datum jsonPathFromParseResult(JsonPathParseResult *jsonpath,
+									 int estimated_len,
+									 struct Node *escontext);
+
 /*
  * Struct for details about external variables passed into jsonpath executor
  */
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v12-0002-Allow-Generic-Type-Subscripting-to-Accept-Dot-No.patch (10.6K, ../../CAK98qZ0whQ=c+JGXbGSEBxCtLgy6sf-YGYqsKTAGsS-wt0wj+A@mail.gmail.com/9-v12-0002-Allow-Generic-Type-Subscripting-to-Accept-Dot-No.patch)
  download | inline diff:
From 21464ae4f20536366ffc06edadae5e76407a4f11 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v12 2/7] Allow Generic Type Subscripting to Accept Dot
 Notation (.) as Input

This change extends generic type subscripting to recognize dot
notation (.) in addition to bracket notation ([]). While this does not
yet provide full support for dot notation, it enables subscripting
containers to process it in the future.

For now, container-specific transform functions only handle
subscripting indices and stop processing when encountering dot
notation. It is up to individual containers to decide how to transform
dot notation in subsequent updates.

Authored-by: Nikita Glukhov <[email protected]>
Reviewed-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Jian He <[email protected]>
---
 src/backend/parser/parse_expr.c   | 66 ++++++++++++++++++++-----------
 src/backend/parser/parse_node.c   | 41 +++++++++++++++++--
 src/backend/parser/parse_target.c |  3 +-
 src/backend/utils/adt/arraysubs.c | 13 ++++--
 src/backend/utils/adt/jsonbsubs.c | 11 +++++-
 src/include/parser/parse_node.h   |  3 +-
 6 files changed, 105 insertions(+), 32 deletions(-)

diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index e1565e11d09..07d46747811 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -442,8 +442,9 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 	ListCell   *i;
 
 	/*
-	 * We have to split any field-selection operations apart from
-	 * subscripting.  Adjacent A_Indices nodes have to be treated as a single
+	 * Combine field names and subscripts into a single indirection list, as
+	 * some subscripting containers, such as jsonb, support field access using
+	 * dot notation. Adjacent A_Indices nodes have to be treated as a single
 	 * multidimensional subscript operation.
 	 */
 	foreach(i, ind->indirection)
@@ -461,19 +462,43 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		}
 		else
 		{
-			Node	   *newresult;
-
 			Assert(IsA(n, String));
+			subscripts = lappend(subscripts, n);
+		}
+	}
+
+	while (subscripts)
+	{
+		/* try processing container subscripts first */
+		Node	   *newresult = (Node *)
+			transformContainerSubscripts(pstate,
+										 result,
+										 exprType(result),
+										 exprTypmod(result),
+										 &subscripts,
+										 false,
+										 true);
+
+		if (!newresult)
+		{
+			/*
+			 * generic subscripting failed; falling back to function call or
+			 * field selection for a composite type.
+			 */
+			Node	   *n;
+
+			Assert(subscripts);
 
-			/* process subscripts before this field selection */
-			while (subscripts)
-				result = (Node *) transformContainerSubscripts(pstate,
-															   result,
-															   exprType(result),
-															   exprTypmod(result),
-															   &subscripts,
-															   false);
+			n = linitial(subscripts);
+
+			if (!IsA(n, String))
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("cannot subscript type %s because it does not support subscripting",
+								format_type_be(exprType(result))),
+						 parser_errposition(pstate, exprLocation(result))));
 
+			/* try to find function for field selection */
 			newresult = ParseFuncOrColumn(pstate,
 										  list_make1(n),
 										  list_make1(result),
@@ -481,19 +506,16 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 										  NULL,
 										  false,
 										  location);
-			if (newresult == NULL)
+
+			if (!newresult)
 				unknown_attribute(pstate, result, strVal(n), location);
-			result = newresult;
+
+			/* consume field select */
+			subscripts = list_delete_first(subscripts);
 		}
+
+		result = newresult;
 	}
-	/* process trailing subscripts, if any */
-	while (subscripts)
-		result = (Node *) transformContainerSubscripts(pstate,
-													   result,
-													   exprType(result),
-													   exprTypmod(result),
-													   &subscripts,
-													   false);
 
 	return result;
 }
diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c
index 19a6b678e67..b3e476eb181 100644
--- a/src/backend/parser/parse_node.c
+++ b/src/backend/parser/parse_node.c
@@ -238,6 +238,8 @@ transformContainerType(Oid *containerType, int32 *containerTypmod)
  * containerTypMod	typmod for the container
  * indirection		Untransformed list of subscripts (must not be NIL)
  * isAssignment		True if this will become a container assignment.
+ * noError			True for return NULL with no error, if the container type
+ * 					is not subscriptable.
  */
 SubscriptingRef *
 transformContainerSubscripts(ParseState *pstate,
@@ -245,13 +247,15 @@ transformContainerSubscripts(ParseState *pstate,
 							 Oid containerType,
 							 int32 containerTypMod,
 							 List **indirection,
-							 bool isAssignment)
+							 bool isAssignment,
+							 bool noError)
 {
 	SubscriptingRef *sbsref;
 	const SubscriptRoutines *sbsroutines;
 	Oid			elementType;
 	bool		isSlice = false;
 	ListCell   *idx;
+	int			indirection_length = list_length(*indirection);
 
 	/*
 	 * Determine the actual container type, smashing any domain.  In the
@@ -267,11 +271,16 @@ transformContainerSubscripts(ParseState *pstate,
 	 */
 	sbsroutines = getSubscriptingRoutines(containerType, &elementType);
 	if (!sbsroutines)
+	{
+		if (noError)
+			return NULL;
+
 		ereport(ERROR,
 				(errcode(ERRCODE_DATATYPE_MISMATCH),
 				 errmsg("cannot subscript type %s because it does not support subscripting",
 						format_type_be(containerType)),
 				 parser_errposition(pstate, exprLocation(containerBase))));
+	}
 
 	/*
 	 * Detect whether any of the indirection items are slice specifiers.
@@ -282,9 +291,9 @@ transformContainerSubscripts(ParseState *pstate,
 	 */
 	foreach(idx, *indirection)
 	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
+		Node	   *ai = lfirst(idx);
 
-		if (ai->is_slice)
+		if (IsA(ai, A_Indices) && castNode(A_Indices, ai)->is_slice)
 		{
 			isSlice = true;
 			break;
@@ -312,6 +321,32 @@ transformContainerSubscripts(ParseState *pstate,
 	sbsroutines->transform(sbsref, indirection, pstate,
 						   isSlice, isAssignment);
 
+	/*
+	 * Error out, if datatype failed to consume any indirection elements.
+	 */
+	if (list_length(*indirection) == indirection_length)
+	{
+		Node	   *ind = linitial(*indirection);
+
+		if (noError)
+			return NULL;
+
+		if (IsA(ind, String))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("type %s does not support dot notation",
+							format_type_be(containerType)),
+					 parser_errposition(pstate, exprLocation(containerBase))));
+		else if (IsA(ind, A_Indices))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("type %s does not support array subscripting",
+							format_type_be(containerType)),
+					 parser_errposition(pstate, exprLocation(containerBase))));
+		else
+			elog(ERROR, "invalid indirection operation: %d", nodeTag(ind));
+	}
+
 	/*
 	 * Verify we got a valid type (this defends, for example, against someone
 	 * using array_subscript_handler as typsubscript without setting typelem).
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 4675a523045..3ef5897f2eb 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -937,7 +937,8 @@ transformAssignmentSubscripts(ParseState *pstate,
 										  containerType,
 										  containerTypMod,
 										  &subscripts,
-										  true);
+										  true,
+										  false);
 
 	typeNeeded = sbsref->refrestype;
 	typmodNeeded = sbsref->reftypmod;
diff --git a/src/backend/utils/adt/arraysubs.c b/src/backend/utils/adt/arraysubs.c
index 234c2c278c1..d03d3519dfd 100644
--- a/src/backend/utils/adt/arraysubs.c
+++ b/src/backend/utils/adt/arraysubs.c
@@ -62,6 +62,7 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	List	   *upperIndexpr = NIL;
 	List	   *lowerIndexpr = NIL;
 	ListCell   *idx;
+	int			ndim;
 
 	/*
 	 * Transform the subscript expressions, and separate upper and lower
@@ -73,9 +74,14 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	 */
 	foreach(idx, *indirection)
 	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
+		A_Indices  *ai;
 		Node	   *subexpr;
 
+		if (!IsA(lfirst(idx), A_Indices))
+			break;
+
+		ai = lfirst_node(A_Indices, idx);
+
 		if (isSlice)
 		{
 			if (ai->lidx)
@@ -145,14 +151,15 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->reflowerindexpr = lowerIndexpr;
 
 	/* Verify subscript list lengths are within implementation limit */
-	if (list_length(upperIndexpr) > MAXDIM)
+	ndim = list_length(upperIndexpr);
+	if (ndim > MAXDIM)
 		ereport(ERROR,
 				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 				 errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
 						list_length(upperIndexpr), MAXDIM)));
 	/* We need not check lowerIndexpr separately */
 
-	*indirection = NIL;
+	*indirection = list_delete_first_n(*indirection, ndim);
 
 	/*
 	 * Determine the result type of the subscripting operation.  It's the same
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index 8ad6aa1ad4f..a0d38a0fd80 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -55,9 +55,14 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	 */
 	foreach(idx, *indirection)
 	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
+		A_Indices  *ai;
 		Node	   *subExpr;
 
+		if (!IsA(lfirst(idx), A_Indices))
+			break;
+
+		ai = lfirst_node(A_Indices, idx);
+
 		if (isSlice)
 		{
 			Node	   *expr = ai->uidx ? ai->uidx : ai->lidx;
@@ -160,7 +165,9 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->refrestype = JSONBOID;
 	sbsref->reftypmod = -1;
 
-	*indirection = NIL;
+	/* Remove processed elements */
+	if (upperIndexpr)
+		*indirection = list_delete_first_n(*indirection, list_length(upperIndexpr));
 }
 
 /*
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 58a4b9df157..5cc3ce58c30 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -362,7 +362,8 @@ extern SubscriptingRef *transformContainerSubscripts(ParseState *pstate,
 													 Oid containerType,
 													 int32 containerTypMod,
 													 List **indirection,
-													 bool isAssignment);
+													 bool isAssignment,
+													 bool noError);
 extern Const *make_const(ParseState *pstate, A_Const *aconst);
 
 #endif							/* PARSE_NODE_H */
-- 
2.39.5 (Apple Git-154)



^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-06-24 13:29                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-07-09 08:01                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
@ 2025-07-10 08:53                                 ` jian he <[email protected]>
  2025-07-10 13:34                                   ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-21 04:53                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  0 siblings, 2 replies; 67+ messages in thread

From: jian he @ 2025-07-10 08:53 UTC (permalink / raw)
  To: Alexandra Wang <[email protected]>; +Cc: Nikita Malakhov <[email protected]>; Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>

On Wed, Jul 9, 2025 at 4:02 PM Alexandra Wang
<[email protected]> wrote:
>
> Thanks again for the patch! It was really helpful! I didn't directly
> apply it as I made a few different choices, but I think I have
> addressed all the points you covered in it.
>
> Let me know your thoughts!
>

hi.

in v12-0001 and v12-0002.
in transformIndirection
        if (!newresult)
        {
            /*
             * generic subscripting failed; falling back to function call or
             * field selection for a composite type.
             */
            Node       *n;
            /* try to find function for field selection */
            newresult = ParseFuncOrColumn(pstate,
                                          list_make1(n),
                                          list_make1(result),
                                          last_srf,
                                          NULL,
                                          false,
                                          location);
}
the above comments mentioning "function call" is wrong?
you passed NULL for (FuncCall *fn) in ParseFuncOrColumn.
and ParseFuncOrColumn comments says
```If fn is null, we're dealing with column syntax not function syntax.``


I think coerce_jsonpath_subscript can be further simplified.
we already have message like:
errhint("jsonb subscript must be coercible to either integer or text."),
no need to pass the third argument a constant (INT4OID).
also
``Oid            targetType = UNKNOWNOID;``
set it as InvalidOid would be better.
attached is a minor refactoring of coerce_jsonpath_subscript
based on (v12-0001 to v12-0004).


after applied v12-0001 to v12-0006
+ /* emit warning conditionally to minimize duplicate warnings */
+ if (list_length(*indirection) > 0)
+ ereport(WARNING,
+ errcode(ERRCODE_WARNING),
+ errmsg("mixed usage of jsonb simplified accessor syntax and jsonb
subscripting."),
+ errhint("use dot-notation for member access, or use non-null integer
constants subscripting for array access."),
+ parser_errposition(pstate, warning_location));

src7=# select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['1'::int8];
WARNING:  mixed usage of jsonb simplified accessor syntax and jsonb
subscripting.
LINE 1: ...t ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['1'::int8]...
                                                             ^
HINT:  use dot-notation for member access, or use non-null integer
constants subscripting for array access.
ERROR:  subscript type bigint is not supported
LINE 1: ...t ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['1'::int8]...
                                                             ^
HINT:  jsonb subscript must be coercible to either integer or text.

The above example looks very bad. location printed twice, hint message
is different.
two messages level (ERROR, WARNING).

also "or use non-null integer constants subscripting for array
access." seems wrong?
as you can see the below hint message saying it could be text or integer.

select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['1'::int8];
ERROR:  subscript type bigint is not supported
LINE 1: ...ect ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['1'::int8]...
                                                             ^
HINT:  jsonb subscript must be coercible to either integer or text.

also select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)[NULL::int4];
return NULL,  so "use non-null integer constants" is wrong.


Attachments:

  [application/octet-stream] v12-0001-minor-refactor-coerce_jsonpath_subscript.no-cfbot (2.1K, ../../CACJufxHqiKbh1RN4-rquYdnS8qK9kEQq=bpt6ED_yo1+OkU8jg@mail.gmail.com/2-v12-0001-minor-refactor-coerce_jsonpath_subscript.no-cfbot)
  download

^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-06-24 13:29                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-07-09 08:01                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-07-10 08:53                                 ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
@ 2025-07-10 13:34                                   ` jian he <[email protected]>
  2025-07-11 05:53                                     ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  1 sibling, 1 reply; 67+ messages in thread

From: jian he @ 2025-07-10 13:34 UTC (permalink / raw)
  To: Alexandra Wang <[email protected]>; +Cc: Nikita Malakhov <[email protected]>; Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>

On Thu, Jul 10, 2025 at 4:53 PM jian he <[email protected]> wrote:
>
> src7=# select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['1'::int8];
> WARNING:  mixed usage of jsonb simplified accessor syntax and jsonb
> subscripting.
> LINE 1: ...t ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['1'::int8]...
>                                                              ^
> HINT:  use dot-notation for member access, or use non-null integer
> constants subscripting for array access.
> ERROR:  subscript type bigint is not supported
> LINE 1: ...t ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['1'::int8]...
>                                                              ^
> HINT:  jsonb subscript must be coercible to either integer or text.
>
> The above example looks very bad. location printed twice, hint message
> is different.
> two messages level (ERROR, WARNING).
>
For plainSELECT statement, we have WARNING only in
src/test/regress/expected/xml.out,  src/test/regress/expected/xml_2.out
for example:
SELECT xpath('/*', '<relativens xmlns=''relative''/>');
WARNING:  line 1: xmlns: URI relative is not absolute
<relativens xmlns='relative'/>
                            ^
                xpath
--------------------------------------
 {"<relativens xmlns=\"relative\"/>"}
(1 row)

so i am not sure a plain SELECT statement issuing WARNING is appropriate.
------------------------------------------
in jsonb_subscript_make_jsonpath we have
    foreach(lc, *indirection)
{
        if (IsA(accessor, String))
            ....
        else if (IsA(accessor, A_Indices))
        else
            /*
             * Unsupported node type for creating jsonpath. Instead of
             * throwing an ERROR, break here so that we create a jsonpath from
             * as many indirection elements as we can and let
             * transformIndirection() fallback to alternative logic to handle
             * the remaining indirection elements.
             */
              break;
}
the above ELSE branch comments look suspicious to me.
transformIndirection->transformContainerSubscripts->jsonb_subscript_transform->jsonb_subscript_make_jsonpath
As you can see, transformIndirection have a long distance from
jsonb_subscript_make_jsonpath,
let transformIndirection handle remaining indirection elements seems not good.

if you look at src/backend/parser/gram.y line 16990.
transformIndirection(ParseState *pstate, A_Indirection *ind)
ind->indirection can be be Node of String, A_Indices, A_Star

also the above ELSE branch never reached in regress tests.
------------------------------------------

typedef struct FieldAccessorExpr
{
    Expr        xpr;
    char       *fieldname;        /* name of the JSONB object field accessed via
                                 * dot notation */
    Oid            faecollid pg_node_attr(query_jumble_ignore);
    int            location;
}            FieldAccessorExpr;

first field as NodeTag should be just fine?
I am not sure the field "location" is needed now, if it is needed, it should be
type as ParseLoc.
we should add it to src/tools/pgindent/typedefs.list





^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-06-24 13:29                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-07-09 08:01                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-07-10 08:53                                 ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-07-10 13:34                                   ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
@ 2025-07-11 05:53                                     ` jian he <[email protected]>
  0 siblings, 0 replies; 67+ messages in thread

From: jian he @ 2025-07-11 05:53 UTC (permalink / raw)
  To: Alexandra Wang <[email protected]>; +Cc: Nikita Malakhov <[email protected]>; Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>

On Thu, Jul 10, 2025 at 9:34 PM jian he <[email protected]> wrote:
>
> ------------------------------------------
> in jsonb_subscript_make_jsonpath we have
>     foreach(lc, *indirection)
> {
>         if (IsA(accessor, String))
>             ....
>         else if (IsA(accessor, A_Indices))
>         else
>             /*
>              * Unsupported node type for creating jsonpath. Instead of
>              * throwing an ERROR, break here so that we create a jsonpath from
>              * as many indirection elements as we can and let
>              * transformIndirection() fallback to alternative logic to handle
>              * the remaining indirection elements.
>              */
>               break;
> }
> the above ELSE branch comments look suspicious to me.
> transformIndirection->transformContainerSubscripts->jsonb_subscript_transform->jsonb_subscript_make_jsonpath
> As you can see, transformIndirection have a long distance from
> jsonb_subscript_make_jsonpath,
> let transformIndirection handle remaining indirection elements seems not good.
>
context v12-0001 to v12-0006.
this ELSE branch comments is wrong, because
+ if (jsonb_check_jsonpath_needed(*indirection))
+ {
+ jsonb_subscript_make_jsonpath(pstate, indirection, sbsref);
+ if (sbsref->refjsonbpath)
+ return;
+ }
in jsonb_check_jsonpath_needed we already use Assert to confirm that
list "indirection"
is either String or A_Indices Node.

in transformContainerSubscripts we have
sbsroutines->transform(sbsref, indirection, pstate,
                           isSlice, isAssignment);
    /*
     * Error out, if datatype failed to consume any indirection elements.
     */
    if (list_length(*indirection) == indirection_length)
    {
        Node       *ind = linitial(*indirection);
        if (noError)
            return NULL;
        if (IsA(ind, String))
            ereport(ERROR,
                    (errcode(ERRCODE_DATATYPE_MISMATCH),
                     errmsg("type %s does not support dot notation",
                            format_type_be(containerType)),
                     parser_errposition(pstate, exprLocation(containerBase))));
        else if (IsA(ind, A_Indices))
            ereport(ERROR,
                    (errcode(ERRCODE_DATATYPE_MISMATCH),
                     errmsg("type %s does not support array subscripting",
                            format_type_be(containerType)),
                     parser_errposition(pstate, exprLocation(containerBase))));
        else
            elog(ERROR, "invalid indirection operation: %d", nodeTag(ind));
    }
sbsroutines->transform currently will call
array_subscript_transform, hstore_subscript_transform, jsonb_subscript_transform

in jsonb_subscript_transform callee we unconditionally do:
    *indirection = list_delete_first_n(*indirection, pathlen);
   *indirection = list_delete_first_n(*indirection, list_length(upperIndexpr));
in array_subscript_transform, we do
*indirection = list_delete_first_n(*indirection, ndim);

That means, if sbsroutines->transform not error out and indirection is
not NIL (which is unlikely)
then sbsroutines->transform will consume some induction elements.
instead of the above verbose ereport(ERROR, error handling, we can use Assert
    Assert(indirection_length > list_length(*indirection));

for the above comments, i did a refactoring based on v12 (0001 to 0006).


Attachments:

  [application/octet-stream] v12-0001-minor-refactor-based-on-v12_0001_to_0006.no-cfbot (3.9K, ../../CACJufxHAPT6Sh6JW-tU0imFbBkD9kU+2vba-yM44UTLx87ckqQ@mail.gmail.com/2-v12-0001-minor-refactor-based-on-v12_0001_to_0006.no-cfbot)
  download

^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-06-24 13:29                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-07-09 08:01                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-07-10 08:53                                 ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
@ 2025-08-21 04:53                                   ` Alexandra Wang <[email protected]>
  2025-08-22 08:19                                     ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  1 sibling, 1 reply; 67+ messages in thread

From: Alexandra Wang @ 2025-08-21 04:53 UTC (permalink / raw)
  To: jian he <[email protected]>; +Cc: Nikita Malakhov <[email protected]>; Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>

Hi Jian,

Thanks for reviewing! I’ve attached v13, which addresses your
feedback.

See my individual replies below, and let me know if you have any
questions!

On Thu, Jul 10, 2025 at 1:54 AM jian he <[email protected]> wrote:

> in v12-0001 and v12-0002.
> in transformIndirection
>         if (!newresult)
>         {
>             /*
>              * generic subscripting failed; falling back to function call
> or
>              * field selection for a composite type.
>              */
>             Node       *n;
>             /* try to find function for field selection */
>             newresult = ParseFuncOrColumn(pstate,
>                                           list_make1(n),
>                                           list_make1(result),
>                                           last_srf,
>                                           NULL,
>                                           false,
>                                           location);
> }
> the above comments mentioning "function call" is wrong?
> you passed NULL for (FuncCall *fn) in ParseFuncOrColumn.
> and ParseFuncOrColumn comments says
> ```If fn is null, we're dealing with column syntax not function syntax.``
>

You are right. Fixed.

On Thu, Jul 10, 2025 at 1:54 AM jian he <[email protected]> wrote:

> I think coerce_jsonpath_subscript can be further simplified.
> we already have message like:
> errhint("jsonb subscript must be coercible to either integer or text."),
> no need to pass the third argument a constant (INT4OID).
> also
> ``Oid            targetType = UNKNOWNOID;``
> set it as InvalidOid would be better.
> attached is a minor refactoring of coerce_jsonpath_subscript
> based on (v12-0001 to v12-0004).
>

Thanks for the patch! I squashed it with v13-0004 and renamed the
function to coerce_jsonpath_subscript_to_int4_or_text() for clarity.

On Thu, Jul 10, 2025 at 1:54 AM jian he <[email protected]> wrote:

> after applied v12-0001 to v12-0006
> + /* emit warning conditionally to minimize duplicate warnings */
> + if (list_length(*indirection) > 0)
> + ereport(WARNING,
> + errcode(ERRCODE_WARNING),
> + errmsg("mixed usage of jsonb simplified accessor syntax and jsonb
> subscripting."),
> + errhint("use dot-notation for member access, or use non-null integer
> constants subscripting for array access."),
> + parser_errposition(pstate, warning_location));
>
> src7=# select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['1'::int8];
> WARNING:  mixed usage of jsonb simplified accessor syntax and jsonb
> subscripting.
> LINE 1: ...t ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['1'::int8]...
>                                                              ^
> HINT:  use dot-notation for member access, or use non-null integer
> constants subscripting for array access.
> ERROR:  subscript type bigint is not supported
> LINE 1: ...t ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['1'::int8]...
>                                                              ^
> HINT:  jsonb subscript must be coercible to either integer or text.
>
> The above example looks very bad. location printed twice, hint message
> is different.
> two messages level (ERROR, WARNING).
>
> also "or use non-null integer constants subscripting for array
> access." seems wrong?
> as you can see the below hint message saying it could be text or integer.
>
> select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['1'::int8];
> ERROR:  subscript type bigint is not supported
> LINE 1: ...ect ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['1'::int8]...
>                                                              ^
> HINT:  jsonb subscript must be coercible to either integer or text.
>
> also select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)[NULL::int4];
> return NULL,  so "use non-null integer constants" is wrong.
>

I see your confusion about the WARNING/ERROR messages. My intent was
to encourage users to use consistent syntax flavors rather than mixing
the SQL standard JSON simplified accessor with the pre-standard
PostgreSQL jsonb subscripting. In the meantime, I want to support
mixed syntax flavors as much as possible. However, your feedback makes
it clear that users don’t need that level of details. So, in v13 I’ve
removed the WARNING message and instead added a comment explaining how
we support mixed syntaxes.

Here's the relevant diff between v12 and v13, hope it helps:

--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -247,7 +247,6 @@ jsonb_subscript_make_jsonpath(ParseState *pstate, List
**indirection, Subscripti
        ListCell   *lc;
        Datum           jsp;
        int                     pathlen = 0;
-       int                     warning_location = -1;

        sbsref->refupperindexpr = NIL;
        sbsref->reflowerindexpr = NIL;
@@ -311,10 +310,27 @@ jsonb_subscript_make_jsonpath(ParseState *pstate,
List **indirection, Subscripti
                                if (jpi_from == NULL)
                                {
                                        /*
-                                        * postpone emitting the warning
until the end of this
-                                        * function
+                                        * Break out of the loop if the
subscript is not a
+                                        * non-null integer constant, so
that we can fall back to
+                                        * jsonb subscripting logic.
+                                        *
+                                        * This is needed to handle cases
with mixed usage of SQL
+                                        * standard json simplified
accessor syntax and PostgreSQL
+                                        * jsonb subscripting syntax, e.g:
+                                        *
+                                        * select (jb).a['b'].c from
jsonb_table;
+                                        *
+                                        * where dot-notation (.a and .c)
is the SQL standard json
+                                        * simplified accessor syntax, and
the ['b'] subscript is
+                                        * the PostgreSQL jsonb
subscripting syntax, because 'b'
+                                        * is not a non-null constant
integer and cannot be used
+                                        * for json array access.
+                                        *
+                                        * In this case, we cannot create a
JsonPath item, so we
+                                        * break out of the loop and let
+                                        * jsonb_subscript_transform()
handle this indirection as
+                                        * a PostgreSQL jsonb subscript.
                                         */
-                                       warning_location =
exprLocation(ai->uidx);
                                        pfree(jpi->value.array.elems);
                                        pfree(jpi);
                                        break;
@@ -360,14 +376,6 @@ jsonb_subscript_make_jsonpath(ParseState *pstate, List
**indirection, Subscripti

        *indirection = list_delete_first_n(*indirection, pathlen);

-       /* emit warning conditionally to minimize duplicate warnings */
-       if (list_length(*indirection) > 0)
-               ereport(WARNING,
-                               errcode(ERRCODE_WARNING),
-                               errmsg("mixed usage of jsonb simplified
accessor syntax and jsonb subscripting."),
-                               errhint("use dot-notation for member
access, or use non-null integer constants subscripting for array access."),
-                               parser_errposition(pstate,
warning_location));
-
        jsp = jsonPathFromParseResult(&jpres, 0, NULL);

On Thu, Jul 10, 2025 at 6:34 AM jian he <[email protected]> wrote:

> typedef struct FieldAccessorExpr
> {
>     Expr        xpr;
>     char       *fieldname;        /* name of the JSONB object field
> accessed via
>                                  * dot notation */
>     Oid            faecollid pg_node_attr(query_jumble_ignore);
>     int            location;
> }            FieldAccessorExpr;
>
> first field as NodeTag should be just fine?
> I am not sure the field "location" is needed now, if it is needed, it
> should be
> type as ParseLoc.
> we should add it to src/tools/pgindent/typedefs.list


Good catch! I changed the first field from Expr to NodeTag, removed
the location field, and added the Node to typedefs.list.

On Thu, Jul 10, 2025 at 10:54 PM jian he <[email protected]>
wrote:

> On Thu, Jul 10, 2025 at 9:34 PM jian he <[email protected]>
> wrote:
> >
> > ------------------------------------------
> > in jsonb_subscript_make_jsonpath we have
> >     foreach(lc, *indirection)
> > {
> >         if (IsA(accessor, String))
> >             ....
> >         else if (IsA(accessor, A_Indices))
> >         else
> >             /*
> >              * Unsupported node type for creating jsonpath. Instead of
> >              * throwing an ERROR, break here so that we create a
> jsonpath from
> >              * as many indirection elements as we can and let
> >              * transformIndirection() fallback to alternative logic to
> handle
> >              * the remaining indirection elements.
> >              */
> >               break;
> > }
> > the above ELSE branch comments look suspicious to me.
>

I kept the "else" branch, but added an Assert with updated comments.

Here's the relevant diff between v12 and v13:

In jsonb_subscript_make_jsonpath():
else
{
- * Unsupported node type for creating jsonpath. Instead of
- * throwing an ERROR, break here so that we create a jsonpath from
- * as many indirection elements as we can and let
- * transformIndirection() fallback to alternative logic to handle
- * the remaining indirection elements.
+ * Unexpected node type in indirection list. This should not
+ * happen with current grammar, but we handle it defensively by
+ * breaking out of the loop rather than crashing. In case of
+ * future grammar changes that might introduce new node types,
+ * this allows us to create a jsonpath from as many indirection
+ * elements as we can and let transformIndirection() fallback to
+ * alternative logic to handle the remaining indirection elements.
+ Assert(false); /* not reachable */
break;
}

Hope the updated comment clarifies why I kept it.

On Thu, Jul 10, 2025 at 10:54 PM jian he <[email protected]>
wrote:

> >
> transformIndirection->transformContainerSubscripts->jsonb_subscript_transform->jsonb_subscript_make_jsonpath
> > As you can see, transformIndirection have a long distance from
> > jsonb_subscript_make_jsonpath,
> > let transformIndirection handle remaining indirection elements seems not
> good.
>

In 0001's commit message, we are given the following promise:

Author: Nikita Glukhov <[email protected]>
Date:   Tue Jul 8 22:18:07 2025 -0700

    Allow transformation of only a sublist of subscripts

    This is a preparation step for allowing subscripting containers to
    transform only a prefix of an indirection list and modify the list
    in-place by removing the processed elements. Currently, all elements
    are consumed, and the list is set to NIL after transformation.

    In the following commit, subscripting containers will gain the
    flexibility to stop transformation when encountering an unsupported
    indirection and return the remaining indirections to the caller.

With this contract provided by the subscripting interface to the
container's transform function, I don't feel terribly inappropriate to
let jsonb's transform function: jsonb_subscript_transform(), to
transform indirections as much as it can, and leave the rest to
transformIndirection().

0002 is a good example that shows why the else branch is useful: it
adds support for String as a subscript node of jsonb, while String is
still unsupported in other in-core container subscripting.

For example, array_subscript_transform() has:
if (!IsA(lfirst(idx), A_Indices))
    break;

The else branch in jsonb_subscript_make_jsonpath() just follows that
same convention.


> context v12-0001 to v12-0006.
> this ELSE branch comments is wrong, because
> + if (jsonb_check_jsonpath_needed(*indirection))
> + {
> + jsonb_subscript_make_jsonpath(pstate, indirection, sbsref);
> + if (sbsref->refjsonbpath)
> + return;
> + }
> in jsonb_check_jsonpath_needed we already use Assert to confirm that
> list "indirection"
> is either String or A_Indices Node.
>

The Assert I added in v13, as well as the one you mentioned in
jsonb_check_jsonpath_needed(), are both only applicable in development
build and won’t help in production. So I’d prefer to break here and
let the callee handle any unexpected Node with an ERROR.

Let me know if this explanation makes sense. If not, and if you think
the current logic is overly complicated, we can discuss further.

On Thu, Jul 10, 2025 at 10:54 PM jian he <[email protected]>
wrote:

> in transformContainerSubscripts we have
> sbsroutines->transform(sbsref, indirection, pstate,
>                            isSlice, isAssignment);
>     /*
>      * Error out, if datatype failed to consume any indirection elements.
>      */
>     if (list_length(*indirection) == indirection_length)
>     {
>         Node       *ind = linitial(*indirection);
>         if (noError)
>             return NULL;
>         if (IsA(ind, String))
>             ereport(ERROR,
>                     (errcode(ERRCODE_DATATYPE_MISMATCH),
>                      errmsg("type %s does not support dot notation",
>                             format_type_be(containerType)),
>                      parser_errposition(pstate,
> exprLocation(containerBase))));
>         else if (IsA(ind, A_Indices))
>             ereport(ERROR,
>                     (errcode(ERRCODE_DATATYPE_MISMATCH),
>                      errmsg("type %s does not support array subscripting",
>                             format_type_be(containerType)),
>                      parser_errposition(pstate,
> exprLocation(containerBase))));
>         else
>             elog(ERROR, "invalid indirection operation: %d", nodeTag(ind));
>     }
> sbsroutines->transform currently will call
> array_subscript_transform, hstore_subscript_transform,
> jsonb_subscript_transform
>
> in jsonb_subscript_transform callee we unconditionally do:
>     *indirection = list_delete_first_n(*indirection, pathlen);
>    *indirection = list_delete_first_n(*indirection,
> list_length(upperIndexpr));
> in array_subscript_transform, we do
> *indirection = list_delete_first_n(*indirection, ndim);
>
> That means, if sbsroutines->transform not error out and indirection is
> not NIL (which is unlikely)
> then sbsroutines->transform will consume some induction elements.
> instead of the above verbose ereport(ERROR, error handling, we can use
> Assert
>     Assert(indirection_length > list_length(*indirection));
>
> for the above comments, i did a refactoring based on v12 (0001 to 0006).


Thanks for the patch! I didn’t apply it for the same reason I
mentioned in the previous quote reply. In the case of an unexpected
Node type, I prefer raising an explicit ERROR rather than using an
Assert() that crashes in a dev build but silently continues in
production. One possible way to hit these ERRORs is by creating a
custom data type that supports subscripting, but only for a subset of
Node types that transformIndirection() allows.

Best,
Alex


Attachments:

  [application/octet-stream] v13-0004-Extract-coerce_jsonpath_subscript.patch (5.8K, ../../CAK98qZ19bC=Qw9rWGOFKyX4B-fg1XQWEbV2OWAawqWC62fx79A@mail.gmail.com/3-v13-0004-Extract-coerce_jsonpath_subscript.patch)
  download | inline diff:
From 3486f5619488ad1ced9acf93e73371bc722d4579 Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v13 4/7] Extract coerce_jsonpath_subscript()

This is a preparation step for a future commit that will reuse the
aforementioned function.

Co-authored-by: Nikita Glukhov <[email protected]>
Co-authored-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Jian He <[email protected]>
---
 src/backend/utils/adt/jsonbsubs.c | 130 +++++++++++++++---------------
 1 file changed, 65 insertions(+), 65 deletions(-)

diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index a0d38a0fd80..f944d1544ca 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -32,6 +32,69 @@ typedef struct JsonbSubWorkspace
 	Datum	   *index;			/* Subscript values in Datum format */
 } JsonbSubWorkspace;
 
+static Node *
+coerce_jsonpath_subscript_to_int4_or_text(ParseState *pstate, Node *subExpr)
+{
+	Oid			subExprType = exprType(subExpr);
+	Oid			targetType = InvalidOid;
+
+	if (subExprType != UNKNOWNOID)
+	{
+		Oid			targets[2] = {INT4OID, TEXTOID};
+
+		/*
+		 * Jsonb can handle multiple subscript types, but cases when a
+		 * subscript could be coerced to multiple target types must be
+		 * avoided, similar to overloaded functions. It could be possibly
+		 * extend with jsonpath in the future.
+		 */
+		for (int i = 0; i < 2; i++)
+		{
+			if (can_coerce_type(1, &subExprType, &targets[i], COERCION_IMPLICIT))
+			{
+				/*
+				 * One type has already succeeded, it means there are two
+				 * coercion targets possible, failure.
+				 */
+				if (OidIsValid(targetType))
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
+							 errhint("jsonb subscript must be coercible to only one type, integer or text."),
+							 parser_errposition(pstate, exprLocation(subExpr))));
+
+				targetType = targets[i];
+			}
+		}
+
+		/*
+		 * No suitable types were found, failure.
+		 */
+		if (!OidIsValid(targetType))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
+					 errhint("jsonb subscript must be coercible to either integer or text."),
+					 parser_errposition(pstate, exprLocation(subExpr))));
+	}
+	else
+		targetType = TEXTOID;
+
+	/*
+	 * We known from can_coerce_type that coercion will succeed, so
+	 * coerce_type could be used. Note the implicit coercion context, which is
+	 * required to handle subscripts of different types, similar to overloaded
+	 * functions.
+	 */
+	subExpr = coerce_type(pstate,
+						  subExpr, subExprType,
+						  targetType, -1,
+						  COERCION_IMPLICIT,
+						  COERCE_IMPLICIT_CAST,
+						  -1);
+
+	return subExpr;
+}
 
 /*
  * Finish parse analysis of a SubscriptingRef expression for a jsonb.
@@ -51,7 +114,7 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 
 	/*
 	 * Transform and convert the subscript expressions. Jsonb subscripting
-	 * does not support slices, look only and the upper index.
+	 * does not support slices, look only at the upper index.
 	 */
 	foreach(idx, *indirection)
 	{
@@ -75,71 +138,8 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 
 		if (ai->uidx)
 		{
-			Oid			subExprType = InvalidOid,
-						targetType = UNKNOWNOID;
-
 			subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
-			subExprType = exprType(subExpr);
-
-			if (subExprType != UNKNOWNOID)
-			{
-				Oid			targets[2] = {INT4OID, TEXTOID};
-
-				/*
-				 * Jsonb can handle multiple subscript types, but cases when a
-				 * subscript could be coerced to multiple target types must be
-				 * avoided, similar to overloaded functions. It could be
-				 * possibly extend with jsonpath in the future.
-				 */
-				for (int i = 0; i < 2; i++)
-				{
-					if (can_coerce_type(1, &subExprType, &targets[i], COERCION_IMPLICIT))
-					{
-						/*
-						 * One type has already succeeded, it means there are
-						 * two coercion targets possible, failure.
-						 */
-						if (targetType != UNKNOWNOID)
-							ereport(ERROR,
-									(errcode(ERRCODE_DATATYPE_MISMATCH),
-									 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
-									 errhint("jsonb subscript must be coercible to only one type, integer or text."),
-									 parser_errposition(pstate, exprLocation(subExpr))));
-
-						targetType = targets[i];
-					}
-				}
-
-				/*
-				 * No suitable types were found, failure.
-				 */
-				if (targetType == UNKNOWNOID)
-					ereport(ERROR,
-							(errcode(ERRCODE_DATATYPE_MISMATCH),
-							 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
-							 errhint("jsonb subscript must be coercible to either integer or text."),
-							 parser_errposition(pstate, exprLocation(subExpr))));
-			}
-			else
-				targetType = TEXTOID;
-
-			/*
-			 * We known from can_coerce_type that coercion will succeed, so
-			 * coerce_type could be used. Note the implicit coercion context,
-			 * which is required to handle subscripts of different types,
-			 * similar to overloaded functions.
-			 */
-			subExpr = coerce_type(pstate,
-								  subExpr, subExprType,
-								  targetType, -1,
-								  COERCION_IMPLICIT,
-								  COERCE_IMPLICIT_CAST,
-								  -1);
-			if (subExpr == NULL)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("jsonb subscript must have text type"),
-						 parser_errposition(pstate, exprLocation(subExpr))));
+			subExpr = coerce_jsonpath_subscript_to_int4_or_text(pstate, subExpr);
 		}
 		else
 		{
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v13-0007-Implement-jsonb-wildcard-member-accessor.patch (31.4K, ../../CAK98qZ19bC=Qw9rWGOFKyX4B-fg1XQWEbV2OWAawqWC62fx79A@mail.gmail.com/4-v13-0007-Implement-jsonb-wildcard-member-accessor.patch)
  download | inline diff:
From 9594fc742a468b9d6d3f88491f55f82b3c8307d5 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v13 7/7] Implement jsonb wildcard member accessor

This commit adds support for wildcard member access in jsonb, as
specified by the JSON simplified accessor syntax in SQL:2023.

Co-authored-by: Nikita Glukhov <[email protected]>
Co-authored-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Mark Dilger <[email protected]>
Reviewed-by: Jian He <[email protected]>
Reviewed-by: Vik Fearing <[email protected]>
Reviewed-by: Nikita Malakhov <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Jelte Fennema-Nio <[email protected]>
Tested-by: Jelte Fennema-Nio <[email protected]>
---
 src/backend/nodes/nodeFuncs.c                 |   8 +
 src/backend/parser/gram.y                     |   2 +
 src/backend/parser/parse_expr.c               |  34 ++-
 src/backend/parser/parse_target.c             |  67 ++++--
 src/backend/utils/adt/jsonbsubs.c             |  12 +-
 src/backend/utils/adt/ruleutils.c             |   6 +-
 src/include/nodes/primnodes.h                 |  12 +
 src/include/parser/parse_expr.h               |   3 +
 .../ecpg/test/expected/sql-sqljson.c          |  18 +-
 .../ecpg/test/expected/sql-sqljson.stderr     |  23 +-
 .../ecpg/test/expected/sql-sqljson.stdout     |   2 +
 src/interfaces/ecpg/test/sql/sqljson.pgc      |   5 +-
 src/test/regress/expected/jsonb.out           | 222 +++++++++++++++++-
 src/test/regress/sql/jsonb.sql                |  42 +++-
 14 files changed, 405 insertions(+), 51 deletions(-)

diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index d1bd575d9bd..5f3038a1c26 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -291,6 +291,9 @@ exprType(const Node *expr)
 			 */
 			type = TEXTOID;
 			break;
+		case T_Star:
+			type = UNKNOWNOID;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			type = InvalidOid;	/* keep compiler quiet */
@@ -1156,6 +1159,9 @@ exprSetCollation(Node *expr, Oid collation)
 		case T_FieldAccessorExpr:
 			((FieldAccessorExpr *) expr)->faecollid = collation;
 			break;
+		case T_Star:
+			Assert(!OidIsValid(collation));
+			break;
 		case T_SubscriptingRef:
 			((SubscriptingRef *) expr)->refcollid = collation;
 			break;
@@ -2140,6 +2146,7 @@ expression_tree_walker_impl(Node *node,
 		case T_CTESearchClause:
 		case T_MergeSupportFunc:
 		case T_FieldAccessorExpr:
+		case T_Star:
 			/* primitive node types with no expression subnodes */
 			break;
 		case T_WithCheckOption:
@@ -3020,6 +3027,7 @@ expression_tree_mutator_impl(Node *node,
 		case T_CTESearchClause:
 		case T_MergeSupportFunc:
 		case T_FieldAccessorExpr:
+		case T_Star:
 			return copyObject(node);
 		case T_WithCheckOption:
 			{
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index db43034b9db..703c7416b0d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -18979,6 +18979,7 @@ check_func_name(List *names, core_yyscan_t yyscanner)
 static List *
 check_indirection(List *indirection, core_yyscan_t yyscanner)
 {
+#if 0
 	ListCell   *l;
 
 	foreach(l, indirection)
@@ -18989,6 +18990,7 @@ check_indirection(List *indirection, core_yyscan_t yyscanner)
 				parser_yyerror("improper use of \"*\"");
 		}
 	}
+#endif
 	return indirection;
 }
 
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index dc1c7e9b75c..7d5c56caa70 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -74,7 +74,6 @@ static Node *transformColumnRef(ParseState *pstate, ColumnRef *cref);
 static Node *transformWholeRowRef(ParseState *pstate,
 								  ParseNamespaceItem *nsitem,
 								  int sublevels_up, int location);
-static Node *transformIndirection(ParseState *pstate, A_Indirection *ind);
 static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
 static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
 static Node *transformJsonObjectConstructor(ParseState *pstate,
@@ -158,7 +157,7 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 			break;
 
 		case T_A_Indirection:
-			result = transformIndirection(pstate, (A_Indirection *) expr);
+			result = transformIndirection(pstate, (A_Indirection *) expr, NULL);
 			break;
 
 		case T_A_ArrayExpr:
@@ -432,8 +431,9 @@ unknown_attribute(ParseState *pstate, Node *relref, const char *attname,
 	}
 }
 
-static Node *
-transformIndirection(ParseState *pstate, A_Indirection *ind)
+Node *
+transformIndirection(ParseState *pstate, A_Indirection *ind,
+					 bool *trailing_star_expansion)
 {
 	Node	   *last_srf = pstate->p_last_srf;
 	Node	   *result = transformExprRecurse(pstate, ind->arg);
@@ -454,12 +454,7 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		if (IsA(n, A_Indices))
 			subscripts = lappend(subscripts, n);
 		else if (IsA(n, A_Star))
-		{
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("row expansion via \"*\" is not supported here"),
-					 parser_errposition(pstate, location)));
-		}
+			subscripts = lappend(subscripts, n);
 		else
 		{
 			Assert(IsA(n, String));
@@ -491,7 +486,21 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 
 			n = linitial(subscripts);
 
-			if (!IsA(n, String))
+			if (IsA(n, A_Star))
+			{
+				/* Success, if trailing star expansion is allowed */
+				if (trailing_star_expansion && list_length(subscripts) == 1)
+				{
+					*trailing_star_expansion = true;
+					return result;
+				}
+
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("row expansion via \"*\" is not supported here"),
+						 parser_errposition(pstate, location)));
+			}
+			else if (!IsA(n, String))
 				ereport(ERROR,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
 						 errmsg("cannot subscript type %s because it does not support subscripting",
@@ -517,6 +526,9 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		result = newresult;
 	}
 
+	if (trailing_star_expansion)
+		*trailing_star_expansion = false;
+
 	return result;
 }
 
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 3ef5897f2eb..141fc1dfeb1 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -48,7 +48,7 @@ static Node *transformAssignmentSubscripts(ParseState *pstate,
 static List *ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
 								 bool make_target_entry);
 static List *ExpandAllTables(ParseState *pstate, int location);
-static List *ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
+static Node *ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
 								   bool make_target_entry, ParseExprKind exprKind);
 static List *ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 							   int sublevels_up, int location,
@@ -134,6 +134,7 @@ transformTargetList(ParseState *pstate, List *targetlist,
 	foreach(o_target, targetlist)
 	{
 		ResTarget  *res = (ResTarget *) lfirst(o_target);
+		Node	   *transformed = NULL;
 
 		/*
 		 * Check for "something.*".  Depending on the complexity of the
@@ -162,13 +163,19 @@ transformTargetList(ParseState *pstate, List *targetlist,
 
 				if (IsA(llast(ind->indirection), A_Star))
 				{
-					/* It is something.*, expand into multiple items */
-					p_target = list_concat(p_target,
-										   ExpandIndirectionStar(pstate,
-																 ind,
-																 true,
-																 exprKind));
-					continue;
+					Node	   *columns = ExpandIndirectionStar(pstate,
+																ind,
+																true,
+																exprKind);
+
+					if (IsA(columns, List))
+					{
+						/* It is something.*, expand into multiple items */
+						p_target = list_concat(p_target, (List *) columns);
+						continue;
+					}
+
+					transformed = (Node *) columns;
 				}
 			}
 		}
@@ -180,7 +187,7 @@ transformTargetList(ParseState *pstate, List *targetlist,
 		p_target = lappend(p_target,
 						   transformTargetEntry(pstate,
 												res->val,
-												NULL,
+												transformed,
 												exprKind,
 												res->name,
 												false));
@@ -251,10 +258,15 @@ transformExpressionList(ParseState *pstate, List *exprlist,
 
 			if (IsA(llast(ind->indirection), A_Star))
 			{
-				/* It is something.*, expand into multiple items */
-				result = list_concat(result,
-									 ExpandIndirectionStar(pstate, ind,
-														   false, exprKind));
+				Node	   *cols = ExpandIndirectionStar(pstate, ind,
+														 false, exprKind);
+
+				if (!cols || IsA(cols, List))
+					/* It is something.*, expand into multiple items */
+					result = list_concat(result, (List *) cols);
+				else
+					result = lappend(result, cols);
+
 				continue;
 			}
 		}
@@ -1345,22 +1357,30 @@ ExpandAllTables(ParseState *pstate, int location)
  * For robustness, we use a separate "make_target_entry" flag to control
  * this rather than relying on exprKind.
  */
-static List *
+static Node *
 ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
 					  bool make_target_entry, ParseExprKind exprKind)
 {
 	Node	   *expr;
+	ParseExprKind sv_expr_kind;
+	bool		trailing_star_expansion = false;
+
+	/* Save and restore identity of expression type we're parsing */
+	Assert(exprKind != EXPR_KIND_NONE);
+	sv_expr_kind = pstate->p_expr_kind;
+	pstate->p_expr_kind = exprKind;
 
 	/* Strip off the '*' to create a reference to the rowtype object */
-	ind = copyObject(ind);
-	ind->indirection = list_truncate(ind->indirection,
-									 list_length(ind->indirection) - 1);
+	expr = transformIndirection(pstate, ind, &trailing_star_expansion);
+
+	pstate->p_expr_kind = sv_expr_kind;
 
-	/* And transform that */
-	expr = transformExpr(pstate, (Node *) ind, exprKind);
+	/* '*' was consumed by generic type subscripting */
+	if (!trailing_star_expansion)
+		return expr;
 
 	/* Expand the rowtype expression into individual fields */
-	return ExpandRowReference(pstate, expr, make_target_entry);
+	return (Node *) ExpandRowReference(pstate, expr, make_target_entry);
 }
 
 /*
@@ -1785,13 +1805,18 @@ FigureColnameInternal(Node *node, char **name)
 				char	   *fname = NULL;
 				ListCell   *l;
 
-				/* find last field name, if any, ignoring "*" and subscripts */
+				/*
+				 * find last field name, if any, ignoring subscripts, and use
+				 * '?column?' when there's a trailing '*'.
+				 */
 				foreach(l, ind->indirection)
 				{
 					Node	   *i = lfirst(l);
 
 					if (IsA(i, String))
 						fname = strVal(i);
+					else if (IsA(i, A_Star))
+						fname = "?column?";
 				}
 				if (fname)
 				{
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index 458ca69d645..0b00530d868 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -124,7 +124,7 @@ jsonb_check_jsonpath_needed(List *indirection)
 	{
 		Node	   *accessor = lfirst(lc);
 
-		if (IsA(accessor, String))
+		if (IsA(accessor, String) || IsA(accessor, A_Star))
 			return true;
 		else
 		{
@@ -340,6 +340,16 @@ jsonb_subscript_make_jsonpath(ParseState *pstate, List **indirection, Subscripti
 				jpi->value.array.elems[0].to = NULL;
 			}
 		}
+		else if (IsA(accessor, A_Star))
+		{
+			Star *star_node;
+			jpi = make_jsonpath_item(jpiAnyKey);
+
+			star_node = makeNode(Star);
+			star_node->type = T_Star;
+
+			sbsref->refupperindexpr = lappend(sbsref->refupperindexpr, star_node);
+		}
 		else
 		{
 			/*
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index baa3ae97d57..ace0eff52e2 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -13010,7 +13010,11 @@ printSubscripts(SubscriptingRef *sbsref, deparse_context *context)
 	{
 		Node *upper = (Node *) lfirst(uplist_item);
 
-		if (upper && IsA(upper, FieldAccessorExpr))
+		if (upper && IsA(upper, Star))
+		{
+			appendStringInfoString(buf, ".*");
+		}
+		else if (upper && IsA(upper, FieldAccessorExpr))
 		{
 			FieldAccessorExpr *fae = (FieldAccessorExpr *) upper;
 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 7e89621bd65..7e418830f22 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -2419,4 +2419,16 @@ typedef struct FieldAccessorExpr
 	Oid			faecollid pg_node_attr(query_jumble_ignore);
 }			FieldAccessorExpr;
 
+/*
+ * Star - represents a wildcard member accessor (e.g., ".*") used in JSONB simplified accessor.
+ *
+ * This node serves as a syntactic placeholder in the expression tree and does not get evaluated, hence it
+ * has no associated type or collation.
+ */
+typedef struct Star
+{
+	NodeTag		type;
+}			Star;
+
+
 #endif							/* PRIMNODES_H */
diff --git a/src/include/parser/parse_expr.h b/src/include/parser/parse_expr.h
index efbaff8e710..c9f6a7724c0 100644
--- a/src/include/parser/parse_expr.h
+++ b/src/include/parser/parse_expr.h
@@ -22,4 +22,7 @@ extern Node *transformExpr(ParseState *pstate, Node *expr, ParseExprKind exprKin
 
 extern const char *ParseExprKindName(ParseExprKind exprKind);
 
+extern Node *transformIndirection(ParseState *pstate, A_Indirection *ind,
+								  bool *trailing_star_expansion);
+
 #endif							/* PARSE_EXPR_H */
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.c b/src/interfaces/ecpg/test/expected/sql-sqljson.c
index 935b47a3b9a..585d9b14445 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.c
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.c
@@ -515,9 +515,9 @@ if (sqlca.sqlcode < 0) sqlprint();}
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 145 "sqljson.pgc"
 
-	// error
+	printf("Found json=%s\n", json);
 
-	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )", ECPGt_EOIT, 
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . * . x )", ECPGt_EOIT, 
 	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
 	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 148 "sqljson.pgc"
@@ -527,7 +527,7 @@ if (sqlca.sqlcode < 0) sqlprint();}
 
 	printf("Found json=%s\n", json);
 
-	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ : ] )", ECPGt_EOIT, 
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )", ECPGt_EOIT, 
 	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
 	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 151 "sqljson.pgc"
@@ -537,12 +537,22 @@ if (sqlca.sqlcode < 0) sqlprint();}
 
 	printf("Found json=%s\n", json);
 
-  { ECPGdisconnect(__LINE__, "CURRENT");
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ : ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 154 "sqljson.pgc"
 
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 154 "sqljson.pgc"
 
+	printf("Found json=%s\n", json);
+
+  { ECPGdisconnect(__LINE__, "CURRENT");
+#line 157 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 157 "sqljson.pgc"
+
 
   return 0;
 }
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
index f3f899c6d87..4b9088545d6 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
@@ -347,22 +347,19 @@ SQL error: schema "jsonb" does not exist on line 121
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 145: using PQexec
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_check_PQresult on line 145: bad response - ERROR:  row expansion via "*" is not supported here
-LINE 1: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x...
-                        ^
+[NO_PID]: ecpg_process_output on line 145: correctly got 1 tuples with 1 fields
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: raising sqlstate 0A000 (sqlcode -400): row expansion via "*" is not supported here on line 145
-[NO_PID]: sqlca: code: -400, state: 0A000
-SQL error: row expansion via "*" is not supported here on line 145
-[NO_PID]: ecpg_execute on line 148: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: ecpg_get_data on line 145: RESULT: [{"b": 1, "c": 2}, [{"x": 1}, {"x": [12, {"y": 1}]}]] offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 148: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . * . x ); with 0 parameter(s) on connection ecpg1_regression
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 148: using PQexec
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_process_output on line 148: correctly got 1 tuples with 1 fields
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_get_data on line 148: RESULT: [{"x": 1}, {"x": [12, {"y": 1}]}] offset: -1; array: no
+[NO_PID]: ecpg_get_data on line 148: RESULT: [1, [12, {"y": 1}]] offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_execute on line 151: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: ecpg_execute on line 151: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] ); with 0 parameter(s) on connection ecpg1_regression
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 151: using PQexec
 [NO_PID]: sqlca: code: 0, state: 00000
@@ -370,5 +367,13 @@ SQL error: row expansion via "*" is not supported here on line 145
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_get_data on line 151: RESULT: [{"x": 1}, {"x": [12, {"y": 1}]}] offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 154: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 154: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 154: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 154: RESULT: [{"x": 1}, {"x": [12, {"y": 1}]}] offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_finish: connection ecpg1_regression closed
 [NO_PID]: sqlca: code: 0, state: 00000
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
index d01a8457f01..145dc95d430 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
@@ -36,5 +36,7 @@ Found json={"x": 1}
 Found json=[1, [12, {"y": 1}]]
 Found json={"x": 1}
 Found json=[12, {"y": 1}]
+Found json=[{"b": 1, "c": 2}, [{"x": 1}, {"x": [12, {"y": 1}]}]]
+Found json=[1, [12, {"y": 1}]]
 Found json=[{"x": 1}, {"x": [12, {"y": 1}]}]
 Found json=[{"x": 1}, {"x": [12, {"y": 1}]}]
diff --git a/src/interfaces/ecpg/test/sql/sqljson.pgc b/src/interfaces/ecpg/test/sql/sqljson.pgc
index 9423d25fd0b..2af50b5da4b 100644
--- a/src/interfaces/ecpg/test/sql/sqljson.pgc
+++ b/src/interfaces/ecpg/test/sql/sqljson.pgc
@@ -143,7 +143,10 @@ EXEC SQL END DECLARE SECTION;
 	printf("Found json=%s\n", json);
 
 	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).*) INTO :json;
-	// error
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).*.x) INTO :json;
+	printf("Found json=%s\n", json);
 
 	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb)['b'][0:]) INTO :json;
 	printf("Found json=%s\n", json);
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index f4a20694102..b99e9cf519f 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -6052,8 +6052,175 @@ SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
  {"y": "YYY", "z": "ZZZ"}
 (1 row)
 
-SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
-ERROR:  type jsonb is not composite
+/* wild card member access */
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+                 ?column?                  
+-------------------------------------------
+ ["c", "d", "f", {"y": "yyy", "z": "zzz"}]
+(1 row)
+
+SELECT (jb).* FROM test_jsonb_dot_notation;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (jb).* FROM test_jsonb_dot_notation t;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (jb).* FROM test_jsonb_dot_notation;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (t.jb).* FROM test_jsonb_dot_notation;
+ERROR:  missing FROM-clause entry for table "t"
+LINE 1: SELECT (t.jb).* FROM test_jsonb_dot_notation;
+                ^
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+                 ?column?                  
+-------------------------------------------
+ ["c", "d", "f", {"y": "yyy", "z": "zzz"}]
+(1 row)
+
+SELECT (jb).a.*.b FROM test_jsonb_dot_notation;
+ b 
+---
+ 
+(1 row)
+
+SELECT (jb).a.*.x FROM test_jsonb_dot_notation;
+ x 
+---
+ 
+(1 row)
+
+SELECT (jb).a.*.y FROM test_jsonb_dot_notation;
+   y   
+-------
+ "yyy"
+(1 row)
+
+SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+    ?column?    
+----------------
+ ["yyy", "zzz"]
+(1 row)
+
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT (jb).*.x FROM test_jsonb_dot_notation t;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+ x 
+---
+ 
+(1 row)
+
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+ x 
+---
+ 
+(1 row)
+
+SELECT ((jb).*)[:].x FROM test_jsonb_dot_notation t;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT (jb).*.x.* FROM test_jsonb_dot_notation;
+           ?column?           
+------------------------------
+ ["yyy", "zzz", "YYY", "ZZZ"]
+(1 row)
+
+SELECT (jb).*.x.y FROM test_jsonb_dot_notation;
+       y        
+----------------
+ ["yyy", "YYY"]
+(1 row)
+
+SELECT (jb).*.x.z FROM test_jsonb_dot_notation;
+       z        
+----------------
+ ["zzz", "ZZZ"]
+(1 row)
+
+SELECT (jb).*.*.y FROM test_jsonb_dot_notation;
+       y        
+----------------
+ ["yyy", "YYY"]
+(1 row)
+
+SELECT (jb).*.*.* FROM test_jsonb_dot_notation;
+           ?column?           
+------------------------------
+ ["yyy", "zzz", "YYY", "ZZZ"]
+(1 row)
+
+SELECT (jb).*.*.*.* FROM test_jsonb_dot_notation;
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT (jb).a.b.c.* FROM test_jsonb_dot_notation;
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+    ?column?    
+----------------
+ ["yyy", "zzz"]
+(1 row)
+
+SELECT (jb).a.*[:].* FROM test_jsonb_dot_notation;
+    ?column?    
+----------------
+ ["yyy", "zzz"]
+(1 row)
+
+SELECT (jb).a.*[*].* FROM test_jsonb_dot_notation; -- not supported
+ERROR:  syntax error at or near "*"
+LINE 1: SELECT (jb).a.*[*].* FROM test_jsonb_dot_notation;
+                        ^
+SELECT (jb).a.**.x FROM test_jsonb_dot_notation; -- not supported
+ERROR:  syntax error at or near "**"
+LINE 1: SELECT (jb).a.**.x FROM test_jsonb_dot_notation;
+                      ^
 -- explains should work
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
                   QUERY PLAN                  
@@ -6081,6 +6248,45 @@ SELECT (jb).a[1] FROM test_jsonb_dot_notation;
  2
 (1 row)
 
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.* FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb).a.*
+(2 rows)
+
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+                 ?column?                  
+-------------------------------------------
+ ["c", "d", "f", {"y": "yyy", "z": "zzz"}]
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:].* FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb).a.*[:].*
+(2 rows)
+
+SELECT (jb).a.*[1:].* FROM test_jsonb_dot_notation;
+ ?column? 
+----------
+ 
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb).a.*[:2].*.b
+(2 rows)
+
+SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
+ b 
+---
+ 
+(1 row)
+
 -- views should work
 CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
 \sv test_jsonb_dot_notation_v1
@@ -6109,6 +6315,17 @@ SELECT * from v3;
  "yyy"
 (1 row)
 
+CREATE VIEW v4 AS SELECT (jb).a.*[:].* FROM test_jsonb_dot_notation;
+\sv v4
+CREATE OR REPLACE VIEW public.v4 AS
+ SELECT (jb).a.*[:].* AS "?column?"
+   FROM test_jsonb_dot_notation
+SELECT * from v4;
+    ?column?    
+----------------
+ ["yyy", "zzz"]
+(1 row)
+
 -- mixed syntax
 DROP VIEW test_jsonb_dot_notation_v1;
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
@@ -6344,4 +6561,5 @@ NOTICE:  [2]
 -- clean up
 DROP VIEW v2;
 DROP VIEW v3;
+DROP VIEW v4;
 DROP TABLE test_jsonb_dot_notation;
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index b8ecefda04d..440ea1c6549 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1629,13 +1629,49 @@ SELECT (jb).a[2:].b FROM test_jsonb_dot_notation;
 SELECT (jb).a[:2].b FROM test_jsonb_dot_notation;
 SELECT (jb).a[:].b FROM test_jsonb_dot_notation;
 SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
-SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
+
+/* wild card member access */
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+SELECT (jb).* FROM test_jsonb_dot_notation;
+SELECT (jb).* FROM test_jsonb_dot_notation t;
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+SELECT (jb).* FROM test_jsonb_dot_notation;
+SELECT (t.jb).* FROM test_jsonb_dot_notation;
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.b FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.x FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.y FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+SELECT (jb).*.x FROM test_jsonb_dot_notation t;
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+SELECT ((jb).*)[:].x FROM test_jsonb_dot_notation t;
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+SELECT (jb).*.x.* FROM test_jsonb_dot_notation;
+SELECT (jb).*.x.y FROM test_jsonb_dot_notation;
+SELECT (jb).*.x.z FROM test_jsonb_dot_notation;
+SELECT (jb).*.*.y FROM test_jsonb_dot_notation;
+SELECT (jb).*.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).*.*.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.b.c.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*[:].* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*[*].* FROM test_jsonb_dot_notation; -- not supported
+SELECT (jb).a.**.x FROM test_jsonb_dot_notation; -- not supported
 
 -- explains should work
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
 SELECT (t.jb).a FROM test_jsonb_dot_notation t;
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
 SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:].* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*[1:].* FROM test_jsonb_dot_notation;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
+SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
 
 -- views should work
 CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
@@ -1646,6 +1682,9 @@ SELECT * from v2;
 CREATE VIEW v3 AS SELECT (jb).a[:3].x.y[-1:] FROM test_jsonb_dot_notation;
 \sv v3
 SELECT * from v3;
+CREATE VIEW v4 AS SELECT (jb).a.*[:].* FROM test_jsonb_dot_notation;
+\sv v4
+SELECT * from v4;
 
 -- mixed syntax
 DROP VIEW test_jsonb_dot_notation_v1;
@@ -1747,4 +1786,5 @@ $$ LANGUAGE plpgsql;
 -- clean up
 DROP VIEW v2;
 DROP VIEW v3;
+DROP VIEW v4;
 DROP TABLE test_jsonb_dot_notation;
\ No newline at end of file
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v13-0006-Implement-Jsonb-subscripting-with-slicing.patch (17.4K, ../../CAK98qZ19bC=Qw9rWGOFKyX4B-fg1XQWEbV2OWAawqWC62fx79A@mail.gmail.com/5-v13-0006-Implement-Jsonb-subscripting-with-slicing.patch)
  download | inline diff:
From 75471b513e83e62c2804881a5d820fcb82f5eaf9 Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v13 6/7] Implement Jsonb subscripting with slicing

Previously, slicing was not supported for jsonb subscripting. This commit
implements subscripting with slicing as part of the JSON simplified accessor
syntax specified in SQL:2023.

Co-authored-by: Nikita Glukhov <[email protected]>
Co-authored-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Mark Dilger <[email protected]>
Reviewed-by: Jian He <[email protected]>
Reviewed-by: Vik Fearing <[email protected]>
Reviewed-by: Nikita Malakhov <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Tested-by: Mark Dilger <[email protected]>
Tested-by: Jian He <[email protected]>
---
 src/backend/utils/adt/jsonbsubs.c             |  33 +++-
 .../ecpg/test/expected/sql-sqljson.c          |  16 +-
 .../ecpg/test/expected/sql-sqljson.stderr     |  26 ++--
 .../ecpg/test/expected/sql-sqljson.stdout     |   3 +
 src/interfaces/ecpg/test/sql/sqljson.pgc      |   7 +-
 src/test/regress/expected/jsonb.out           | 145 ++++++++++++++++--
 src/test/regress/sql/jsonb.sql                |  53 ++++++-
 7 files changed, 243 insertions(+), 40 deletions(-)

diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index 5808383db26..458ca69d645 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -192,9 +192,10 @@ make_jsonpath_item_int(int32 val, List **exprs)
  * - pstate: parse state context
  * - expr: input expression node
  * - exprs: list of expression nodes (updated in place)
+ * - no_error: returns NULL when the data type doesn't match. Otherwise, emits an ERROR.
  */
 static JsonPathParseItem *
-make_jsonpath_item_expr(ParseState *pstate, Node *expr, List **exprs)
+make_jsonpath_item_expr(ParseState *pstate, Node *expr, List **exprs, bool no_error)
 {
 	Const	   *cnst;
 
@@ -207,7 +208,14 @@ make_jsonpath_item_expr(ParseState *pstate, Node *expr, List **exprs)
 			return make_jsonpath_item_int(DatumGetInt32(cnst->constvalue), exprs);
 	}
 
-	return NULL;
+	if (no_error)
+		return NULL;
+	else
+		ereport(ERROR,
+				errcode(ERRCODE_DATATYPE_MISMATCH),
+				errmsg("only non-null integer constants are supported for jsonb simplified accessor subscripting"),
+				errhint("use int data type for subscripting with slicing."),
+				parser_errposition(pstate, exprLocation(expr)));
 }
 
 /*
@@ -277,19 +285,28 @@ jsonb_subscript_make_jsonpath(ParseState *pstate, List **indirection, Subscripti
 
 			if (ai->is_slice)
 			{
-				Node	   *expr = ai->uidx ? ai->uidx : ai->lidx;
+				while (list_length(sbsref->reflowerindexpr) < list_length(sbsref->refupperindexpr))
+					sbsref->reflowerindexpr = lappend(sbsref->reflowerindexpr, NULL);
+
+				if (ai->lidx)
+					jpi->value.array.elems[0].from = make_jsonpath_item_expr(pstate, ai->lidx, &sbsref->reflowerindexpr, false);
+				else
+					jpi->value.array.elems[0].from = make_jsonpath_item_int(0, &sbsref->reflowerindexpr);
 
-				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("jsonb subscript does not support slices"),
-						 parser_errposition(pstate, exprLocation(expr))));
+				if (ai->uidx)
+					jpi->value.array.elems[0].to = make_jsonpath_item_expr(pstate, ai->uidx, &sbsref->refupperindexpr, false);
+				else
+				{
+					jpi->value.array.elems[0].to = make_jsonpath_item(jpiLast);
+					sbsref->refupperindexpr = lappend(sbsref->refupperindexpr, NULL);
+				}
 			}
 			else
 			{
 				JsonPathParseItem *jpi_from = NULL;
 
 				Assert(ai->uidx && !ai->lidx);
-				jpi_from = make_jsonpath_item_expr(pstate, ai->uidx, &sbsref->refupperindexpr);
+				jpi_from = make_jsonpath_item_expr(pstate, ai->uidx, &sbsref->refupperindexpr, true);
 				if (jpi_from == NULL)
 				{
 					/*
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.c b/src/interfaces/ecpg/test/expected/sql-sqljson.c
index e6a7ece6dab..935b47a3b9a 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.c
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.c
@@ -505,7 +505,7 @@ if (sqlca.sqlcode < 0) sqlprint();}
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 142 "sqljson.pgc"
 
-	// error
+	printf("Found json=%s\n", json);
 
 	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . * )", ECPGt_EOIT, 
 	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
@@ -525,14 +525,24 @@ if (sqlca.sqlcode < 0) sqlprint();}
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 148 "sqljson.pgc"
 
-	// error
+	printf("Found json=%s\n", json);
 
-  { ECPGdisconnect(__LINE__, "CURRENT");
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ : ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 151 "sqljson.pgc"
 
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 151 "sqljson.pgc"
 
+	printf("Found json=%s\n", json);
+
+  { ECPGdisconnect(__LINE__, "CURRENT");
+#line 154 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 154 "sqljson.pgc"
+
 
   return 0;
 }
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
index 19f8c58af06..f3f899c6d87 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
@@ -339,13 +339,10 @@ SQL error: schema "jsonb" does not exist on line 121
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 142: using PQexec
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_check_PQresult on line 142: bad response - ERROR:  jsonb subscript does not support slices
-LINE 1: ..., {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] )
-                                                                ^
+[NO_PID]: ecpg_process_output on line 142: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 142: RESULT: [12, {"y": 1}] offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: raising sqlstate 42804 (sqlcode -400): jsonb subscript does not support slices on line 142
-[NO_PID]: sqlca: code: -400, state: 42804
-SQL error: jsonb subscript does not support slices on line 142
 [NO_PID]: ecpg_execute on line 145: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . * ); with 0 parameter(s) on connection ecpg1_regression
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 145: using PQexec
@@ -361,12 +358,17 @@ SQL error: row expansion via "*" is not supported here on line 145
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 148: using PQexec
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_check_PQresult on line 148: bad response - ERROR:  jsonb subscript does not support slices
-LINE 1: ...x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )
-                                                                ^
+[NO_PID]: ecpg_process_output on line 148: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 148: RESULT: [{"x": 1}, {"x": [12, {"y": 1}]}] offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 151: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 151: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 151: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 151: RESULT: [{"x": 1}, {"x": [12, {"y": 1}]}] offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: raising sqlstate 42804 (sqlcode -400): jsonb subscript does not support slices on line 148
-[NO_PID]: sqlca: code: -400, state: 42804
-SQL error: jsonb subscript does not support slices on line 148
 [NO_PID]: ecpg_finish: connection ecpg1_regression closed
 [NO_PID]: sqlca: code: 0, state: 00000
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
index 442d36931f1..d01a8457f01 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
@@ -35,3 +35,6 @@ Found json=null
 Found json={"x": 1}
 Found json=[1, [12, {"y": 1}]]
 Found json={"x": 1}
+Found json=[12, {"y": 1}]
+Found json=[{"x": 1}, {"x": [12, {"y": 1}]}]
+Found json=[{"x": 1}, {"x": [12, {"y": 1}]}]
diff --git a/src/interfaces/ecpg/test/sql/sqljson.pgc b/src/interfaces/ecpg/test/sql/sqljson.pgc
index 57a9bff424d..9423d25fd0b 100644
--- a/src/interfaces/ecpg/test/sql/sqljson.pgc
+++ b/src/interfaces/ecpg/test/sql/sqljson.pgc
@@ -140,13 +140,16 @@ EXEC SQL END DECLARE SECTION;
 	printf("Found json=%s\n", json);
 
 	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b[1].x[0:]) INTO :json;
-	// error
+	printf("Found json=%s\n", json);
 
 	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).*) INTO :json;
 	// error
 
 	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb)['b'][0:]) INTO :json;
-	// error
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b[:]) INTO :json;
+	printf("Found json=%s\n", json);
 
   EXEC SQL DISCONNECT;
 
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index d781f1518eb..f4a20694102 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5247,23 +5247,34 @@ select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b
 (1 row)
 
 select ('{"a": 1}'::jsonb)['a':'b']; -- fails
-ERROR:  jsonb subscript does not support slices
+ERROR:  only non-null integer constants are supported for jsonb simplified accessor subscripting
 LINE 1: select ('{"a": 1}'::jsonb)['a':'b'];
-                                       ^
+                                   ^
+HINT:  use int data type for subscripting with slicing.
 select ('[1, "2", null]'::jsonb)[1:2];
-ERROR:  jsonb subscript does not support slices
-LINE 1: select ('[1, "2", null]'::jsonb)[1:2];
-                                           ^
+    jsonb    
+-------------
+ ["2", null]
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[:2];
-ERROR:  jsonb subscript does not support slices
-LINE 1: select ('[1, "2", null]'::jsonb)[:2];
-                                          ^
+     jsonb      
+----------------
+ [1, "2", null]
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[1:];
-ERROR:  jsonb subscript does not support slices
-LINE 1: select ('[1, "2", null]'::jsonb)[1:];
-                                         ^
+    jsonb    
+-------------
+ ["2", null]
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[:];
-ERROR:  jsonb subscript does not support slices
+     jsonb      
+----------------
+ [1, "2", null]
+(1 row)
+
 create TEMP TABLE test_jsonb_subscript (
        id int,
        test_json jsonb
@@ -6005,8 +6016,42 @@ SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
  
 (1 row)
 
-SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t; -- fails
-ERROR:  jsonb subscript does not support slices
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
+            x             
+--------------------------
+ {"y": "YYY", "z": "ZZZ"}
+(1 row)
+
+SELECT (jb).a[2:3].b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT (jb).a[2:].b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT (jb).a[:2].b FROM test_jsonb_dot_notation;
+  b  
+-----
+ "c"
+(1 row)
+
+SELECT (jb).a[:].b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
+            x             
+--------------------------
+ {"y": "YYY", "z": "ZZZ"}
+(1 row)
+
 SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
 ERROR:  type jsonb is not composite
 -- explains should work
@@ -6042,6 +6087,28 @@ CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_d
 CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
  SELECT (jb).a[3].x.y AS y
    FROM test_jsonb_dot_notation
+CREATE VIEW v2 AS SELECT (jb).a[3:].x.y[:-1] FROM test_jsonb_dot_notation;
+\sv v2
+CREATE OR REPLACE VIEW public.v2 AS
+ SELECT (jb).a[3:].x.y[0:'-1'::integer] AS y
+   FROM test_jsonb_dot_notation
+SELECT * from v2;
+ y 
+---
+ 
+(1 row)
+
+CREATE VIEW v3 AS SELECT (jb).a[:3].x.y[-1:] FROM test_jsonb_dot_notation;
+\sv v3
+CREATE OR REPLACE VIEW public.v3 AS
+ SELECT (jb).a[0:3].x.y['-1'::integer:] AS y
+   FROM test_jsonb_dot_notation
+SELECT * from v3;
+   y   
+-------
+ "yyy"
+(1 row)
+
 -- mixed syntax
 DROP VIEW test_jsonb_dot_notation_v1;
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
@@ -6226,5 +6293,55 @@ SELECT * from test_jsonb_dot_notation_v1;
 (1 row)
 
 DROP VIEW public.test_jsonb_dot_notation_v1;
+-- jsonb array access in plpgsql
+DO $$
+    DECLARE
+        a jsonb := '[1,2,3,4,5,6,7]'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := a[2:];
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+NOTICE:  [1, 2, 3, 4, 5, 6, 7]
+NOTICE:  [3, 4, 5, 6, 7]
+NOTICE:  [5, 6, 7]
+NOTICE:  7
+-- jsonb dot access in plpgsql
+DO $$
+    DECLARE
+        a jsonb := '{"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := COALESCE(a."NU", a[2]); -- fails
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+NOTICE:  {"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}
+ERROR:  missing FROM-clause entry for table "a"
+LINE 1: a := COALESCE(a."NU", a[2])
+                      ^
+QUERY:  a := COALESCE(a."NU", a[2])
+CONTEXT:  PL/pgSQL function inline_code_block line 8 at assignment
+DO $$
+    DECLARE
+        a jsonb := '{"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := COALESCE((a)."NU", a[2]); -- succeeds
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+NOTICE:  {"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}
+NOTICE:  [{"": [[3]]}, [6], [2], "bCi"]
+NOTICE:  [2]
 -- clean up
+DROP VIEW v2;
+DROP VIEW v3;
 DROP TABLE test_jsonb_dot_notation;
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index a0fad90a9d5..b8ecefda04d 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1623,7 +1623,12 @@ SELECT (jb).a[2].b FROM test_jsonb_dot_notation;
 SELECT (jb).a.x.y FROM test_jsonb_dot_notation;
 SELECT (jb).b.x.z FROM test_jsonb_dot_notation;
 SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
-SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t; -- fails
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
+SELECT (jb).a[2:3].b FROM test_jsonb_dot_notation;
+SELECT (jb).a[2:].b FROM test_jsonb_dot_notation;
+SELECT (jb).a[:2].b FROM test_jsonb_dot_notation;
+SELECT (jb).a[:].b FROM test_jsonb_dot_notation;
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
 SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
 
 -- explains should work
@@ -1635,6 +1640,12 @@ SELECT (jb).a[1] FROM test_jsonb_dot_notation;
 -- views should work
 CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
 \sv test_jsonb_dot_notation_v1
+CREATE VIEW v2 AS SELECT (jb).a[3:].x.y[:-1] FROM test_jsonb_dot_notation;
+\sv v2
+SELECT * from v2;
+CREATE VIEW v3 AS SELECT (jb).a[:3].x.y[-1:] FROM test_jsonb_dot_notation;
+\sv v3
+SELECT * from v3;
 
 -- mixed syntax
 DROP VIEW test_jsonb_dot_notation_v1;
@@ -1695,5 +1706,45 @@ SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
 SELECT * from test_jsonb_dot_notation_v1;
 DROP VIEW public.test_jsonb_dot_notation_v1;
 
+-- jsonb array access in plpgsql
+DO $$
+    DECLARE
+        a jsonb := '[1,2,3,4,5,6,7]'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := a[2:];
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+
+-- jsonb dot access in plpgsql
+DO $$
+    DECLARE
+        a jsonb := '{"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := COALESCE(a."NU", a[2]); -- fails
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+
+DO $$
+    DECLARE
+        a jsonb := '{"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := COALESCE((a)."NU", a[2]); -- succeeds
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+
 -- clean up
+DROP VIEW v2;
+DROP VIEW v3;
 DROP TABLE test_jsonb_dot_notation;
\ No newline at end of file
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v13-0001-Allow-transformation-of-only-a-sublist-of-subscr.patch (9.0K, ../../CAK98qZ19bC=Qw9rWGOFKyX4B-fg1XQWEbV2OWAawqWC62fx79A@mail.gmail.com/6-v13-0001-Allow-transformation-of-only-a-sublist-of-subscr.patch)
  download | inline diff:
From 140fd8bb3d38e685e52a6c2fa697b3a9103061ff Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v13 1/7] Allow transformation of only a sublist of subscripts

This is a preparation step for allowing subscripting containers to
transform only a prefix of an indirection list and modify the list
in-place by removing the processed elements. Currently, all elements
are consumed, and the list is set to NIL after transformation.

In the following commit, subscripting containers will gain the
flexibility to stop transformation when encountering an unsupported
indirection and return the remaining indirections to the caller.

Reviewed-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Jian He <[email protected]>
---
 contrib/hstore/hstore_subs.c      | 10 ++++++----
 src/backend/parser/parse_expr.c   |  9 ++++-----
 src/backend/parser/parse_node.c   |  4 ++--
 src/backend/parser/parse_target.c |  2 +-
 src/backend/utils/adt/arraysubs.c |  6 ++++--
 src/backend/utils/adt/jsonbsubs.c |  6 ++++--
 src/include/nodes/subscripting.h  |  7 ++++++-
 src/include/parser/parse_node.h   |  2 +-
 8 files changed, 28 insertions(+), 18 deletions(-)

diff --git a/contrib/hstore/hstore_subs.c b/contrib/hstore/hstore_subs.c
index 3d03f66fa0d..1b29543ab67 100644
--- a/contrib/hstore/hstore_subs.c
+++ b/contrib/hstore/hstore_subs.c
@@ -40,7 +40,7 @@
  */
 static void
 hstore_subscript_transform(SubscriptingRef *sbsref,
-						   List *indirection,
+						   List **indirection,
 						   ParseState *pstate,
 						   bool isSlice,
 						   bool isAssignment)
@@ -49,15 +49,15 @@ hstore_subscript_transform(SubscriptingRef *sbsref,
 	Node	   *subexpr;
 
 	/* We support only single-subscript, non-slice cases */
-	if (isSlice || list_length(indirection) != 1)
+	if (isSlice || list_length(*indirection) != 1)
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("hstore allows only one subscript"),
 				 parser_errposition(pstate,
-									exprLocation((Node *) indirection))));
+									exprLocation((Node *) *indirection))));
 
 	/* Transform the subscript expression to type text */
-	ai = linitial_node(A_Indices, indirection);
+	ai = linitial_node(A_Indices, *indirection);
 	Assert(ai->uidx != NULL && ai->lidx == NULL && !ai->is_slice);
 
 	subexpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
@@ -81,6 +81,8 @@ hstore_subscript_transform(SubscriptingRef *sbsref,
 	/* Determine the result type of the subscripting operation; always text */
 	sbsref->refrestype = TEXTOID;
 	sbsref->reftypmod = -1;
+
+	*indirection = NIL;
 }
 
 /*
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index d66276801c6..e1565e11d09 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -466,14 +466,13 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 			Assert(IsA(n, String));
 
 			/* process subscripts before this field selection */
-			if (subscripts)
+			while (subscripts)
 				result = (Node *) transformContainerSubscripts(pstate,
 															   result,
 															   exprType(result),
 															   exprTypmod(result),
-															   subscripts,
+															   &subscripts,
 															   false);
-			subscripts = NIL;
 
 			newresult = ParseFuncOrColumn(pstate,
 										  list_make1(n),
@@ -488,12 +487,12 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		}
 	}
 	/* process trailing subscripts, if any */
-	if (subscripts)
+	while (subscripts)
 		result = (Node *) transformContainerSubscripts(pstate,
 													   result,
 													   exprType(result),
 													   exprTypmod(result),
-													   subscripts,
+													   &subscripts,
 													   false);
 
 	return result;
diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c
index 203b7a32178..f05baa50a15 100644
--- a/src/backend/parser/parse_node.c
+++ b/src/backend/parser/parse_node.c
@@ -244,7 +244,7 @@ transformContainerSubscripts(ParseState *pstate,
 							 Node *containerBase,
 							 Oid containerType,
 							 int32 containerTypMod,
-							 List *indirection,
+							 List **indirection,
 							 bool isAssignment)
 {
 	SubscriptingRef *sbsref;
@@ -280,7 +280,7 @@ transformContainerSubscripts(ParseState *pstate,
 	 * element.  If any of the items are slice specifiers (lower:upper), then
 	 * the subscript expression means a container slice operation.
 	 */
-	foreach(idx, indirection)
+	foreach(idx, *indirection)
 	{
 		A_Indices  *ai = lfirst_node(A_Indices, idx);
 
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 4aba0d9d4d5..4675a523045 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -936,7 +936,7 @@ transformAssignmentSubscripts(ParseState *pstate,
 										  basenode,
 										  containerType,
 										  containerTypMod,
-										  subscripts,
+										  &subscripts,
 										  true);
 
 	typeNeeded = sbsref->refrestype;
diff --git a/src/backend/utils/adt/arraysubs.c b/src/backend/utils/adt/arraysubs.c
index 2940fb8e8d7..234c2c278c1 100644
--- a/src/backend/utils/adt/arraysubs.c
+++ b/src/backend/utils/adt/arraysubs.c
@@ -54,7 +54,7 @@ typedef struct ArraySubWorkspace
  */
 static void
 array_subscript_transform(SubscriptingRef *sbsref,
-						  List *indirection,
+						  List **indirection,
 						  ParseState *pstate,
 						  bool isSlice,
 						  bool isAssignment)
@@ -71,7 +71,7 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	 * indirection items to slices by treating the single subscript as the
 	 * upper bound and supplying an assumed lower bound of 1.
 	 */
-	foreach(idx, indirection)
+	foreach(idx, *indirection)
 	{
 		A_Indices  *ai = lfirst_node(A_Indices, idx);
 		Node	   *subexpr;
@@ -152,6 +152,8 @@ array_subscript_transform(SubscriptingRef *sbsref,
 						list_length(upperIndexpr), MAXDIM)));
 	/* We need not check lowerIndexpr separately */
 
+	*indirection = NIL;
+
 	/*
 	 * Determine the result type of the subscripting operation.  It's the same
 	 * as the array type if we're slicing, else it's the element type.  In
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index de64d498512..8ad6aa1ad4f 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -41,7 +41,7 @@ typedef struct JsonbSubWorkspace
  */
 static void
 jsonb_subscript_transform(SubscriptingRef *sbsref,
-						  List *indirection,
+						  List **indirection,
 						  ParseState *pstate,
 						  bool isSlice,
 						  bool isAssignment)
@@ -53,7 +53,7 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	 * Transform and convert the subscript expressions. Jsonb subscripting
 	 * does not support slices, look only and the upper index.
 	 */
-	foreach(idx, indirection)
+	foreach(idx, *indirection)
 	{
 		A_Indices  *ai = lfirst_node(A_Indices, idx);
 		Node	   *subExpr;
@@ -159,6 +159,8 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	/* Determine the result type of the subscripting operation; always jsonb */
 	sbsref->refrestype = JSONBOID;
 	sbsref->reftypmod = -1;
+
+	*indirection = NIL;
 }
 
 /*
diff --git a/src/include/nodes/subscripting.h b/src/include/nodes/subscripting.h
index 234e8ad8012..5d576af346f 100644
--- a/src/include/nodes/subscripting.h
+++ b/src/include/nodes/subscripting.h
@@ -71,6 +71,11 @@ struct SubscriptExecSteps;
  * does not care to support slicing, it can just throw an error if isSlice.)
  * See array_subscript_transform() for sample code.
  *
+ * The transform method receives a pointer to a list of raw indirections.
+ * This allows the method to parse a sublist of the indirections (typically
+ * the prefix) and modify the original list in place, enabling the caller to
+ * either process the remaining indirections differently or raise an error.
+ *
  * The transform method is also responsible for identifying the result type
  * of the subscripting operation.  At call, refcontainertype and reftypmod
  * describe the container type (this will be a base type not a domain), and
@@ -93,7 +98,7 @@ struct SubscriptExecSteps;
  * assignment must return.
  */
 typedef void (*SubscriptTransform) (SubscriptingRef *sbsref,
-									List *indirection,
+									List **indirection,
 									struct ParseState *pstate,
 									bool isSlice,
 									bool isAssignment);
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index f7d07c84542..58a4b9df157 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -361,7 +361,7 @@ extern SubscriptingRef *transformContainerSubscripts(ParseState *pstate,
 													 Node *containerBase,
 													 Oid containerType,
 													 int32 containerTypMod,
-													 List *indirection,
+													 List **indirection,
 													 bool isAssignment);
 extern Const *make_const(ParseState *pstate, A_Const *aconst);
 
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v13-0005-Implement-read-only-dot-notation-for-jsonb.patch (62.3K, ../../CAK98qZ19bC=Qw9rWGOFKyX4B-fg1XQWEbV2OWAawqWC62fx79A@mail.gmail.com/7-v13-0005-Implement-read-only-dot-notation-for-jsonb.patch)
  download | inline diff:
From 7e9278c86d62e747bca862b6bc8a11e31fcd36ab Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v13 5/7] Implement read-only dot notation for jsonb

This patch introduces JSONB member access using dot notation that
aligns with the JSON simplified accessor specified in SQL:2023.

Examples:

-- Setup
create table t(x int, y jsonb);
insert into t select 1, '{"a": 1, "b": 42}'::jsonb;
insert into t select 1, '{"a": 2, "b": {"c": 42}}'::jsonb;
insert into t select 1, '{"a": 3, "b": {"c": "42"}, "d":[11, 12]}'::jsonb;

-- Existing syntax in PostgreSQL that predates the SQL standard:
select (t.y)->'b' from t;
select (t.y)->'b'->'c' from t;
select (t.y)->'d'->0 from t;

-- JSON simplified accessor specified by the SQL standard:
select (t.y).b from t;
select (t.y).b.c from t;
select (t.y).d[0] from t;

The SQL standard states that simplified access is equivalent to:
JSON_QUERY (VEP, 'lax $.JC' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR)

where:
  VEP = <value expression primary>
  JC = <JSON simplified accessor op chain>

For example, the JSON_QUERY equivalents of the above queries are:

select json_query(y, 'lax $.b' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t;
select json_query(y, 'lax $.b.c' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t;
select json_query(y, 'lax $.d[0]' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t;

Implementation details:

Extends the existing container subscripting interface to support
container-specific information, specifically a JSONPath expression for
jsonb.

During query transformation, if dot-notation is present, constructs a
JSONPath expression representing the access chain.

During execution, if a JSONPath expression is present in
JsonbSubWorkspace, executes it via JsonPathQuery().

Does not transform accessors directly into JSON_QUERY during
transformation to preserve the original query structure for EXPLAIN
and CREATE VIEW.

Co-authored-by: Nikita Glukhov <[email protected]>
Co-authored-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Mark Dilger <[email protected]>
Reviewed-by: Jian He <[email protected]>
Reviewed-by: Vik Fearing <[email protected]>
Reviewed-by: Nikita Malakhov <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Tested-by: Jelte Fennema-Nio <[email protected]>
---
 src/backend/catalog/sql_features.txt          |   4 +-
 src/backend/executor/execExpr.c               |  76 ++--
 src/backend/nodes/nodeFuncs.c                 |  12 +
 src/backend/utils/adt/jsonbsubs.c             | 352 +++++++++++++--
 src/backend/utils/adt/ruleutils.c             |  43 +-
 src/include/nodes/primnodes.h                 |  54 ++-
 .../ecpg/test/expected/sql-sqljson.c          | 112 ++++-
 .../ecpg/test/expected/sql-sqljson.stderr     | 100 +++++
 .../ecpg/test/expected/sql-sqljson.stdout     |   7 +
 src/interfaces/ecpg/test/sql/sqljson.pgc      |  33 ++
 src/test/regress/expected/jsonb.out           | 401 +++++++++++++++++-
 src/test/regress/sql/jsonb.sql                | 111 ++++-
 src/tools/pgindent/typedefs.list              |   1 +
 13 files changed, 1206 insertions(+), 100 deletions(-)

diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_features.txt
index ebe85337c28..457e993305e 100644
--- a/src/backend/catalog/sql_features.txt
+++ b/src/backend/catalog/sql_features.txt
@@ -568,8 +568,8 @@ T838	JSON_TABLE: PLAN DEFAULT clause			NO
 T839	Formatted cast of datetimes to/from character strings			NO	
 T840	Hex integer literals in SQL/JSON path language			YES	
 T851	SQL/JSON: optional keywords for default syntax			YES	
-T860	SQL/JSON simplified accessor: column reference only			NO	
-T861	SQL/JSON simplified accessor: case-sensitive JSON member accessor			NO	
+T860	SQL/JSON simplified accessor: column reference only			YES	
+T861	SQL/JSON simplified accessor: case-sensitive JSON member accessor			YES	
 T862	SQL/JSON simplified accessor: wildcard member accessor			NO	
 T863	SQL/JSON simplified accessor: single-quoted string literal as member accessor			NO	
 T864	SQL/JSON simplified accessor			NO	
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index f1569879b52..75e57ca0c79 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -3320,50 +3320,52 @@ ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref,
 								   state->steps_len - 1);
 	}
 
-	/* Evaluate upper subscripts */
-	i = 0;
-	foreach(lc, sbsref->refupperindexpr)
+	/* Evaluate upper subscripts, unless refjsonbpath is used for execution */
+	if (!sbsref->refjsonbpath)
 	{
-		Expr	   *e = (Expr *) lfirst(lc);
-
-		/* When slicing, individual subscript bounds can be omitted */
-		if (!e)
-		{
-			sbsrefstate->upperprovided[i] = false;
-			sbsrefstate->upperindexnull[i] = true;
-		}
-		else
-		{
-			sbsrefstate->upperprovided[i] = true;
-			/* Each subscript is evaluated into appropriate array entry */
-			ExecInitExprRec(e, state,
-							&sbsrefstate->upperindex[i],
-							&sbsrefstate->upperindexnull[i]);
+		i = 0;
+		foreach(lc, sbsref->refupperindexpr) {
+			Expr *e = (Expr *) lfirst(lc);
+
+			/* When slicing, individual subscript bounds can be omitted */
+			if (!e) {
+				sbsrefstate->upperprovided[i] = false;
+				sbsrefstate->upperindexnull[i] = true;
+			} else {
+				sbsrefstate->upperprovided[i] = true;
+				/* Each subscript is evaluated into appropriate array entry */
+				ExecInitExprRec(e, state,
+								&sbsrefstate->upperindex[i],
+								&sbsrefstate->upperindexnull[i]);
+			}
+			i++;
 		}
-		i++;
 	}
 
-	/* Evaluate lower subscripts similarly */
-	i = 0;
-	foreach(lc, sbsref->reflowerindexpr)
+	/* Evaluate lower subscripts similarly, unless refjsonbpath is used for execution  */
+	if (!sbsref->refjsonbpath)
 	{
-		Expr	   *e = (Expr *) lfirst(lc);
-
-		/* When slicing, individual subscript bounds can be omitted */
-		if (!e)
+		i = 0;
+		foreach(lc, sbsref->reflowerindexpr)
 		{
-			sbsrefstate->lowerprovided[i] = false;
-			sbsrefstate->lowerindexnull[i] = true;
-		}
-		else
-		{
-			sbsrefstate->lowerprovided[i] = true;
-			/* Each subscript is evaluated into appropriate array entry */
-			ExecInitExprRec(e, state,
-							&sbsrefstate->lowerindex[i],
-							&sbsrefstate->lowerindexnull[i]);
+			Expr	   *e = (Expr *) lfirst(lc);
+
+			/* When slicing, individual subscript bounds can be omitted */
+			if (!e)
+			{
+				sbsrefstate->lowerprovided[i] = false;
+				sbsrefstate->lowerindexnull[i] = true;
+			}
+			else
+			{
+				sbsrefstate->lowerprovided[i] = true;
+				/* Each subscript is evaluated into appropriate array entry */
+				ExecInitExprRec(e, state,
+								&sbsrefstate->lowerindex[i],
+								&sbsrefstate->lowerindexnull[i]);
+			}
+			i++;
 		}
-		i++;
 	}
 
 	/* SBSREF_SUBSCRIPTS checks and converts all the subscripts at once */
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 7bc823507f1..d1bd575d9bd 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -284,6 +284,13 @@ exprType(const Node *expr)
 		case T_PlaceHolderVar:
 			type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_FieldAccessorExpr:
+			/*
+			 * FieldAccessorExpr is not evaluable. Treat it as TEXT for collation,
+			 * deparsing, and similar purposes, since it represents a JSON field name.
+			 */
+			type = TEXTOID;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			type = InvalidOid;	/* keep compiler quiet */
@@ -1146,6 +1153,9 @@ exprSetCollation(Node *expr, Oid collation)
 		case T_MergeSupportFunc:
 			((MergeSupportFunc *) expr)->msfcollid = collation;
 			break;
+		case T_FieldAccessorExpr:
+			((FieldAccessorExpr *) expr)->faecollid = collation;
+			break;
 		case T_SubscriptingRef:
 			((SubscriptingRef *) expr)->refcollid = collation;
 			break;
@@ -2129,6 +2139,7 @@ expression_tree_walker_impl(Node *node,
 		case T_SortGroupClause:
 		case T_CTESearchClause:
 		case T_MergeSupportFunc:
+		case T_FieldAccessorExpr:
 			/* primitive node types with no expression subnodes */
 			break;
 		case T_WithCheckOption:
@@ -3008,6 +3019,7 @@ expression_tree_mutator_impl(Node *node,
 		case T_SortGroupClause:
 		case T_CTESearchClause:
 		case T_MergeSupportFunc:
+		case T_FieldAccessorExpr:
 			return copyObject(node);
 		case T_WithCheckOption:
 			{
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index f944d1544ca..5808383db26 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -15,21 +15,30 @@
 #include "postgres.h"
 
 #include "executor/execExpr.h"
+#include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "nodes/subscripting.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_expr.h"
 #include "utils/builtins.h"
 #include "utils/jsonb.h"
+#include "utils/jsonpath.h"
 
 
-/* SubscriptingRefState.workspace for jsonb subscripting execution */
+/*
+ * SubscriptingRefState.workspace for generic jsonb subscripting execution.
+ *
+ * Stores state for both jsonb simple subscripting and dot notation access.
+ * Dot notation additionally uses `jsonpath` for JsonPath evaluation.
+ */
 typedef struct JsonbSubWorkspace
 {
 	bool		expectArray;	/* jsonb root is expected to be an array */
 	Oid		   *indexOid;		/* OID of coerced subscript expression, could
 								 * be only integer or text */
 	Datum	   *index;			/* Subscript values in Datum format */
+	JsonPath   *jsonpath;		/* JsonPath for dot notation execution via
+								 * JsonPathQuery() */
 } JsonbSubWorkspace;
 
 static Node *
@@ -96,6 +105,255 @@ coerce_jsonpath_subscript_to_int4_or_text(ParseState *pstate, Node *subExpr)
 	return subExpr;
 }
 
+/*
+ * During transformation, determine whether to build a JsonPath
+ * for JsonPathQuery() execution.
+ *
+ * JsonPath is needed if the indirection list includes:
+ * - String-based access (dot notation)
+ * - Slice-based subscripting
+ *
+ * Otherwise, simple jsonb subscripting is enough.
+ */
+static bool
+jsonb_check_jsonpath_needed(List *indirection)
+{
+	ListCell   *lc;
+
+	foreach(lc, indirection)
+	{
+		Node	   *accessor = lfirst(lc);
+
+		if (IsA(accessor, String))
+			return true;
+		else
+		{
+			Assert(IsA(accessor, A_Indices));
+
+			if (castNode(A_Indices, accessor)->is_slice)
+				return true;
+		}
+	}
+
+	return false;
+}
+
+/*
+ * Helper functions for constructing JsonPath expressions.
+ *
+ * The make_jsonpath_item_* functions create various types of JsonPathParseItem
+ * nodes, which are used to build JsonPath expressions for jsonb simplified
+ * accessor.
+ */
+
+static JsonPathParseItem *
+make_jsonpath_item(JsonPathItemType type)
+{
+	JsonPathParseItem *v = palloc(sizeof(*v));
+
+	v->type = type;
+	v->next = NULL;
+
+	return v;
+}
+
+/*
+ * Build a JsonPathParseItem for a constant integer value.
+ *
+ * This function constructs a jpiNumeric item for use in JsonPath,
+ * and appends a matching Const(INT4) node to the given expression list
+ * for use in EXPLAIN, views, etc.
+ *
+ * Parameters:
+ * - val: integer constant value
+ * - exprs: list of expression nodes (updated in place)
+ */
+static JsonPathParseItem *
+make_jsonpath_item_int(int32 val, List **exprs)
+{
+	JsonPathParseItem *jpi = make_jsonpath_item(jpiNumeric);
+
+	jpi->value.numeric =
+		DatumGetNumeric(DirectFunctionCall1(int4_numeric, Int32GetDatum(val)));
+
+	*exprs = lappend(*exprs, makeConst(INT4OID, -1, InvalidOid, 4,
+									   Int32GetDatum(val), false, true));
+
+	return jpi;
+}
+
+/*
+ * Convert a constant integer expression into a JsonPathParseItem.
+ *
+ * The input expression must be a non-null constant of type INT4. Returns NULL otherwise.
+ * The function extracts the value and delegates it to make_jsonpath_item_int().
+ *
+ * Parameters:
+ * - pstate: parse state context
+ * - expr: input expression node
+ * - exprs: list of expression nodes (updated in place)
+ */
+static JsonPathParseItem *
+make_jsonpath_item_expr(ParseState *pstate, Node *expr, List **exprs)
+{
+	Const	   *cnst;
+
+	expr = transformExpr(pstate, expr, pstate->p_expr_kind);
+
+	if (IsA(expr, Const))
+	{
+		cnst = (Const *) expr;
+		if (cnst->consttype == INT4OID && !(cnst->constisnull))
+			return make_jsonpath_item_int(DatumGetInt32(cnst->constvalue), exprs);
+	}
+
+	return NULL;
+}
+
+/*
+ * Constructs a JsonPath expression from a list of indirections.
+ * This function is used when jsonb subscripting involves dot notation,
+ * requiring JsonPath-based evaluation.
+ *
+ * The function modifies the indirection list in place, removing processed
+ * elements as it converts them into JsonPath components, as follows:
+ * - String keys (dot notation) -> jpiKey items.
+ * - Array indices -> jpiIndexArray items.
+ *
+ * In addition to building the JsonPath expression, this function populates
+ * the following fields of the given SubscriptingRef:
+ * - refjsonbpath: the generated JsonPath
+ * - refupperindexpr: upper index expressions (object keys or array indexes)
+ * - reflowerindexpr: lower index expressions, remains NIL as slices are not yet supported.
+ *
+ * Parameters:
+ * - pstate: Parse state context.
+ * - indirection: List of subscripting expressions (modified in-place).
+ * - sbsref: SubscriptingRef node to update
+ */
+static void
+jsonb_subscript_make_jsonpath(ParseState *pstate, List **indirection, SubscriptingRef *sbsref)
+{
+	JsonPathParseResult jpres;
+	JsonPathParseItem *path = make_jsonpath_item(jpiRoot);
+	ListCell   *lc;
+	Datum		jsp;
+	int			pathlen = 0;
+
+	sbsref->refupperindexpr = NIL;
+	sbsref->reflowerindexpr = NIL;
+	sbsref->refjsonbpath = NULL;
+
+	jpres.expr = path;
+	jpres.lax = true;
+
+	foreach(lc, *indirection)
+	{
+		Node	   *accessor = lfirst(lc);
+		JsonPathParseItem *jpi;
+
+		if (IsA(accessor, String))
+		{
+			char	   *field = strVal(accessor);
+			FieldAccessorExpr *accessor_expr;
+
+			jpi = make_jsonpath_item(jpiKey);
+			jpi->value.string.val = field;
+			jpi->value.string.len = strlen(field);
+
+			accessor_expr = makeNode(FieldAccessorExpr);
+			accessor_expr->type = T_FieldAccessorExpr;
+			accessor_expr->fieldname = field;
+
+			sbsref->refupperindexpr = lappend(sbsref->refupperindexpr, accessor_expr);
+		}
+		else if (IsA(accessor, A_Indices))
+		{
+			A_Indices  *ai = castNode(A_Indices, accessor);
+
+			jpi = make_jsonpath_item(jpiIndexArray);
+			jpi->value.array.nelems = 1;
+			jpi->value.array.elems = palloc(sizeof(jpi->value.array.elems[0]));
+
+			if (ai->is_slice)
+			{
+				Node	   *expr = ai->uidx ? ai->uidx : ai->lidx;
+
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("jsonb subscript does not support slices"),
+						 parser_errposition(pstate, exprLocation(expr))));
+			}
+			else
+			{
+				JsonPathParseItem *jpi_from = NULL;
+
+				Assert(ai->uidx && !ai->lidx);
+				jpi_from = make_jsonpath_item_expr(pstate, ai->uidx, &sbsref->refupperindexpr);
+				if (jpi_from == NULL)
+				{
+					/*
+					 * Break out of the loop if the subscript is not a
+					 * non-null integer constant, so that we can fall back to
+					 * jsonb subscripting logic.
+					 *
+					 * This is needed to handle cases with mixed usage of SQL
+					 * standard json simplified accessor syntax and PostgreSQL
+					 * jsonb subscripting syntax, e.g:
+					 *
+					 * select (jb).a['b'].c from jsonb_table;
+					 *
+					 * where dot-notation (.a and .c) is the SQL standard json
+					 * simplified accessor syntax, and the ['b'] subscript is
+					 * the PostgreSQL jsonb subscripting syntax, because 'b'
+					 * is not a non-null constant integer and cannot be used
+					 * for json array access.
+					 *
+					 * In this case, we cannot create a JsonPath item, so we
+					 * break out of the loop and let
+					 * jsonb_subscript_transform() handle this indirection as
+					 * a PostgreSQL jsonb subscript.
+					 */
+					pfree(jpi->value.array.elems);
+					pfree(jpi);
+					break;
+				}
+
+				jpi->value.array.elems[0].from = jpi_from;
+				jpi->value.array.elems[0].to = NULL;
+			}
+		}
+		else
+		{
+			/*
+			 * Unexpected node type in indirection list. This should not
+			 * happen with current grammar, but we handle it defensively by
+			 * breaking out of the loop rather than crashing. In case of
+			 * future grammar changes that might introduce new node types,
+			 * this allows us to create a jsonpath from as many indirection
+			 * elements as we can and let transformIndirection() fallback to
+			 * alternative logic to handle the remaining indirection elements.
+			 */
+			Assert(false);		/* not reachable */
+			break;
+		}
+
+		/* append path item */
+		path->next = jpi;
+		path = jpi;
+		pathlen++;
+	}
+
+	if (pathlen == 0)
+		return;
+
+	*indirection = list_delete_first_n(*indirection, pathlen);
+
+	jsp = jsonPathFromParseResult(&jpres, 0, NULL);
+
+	sbsref->refjsonbpath = (Node *) makeConst(JSONPATHOID, -1, InvalidOid, -1, jsp, false, false);
+}
+
 /*
  * Finish parse analysis of a SubscriptingRef expression for a jsonb.
  *
@@ -112,47 +370,43 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	List	   *upperIndexpr = NIL;
 	ListCell   *idx;
 
+	/* Determine the result type of the subscripting operation; always jsonb */
+	sbsref->refrestype = JSONBOID;
+	sbsref->reftypmod = -1;
+
+	if (isSlice || jsonb_check_jsonpath_needed(*indirection))
+	{
+		jsonb_subscript_make_jsonpath(pstate, indirection, sbsref);
+		if (sbsref->refjsonbpath)
+			return;
+	}
+
 	/*
-	 * Transform and convert the subscript expressions. Jsonb subscripting
-	 * does not support slices, look only at the upper index.
+	 * We would only reach here if json simplified accessor is not needed, or
+	 * if jsonb_subscript_make_jsonpath() didn't consume any indirection
+	 * element — either way, the first indirection element could not be
+	 * converted into a JsonPath component. This happens when it's a non-slice
+	 * A_Indices with a non-integer upper index. The code below falls back to
+	 * traditional jsonb subscripting for such cases.
 	 */
 	foreach(idx, *indirection)
 	{
+		Node	   *i = lfirst(idx);
 		A_Indices  *ai;
 		Node	   *subExpr;
 
 		if (!IsA(lfirst(idx), A_Indices))
 			break;
 
-		ai = lfirst_node(A_Indices, idx);
+		ai = castNode(A_Indices, i);
 
-		if (isSlice)
-		{
-			Node	   *expr = ai->uidx ? ai->uidx : ai->lidx;
+		if (ai->is_slice)
+			break;
 
-			ereport(ERROR,
-					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("jsonb subscript does not support slices"),
-					 parser_errposition(pstate, exprLocation(expr))));
-		}
+		Assert(!ai->lidx && ai->uidx);
 
-		if (ai->uidx)
-		{
-			subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
-			subExpr = coerce_jsonpath_subscript_to_int4_or_text(pstate, subExpr);
-		}
-		else
-		{
-			/*
-			 * Slice with omitted upper bound. Should not happen as we already
-			 * errored out on slice earlier, but handle this just in case.
-			 */
-			Assert(isSlice && ai->is_slice);
-			ereport(ERROR,
-					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("jsonb subscript does not support slices"),
-					 parser_errposition(pstate, exprLocation(ai->uidx))));
-		}
+		subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
+		subExpr = coerce_jsonpath_subscript_to_int4_or_text(pstate, subExpr);
 
 		upperIndexpr = lappend(upperIndexpr, subExpr);
 	}
@@ -161,10 +415,6 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->refupperindexpr = upperIndexpr;
 	sbsref->reflowerindexpr = NIL;
 
-	/* Determine the result type of the subscripting operation; always jsonb */
-	sbsref->refrestype = JSONBOID;
-	sbsref->reftypmod = -1;
-
 	/* Remove processed elements */
 	if (upperIndexpr)
 		*indirection = list_delete_first_n(*indirection, list_length(upperIndexpr));
@@ -219,7 +469,7 @@ jsonb_subscript_check_subscripts(ExprState *state,
 			 * For jsonb fetch and assign functions we need to provide path in
 			 * text format. Convert if it's not already text.
 			 */
-			if (workspace->indexOid[i] == INT4OID)
+			if (!workspace->jsonpath && workspace->indexOid[i] == INT4OID)
 			{
 				Datum		datum = sbsrefstate->upperindex[i];
 				char	   *cs = DatumGetCString(DirectFunctionCall1(int4out, datum));
@@ -247,17 +497,32 @@ jsonb_subscript_fetch(ExprState *state,
 {
 	SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
 	JsonbSubWorkspace *workspace = (JsonbSubWorkspace *) sbsrefstate->workspace;
-	Jsonb	   *jsonbSource;
 
 	/* Should not get here if source jsonb (or any subscript) is null */
 	Assert(!(*op->resnull));
 
-	jsonbSource = DatumGetJsonbP(*op->resvalue);
-	*op->resvalue = jsonb_get_element(jsonbSource,
-									  workspace->index,
-									  sbsrefstate->numupper,
-									  op->resnull,
-									  false);
+	if (workspace->jsonpath)
+	{
+		bool		empty = false;
+		bool		error = false;
+
+		*op->resvalue = JsonPathQuery(*op->resvalue, workspace->jsonpath,
+									  JSW_CONDITIONAL,
+									  &empty, &error, NULL,
+									  NULL);
+
+		*op->resnull = empty || error;
+	}
+	else
+	{
+		Jsonb	   *jsonbSource = DatumGetJsonbP(*op->resvalue);
+
+		*op->resvalue = jsonb_get_element(jsonbSource,
+										  workspace->index,
+										  sbsrefstate->numupper,
+										  op->resnull,
+										  false);
+	}
 }
 
 /*
@@ -365,7 +630,7 @@ jsonb_exec_setup(const SubscriptingRef *sbsref,
 {
 	JsonbSubWorkspace *workspace;
 	ListCell   *lc;
-	int			nupper = sbsref->refupperindexpr->length;
+	int			nupper = list_length(sbsref->refupperindexpr);
 	char	   *ptr;
 
 	/* Allocate type-specific workspace with space for per-subscript data */
@@ -374,6 +639,9 @@ jsonb_exec_setup(const SubscriptingRef *sbsref,
 	workspace->expectArray = false;
 	ptr = ((char *) workspace) + MAXALIGN(sizeof(JsonbSubWorkspace));
 
+	if (sbsref->refjsonbpath)
+		workspace->jsonpath = DatumGetJsonPathP(castNode(Const, sbsref->refjsonbpath)->constvalue);
+
 	/*
 	 * This coding assumes sizeof(Datum) >= sizeof(Oid), else we might
 	 * misalign the indexOid pointer
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 3d6e6bdbfd2..baa3ae97d57 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9329,10 +9329,13 @@ get_rule_expr(Node *node, deparse_context *context,
 				 * Parenthesize the argument unless it's a simple Var or a
 				 * FieldSelect.  (In particular, if it's another
 				 * SubscriptingRef, we *must* parenthesize to avoid
-				 * confusion.)
+				 * confusion.) Always add parenthesis if JSON simplified
+				 * accessor is used, for now.
 				 */
-				need_parens = !IsA(sbsref->refexpr, Var) &&
-					!IsA(sbsref->refexpr, FieldSelect);
+				need_parens = (!IsA(sbsref->refexpr, Var) &&
+					!IsA(sbsref->refexpr, FieldSelect)) ||
+						sbsref->refjsonbpath;
+
 				if (need_parens)
 					appendStringInfoChar(buf, '(');
 				get_rule_expr((Node *) sbsref->refexpr, context, showimplicit);
@@ -13005,17 +13008,35 @@ printSubscripts(SubscriptingRef *sbsref, deparse_context *context)
 	lowlist_item = list_head(sbsref->reflowerindexpr);	/* could be NULL */
 	foreach(uplist_item, sbsref->refupperindexpr)
 	{
-		appendStringInfoChar(buf, '[');
-		if (lowlist_item)
+		Node *upper = (Node *) lfirst(uplist_item);
+
+		if (upper && IsA(upper, FieldAccessorExpr))
 		{
+			FieldAccessorExpr *fae = (FieldAccessorExpr *) upper;
+
+			/* Use dot-notation for field access */
+			appendStringInfoChar(buf, '.');
+			appendStringInfoString(buf, quote_identifier(fae->fieldname));
+
+			/* Skip matching low index — field access doesn't use slices */
+			if (lowlist_item)
+				lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
+		}
+		else
+		{
+			/* Use JSONB array subscripting */
+			appendStringInfoChar(buf, '[');
+			if (lowlist_item)
+			{
+				/* If subexpression is NULL, get_rule_expr prints nothing */
+				get_rule_expr((Node *) lfirst(lowlist_item), context, false);
+				appendStringInfoChar(buf, ':');
+				lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
+			}
 			/* If subexpression is NULL, get_rule_expr prints nothing */
-			get_rule_expr((Node *) lfirst(lowlist_item), context, false);
-			appendStringInfoChar(buf, ':');
-			lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
+			get_rule_expr(upper, context, false);
+			appendStringInfoChar(buf, ']');
 		}
-		/* If subexpression is NULL, get_rule_expr prints nothing */
-		get_rule_expr((Node *) lfirst(uplist_item), context, false);
-		appendStringInfoChar(buf, ']');
 	}
 }
 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 6dfca3cb35b..7e89621bd65 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -708,18 +708,30 @@ typedef struct SubscriptingRef
 	int32		reftypmod pg_node_attr(query_jumble_ignore);
 	/* collation of result, or InvalidOid if none */
 	Oid			refcollid pg_node_attr(query_jumble_ignore);
-	/* expressions that evaluate to upper container indexes */
+
+	/*
+	 * expressions that evaluate to upper container indexes or expressions
+	 * that are collected but not evaluated when refjsonbpath is set.
+	 */
 	List	   *refupperindexpr;
 
 	/*
-	 * expressions that evaluate to lower container indexes, or NIL for single
-	 * container element.
+	 * expressions that evaluate to lower container indexes, or NIL for a
+	 * single container element, or expressions that are collected but not
+	 * evaluated when refjsonbpath is set.
 	 */
 	List	   *reflowerindexpr;
 	/* the expression that evaluates to a container value */
 	Expr	   *refexpr;
 	/* expression for the source value, or NULL if fetch */
 	Expr	   *refassgnexpr;
+
+	/*
+	 * container-specific extra information, currently used only by jsonb.
+	 * stores a JsonPath expression when jsonb dot notation is used. NULL for
+	 * simple subscripting.
+	 */
+	Node	   *refjsonbpath;
 } SubscriptingRef;
 
 /*
@@ -2371,4 +2383,40 @@ typedef struct OnConflictExpr
 	List	   *exclRelTlist;	/* tlist of the EXCLUDED pseudo relation */
 } OnConflictExpr;
 
+/*
+ * FieldAccessorExpr - represents a single object member access using dot-notation
+ *		in JSON simplified accessor syntax (e.g., jsonb_col.a).
+ *
+ * These nodes appear as list elements in SubscriptingRef.refupperindexpr to
+ * indicate JSON object key access. They are not evaluable expressions by
+ * themselves but serve as placeholders to preserve source-level syntax for
+ * rule rewriting and deparsing (e.g., in EXPLAIN and view definitions).
+ * Execution is handled by the enclosing SubscriptingRef.
+ *
+ * If dot-notation is used in a SubscriptingRef, the JSON path is represented
+ * as a flat list of FieldAccessorExpr nodes (for object field access), Const
+ * nodes (for array indexes), and NULLs (for omitted slice bounds), rather than
+ * through nested expression trees.
+ *
+ * Note: The flat representation avoids nested FieldAccessorExpr chains,
+ * simplifying evaluation and enabling standard-compliant behavior such as
+ * conditional array wrapping. This avoids the need for position-aware
+ * wrapping/unwrapping logic during execution.
+ *
+ * For example, in the expression:
+ *		('{"a": [{"b": 1}]}'::jsonb).a[0].b
+ * the SubscriptingRef will contain:
+ *		- refexpr: the base expression (the jsonb value)
+ *		- refupperindexpr: [FieldAccessorExpr("a"), Const(0),
+ *			FieldAccessorExpr("b")]
+ *		- reflowerindexpr: [NULL, NULL, NULL] (slice lower bounds not used here)
+ */
+typedef struct FieldAccessorExpr
+{
+	NodeTag		type;
+	char	   *fieldname;		/* name of the JSONB object field accessed via
+								 * dot notation */
+	Oid			faecollid pg_node_attr(query_jumble_ignore);
+}			FieldAccessorExpr;
+
 #endif							/* PRIMNODES_H */
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.c b/src/interfaces/ecpg/test/expected/sql-sqljson.c
index 39221f9ea5d..e6a7ece6dab 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.c
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.c
@@ -417,12 +417,122 @@ if (sqlca.sqlcode < 0) sqlprint();}
 	  for (int i = 0; i < sizeof(is_json); i++)
 		  printf("Found is_json[%d]: %s\n", i, is_json[i] ? "true" : "false");
 
-  { ECPGdisconnect(__LINE__, "CURRENT");
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . \"a\" )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 118 "sqljson.pgc"
 
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 118 "sqljson.pgc"
 
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb . \"a\" )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 121 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 121 "sqljson.pgc"
+
+	// error
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . a )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 124 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 124 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . a . b )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 127 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 127 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( coalesce ( json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . c ) , 'null' ) )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 130 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 130 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ 0 ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 133 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 133 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b . x )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 136 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 136 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) [ 'b' ] [ 0 ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 139 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 139 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 142 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 142 "sqljson.pgc"
+
+	// error
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . * )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 145 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 145 "sqljson.pgc"
+
+	// error
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 148 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 148 "sqljson.pgc"
+
+	// error
+
+  { ECPGdisconnect(__LINE__, "CURRENT");
+#line 151 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 151 "sqljson.pgc"
+
 
   return 0;
 }
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
index e55a95dd711..19f8c58af06 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
@@ -268,5 +268,105 @@ SQL error: cannot use type jsonb in RETURNING clause of JSON_SERIALIZE() on line
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_get_data on line 102: RESULT: f offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 118: query: select json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . "a" ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 118: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 118: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 118: RESULT: {"b": 1, "c": 2} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 121: query: select json ( '{"a": {"b": 1, "c": 2}}' :: jsonb . "a" ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 121: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 121: bad response - ERROR:  schema "jsonb" does not exist
+LINE 1: select json ( '{"a": {"b": 1, "c": 2}}' :: jsonb . "a" )
+                                                   ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 3F000 (sqlcode -400): schema "jsonb" does not exist on line 121
+[NO_PID]: sqlca: code: -400, state: 3F000
+SQL error: schema "jsonb" does not exist on line 121
+[NO_PID]: ecpg_execute on line 124: query: select json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . a ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 124: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 124: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 124: RESULT: {"b": 1, "c": 2} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 127: query: select json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . a . b ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 127: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 127: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 127: RESULT: 1 offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 130: query: select json ( coalesce ( json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . c ) , 'null' ) ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 130: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 130: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 130: RESULT: null offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 133: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 0 ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 133: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 133: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 133: RESULT: {"x": 1} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 136: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b . x ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 136: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 136: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 136: RESULT: [1, [12, {"y": 1}]] offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 139: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 139: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 139: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 139: RESULT: {"x": 1} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 142: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 142: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 142: bad response - ERROR:  jsonb subscript does not support slices
+LINE 1: ..., {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] )
+                                                                ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 42804 (sqlcode -400): jsonb subscript does not support slices on line 142
+[NO_PID]: sqlca: code: -400, state: 42804
+SQL error: jsonb subscript does not support slices on line 142
+[NO_PID]: ecpg_execute on line 145: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . * ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 145: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 145: bad response - ERROR:  row expansion via "*" is not supported here
+LINE 1: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x...
+                        ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 0A000 (sqlcode -400): row expansion via "*" is not supported here on line 145
+[NO_PID]: sqlca: code: -400, state: 0A000
+SQL error: row expansion via "*" is not supported here on line 145
+[NO_PID]: ecpg_execute on line 148: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 148: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 148: bad response - ERROR:  jsonb subscript does not support slices
+LINE 1: ...x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )
+                                                                ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 42804 (sqlcode -400): jsonb subscript does not support slices on line 148
+[NO_PID]: sqlca: code: -400, state: 42804
+SQL error: jsonb subscript does not support slices on line 148
 [NO_PID]: ecpg_finish: connection ecpg1_regression closed
 [NO_PID]: sqlca: code: 0, state: 00000
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
index 83f8df13e5a..442d36931f1 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
@@ -28,3 +28,10 @@ Found is_json[4]: false
 Found is_json[5]: false
 Found is_json[6]: true
 Found is_json[7]: false
+Found json={"b": 1, "c": 2}
+Found json={"b": 1, "c": 2}
+Found json=1
+Found json=null
+Found json={"x": 1}
+Found json=[1, [12, {"y": 1}]]
+Found json={"x": 1}
diff --git a/src/interfaces/ecpg/test/sql/sqljson.pgc b/src/interfaces/ecpg/test/sql/sqljson.pgc
index ddcbcc3b3cb..57a9bff424d 100644
--- a/src/interfaces/ecpg/test/sql/sqljson.pgc
+++ b/src/interfaces/ecpg/test/sql/sqljson.pgc
@@ -115,6 +115,39 @@ EXEC SQL END DECLARE SECTION;
 	  for (int i = 0; i < sizeof(is_json); i++)
 		  printf("Found is_json[%d]: %s\n", i, is_json[i] ? "true" : "false");
 
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}}'::jsonb)."a") INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON('{"a": {"b": 1, "c": 2}}'::jsonb."a") INTO :json;
+	// error
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}}'::jsonb).a) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}}'::jsonb).a.b) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(COALESCE(JSON(('{"a": {"b": 1, "c": 2}}'::jsonb).c), 'null')) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b[0]) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b.x) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb)['b'][0]) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b[1].x[0:]) INTO :json;
+	// error
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).*) INTO :json;
+	// error
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb)['b'][0:]) INTO :json;
+	// error
+
   EXEC SQL DISCONNECT;
 
   return 0;
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5a1eb18aba2..d781f1518eb 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -4989,6 +4989,12 @@ select ('123'::jsonb)['a'];
  
 (1 row)
 
+select ('123'::jsonb).a;
+ a 
+---
+ 
+(1 row)
+
 select ('123'::jsonb)[0];
  jsonb 
 -------
@@ -5001,12 +5007,24 @@ select ('123'::jsonb)[NULL];
  
 (1 row)
 
+select ('123'::jsonb).NULL;
+ null 
+------
+ 
+(1 row)
+
 select ('{"a": 1}'::jsonb)['a'];
  jsonb 
 -------
  1
 (1 row)
 
+select ('{"a": 1}'::jsonb).a;
+ a 
+---
+ 1
+(1 row)
+
 select ('{"a": 1}'::jsonb)[0];
  jsonb 
 -------
@@ -5019,6 +5037,12 @@ select ('{"a": 1}'::jsonb)['not_exist'];
  
 (1 row)
 
+select ('{"a": 1}'::jsonb)."not_exist";
+ not_exist 
+-----------
+ 
+(1 row)
+
 select ('{"a": 1}'::jsonb)[NULL];
  jsonb 
 -------
@@ -5031,6 +5055,12 @@ select ('[1, "2", null]'::jsonb)['a'];
  
 (1 row)
 
+select ('[1, "2", null]'::jsonb).a;
+ a 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[0];
  jsonb 
 -------
@@ -5043,6 +5073,12 @@ select ('[1, "2", null]'::jsonb)['1'];
  "2"
 (1 row)
 
+select ('[1, "2", null]'::jsonb)."1";
+ 1 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[1.0];
 ERROR:  subscript type numeric is not supported
 LINE 1: select ('[1, "2", null]'::jsonb)[1.0];
@@ -5072,6 +5108,12 @@ select ('[1, "2", null]'::jsonb)[1]['a'];
  
 (1 row)
 
+select ('[1, "2", null]'::jsonb)[1].a;
+ a 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[1][0];
  jsonb 
 -------
@@ -5084,56 +5126,127 @@ select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['b'];
  "c"
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).b;
+  b  
+-----
+ "c"
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'];
    jsonb   
 -----------
  [1, 2, 3]
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d;
+     d     
+-----------
+ [1, 2, 3]
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'][1];
  jsonb 
 -------
  2
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d[1];
+ d 
+---
+ 2
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d']['a'];
  jsonb 
 -------
  
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['a'];
+ d 
+---
+ 
+(1 row)
+
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d.a;
+ a 
+---
+ 
+(1 row)
+
+select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d['a'][0];
+ d 
+---
+ 1
+(1 row)
+
+select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d.a[0];
+ a 
+---
+ 1
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1'];
      jsonb     
 ---------------
  {"a2": "aaa"}
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1;
+      a1       
+---------------
+ {"a2": "aaa"}
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2'];
  jsonb 
 -------
  "aaa"
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2;
+  a2   
+-------
+ "aaa"
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']['a3'];
  jsonb 
 -------
  
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2.a3;
+ a3 
+----
+ 
+(1 row)
+
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'];
          jsonb         
 -----------------------
  ["aaa", "bbb", "ccc"]
 (1 row)
 
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1;
+          b1           
+-----------------------
+ ["aaa", "bbb", "ccc"]
+(1 row)
+
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'][2];
  jsonb 
 -------
  "ccc"
 (1 row)
 
--- slices are not supported
-select ('{"a": 1}'::jsonb)['a':'b'];
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1[2];
+  b1   
+-------
+ "ccc"
+(1 row)
+
+select ('{"a": 1}'::jsonb)['a':'b']; -- fails
 ERROR:  jsonb subscript does not support slices
 LINE 1: select ('{"a": 1}'::jsonb)['a':'b'];
                                        ^
@@ -5831,3 +5944,287 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
  12345
 (1 row)
 
+-- dot notation
+CREATE TABLE test_jsonb_dot_notation AS
+SELECT '{"a": [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], "b": [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]}'::jsonb jb;
+SELECT (jb).a FROM test_jsonb_dot_notation;
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+SELECT (jb)."a" FROM test_jsonb_dot_notation; -- double quote should work
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+SELECT (jb).'a' FROM test_jsonb_dot_notation; -- single quote should not work
+ERROR:  syntax error at or near "'a'"
+LINE 1: SELECT (jb).'a' FROM test_jsonb_dot_notation;
+                    ^
+SELECT (jb).b FROM test_jsonb_dot_notation;
+                         b                         
+---------------------------------------------------
+ [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]
+(1 row)
+
+SELECT (jb).c FROM test_jsonb_dot_notation;
+ c 
+---
+ 
+(1 row)
+
+SELECT (jb).a.b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT (jb).a[2].b FROM test_jsonb_dot_notation;
+  b  
+-----
+ "c"
+(1 row)
+
+SELECT (jb).a.x.y FROM test_jsonb_dot_notation;
+   y   
+-------
+ "yyy"
+(1 row)
+
+SELECT (jb).b.x.z FROM test_jsonb_dot_notation;
+   z   
+-------
+ "ZZZ"
+(1 row)
+
+SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
+ c 
+---
+ 
+(1 row)
+
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t; -- fails
+ERROR:  jsonb subscript does not support slices
+SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
+ERROR:  type jsonb is not composite
+-- explains should work
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+                  QUERY PLAN                  
+----------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation t
+   Output: (jb).a
+(2 rows)
+
+SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb).a[1]
+(2 rows)
+
+SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+ a 
+---
+ 2
+(1 row)
+
+-- views should work
+CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT (jb).a[3].x.y AS y
+   FROM test_jsonb_dot_notation
+-- mixed syntax
+DROP VIEW test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb['a'::text]).b
+(2 rows)
+
+SELECT (jb)['a'].b FROM test_jsonb_dot_notation; -- returns an array due to lax mode
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT (jb['a'::text]).b AS b
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: ((jb).a)['b'::text]
+(2 rows)
+
+SELECT (jb).a['b'] FROM test_jsonb_dot_notation; -- returns NULL due to strict mode with warnings
+ a 
+---
+ 
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT ((jb).a)['b'::text] AS a
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+ a 
+---
+ 
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: ((jb).b.x)['z'::text]
+(2 rows)
+
+SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation; -- warnings
+   x   
+-------
+ "ZZZ"
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT ((jb).b.x)['z'::text] AS x
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+   x   
+-------
+ "ZZZ"
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: ((jb['b'::text]).x)['z'::text]
+(2 rows)
+
+SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;  -- warnings
+   x   
+-------
+ "ZZZ"
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT ((jb['b'::text]).x)['z'::text] AS x
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+   x   
+-------
+ "ZZZ"
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (((jb).b)['x'::text]).z
+(2 rows)
+
+SELECT (jb).b['x'].z FROM test_jsonb_dot_notation; -- returns NULL with warnings
+ z 
+---
+ 
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT (((jb).b)['x'::text]).z AS z
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+ z 
+---
+ 
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: ((jb).b)['x'::text]['z'::text]
+(2 rows)
+
+SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation; -- returns NULL with warnings
+ b 
+---
+ 
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT ((jb).b)['x'::text]['z'::text] AS b
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+ b 
+---
+ 
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb['b'::text]['x'::text]).z
+(2 rows)
+
+SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation; -- returns NULL with warnings
+ z 
+---
+ 
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT (jb['b'::text]['x'::text]).z AS z
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+ z 
+---
+ 
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+-- clean up
+DROP TABLE test_jsonb_dot_notation;
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 57c11acddfe..a0fad90a9d5 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1304,33 +1304,51 @@ select jsonb_insert('{"a": {"b": "value"}}', '{a, b}', '"new_value"', true);
 
 -- jsonb subscript
 select ('123'::jsonb)['a'];
+select ('123'::jsonb).a;
 select ('123'::jsonb)[0];
 select ('123'::jsonb)[NULL];
+select ('123'::jsonb).NULL;
 select ('{"a": 1}'::jsonb)['a'];
+select ('{"a": 1}'::jsonb).a;
 select ('{"a": 1}'::jsonb)[0];
 select ('{"a": 1}'::jsonb)['not_exist'];
+select ('{"a": 1}'::jsonb)."not_exist";
 select ('{"a": 1}'::jsonb)[NULL];
 select ('[1, "2", null]'::jsonb)['a'];
+select ('[1, "2", null]'::jsonb).a;
 select ('[1, "2", null]'::jsonb)[0];
 select ('[1, "2", null]'::jsonb)['1'];
+select ('[1, "2", null]'::jsonb)."1";
 select ('[1, "2", null]'::jsonb)[1.0];
 select ('[1, "2", null]'::jsonb)[2];
 select ('[1, "2", null]'::jsonb)[3];
 select ('[1, "2", null]'::jsonb)[-2];
 select ('[1, "2", null]'::jsonb)[1]['a'];
+select ('[1, "2", null]'::jsonb)[1].a;
 select ('[1, "2", null]'::jsonb)[1][0];
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['b'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).b;
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d;
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'][1];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d[1];
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d']['a'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['a'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d.a;
+select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d['a'][0];
+select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d.a[0];
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1;
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2;
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']['a3'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2.a3;
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'];
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1;
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'][2];
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1[2];
 
--- slices are not supported
-select ('{"a": 1}'::jsonb)['a':'b'];
+select ('{"a": 1}'::jsonb)['a':'b']; -- fails
 select ('[1, "2", null]'::jsonb)[1:2];
 select ('[1, "2", null]'::jsonb)[:2];
 select ('[1, "2", null]'::jsonb)[1:];
@@ -1590,3 +1608,92 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::float8;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int2;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int4;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
+
+-- dot notation
+CREATE TABLE test_jsonb_dot_notation AS
+SELECT '{"a": [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], "b": [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]}'::jsonb jb;
+
+SELECT (jb).a FROM test_jsonb_dot_notation;
+SELECT (jb)."a" FROM test_jsonb_dot_notation; -- double quote should work
+SELECT (jb).'a' FROM test_jsonb_dot_notation; -- single quote should not work
+SELECT (jb).b FROM test_jsonb_dot_notation;
+SELECT (jb).c FROM test_jsonb_dot_notation;
+SELECT (jb).a.b FROM test_jsonb_dot_notation;
+SELECT (jb).a[2].b FROM test_jsonb_dot_notation;
+SELECT (jb).a.x.y FROM test_jsonb_dot_notation;
+SELECT (jb).b.x.z FROM test_jsonb_dot_notation;
+SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t; -- fails
+SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
+
+-- explains should work
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+
+-- views should work
+CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+
+-- mixed syntax
+DROP VIEW test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
+SELECT (jb)['a'].b FROM test_jsonb_dot_notation; -- returns an array due to lax mode
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+SELECT (jb).a['b'] FROM test_jsonb_dot_notation; -- returns NULL due to strict mode with warnings
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation; -- warnings
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;  -- warnings
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+SELECT (jb).b['x'].z FROM test_jsonb_dot_notation; -- returns NULL with warnings
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation; -- returns NULL with warnings
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
+SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation; -- returns NULL with warnings
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+-- clean up
+DROP TABLE test_jsonb_dot_notation;
\ No newline at end of file
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index e4a9ec65ab4..6fca8eb7b23 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -805,6 +805,7 @@ FdwRoutine
 FetchDirection
 FetchDirectionKeywords
 FetchStmt
+FieldAccessorExpr
 FieldSelect
 FieldStore
 File
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v13-0003-Export-jsonPathFromParseResult.patch (2.7K, ../../CAK98qZ19bC=Qw9rWGOFKyX4B-fg1XQWEbV2OWAawqWC62fx79A@mail.gmail.com/8-v13-0003-Export-jsonPathFromParseResult.patch)
  download | inline diff:
From 8766d64a43f0d84c0395781edcc210753a908556 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v13 3/7] Export jsonPathFromParseResult()

This is a preparation step for a future commit that will reuse the
aforementioned function.

Authored-by: Nikita Glukhov <[email protected]>
Reviewed-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Jian He <[email protected]>
---
 src/backend/utils/adt/jsonpath.c | 19 +++++++++++++++----
 src/include/utils/jsonpath.h     |  4 ++++
 2 files changed, 19 insertions(+), 4 deletions(-)

diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index 762f7e8a09d..976aee84cf2 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -166,15 +166,13 @@ jsonpath_send(PG_FUNCTION_ARGS)
  * Converts C-string to a jsonpath value.
  *
  * Uses jsonpath parser to turn string into an AST, then
- * flattenJsonPathParseItem() does second pass turning AST into binary
+ * jsonPathFromParseResult() does second pass turning AST into binary
  * representation of jsonpath.
  */
 static Datum
 jsonPathFromCstring(char *in, int len, struct Node *escontext)
 {
 	JsonPathParseResult *jsonpath = parsejsonpath(in, len, escontext);
-	JsonPath   *res;
-	StringInfoData buf;
 
 	if (SOFT_ERROR_OCCURRED(escontext))
 		return (Datum) 0;
@@ -185,8 +183,21 @@ jsonPathFromCstring(char *in, int len, struct Node *escontext)
 				 errmsg("invalid input syntax for type %s: \"%s\"", "jsonpath",
 						in)));
 
+	return jsonPathFromParseResult(jsonpath, 4 * len, escontext);
+}
+
+/*
+ * Converts jsonpath Abstract Syntax Tree (AST) into jsonpath value in binary.
+ */
+Datum
+jsonPathFromParseResult(JsonPathParseResult *jsonpath, int estimated_len,
+						struct Node *escontext)
+{
+	JsonPath   *res;
+	StringInfoData buf;
+
 	initStringInfo(&buf);
-	enlargeStringInfo(&buf, 4 * len /* estimation */ );
+	enlargeStringInfo(&buf, estimated_len);
 
 	appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
 
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index 23a76d233e9..e05941623e7 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -281,6 +281,10 @@ extern JsonPathParseResult *parsejsonpath(const char *str, int len,
 extern bool jspConvertRegexFlags(uint32 xflags, int *result,
 								 struct Node *escontext);
 
+extern Datum jsonPathFromParseResult(JsonPathParseResult *jsonpath,
+									 int estimated_len,
+									 struct Node *escontext);
+
 /*
  * Struct for details about external variables passed into jsonpath executor
  */
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v13-0002-Allow-Generic-Type-Subscripting-to-Accept-Dot-No.patch (10.5K, ../../CAK98qZ19bC=Qw9rWGOFKyX4B-fg1XQWEbV2OWAawqWC62fx79A@mail.gmail.com/9-v13-0002-Allow-Generic-Type-Subscripting-to-Accept-Dot-No.patch)
  download | inline diff:
From 711a28579fe187177cbf47a1f70edfc06484cd45 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v13 2/7] Allow Generic Type Subscripting to Accept Dot
 Notation (.) as Input

This change extends generic type subscripting to recognize dot
notation (.) in addition to bracket notation ([]). While this does not
yet provide full support for dot notation, it enables subscripting
containers to process it in the future.

For now, container-specific transform functions only handle
subscripting indices and stop processing when encountering dot
notation. It is up to individual containers to decide how to transform
dot notation in subsequent updates.

Authored-by: Nikita Glukhov <[email protected]>
Reviewed-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Jian He <[email protected]>
---
 src/backend/parser/parse_expr.c   | 66 ++++++++++++++++++++-----------
 src/backend/parser/parse_node.c   | 41 +++++++++++++++++--
 src/backend/parser/parse_target.c |  3 +-
 src/backend/utils/adt/arraysubs.c | 13 ++++--
 src/backend/utils/adt/jsonbsubs.c | 11 +++++-
 src/include/parser/parse_node.h   |  3 +-
 6 files changed, 105 insertions(+), 32 deletions(-)

diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index e1565e11d09..dc1c7e9b75c 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -442,8 +442,9 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 	ListCell   *i;
 
 	/*
-	 * We have to split any field-selection operations apart from
-	 * subscripting.  Adjacent A_Indices nodes have to be treated as a single
+	 * Combine field names and subscripts into a single indirection list, as
+	 * some subscripting containers, such as jsonb, support field access using
+	 * dot notation. Adjacent A_Indices nodes have to be treated as a single
 	 * multidimensional subscript operation.
 	 */
 	foreach(i, ind->indirection)
@@ -461,19 +462,43 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		}
 		else
 		{
-			Node	   *newresult;
-
 			Assert(IsA(n, String));
+			subscripts = lappend(subscripts, n);
+		}
+	}
+
+	while (subscripts)
+	{
+		/* try processing container subscripts first */
+		Node	   *newresult = (Node *)
+			transformContainerSubscripts(pstate,
+										 result,
+										 exprType(result),
+										 exprTypmod(result),
+										 &subscripts,
+										 false,
+										 true);
+
+		if (!newresult)
+		{
+			/*
+			 * generic subscripting failed; falling back to field selection
+			 * for a composite type.
+			 */
+			Node	   *n;
+
+			Assert(subscripts);
 
-			/* process subscripts before this field selection */
-			while (subscripts)
-				result = (Node *) transformContainerSubscripts(pstate,
-															   result,
-															   exprType(result),
-															   exprTypmod(result),
-															   &subscripts,
-															   false);
+			n = linitial(subscripts);
+
+			if (!IsA(n, String))
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("cannot subscript type %s because it does not support subscripting",
+								format_type_be(exprType(result))),
+						 parser_errposition(pstate, exprLocation(result))));
 
+			/* try to find function for field selection */
 			newresult = ParseFuncOrColumn(pstate,
 										  list_make1(n),
 										  list_make1(result),
@@ -481,19 +506,16 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 										  NULL,
 										  false,
 										  location);
-			if (newresult == NULL)
+
+			if (!newresult)
 				unknown_attribute(pstate, result, strVal(n), location);
-			result = newresult;
+
+			/* consume field select */
+			subscripts = list_delete_first(subscripts);
 		}
+
+		result = newresult;
 	}
-	/* process trailing subscripts, if any */
-	while (subscripts)
-		result = (Node *) transformContainerSubscripts(pstate,
-													   result,
-													   exprType(result),
-													   exprTypmod(result),
-													   &subscripts,
-													   false);
 
 	return result;
 }
diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c
index f05baa50a15..c44a5a0effd 100644
--- a/src/backend/parser/parse_node.c
+++ b/src/backend/parser/parse_node.c
@@ -238,6 +238,8 @@ transformContainerType(Oid *containerType, int32 *containerTypmod)
  * containerTypMod	typmod for the container
  * indirection		Untransformed list of subscripts (must not be NIL)
  * isAssignment		True if this will become a container assignment.
+ * noError			True for return NULL with no error, if the container type
+ * 					is not subscriptable.
  */
 SubscriptingRef *
 transformContainerSubscripts(ParseState *pstate,
@@ -245,13 +247,15 @@ transformContainerSubscripts(ParseState *pstate,
 							 Oid containerType,
 							 int32 containerTypMod,
 							 List **indirection,
-							 bool isAssignment)
+							 bool isAssignment,
+							 bool noError)
 {
 	SubscriptingRef *sbsref;
 	const SubscriptRoutines *sbsroutines;
 	Oid			elementType;
 	bool		isSlice = false;
 	ListCell   *idx;
+	int			indirection_length = list_length(*indirection);
 
 	/*
 	 * Determine the actual container type, smashing any domain.  In the
@@ -267,11 +271,16 @@ transformContainerSubscripts(ParseState *pstate,
 	 */
 	sbsroutines = getSubscriptingRoutines(containerType, &elementType);
 	if (!sbsroutines)
+	{
+		if (noError)
+			return NULL;
+
 		ereport(ERROR,
 				(errcode(ERRCODE_DATATYPE_MISMATCH),
 				 errmsg("cannot subscript type %s because it does not support subscripting",
 						format_type_be(containerType)),
 				 parser_errposition(pstate, exprLocation(containerBase))));
+	}
 
 	/*
 	 * Detect whether any of the indirection items are slice specifiers.
@@ -282,9 +291,9 @@ transformContainerSubscripts(ParseState *pstate,
 	 */
 	foreach(idx, *indirection)
 	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
+		Node	   *ai = lfirst(idx);
 
-		if (ai->is_slice)
+		if (IsA(ai, A_Indices) && castNode(A_Indices, ai)->is_slice)
 		{
 			isSlice = true;
 			break;
@@ -312,6 +321,32 @@ transformContainerSubscripts(ParseState *pstate,
 	sbsroutines->transform(sbsref, indirection, pstate,
 						   isSlice, isAssignment);
 
+	/*
+	 * Error out, if datatype failed to consume any indirection elements.
+	 */
+	if (list_length(*indirection) == indirection_length)
+	{
+		Node	   *ind = linitial(*indirection);
+
+		if (noError)
+			return NULL;
+
+		if (IsA(ind, String))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("type %s does not support dot notation",
+							format_type_be(containerType)),
+					 parser_errposition(pstate, exprLocation(containerBase))));
+		else if (IsA(ind, A_Indices))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("type %s does not support array subscripting",
+							format_type_be(containerType)),
+					 parser_errposition(pstate, exprLocation(containerBase))));
+		else
+			elog(ERROR, "invalid indirection operation: %d", nodeTag(ind));
+	}
+
 	/*
 	 * Verify we got a valid type (this defends, for example, against someone
 	 * using array_subscript_handler as typsubscript without setting typelem).
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 4675a523045..3ef5897f2eb 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -937,7 +937,8 @@ transformAssignmentSubscripts(ParseState *pstate,
 										  containerType,
 										  containerTypMod,
 										  &subscripts,
-										  true);
+										  true,
+										  false);
 
 	typeNeeded = sbsref->refrestype;
 	typmodNeeded = sbsref->reftypmod;
diff --git a/src/backend/utils/adt/arraysubs.c b/src/backend/utils/adt/arraysubs.c
index 234c2c278c1..d03d3519dfd 100644
--- a/src/backend/utils/adt/arraysubs.c
+++ b/src/backend/utils/adt/arraysubs.c
@@ -62,6 +62,7 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	List	   *upperIndexpr = NIL;
 	List	   *lowerIndexpr = NIL;
 	ListCell   *idx;
+	int			ndim;
 
 	/*
 	 * Transform the subscript expressions, and separate upper and lower
@@ -73,9 +74,14 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	 */
 	foreach(idx, *indirection)
 	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
+		A_Indices  *ai;
 		Node	   *subexpr;
 
+		if (!IsA(lfirst(idx), A_Indices))
+			break;
+
+		ai = lfirst_node(A_Indices, idx);
+
 		if (isSlice)
 		{
 			if (ai->lidx)
@@ -145,14 +151,15 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->reflowerindexpr = lowerIndexpr;
 
 	/* Verify subscript list lengths are within implementation limit */
-	if (list_length(upperIndexpr) > MAXDIM)
+	ndim = list_length(upperIndexpr);
+	if (ndim > MAXDIM)
 		ereport(ERROR,
 				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 				 errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
 						list_length(upperIndexpr), MAXDIM)));
 	/* We need not check lowerIndexpr separately */
 
-	*indirection = NIL;
+	*indirection = list_delete_first_n(*indirection, ndim);
 
 	/*
 	 * Determine the result type of the subscripting operation.  It's the same
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index 8ad6aa1ad4f..a0d38a0fd80 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -55,9 +55,14 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	 */
 	foreach(idx, *indirection)
 	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
+		A_Indices  *ai;
 		Node	   *subExpr;
 
+		if (!IsA(lfirst(idx), A_Indices))
+			break;
+
+		ai = lfirst_node(A_Indices, idx);
+
 		if (isSlice)
 		{
 			Node	   *expr = ai->uidx ? ai->uidx : ai->lidx;
@@ -160,7 +165,9 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->refrestype = JSONBOID;
 	sbsref->reftypmod = -1;
 
-	*indirection = NIL;
+	/* Remove processed elements */
+	if (upperIndexpr)
+		*indirection = list_delete_first_n(*indirection, list_length(upperIndexpr));
 }
 
 /*
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 58a4b9df157..5cc3ce58c30 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -362,7 +362,8 @@ extern SubscriptingRef *transformContainerSubscripts(ParseState *pstate,
 													 Oid containerType,
 													 int32 containerTypMod,
 													 List **indirection,
-													 bool isAssignment);
+													 bool isAssignment,
+													 bool noError);
 extern Const *make_const(ParseState *pstate, A_Const *aconst);
 
 #endif							/* PARSE_NODE_H */
-- 
2.39.5 (Apple Git-154)



^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-06-24 13:29                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-07-09 08:01                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-07-10 08:53                                 ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-21 04:53                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
@ 2025-08-22 08:19                                     ` jian he <[email protected]>
  2025-08-22 19:33                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  0 siblings, 1 reply; 67+ messages in thread

From: jian he @ 2025-08-22 08:19 UTC (permalink / raw)
  To: Alexandra Wang <[email protected]>; +Cc: Nikita Malakhov <[email protected]>; Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>

On Thu, Aug 21, 2025 at 12:54 PM Alexandra Wang
<[email protected]> wrote:
>
> Hi Jian,
>
> Thanks for reviewing! I’ve attached v13, which addresses your
> feedback.
>

hi.
in the context of v13-0001 and v13-0002.

In transformIndirection:
while (subscripts)
{
   Node       *newresult = (Node *)  =
transformContainerSubscripts(....&subscripts...)
}

In transformContainerSubscripts:
    sbsroutines->transform(sbsref, indirection, pstate,
                           isSlice, isAssignment);
    /*
     * Error out, if datatype failed to consume any indirection elements.
     */
    if (list_length(*indirection) == indirection_length)
    {
    }
As you can see from the above code pattern, "sbsroutines->transform" is
responsible for consuming the "indirection".
If it doesn’t consume it, the function should either return NULL or raise an
error.
But what happens if the elements consumed by "sbsroutines->transform"
are not contiguous?

jsonb_subscript_transform below changes in v13-0002
+ if (!IsA(lfirst(idx), A_Indices))
+ break;
may cause elements consumed not contiguous.


diff --git a/src/backend/utils/adt/jsonbsubs.c
b/src/backend/utils/adt/jsonbsubs.c
index 8ad6aa1ad4f..a0d38a0fd80 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -55,9 +55,14 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
  */
  foreach(idx, *indirection)
  {
- A_Indices  *ai = lfirst_node(A_Indices, idx);
+ A_Indices  *ai;
  Node   *subExpr;

+ if (!IsA(lfirst(idx), A_Indices))
+ break;
+
+ ai = lfirst_node(A_Indices, idx);
+
  if (isSlice)
  {
  Node   *expr = ai->uidx ? ai->uidx : ai->lidx;
@@ -160,7 +165,9 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
  sbsref->refrestype = JSONBOID;
  sbsref->reftypmod = -1;

- *indirection = NIL;
+ /* Remove processed elements */
+ if (upperIndexpr)
+ *indirection = list_delete_first_n(*indirection, list_length(upperIndexpr));
 }

If the branch ``if (!IsA(lfirst(idx), A_Indices))`` is ever reached, then
``*indirection = list_delete_first_n(*indirection, list_length(upperIndexpr));``
might produce an incorrect result?

However, it appears that this branch is currently unreachable,
at least regress tests not coverage for
+ if (!IsA(lfirst(idx), A_Indices))
+ break;

Later patch we may have resolved this issue, but in the context of
0001 and 0002,
this seem inconsistent?





^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-06-24 13:29                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-07-09 08:01                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-07-10 08:53                                 ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-21 04:53                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-22 08:19                                     ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
@ 2025-08-22 19:33                                       ` Alexandra Wang <[email protected]>
  2025-08-25 08:09                                         ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  0 siblings, 1 reply; 67+ messages in thread

From: Alexandra Wang @ 2025-08-22 19:33 UTC (permalink / raw)
  To: jian he <[email protected]>; +Cc: Nikita Malakhov <[email protected]>; Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>

Hi Jian,

On Fri, Aug 22, 2025 at 1:20 AM jian he <[email protected]> wrote:

> On Thu, Aug 21, 2025 at 12:54 PM Alexandra Wang
> <[email protected]> wrote:
> >
> > Hi Jian,
> >
> > Thanks for reviewing! I’ve attached v13, which addresses your
> > feedback.
> >
>
> hi.
> in the context of v13-0001 and v13-0002.
>
> In transformIndirection:
> while (subscripts)
> {
>    Node       *newresult = (Node *)  =
> transformContainerSubscripts(....&subscripts...)
> }
>
> In transformContainerSubscripts:
>     sbsroutines->transform(sbsref, indirection, pstate,
>                            isSlice, isAssignment);
>     /*
>      * Error out, if datatype failed to consume any indirection elements.
>      */
>     if (list_length(*indirection) == indirection_length)
>     {
>     }
> As you can see from the above code pattern, "sbsroutines->transform" is
> responsible for consuming the "indirection".
> If it doesn’t consume it, the function should either return NULL or raise
> an
> error.
> But what happens if the elements consumed by "sbsroutines->transform"
> are not contiguous?
>
> jsonb_subscript_transform below changes in v13-0002
> + if (!IsA(lfirst(idx), A_Indices))
> + break;
> may cause elements consumed not contiguous.
>
>
> diff --git a/src/backend/utils/adt/jsonbsubs.c
> b/src/backend/utils/adt/jsonbsubs.c
> index 8ad6aa1ad4f..a0d38a0fd80 100644
> --- a/src/backend/utils/adt/jsonbsubs.c
> +++ b/src/backend/utils/adt/jsonbsubs.c
> @@ -55,9 +55,14 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
>   */
>   foreach(idx, *indirection)
>   {
> - A_Indices  *ai = lfirst_node(A_Indices, idx);
> + A_Indices  *ai;
>   Node   *subExpr;
>
> + if (!IsA(lfirst(idx), A_Indices))
> + break;
> +
> + ai = lfirst_node(A_Indices, idx);
> +
>   if (isSlice)
>   {
>   Node   *expr = ai->uidx ? ai->uidx : ai->lidx;
> @@ -160,7 +165,9 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
>   sbsref->refrestype = JSONBOID;
>   sbsref->reftypmod = -1;
>
> - *indirection = NIL;
> + /* Remove processed elements */
> + if (upperIndexpr)
> + *indirection = list_delete_first_n(*indirection,
> list_length(upperIndexpr));
>  }
>
> If the branch ``if (!IsA(lfirst(idx), A_Indices))`` is ever reached, then
> ``*indirection = list_delete_first_n(*indirection,
> list_length(upperIndexpr));``
> might produce an incorrect result?
>
> However, it appears that this branch is currently unreachable,
> at least regress tests not coverage for
> + if (!IsA(lfirst(idx), A_Indices))
> + break;
>
> Later patch we may have resolved this issue, but in the context of
> 0001 and 0002,
> this seem inconsistent?
>

I don’t understand the question. In the case of an unsupported Node
type (not an Indices in patch 0001 or 0002), we break out of the loop
to stop transforming the remaining subscripts. So there won’t be any
‘not contiguous’ indirection elements.

Best,
Alex


^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-06-24 13:29                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-07-09 08:01                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-07-10 08:53                                 ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-21 04:53                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-22 08:19                                     ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-22 19:33                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
@ 2025-08-25 08:09                                         ` jian he <[email protected]>
  2025-08-26 03:52                                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  0 siblings, 1 reply; 67+ messages in thread

From: jian he @ 2025-08-25 08:09 UTC (permalink / raw)
  To: Alexandra Wang <[email protected]>; +Cc: Nikita Malakhov <[email protected]>; Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>

On Sat, Aug 23, 2025 at 3:34 AM Alexandra Wang
<[email protected]> wrote:
>
> I don’t understand the question. In the case of an unsupported Node
> type (not an Indices in patch 0001 or 0002), we break out of the loop
> to stop transforming the remaining subscripts. So there won’t be any
> ‘not contiguous’ indirection elements.
>

Sorry, my last message is wrong.
this time, I applied v13-0001 to v13-0005.
and I found some minor issues.....

v13-0002-Allow-Generic-Type-Subscripting-to-Accept-Dot-No.patch
+ /*
+ * Error out, if datatype failed to consume any indirection elements.
+ */
+ if (list_length(*indirection) == indirection_length)
+ {
+ Node   *ind = linitial(*indirection);
+
+ if (noError)
+ return NULL;
+
+ if (IsA(ind, String))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("type %s does not support dot notation",
+ format_type_be(containerType)),
+ parser_errposition(pstate, exprLocation(containerBase))));
+ else if (IsA(ind, A_Indices))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("type %s does not support array subscripting",
+ format_type_be(containerType)),
+ parser_errposition(pstate, exprLocation(containerBase))));
+ else
+ elog(ERROR, "invalid indirection operation: %d", nodeTag(ind));
+ }

these error message still not reached after I apply
v13-0005-Implement-read-only-dot-notation-for-jsonb.patch
maybe we can simply do

+ if (noError)
+ return NULL;
+
+ ereport(ERROR,
+ errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("type %s does not support subscripting",
+ format_type_be(containerType)),
+ parser_errposition(pstate, exprLocation(containerBase)));
?

for V13-0004.
in master we have
            if (subExpr == NULL)
                ereport(ERROR,
                        (errcode(ERRCODE_DATATYPE_MISMATCH),
                         errmsg("jsonb subscript must have text type"),
                         parser_errposition(pstate, exprLocation(subExpr))));
but this part is removed from v13-0004-Extract-coerce_jsonpath_subscript
?

coerce_jsonpath_subscript_to_int4_or_text
maybe we can just rename to
coerce_jsonpath_subscript,
then add some comments explaining why currently only support result node coerce
to text or int4 data type.

for v13-0005-Implement-read-only-dot-notation-for-jsonb.patch

+ i = 0;
+ foreach(lc, sbsref->refupperindexpr) {
+ Expr *e = (Expr *) lfirst(lc);
+
+ /* When slicing, individual subscript bounds can be omitted */
+ if (!e) {
+ sbsrefstate->upperprovided[i] = false;
+ sbsrefstate->upperindexnull[i] = true;
+ } else {
+ sbsrefstate->upperprovided[i] = true;
+ /* Each subscript is evaluated into appropriate array entry */
+ ExecInitExprRec(e, state,
+ &sbsrefstate->upperindex[i],
+ &sbsrefstate->upperindexnull[i]);
+ }
+ i++;
  }
curly braces should be put into the next new line.

there are two
+ if (!sbsref->refjsonbpath)
+{
+}
maybe we can put these two together.


+SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation; -- returns NULL
with warnings
+ z
+---
+
+(1 row)
there is no warning, "returns NULL with warnings" should be removed.


+-- clean up
+DROP TABLE test_jsonb_dot_notation;
\ No newline at end of file

to remove "\ No newline at end of file"
you need to add a newline to jsonb.sql, you may see other regress sql
test files.


+ foreach(lc, *indirection)
+ if()
+ {
+ }
+ else
+ {
+ /*
+ * Unexpected node type in indirection list. This should not
+ * happen with current grammar, but we handle it defensively by
+ * breaking out of the loop rather than crashing. In case of
+ * future grammar changes that might introduce new node types,
+ * this allows us to create a jsonpath from as many indirection
+ * elements as we can and let transformIndirection() fallback to
+ * alternative logic to handle the remaining indirection elements.
+ */
+ Assert(false); /* not reachable */
+ break;
+ }
+
+ /* append path item */
+ path->next = jpi;
+ path = jpi;
+ pathlen++;

If the above else branch is reached, it will crash due to `Assert(false);`,
which contradicts the preceding comments.
to make the comments accurate, we need remove
" Assert(false); /* not reachable */"
?


  /*
- * Transform and convert the subscript expressions. Jsonb subscripting
- * does not support slices, look only at the upper index.
+ * We would only reach here if json simplified accessor is not needed, or
+ * if jsonb_subscript_make_jsonpath() didn't consume any indirection
+ * element — either way, the first indirection element could not be
+ * converted into a JsonPath component. This happens when it's a non-slice
+ * A_Indices with a non-integer upper index. The code below falls back to
+ * traditional jsonb subscripting for such cases.
  */
the above comments, "non-integer" part is wrong?
For example, the below two SELECTs both use "traditional" jsonb
subscripting logic.
CREATE TABLE ts1 AS SELECT '[1, [2]]' ::jsonb jb;
SELECT (jb)[1] FROM ts1;
SELECT (jb)['a'] FROM ts1;





^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-06-24 13:29                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-07-09 08:01                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-07-10 08:53                                 ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-21 04:53                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-22 08:19                                     ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-22 19:33                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-25 08:09                                         ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
@ 2025-08-26 03:52                                           ` Alexandra Wang <[email protected]>
  2025-08-27 04:15                                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-27 07:26                                             ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-08-29 03:42                                             ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-08-29 07:22                                             ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  0 siblings, 4 replies; 67+ messages in thread

From: Alexandra Wang @ 2025-08-26 03:52 UTC (permalink / raw)
  To: jian he <[email protected]>; +Cc: Nikita Malakhov <[email protected]>; Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>

Hi Jian,

I’ve attached v14, which includes only indentation and comment changes
from v13.

On Mon, Aug 25, 2025 at 1:10 AM jian he <[email protected]> wrote:

> On Sat, Aug 23, 2025 at 3:34 AM Alexandra Wang
> <[email protected]> wrote:
> >
> > I don’t understand the question. In the case of an unsupported Node
> > type (not an Indices in patch 0001 or 0002), we break out of the loop
> > to stop transforming the remaining subscripts. So there won’t be any
> > ‘not contiguous’ indirection elements.
> >
>
> Sorry, my last message is wrong.
> this time, I applied v13-0001 to v13-0005.
> and I found some minor issues.....
>

No problem. Thanks for reviewing.

On Mon, Aug 25, 2025 at 1:10 AM jian he <[email protected]> wrote:

> v13-0002-Allow-Generic-Type-Subscripting-to-Accept-Dot-No.patch
> + /*
> + * Error out, if datatype failed to consume any indirection elements.
> + */
> + if (list_length(*indirection) == indirection_length)
> + {
> + Node   *ind = linitial(*indirection);
> +
> + if (noError)
> + return NULL;
> +
> + if (IsA(ind, String))
> + ereport(ERROR,
> + (errcode(ERRCODE_DATATYPE_MISMATCH),
> + errmsg("type %s does not support dot notation",
> + format_type_be(containerType)),
> + parser_errposition(pstate, exprLocation(containerBase))));
> + else if (IsA(ind, A_Indices))
> + ereport(ERROR,
> + (errcode(ERRCODE_DATATYPE_MISMATCH),
> + errmsg("type %s does not support array subscripting",
> + format_type_be(containerType)),
> + parser_errposition(pstate, exprLocation(containerBase))));
> + else
> + elog(ERROR, "invalid indirection operation: %d", nodeTag(ind));
> + }
>
> these error message still not reached after I apply
> v13-0005-Implement-read-only-dot-notation-for-jsonb.patch
> maybe we can simply do
>

> + if (noError)
> + return NULL;
> +
> + ereport(ERROR,
> + errcode(ERRCODE_DATATYPE_MISMATCH),
> + errmsg("type %s does not support subscripting",
> + format_type_be(containerType)),
> + parser_errposition(pstate, exprLocation(containerBase)));
> ?
>

The regression tests do not currently trigger these ERROR cases
because Assignment for dot-notation has not yet been implemented. At
present, the noError argument of transformContainerSubscripts() is set
to true for SELECT and false for UPDATE. As a result, the
ereport(ERROR) calls are never reached in SELECT queries. This patch
series also does not aim to implement dot-notation for UPDATE
statements that assign to a jsonb field. We have therefore made only
minimal adjustments in transformAssignmentIndirection(). Since the
assignment code path is separate, it likewise does not reach the
ereport(ERROR) cases with in-core data types. Once dot-notation
support for Assignment is added, those ERROR messages will become
relevant.

That said, the ERROR messages are not "dead code." They can still be
triggered when working with custom data types. For example, you can
define a custom array-like type by copying the in-core array type and
slightly modifying its *_subscript_transform() function. For now, you
can simply update array_subscript_transform() in-place with the
following diff:

--- a/src/backend/utils/adt/arraysubs.c
+++ b/src/backend/utils/adt/arraysubs.c
@@ -82,6 +82,8 @@ array_subscript_transform(SubscriptingRef *sbsref,

                ai = lfirst_node(A_Indices, idx);

+               if (isSlice)
+                       break;
                if (isSlice)
                {
                        if (ai->lidx)

This way, we have a *_subscript_transform function that you can
register for a custom array-like data type that does not handle
slicing. Then you can hit the error message like this:

test=# update t set arr[1:] = 1;
ERROR:  42804: type integer[] does not support array subscripting
LINE 1: update t set arr[1:] = 1;
                     ^
LOCATION:  transformContainerSubscripts, parse_node.c:345

test=# \d t
                  Table "public.t"
 Column |   Type    | Collation | Nullable | Default
--------+-----------+-----------+----------+---------
 arr    | integer[] |           |          |

I know this error message is not very precise for the demo case in
particular (since one might expect something about slicing), but it
still demonstrates how this code path is relevant. In the meantime, I
don’t think it’s worth adding a new data type to exercise this error
message, given that we are likely to make changes to the assignment
code path for in-core types.

On Mon, Aug 25, 2025 at 1:10 AM jian he <[email protected]> wrote:

> for V13-0004.
> in master we have
>             if (subExpr == NULL)
>                 ereport(ERROR,
>                         (errcode(ERRCODE_DATATYPE_MISMATCH),
>                          errmsg("jsonb subscript must have text type"),
>                          parser_errposition(pstate,
> exprLocation(subExpr))));
> but this part is removed from v13-0004-Extract-coerce_jsonpath_subscript
> ?

I removed this one because you asked me to (see our past conversation
for v11).
On Wed, Jul 9, 2025 at 1:01 AM Alexandra Wang <[email protected]>
wrote:

> On Tue, Jun 24, 2025 at 10:57 PM jian he <[email protected]>
> wrote:
>
>> v11-0004-Extract-coerce_jsonpath_subscript.patch
>> + /*
>> + * We known from can_coerce_type that coercion will succeed, so
>> + * coerce_type could be used. Note the implicit coercion context, which
>> is
>> + * required to handle subscripts of different types, similar to
>> overloaded
>> + * functions.
>> + */
>> + subExpr = coerce_type(pstate,
>> +  subExpr, subExprType,
>> +  targetType, -1,
>> +  COERCION_IMPLICIT,
>> +  COERCE_IMPLICIT_CAST,
>> +  -1);
>> + if (subExpr == NULL)
>> + ereport(ERROR,
>> + (errcode(ERRCODE_DATATYPE_MISMATCH),
>> + errmsg("jsonb subscript must have text type"),
>>
>> the targetType can be "integer", then the error message
>> errmsg("jsonb subscript must have text type") would be wrong?
>> Also this error handling is not necessary.
>> since we can_coerce_type already tell us that coercion will succeed.
>> Also, do we really put v11-0004 as a separate patch?
>>
>
> Thanks for pointing this out! I’ve removed the unnecessary error
> handling as you suggested. I kept v12-0004 as a separate patch to make
> the review easier, but I’ll leave it up to the committer to decide
> whether to squash it with the other commits.
>

I think either way works, but I’d appreciate it if we could settle on
one option so we don’t keep going back and forth.

On Mon, Aug 25, 2025 at 1:10 AM jian he <[email protected]> wrote:

> coerce_jsonpath_subscript_to_int4_or_text
> maybe we can just rename to
> coerce_jsonpath_subscript,
> then add some comments explaining why currently only support result node
> coerce
> to text or int4 data type.
>

What is the reason for renaming this function to a less explicit name?
It was previously called coerce_jsonpath_subscript() because we passed
INT4OID as an argument to specify the target type. I followed your
earlier review comment (for v12) to remove that argument and hard-code
INT4OID within the function. Given that change, I think it’s clearer
to keep a more explicit name that describes what the function
achieves.

On Mon, Aug 25, 2025 at 1:10 AM jian he <[email protected]> wrote:

> for v13-0005-Implement-read-only-dot-notation-for-jsonb.patch
>
> + i = 0;
> + foreach(lc, sbsref->refupperindexpr) {
> + Expr *e = (Expr *) lfirst(lc);
> +
> + /* When slicing, individual subscript bounds can be omitted */
> + if (!e) {
> + sbsrefstate->upperprovided[i] = false;
> + sbsrefstate->upperindexnull[i] = true;
> + } else {
> + sbsrefstate->upperprovided[i] = true;
> + /* Each subscript is evaluated into appropriate array entry */
> + ExecInitExprRec(e, state,
> + &sbsrefstate->upperindex[i],
> + &sbsrefstate->upperindexnull[i]);
> + }
> + i++;
>   }
> curly braces should be put into the next new line.
>

Fixed. Thank you!

On Mon, Aug 25, 2025 at 1:10 AM jian he <[email protected]> wrote:

> there are two
> + if (!sbsref->refjsonbpath)
> +{
> +}
> maybe we can put these two together.
>

The code blocks are different enough, and merging them would only
reduce readability in my opinion.

On Mon, Aug 25, 2025 at 1:10 AM jian he <[email protected]> wrote:

> +SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation; -- returns NULL
> with warnings
> + z
> +---
> +
> +(1 row)
> there is no warning, "returns NULL with warnings" should be removed.
>

Fixed. Thank you!

On Mon, Aug 25, 2025 at 1:10 AM jian he <[email protected]> wrote:

> +-- clean up
> +DROP TABLE test_jsonb_dot_notation;
> \ No newline at end of file
>
> to remove "\ No newline at end of file"
> you need to add a newline to jsonb.sql, you may see other regress sql
> test files.
>

Fixed. Thank you!

On Mon, Aug 25, 2025 at 1:10 AM jian he <[email protected]> wrote:

> + foreach(lc, *indirection)
> + if()
> + {
> + }
> + else
> + {
> + /*
> + * Unexpected node type in indirection list. This should not
> + * happen with current grammar, but we handle it defensively by
> + * breaking out of the loop rather than crashing. In case of
> + * future grammar changes that might introduce new node types,
> + * this allows us to create a jsonpath from as many indirection
> + * elements as we can and let transformIndirection() fallback to
> + * alternative logic to handle the remaining indirection elements.
> + */
> + Assert(false); /* not reachable */
> + break;
> + }
> +
> + /* append path item */
> + path->next = jpi;
> + path = jpi;
> + pathlen++;
>
> If the above else branch is reached, it will crash due to `Assert(false);`,
> which contradicts the preceding comments.
> to make the comments accurate, we need remove
> " Assert(false); /* not reachable */"
> ?
>

The Assert is intentional. We want to fail fast for the “not
reachable” case in the development build, while breaking out of the
loop in the production build.

 On Mon, Aug 25, 2025 at 1:10 AM jian he <[email protected]>
wrote:

>   /*
> - * Transform and convert the subscript expressions. Jsonb subscripting
> - * does not support slices, look only at the upper index.
> + * We would only reach here if json simplified accessor is not needed, or
> + * if jsonb_subscript_make_jsonpath() didn't consume any indirection
> + * element — either way, the first indirection element could not be
> + * converted into a JsonPath component. This happens when it's a non-slice
> + * A_Indices with a non-integer upper index. The code below falls back to
> + * traditional jsonb subscripting for such cases.
>   */
> the above comments, "non-integer" part is wrong?
> For example, the below two SELECTs both use "traditional" jsonb
> subscripting logic.
> CREATE TABLE ts1 AS SELECT '[1, [2]]' ::jsonb jb;
> SELECT (jb)[1] FROM ts1;
> SELECT (jb)['a'] FROM ts1;
>

I updated the comment. In the updated comment, I also chose to use
“pre-standard” json subscripting instead of “traditional” json
subscripting. Hope that helps!



Best,
Alex


Attachments:

  [application/octet-stream] v14-0002-Allow-Generic-Type-Subscripting-to-Accept-Dot-No.patch (10.5K, ../../CAK98qZ35eF+9MZuqR4HNrmebyBFdNiNLiLZHpQPB7S7OUk-DDQ@mail.gmail.com/3-v14-0002-Allow-Generic-Type-Subscripting-to-Accept-Dot-No.patch)
  download | inline diff:
From 823d053382966d1bf2335c4d950cf8d58694e4bc Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v14 2/7] Allow Generic Type Subscripting to Accept Dot
 Notation (.) as Input

This change extends generic type subscripting to recognize dot
notation (.) in addition to bracket notation ([]). While this does not
yet provide full support for dot notation, it enables subscripting
containers to process it in the future.

For now, container-specific transform functions only handle
subscripting indices and stop processing when encountering dot
notation. It is up to individual containers to decide how to transform
dot notation in subsequent updates.

Authored-by: Nikita Glukhov <[email protected]>
Reviewed-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Jian He <[email protected]>
---
 src/backend/parser/parse_expr.c   | 66 ++++++++++++++++++++-----------
 src/backend/parser/parse_node.c   | 41 +++++++++++++++++--
 src/backend/parser/parse_target.c |  3 +-
 src/backend/utils/adt/arraysubs.c | 13 ++++--
 src/backend/utils/adt/jsonbsubs.c | 11 +++++-
 src/include/parser/parse_node.h   |  3 +-
 6 files changed, 105 insertions(+), 32 deletions(-)

diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index e1565e11d09..dc1c7e9b75c 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -442,8 +442,9 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 	ListCell   *i;
 
 	/*
-	 * We have to split any field-selection operations apart from
-	 * subscripting.  Adjacent A_Indices nodes have to be treated as a single
+	 * Combine field names and subscripts into a single indirection list, as
+	 * some subscripting containers, such as jsonb, support field access using
+	 * dot notation. Adjacent A_Indices nodes have to be treated as a single
 	 * multidimensional subscript operation.
 	 */
 	foreach(i, ind->indirection)
@@ -461,19 +462,43 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		}
 		else
 		{
-			Node	   *newresult;
-
 			Assert(IsA(n, String));
+			subscripts = lappend(subscripts, n);
+		}
+	}
+
+	while (subscripts)
+	{
+		/* try processing container subscripts first */
+		Node	   *newresult = (Node *)
+			transformContainerSubscripts(pstate,
+										 result,
+										 exprType(result),
+										 exprTypmod(result),
+										 &subscripts,
+										 false,
+										 true);
+
+		if (!newresult)
+		{
+			/*
+			 * generic subscripting failed; falling back to field selection
+			 * for a composite type.
+			 */
+			Node	   *n;
+
+			Assert(subscripts);
 
-			/* process subscripts before this field selection */
-			while (subscripts)
-				result = (Node *) transformContainerSubscripts(pstate,
-															   result,
-															   exprType(result),
-															   exprTypmod(result),
-															   &subscripts,
-															   false);
+			n = linitial(subscripts);
+
+			if (!IsA(n, String))
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("cannot subscript type %s because it does not support subscripting",
+								format_type_be(exprType(result))),
+						 parser_errposition(pstate, exprLocation(result))));
 
+			/* try to find function for field selection */
 			newresult = ParseFuncOrColumn(pstate,
 										  list_make1(n),
 										  list_make1(result),
@@ -481,19 +506,16 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 										  NULL,
 										  false,
 										  location);
-			if (newresult == NULL)
+
+			if (!newresult)
 				unknown_attribute(pstate, result, strVal(n), location);
-			result = newresult;
+
+			/* consume field select */
+			subscripts = list_delete_first(subscripts);
 		}
+
+		result = newresult;
 	}
-	/* process trailing subscripts, if any */
-	while (subscripts)
-		result = (Node *) transformContainerSubscripts(pstate,
-													   result,
-													   exprType(result),
-													   exprTypmod(result),
-													   &subscripts,
-													   false);
 
 	return result;
 }
diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c
index f05baa50a15..c44a5a0effd 100644
--- a/src/backend/parser/parse_node.c
+++ b/src/backend/parser/parse_node.c
@@ -238,6 +238,8 @@ transformContainerType(Oid *containerType, int32 *containerTypmod)
  * containerTypMod	typmod for the container
  * indirection		Untransformed list of subscripts (must not be NIL)
  * isAssignment		True if this will become a container assignment.
+ * noError			True for return NULL with no error, if the container type
+ * 					is not subscriptable.
  */
 SubscriptingRef *
 transformContainerSubscripts(ParseState *pstate,
@@ -245,13 +247,15 @@ transformContainerSubscripts(ParseState *pstate,
 							 Oid containerType,
 							 int32 containerTypMod,
 							 List **indirection,
-							 bool isAssignment)
+							 bool isAssignment,
+							 bool noError)
 {
 	SubscriptingRef *sbsref;
 	const SubscriptRoutines *sbsroutines;
 	Oid			elementType;
 	bool		isSlice = false;
 	ListCell   *idx;
+	int			indirection_length = list_length(*indirection);
 
 	/*
 	 * Determine the actual container type, smashing any domain.  In the
@@ -267,11 +271,16 @@ transformContainerSubscripts(ParseState *pstate,
 	 */
 	sbsroutines = getSubscriptingRoutines(containerType, &elementType);
 	if (!sbsroutines)
+	{
+		if (noError)
+			return NULL;
+
 		ereport(ERROR,
 				(errcode(ERRCODE_DATATYPE_MISMATCH),
 				 errmsg("cannot subscript type %s because it does not support subscripting",
 						format_type_be(containerType)),
 				 parser_errposition(pstate, exprLocation(containerBase))));
+	}
 
 	/*
 	 * Detect whether any of the indirection items are slice specifiers.
@@ -282,9 +291,9 @@ transformContainerSubscripts(ParseState *pstate,
 	 */
 	foreach(idx, *indirection)
 	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
+		Node	   *ai = lfirst(idx);
 
-		if (ai->is_slice)
+		if (IsA(ai, A_Indices) && castNode(A_Indices, ai)->is_slice)
 		{
 			isSlice = true;
 			break;
@@ -312,6 +321,32 @@ transformContainerSubscripts(ParseState *pstate,
 	sbsroutines->transform(sbsref, indirection, pstate,
 						   isSlice, isAssignment);
 
+	/*
+	 * Error out, if datatype failed to consume any indirection elements.
+	 */
+	if (list_length(*indirection) == indirection_length)
+	{
+		Node	   *ind = linitial(*indirection);
+
+		if (noError)
+			return NULL;
+
+		if (IsA(ind, String))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("type %s does not support dot notation",
+							format_type_be(containerType)),
+					 parser_errposition(pstate, exprLocation(containerBase))));
+		else if (IsA(ind, A_Indices))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("type %s does not support array subscripting",
+							format_type_be(containerType)),
+					 parser_errposition(pstate, exprLocation(containerBase))));
+		else
+			elog(ERROR, "invalid indirection operation: %d", nodeTag(ind));
+	}
+
 	/*
 	 * Verify we got a valid type (this defends, for example, against someone
 	 * using array_subscript_handler as typsubscript without setting typelem).
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 4675a523045..3ef5897f2eb 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -937,7 +937,8 @@ transformAssignmentSubscripts(ParseState *pstate,
 										  containerType,
 										  containerTypMod,
 										  &subscripts,
-										  true);
+										  true,
+										  false);
 
 	typeNeeded = sbsref->refrestype;
 	typmodNeeded = sbsref->reftypmod;
diff --git a/src/backend/utils/adt/arraysubs.c b/src/backend/utils/adt/arraysubs.c
index 234c2c278c1..d03d3519dfd 100644
--- a/src/backend/utils/adt/arraysubs.c
+++ b/src/backend/utils/adt/arraysubs.c
@@ -62,6 +62,7 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	List	   *upperIndexpr = NIL;
 	List	   *lowerIndexpr = NIL;
 	ListCell   *idx;
+	int			ndim;
 
 	/*
 	 * Transform the subscript expressions, and separate upper and lower
@@ -73,9 +74,14 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	 */
 	foreach(idx, *indirection)
 	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
+		A_Indices  *ai;
 		Node	   *subexpr;
 
+		if (!IsA(lfirst(idx), A_Indices))
+			break;
+
+		ai = lfirst_node(A_Indices, idx);
+
 		if (isSlice)
 		{
 			if (ai->lidx)
@@ -145,14 +151,15 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->reflowerindexpr = lowerIndexpr;
 
 	/* Verify subscript list lengths are within implementation limit */
-	if (list_length(upperIndexpr) > MAXDIM)
+	ndim = list_length(upperIndexpr);
+	if (ndim > MAXDIM)
 		ereport(ERROR,
 				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 				 errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
 						list_length(upperIndexpr), MAXDIM)));
 	/* We need not check lowerIndexpr separately */
 
-	*indirection = NIL;
+	*indirection = list_delete_first_n(*indirection, ndim);
 
 	/*
 	 * Determine the result type of the subscripting operation.  It's the same
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index 8ad6aa1ad4f..a0d38a0fd80 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -55,9 +55,14 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	 */
 	foreach(idx, *indirection)
 	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
+		A_Indices  *ai;
 		Node	   *subExpr;
 
+		if (!IsA(lfirst(idx), A_Indices))
+			break;
+
+		ai = lfirst_node(A_Indices, idx);
+
 		if (isSlice)
 		{
 			Node	   *expr = ai->uidx ? ai->uidx : ai->lidx;
@@ -160,7 +165,9 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->refrestype = JSONBOID;
 	sbsref->reftypmod = -1;
 
-	*indirection = NIL;
+	/* Remove processed elements */
+	if (upperIndexpr)
+		*indirection = list_delete_first_n(*indirection, list_length(upperIndexpr));
 }
 
 /*
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 58a4b9df157..5cc3ce58c30 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -362,7 +362,8 @@ extern SubscriptingRef *transformContainerSubscripts(ParseState *pstate,
 													 Oid containerType,
 													 int32 containerTypMod,
 													 List **indirection,
-													 bool isAssignment);
+													 bool isAssignment,
+													 bool noError);
 extern Const *make_const(ParseState *pstate, A_Const *aconst);
 
 #endif							/* PARSE_NODE_H */
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v14-0003-Export-jsonPathFromParseResult.patch (2.7K, ../../CAK98qZ35eF+9MZuqR4HNrmebyBFdNiNLiLZHpQPB7S7OUk-DDQ@mail.gmail.com/4-v14-0003-Export-jsonPathFromParseResult.patch)
  download | inline diff:
From 8555a4774e6bcfd0dc2cb3af6898519c02931f61 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v14 3/7] Export jsonPathFromParseResult()

This is a preparation step for a future commit that will reuse the
aforementioned function.

Authored-by: Nikita Glukhov <[email protected]>
Reviewed-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Jian He <[email protected]>
---
 src/backend/utils/adt/jsonpath.c | 19 +++++++++++++++----
 src/include/utils/jsonpath.h     |  4 ++++
 2 files changed, 19 insertions(+), 4 deletions(-)

diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index 762f7e8a09d..976aee84cf2 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -166,15 +166,13 @@ jsonpath_send(PG_FUNCTION_ARGS)
  * Converts C-string to a jsonpath value.
  *
  * Uses jsonpath parser to turn string into an AST, then
- * flattenJsonPathParseItem() does second pass turning AST into binary
+ * jsonPathFromParseResult() does second pass turning AST into binary
  * representation of jsonpath.
  */
 static Datum
 jsonPathFromCstring(char *in, int len, struct Node *escontext)
 {
 	JsonPathParseResult *jsonpath = parsejsonpath(in, len, escontext);
-	JsonPath   *res;
-	StringInfoData buf;
 
 	if (SOFT_ERROR_OCCURRED(escontext))
 		return (Datum) 0;
@@ -185,8 +183,21 @@ jsonPathFromCstring(char *in, int len, struct Node *escontext)
 				 errmsg("invalid input syntax for type %s: \"%s\"", "jsonpath",
 						in)));
 
+	return jsonPathFromParseResult(jsonpath, 4 * len, escontext);
+}
+
+/*
+ * Converts jsonpath Abstract Syntax Tree (AST) into jsonpath value in binary.
+ */
+Datum
+jsonPathFromParseResult(JsonPathParseResult *jsonpath, int estimated_len,
+						struct Node *escontext)
+{
+	JsonPath   *res;
+	StringInfoData buf;
+
 	initStringInfo(&buf);
-	enlargeStringInfo(&buf, 4 * len /* estimation */ );
+	enlargeStringInfo(&buf, estimated_len);
 
 	appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
 
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index 23a76d233e9..e05941623e7 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -281,6 +281,10 @@ extern JsonPathParseResult *parsejsonpath(const char *str, int len,
 extern bool jspConvertRegexFlags(uint32 xflags, int *result,
 								 struct Node *escontext);
 
+extern Datum jsonPathFromParseResult(JsonPathParseResult *jsonpath,
+									 int estimated_len,
+									 struct Node *escontext);
+
 /*
  * Struct for details about external variables passed into jsonpath executor
  */
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v14-0001-Allow-transformation-of-only-a-sublist-of-subscr.patch (9.0K, ../../CAK98qZ35eF+9MZuqR4HNrmebyBFdNiNLiLZHpQPB7S7OUk-DDQ@mail.gmail.com/5-v14-0001-Allow-transformation-of-only-a-sublist-of-subscr.patch)
  download | inline diff:
From 74824f1cc949041c0b5e0a35a668ab73ce08a23d Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v14 1/7] Allow transformation of only a sublist of subscripts

This is a preparation step for allowing subscripting containers to
transform only a prefix of an indirection list and modify the list
in-place by removing the processed elements. Currently, all elements
are consumed, and the list is set to NIL after transformation.

In the following commit, subscripting containers will gain the
flexibility to stop transformation when encountering an unsupported
indirection and return the remaining indirections to the caller.

Reviewed-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Jian He <[email protected]>
---
 contrib/hstore/hstore_subs.c      | 10 ++++++----
 src/backend/parser/parse_expr.c   |  9 ++++-----
 src/backend/parser/parse_node.c   |  4 ++--
 src/backend/parser/parse_target.c |  2 +-
 src/backend/utils/adt/arraysubs.c |  6 ++++--
 src/backend/utils/adt/jsonbsubs.c |  6 ++++--
 src/include/nodes/subscripting.h  |  7 ++++++-
 src/include/parser/parse_node.h   |  2 +-
 8 files changed, 28 insertions(+), 18 deletions(-)

diff --git a/contrib/hstore/hstore_subs.c b/contrib/hstore/hstore_subs.c
index 3d03f66fa0d..1b29543ab67 100644
--- a/contrib/hstore/hstore_subs.c
+++ b/contrib/hstore/hstore_subs.c
@@ -40,7 +40,7 @@
  */
 static void
 hstore_subscript_transform(SubscriptingRef *sbsref,
-						   List *indirection,
+						   List **indirection,
 						   ParseState *pstate,
 						   bool isSlice,
 						   bool isAssignment)
@@ -49,15 +49,15 @@ hstore_subscript_transform(SubscriptingRef *sbsref,
 	Node	   *subexpr;
 
 	/* We support only single-subscript, non-slice cases */
-	if (isSlice || list_length(indirection) != 1)
+	if (isSlice || list_length(*indirection) != 1)
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("hstore allows only one subscript"),
 				 parser_errposition(pstate,
-									exprLocation((Node *) indirection))));
+									exprLocation((Node *) *indirection))));
 
 	/* Transform the subscript expression to type text */
-	ai = linitial_node(A_Indices, indirection);
+	ai = linitial_node(A_Indices, *indirection);
 	Assert(ai->uidx != NULL && ai->lidx == NULL && !ai->is_slice);
 
 	subexpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
@@ -81,6 +81,8 @@ hstore_subscript_transform(SubscriptingRef *sbsref,
 	/* Determine the result type of the subscripting operation; always text */
 	sbsref->refrestype = TEXTOID;
 	sbsref->reftypmod = -1;
+
+	*indirection = NIL;
 }
 
 /*
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index d66276801c6..e1565e11d09 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -466,14 +466,13 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 			Assert(IsA(n, String));
 
 			/* process subscripts before this field selection */
-			if (subscripts)
+			while (subscripts)
 				result = (Node *) transformContainerSubscripts(pstate,
 															   result,
 															   exprType(result),
 															   exprTypmod(result),
-															   subscripts,
+															   &subscripts,
 															   false);
-			subscripts = NIL;
 
 			newresult = ParseFuncOrColumn(pstate,
 										  list_make1(n),
@@ -488,12 +487,12 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		}
 	}
 	/* process trailing subscripts, if any */
-	if (subscripts)
+	while (subscripts)
 		result = (Node *) transformContainerSubscripts(pstate,
 													   result,
 													   exprType(result),
 													   exprTypmod(result),
-													   subscripts,
+													   &subscripts,
 													   false);
 
 	return result;
diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c
index 203b7a32178..f05baa50a15 100644
--- a/src/backend/parser/parse_node.c
+++ b/src/backend/parser/parse_node.c
@@ -244,7 +244,7 @@ transformContainerSubscripts(ParseState *pstate,
 							 Node *containerBase,
 							 Oid containerType,
 							 int32 containerTypMod,
-							 List *indirection,
+							 List **indirection,
 							 bool isAssignment)
 {
 	SubscriptingRef *sbsref;
@@ -280,7 +280,7 @@ transformContainerSubscripts(ParseState *pstate,
 	 * element.  If any of the items are slice specifiers (lower:upper), then
 	 * the subscript expression means a container slice operation.
 	 */
-	foreach(idx, indirection)
+	foreach(idx, *indirection)
 	{
 		A_Indices  *ai = lfirst_node(A_Indices, idx);
 
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 4aba0d9d4d5..4675a523045 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -936,7 +936,7 @@ transformAssignmentSubscripts(ParseState *pstate,
 										  basenode,
 										  containerType,
 										  containerTypMod,
-										  subscripts,
+										  &subscripts,
 										  true);
 
 	typeNeeded = sbsref->refrestype;
diff --git a/src/backend/utils/adt/arraysubs.c b/src/backend/utils/adt/arraysubs.c
index 2940fb8e8d7..234c2c278c1 100644
--- a/src/backend/utils/adt/arraysubs.c
+++ b/src/backend/utils/adt/arraysubs.c
@@ -54,7 +54,7 @@ typedef struct ArraySubWorkspace
  */
 static void
 array_subscript_transform(SubscriptingRef *sbsref,
-						  List *indirection,
+						  List **indirection,
 						  ParseState *pstate,
 						  bool isSlice,
 						  bool isAssignment)
@@ -71,7 +71,7 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	 * indirection items to slices by treating the single subscript as the
 	 * upper bound and supplying an assumed lower bound of 1.
 	 */
-	foreach(idx, indirection)
+	foreach(idx, *indirection)
 	{
 		A_Indices  *ai = lfirst_node(A_Indices, idx);
 		Node	   *subexpr;
@@ -152,6 +152,8 @@ array_subscript_transform(SubscriptingRef *sbsref,
 						list_length(upperIndexpr), MAXDIM)));
 	/* We need not check lowerIndexpr separately */
 
+	*indirection = NIL;
+
 	/*
 	 * Determine the result type of the subscripting operation.  It's the same
 	 * as the array type if we're slicing, else it's the element type.  In
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index de64d498512..8ad6aa1ad4f 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -41,7 +41,7 @@ typedef struct JsonbSubWorkspace
  */
 static void
 jsonb_subscript_transform(SubscriptingRef *sbsref,
-						  List *indirection,
+						  List **indirection,
 						  ParseState *pstate,
 						  bool isSlice,
 						  bool isAssignment)
@@ -53,7 +53,7 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	 * Transform and convert the subscript expressions. Jsonb subscripting
 	 * does not support slices, look only and the upper index.
 	 */
-	foreach(idx, indirection)
+	foreach(idx, *indirection)
 	{
 		A_Indices  *ai = lfirst_node(A_Indices, idx);
 		Node	   *subExpr;
@@ -159,6 +159,8 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	/* Determine the result type of the subscripting operation; always jsonb */
 	sbsref->refrestype = JSONBOID;
 	sbsref->reftypmod = -1;
+
+	*indirection = NIL;
 }
 
 /*
diff --git a/src/include/nodes/subscripting.h b/src/include/nodes/subscripting.h
index 234e8ad8012..5d576af346f 100644
--- a/src/include/nodes/subscripting.h
+++ b/src/include/nodes/subscripting.h
@@ -71,6 +71,11 @@ struct SubscriptExecSteps;
  * does not care to support slicing, it can just throw an error if isSlice.)
  * See array_subscript_transform() for sample code.
  *
+ * The transform method receives a pointer to a list of raw indirections.
+ * This allows the method to parse a sublist of the indirections (typically
+ * the prefix) and modify the original list in place, enabling the caller to
+ * either process the remaining indirections differently or raise an error.
+ *
  * The transform method is also responsible for identifying the result type
  * of the subscripting operation.  At call, refcontainertype and reftypmod
  * describe the container type (this will be a base type not a domain), and
@@ -93,7 +98,7 @@ struct SubscriptExecSteps;
  * assignment must return.
  */
 typedef void (*SubscriptTransform) (SubscriptingRef *sbsref,
-									List *indirection,
+									List **indirection,
 									struct ParseState *pstate,
 									bool isSlice,
 									bool isAssignment);
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index f7d07c84542..58a4b9df157 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -361,7 +361,7 @@ extern SubscriptingRef *transformContainerSubscripts(ParseState *pstate,
 													 Node *containerBase,
 													 Oid containerType,
 													 int32 containerTypMod,
-													 List *indirection,
+													 List **indirection,
 													 bool isAssignment);
 extern Const *make_const(ParseState *pstate, A_Const *aconst);
 
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v14-0005-Implement-read-only-dot-notation-for-jsonb.patch (62.2K, ../../CAK98qZ35eF+9MZuqR4HNrmebyBFdNiNLiLZHpQPB7S7OUk-DDQ@mail.gmail.com/6-v14-0005-Implement-read-only-dot-notation-for-jsonb.patch)
  download | inline diff:
From d94b63a492134c8bc53938eaa2225eade6f5155a Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v14 5/7] Implement read-only dot notation for jsonb

This patch introduces JSONB member access using dot notation that
aligns with the JSON simplified accessor specified in SQL:2023.

Examples:

-- Setup
create table t(x int, y jsonb);
insert into t select 1, '{"a": 1, "b": 42}'::jsonb;
insert into t select 1, '{"a": 2, "b": {"c": 42}}'::jsonb;
insert into t select 1, '{"a": 3, "b": {"c": "42"}, "d":[11, 12]}'::jsonb;

-- Existing syntax in PostgreSQL that predates the SQL standard:
select (t.y)->'b' from t;
select (t.y)->'b'->'c' from t;
select (t.y)->'d'->0 from t;

-- JSON simplified accessor specified by the SQL standard:
select (t.y).b from t;
select (t.y).b.c from t;
select (t.y).d[0] from t;

The SQL standard states that simplified access is equivalent to:
JSON_QUERY (VEP, 'lax $.JC' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR)

where:
  VEP = <value expression primary>
  JC = <JSON simplified accessor op chain>

For example, the JSON_QUERY equivalents of the above queries are:

select json_query(y, 'lax $.b' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t;
select json_query(y, 'lax $.b.c' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t;
select json_query(y, 'lax $.d[0]' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t;

Implementation details:

Extends the existing container subscripting interface to support
container-specific information, specifically a JSONPath expression for
jsonb.

During query transformation, if dot-notation is present, constructs a
JSONPath expression representing the access chain.

During execution, if a JSONPath expression is present in
JsonbSubWorkspace, executes it via JsonPathQuery().

Does not transform accessors directly into JSON_QUERY during
transformation to preserve the original query structure for EXPLAIN
and CREATE VIEW.

Co-authored-by: Nikita Glukhov <[email protected]>
Co-authored-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Mark Dilger <[email protected]>
Reviewed-by: Jian He <[email protected]>
Reviewed-by: Vik Fearing <[email protected]>
Reviewed-by: Nikita Malakhov <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Tested-by: Jelte Fennema-Nio <[email protected]>
---
 src/backend/catalog/sql_features.txt          |   4 +-
 src/backend/executor/execExpr.c               |  76 ++--
 src/backend/nodes/nodeFuncs.c                 |  12 +
 src/backend/utils/adt/jsonbsubs.c             | 354 ++++++++++++++--
 src/backend/utils/adt/ruleutils.c             |  43 +-
 src/include/nodes/primnodes.h                 |  54 ++-
 .../ecpg/test/expected/sql-sqljson.c          | 112 ++++-
 .../ecpg/test/expected/sql-sqljson.stderr     | 100 +++++
 .../ecpg/test/expected/sql-sqljson.stdout     |   7 +
 src/interfaces/ecpg/test/sql/sqljson.pgc      |  33 ++
 src/test/regress/expected/jsonb.out           | 401 +++++++++++++++++-
 src/test/regress/sql/jsonb.sql                | 111 ++++-
 src/tools/pgindent/typedefs.list              |   1 +
 13 files changed, 1209 insertions(+), 99 deletions(-)

diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_features.txt
index ebe85337c28..457e993305e 100644
--- a/src/backend/catalog/sql_features.txt
+++ b/src/backend/catalog/sql_features.txt
@@ -568,8 +568,8 @@ T838	JSON_TABLE: PLAN DEFAULT clause			NO
 T839	Formatted cast of datetimes to/from character strings			NO	
 T840	Hex integer literals in SQL/JSON path language			YES	
 T851	SQL/JSON: optional keywords for default syntax			YES	
-T860	SQL/JSON simplified accessor: column reference only			NO	
-T861	SQL/JSON simplified accessor: case-sensitive JSON member accessor			NO	
+T860	SQL/JSON simplified accessor: column reference only			YES	
+T861	SQL/JSON simplified accessor: case-sensitive JSON member accessor			YES	
 T862	SQL/JSON simplified accessor: wildcard member accessor			NO	
 T863	SQL/JSON simplified accessor: single-quoted string literal as member accessor			NO	
 T864	SQL/JSON simplified accessor			NO	
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index f1569879b52..eac31b097e4 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -3320,50 +3320,54 @@ ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref,
 								   state->steps_len - 1);
 	}
 
-	/* Evaluate upper subscripts */
-	i = 0;
-	foreach(lc, sbsref->refupperindexpr)
+	/* Evaluate upper subscripts, unless refjsonbpath is used for execution */
+	if (!sbsref->refjsonbpath)
 	{
-		Expr	   *e = (Expr *) lfirst(lc);
+		i = 0;
+		foreach(lc, sbsref->refupperindexpr) {
+			Expr *e = (Expr *) lfirst(lc);
 
-		/* When slicing, individual subscript bounds can be omitted */
-		if (!e)
-		{
-			sbsrefstate->upperprovided[i] = false;
-			sbsrefstate->upperindexnull[i] = true;
-		}
-		else
-		{
-			sbsrefstate->upperprovided[i] = true;
-			/* Each subscript is evaluated into appropriate array entry */
-			ExecInitExprRec(e, state,
-							&sbsrefstate->upperindex[i],
-							&sbsrefstate->upperindexnull[i]);
+			/* When slicing, individual subscript bounds can be omitted */
+			if (!e)
+			{
+				sbsrefstate->upperprovided[i] = false;
+				sbsrefstate->upperindexnull[i] = true;
+			}
+			else {
+				sbsrefstate->upperprovided[i] = true;
+				/* Each subscript is evaluated into appropriate array entry */
+				ExecInitExprRec(e, state,
+								&sbsrefstate->upperindex[i],
+								&sbsrefstate->upperindexnull[i]);
+			}
+			i++;
 		}
-		i++;
 	}
 
-	/* Evaluate lower subscripts similarly */
-	i = 0;
-	foreach(lc, sbsref->reflowerindexpr)
+	/* Evaluate lower subscripts similarly, unless refjsonbpath is used for execution  */
+	if (!sbsref->refjsonbpath)
 	{
-		Expr	   *e = (Expr *) lfirst(lc);
-
-		/* When slicing, individual subscript bounds can be omitted */
-		if (!e)
+		i = 0;
+		foreach(lc, sbsref->reflowerindexpr)
 		{
-			sbsrefstate->lowerprovided[i] = false;
-			sbsrefstate->lowerindexnull[i] = true;
-		}
-		else
-		{
-			sbsrefstate->lowerprovided[i] = true;
-			/* Each subscript is evaluated into appropriate array entry */
-			ExecInitExprRec(e, state,
-							&sbsrefstate->lowerindex[i],
-							&sbsrefstate->lowerindexnull[i]);
+			Expr	   *e = (Expr *) lfirst(lc);
+
+			/* When slicing, individual subscript bounds can be omitted */
+			if (!e)
+			{
+				sbsrefstate->lowerprovided[i] = false;
+				sbsrefstate->lowerindexnull[i] = true;
+			}
+			else
+			{
+				sbsrefstate->lowerprovided[i] = true;
+				/* Each subscript is evaluated into appropriate array entry */
+				ExecInitExprRec(e, state,
+								&sbsrefstate->lowerindex[i],
+								&sbsrefstate->lowerindexnull[i]);
+			}
+			i++;
 		}
-		i++;
 	}
 
 	/* SBSREF_SUBSCRIPTS checks and converts all the subscripts at once */
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 7bc823507f1..d1bd575d9bd 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -284,6 +284,13 @@ exprType(const Node *expr)
 		case T_PlaceHolderVar:
 			type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_FieldAccessorExpr:
+			/*
+			 * FieldAccessorExpr is not evaluable. Treat it as TEXT for collation,
+			 * deparsing, and similar purposes, since it represents a JSON field name.
+			 */
+			type = TEXTOID;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			type = InvalidOid;	/* keep compiler quiet */
@@ -1146,6 +1153,9 @@ exprSetCollation(Node *expr, Oid collation)
 		case T_MergeSupportFunc:
 			((MergeSupportFunc *) expr)->msfcollid = collation;
 			break;
+		case T_FieldAccessorExpr:
+			((FieldAccessorExpr *) expr)->faecollid = collation;
+			break;
 		case T_SubscriptingRef:
 			((SubscriptingRef *) expr)->refcollid = collation;
 			break;
@@ -2129,6 +2139,7 @@ expression_tree_walker_impl(Node *node,
 		case T_SortGroupClause:
 		case T_CTESearchClause:
 		case T_MergeSupportFunc:
+		case T_FieldAccessorExpr:
 			/* primitive node types with no expression subnodes */
 			break;
 		case T_WithCheckOption:
@@ -3008,6 +3019,7 @@ expression_tree_mutator_impl(Node *node,
 		case T_SortGroupClause:
 		case T_CTESearchClause:
 		case T_MergeSupportFunc:
+		case T_FieldAccessorExpr:
 			return copyObject(node);
 		case T_WithCheckOption:
 			{
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index f944d1544ca..ddf45dd73b2 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -15,21 +15,30 @@
 #include "postgres.h"
 
 #include "executor/execExpr.h"
+#include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "nodes/subscripting.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_expr.h"
 #include "utils/builtins.h"
 #include "utils/jsonb.h"
+#include "utils/jsonpath.h"
 
 
-/* SubscriptingRefState.workspace for jsonb subscripting execution */
+/*
+ * SubscriptingRefState.workspace for generic jsonb subscripting execution.
+ *
+ * Stores state for both jsonb simple subscripting and dot notation access.
+ * Dot notation additionally uses `jsonpath` for JsonPath evaluation.
+ */
 typedef struct JsonbSubWorkspace
 {
 	bool		expectArray;	/* jsonb root is expected to be an array */
 	Oid		   *indexOid;		/* OID of coerced subscript expression, could
 								 * be only integer or text */
 	Datum	   *index;			/* Subscript values in Datum format */
+	JsonPath   *jsonpath;		/* JsonPath for dot notation execution via
+								 * JsonPathQuery() */
 } JsonbSubWorkspace;
 
 static Node *
@@ -96,6 +105,255 @@ coerce_jsonpath_subscript_to_int4_or_text(ParseState *pstate, Node *subExpr)
 	return subExpr;
 }
 
+/*
+ * During transformation, determine whether to build a JsonPath
+ * for JsonPathQuery() execution.
+ *
+ * JsonPath is needed if the indirection list includes:
+ * - String-based access (dot notation)
+ * - Slice-based subscripting
+ *
+ * Otherwise, simple jsonb subscripting is enough.
+ */
+static bool
+jsonb_check_jsonpath_needed(List *indirection)
+{
+	ListCell   *lc;
+
+	foreach(lc, indirection)
+	{
+		Node	   *accessor = lfirst(lc);
+
+		if (IsA(accessor, String))
+			return true;
+		else
+		{
+			Assert(IsA(accessor, A_Indices));
+
+			if (castNode(A_Indices, accessor)->is_slice)
+				return true;
+		}
+	}
+
+	return false;
+}
+
+/*
+ * Helper functions for constructing JsonPath expressions.
+ *
+ * The make_jsonpath_item_* functions create various types of JsonPathParseItem
+ * nodes, which are used to build JsonPath expressions for jsonb simplified
+ * accessor.
+ */
+
+static JsonPathParseItem *
+make_jsonpath_item(JsonPathItemType type)
+{
+	JsonPathParseItem *v = palloc(sizeof(*v));
+
+	v->type = type;
+	v->next = NULL;
+
+	return v;
+}
+
+/*
+ * Build a JsonPathParseItem for a constant integer value.
+ *
+ * This function constructs a jpiNumeric item for use in JsonPath,
+ * and appends a matching Const(INT4) node to the given expression list
+ * for use in EXPLAIN, views, etc.
+ *
+ * Parameters:
+ * - val: integer constant value
+ * - exprs: list of expression nodes (updated in place)
+ */
+static JsonPathParseItem *
+make_jsonpath_item_int(int32 val, List **exprs)
+{
+	JsonPathParseItem *jpi = make_jsonpath_item(jpiNumeric);
+
+	jpi->value.numeric =
+		DatumGetNumeric(DirectFunctionCall1(int4_numeric, Int32GetDatum(val)));
+
+	*exprs = lappend(*exprs, makeConst(INT4OID, -1, InvalidOid, 4,
+									   Int32GetDatum(val), false, true));
+
+	return jpi;
+}
+
+/*
+ * Convert a constant integer expression into a JsonPathParseItem.
+ *
+ * The input expression must be a non-null constant of type INT4. Returns NULL otherwise.
+ * The function extracts the value and delegates it to make_jsonpath_item_int().
+ *
+ * Parameters:
+ * - pstate: parse state context
+ * - expr: input expression node
+ * - exprs: list of expression nodes (updated in place)
+ */
+static JsonPathParseItem *
+make_jsonpath_item_expr(ParseState *pstate, Node *expr, List **exprs)
+{
+	Const	   *cnst;
+
+	expr = transformExpr(pstate, expr, pstate->p_expr_kind);
+
+	if (IsA(expr, Const))
+	{
+		cnst = (Const *) expr;
+		if (cnst->consttype == INT4OID && !(cnst->constisnull))
+			return make_jsonpath_item_int(DatumGetInt32(cnst->constvalue), exprs);
+	}
+
+	return NULL;
+}
+
+/*
+ * Constructs a JsonPath expression from a list of indirections.
+ * This function is used when jsonb subscripting involves dot notation,
+ * requiring JsonPath-based evaluation.
+ *
+ * The function modifies the indirection list in place, removing processed
+ * elements as it converts them into JsonPath components, as follows:
+ * - String keys (dot notation) -> jpiKey items.
+ * - Array indices -> jpiIndexArray items.
+ *
+ * In addition to building the JsonPath expression, this function populates
+ * the following fields of the given SubscriptingRef:
+ * - refjsonbpath: the generated JsonPath
+ * - refupperindexpr: upper index expressions (object keys or array indexes)
+ * - reflowerindexpr: lower index expressions, remains NIL as slices are not yet supported.
+ *
+ * Parameters:
+ * - pstate: Parse state context.
+ * - indirection: List of subscripting expressions (modified in-place).
+ * - sbsref: SubscriptingRef node to update
+ */
+static void
+jsonb_subscript_make_jsonpath(ParseState *pstate, List **indirection, SubscriptingRef *sbsref)
+{
+	JsonPathParseResult jpres;
+	JsonPathParseItem *path = make_jsonpath_item(jpiRoot);
+	ListCell   *lc;
+	Datum		jsp;
+	int			pathlen = 0;
+
+	sbsref->refupperindexpr = NIL;
+	sbsref->reflowerindexpr = NIL;
+	sbsref->refjsonbpath = NULL;
+
+	jpres.expr = path;
+	jpres.lax = true;
+
+	foreach(lc, *indirection)
+	{
+		Node	   *accessor = lfirst(lc);
+		JsonPathParseItem *jpi;
+
+		if (IsA(accessor, String))
+		{
+			char	   *field = strVal(accessor);
+			FieldAccessorExpr *accessor_expr;
+
+			jpi = make_jsonpath_item(jpiKey);
+			jpi->value.string.val = field;
+			jpi->value.string.len = strlen(field);
+
+			accessor_expr = makeNode(FieldAccessorExpr);
+			accessor_expr->type = T_FieldAccessorExpr;
+			accessor_expr->fieldname = field;
+
+			sbsref->refupperindexpr = lappend(sbsref->refupperindexpr, accessor_expr);
+		}
+		else if (IsA(accessor, A_Indices))
+		{
+			A_Indices  *ai = castNode(A_Indices, accessor);
+
+			jpi = make_jsonpath_item(jpiIndexArray);
+			jpi->value.array.nelems = 1;
+			jpi->value.array.elems = palloc(sizeof(jpi->value.array.elems[0]));
+
+			if (ai->is_slice)
+			{
+				Node	   *expr = ai->uidx ? ai->uidx : ai->lidx;
+
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("jsonb subscript does not support slices"),
+						 parser_errposition(pstate, exprLocation(expr))));
+			}
+			else
+			{
+				JsonPathParseItem *jpi_from = NULL;
+
+				Assert(ai->uidx && !ai->lidx);
+				jpi_from = make_jsonpath_item_expr(pstate, ai->uidx, &sbsref->refupperindexpr);
+				if (jpi_from == NULL)
+				{
+					/*
+					 * Break out of the loop if the subscript is not a
+					 * non-null integer constant, so that we can fall back to
+					 * jsonb subscripting logic.
+					 *
+					 * This is needed to handle cases with mixed usage of SQL
+					 * standard json simplified accessor syntax and PostgreSQL
+					 * jsonb subscripting syntax, e.g:
+					 *
+					 * select (jb).a['b'].c from jsonb_table;
+					 *
+					 * where dot-notation (.a and .c) is the SQL standard json
+					 * simplified accessor syntax, and the ['b'] subscript is
+					 * the PostgreSQL jsonb subscripting syntax, because 'b'
+					 * is not a non-null constant integer and cannot be used
+					 * for json array access.
+					 *
+					 * In this case, we cannot create a JsonPath item, so we
+					 * break out of the loop and let
+					 * jsonb_subscript_transform() handle this indirection as
+					 * a PostgreSQL jsonb subscript.
+					 */
+					pfree(jpi->value.array.elems);
+					pfree(jpi);
+					break;
+				}
+
+				jpi->value.array.elems[0].from = jpi_from;
+				jpi->value.array.elems[0].to = NULL;
+			}
+		}
+		else
+		{
+			/*
+			 * Unexpected node type in indirection list. This should not
+			 * happen with current grammar, but we handle it defensively by
+			 * breaking out of the loop rather than crashing. In case of
+			 * future grammar changes that might introduce new node types,
+			 * this allows us to create a jsonpath from as many indirection
+			 * elements as we can and let transformIndirection() fallback to
+			 * alternative logic to handle the remaining indirection elements.
+			 */
+			Assert(false);		/* not reachable */
+			break;
+		}
+
+		/* append path item */
+		path->next = jpi;
+		path = jpi;
+		pathlen++;
+	}
+
+	if (pathlen == 0)
+		return;
+
+	*indirection = list_delete_first_n(*indirection, pathlen);
+
+	jsp = jsonPathFromParseResult(&jpres, 0, NULL);
+
+	sbsref->refjsonbpath = (Node *) makeConst(JSONPATHOID, -1, InvalidOid, -1, jsp, false, false);
+}
+
 /*
  * Finish parse analysis of a SubscriptingRef expression for a jsonb.
  *
@@ -112,47 +370,45 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	List	   *upperIndexpr = NIL;
 	ListCell   *idx;
 
+	/* Determine the result type of the subscripting operation; always jsonb */
+	sbsref->refrestype = JSONBOID;
+	sbsref->reftypmod = -1;
+
+	if (isSlice || jsonb_check_jsonpath_needed(*indirection))
+	{
+		jsonb_subscript_make_jsonpath(pstate, indirection, sbsref);
+		if (sbsref->refjsonbpath)
+			return;
+	}
+
 	/*
-	 * Transform and convert the subscript expressions. Jsonb subscripting
-	 * does not support slices, look only at the upper index.
+	 * We reach here only in two cases: (a) the JSON simplified accessor is
+	 * not needed at all (for example, a plain array subscript like [1]), or
+	 * (b) jsonb_subscript_make_jsonpath() examined the first indirection
+	 * element but could not turn it into a JsonPath component (for example,
+	 * ['a']).
+	 *
+	 * In both cases we fall back to pre-standard jsonb subscripting, coercing
+	 * each subscript to array index or object key as needed.
 	 */
 	foreach(idx, *indirection)
 	{
+		Node	   *i = lfirst(idx);
 		A_Indices  *ai;
 		Node	   *subExpr;
 
 		if (!IsA(lfirst(idx), A_Indices))
 			break;
 
-		ai = lfirst_node(A_Indices, idx);
+		ai = castNode(A_Indices, i);
 
-		if (isSlice)
-		{
-			Node	   *expr = ai->uidx ? ai->uidx : ai->lidx;
+		if (ai->is_slice)
+			break;
 
-			ereport(ERROR,
-					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("jsonb subscript does not support slices"),
-					 parser_errposition(pstate, exprLocation(expr))));
-		}
+		Assert(!ai->lidx && ai->uidx);
 
-		if (ai->uidx)
-		{
-			subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
-			subExpr = coerce_jsonpath_subscript_to_int4_or_text(pstate, subExpr);
-		}
-		else
-		{
-			/*
-			 * Slice with omitted upper bound. Should not happen as we already
-			 * errored out on slice earlier, but handle this just in case.
-			 */
-			Assert(isSlice && ai->is_slice);
-			ereport(ERROR,
-					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("jsonb subscript does not support slices"),
-					 parser_errposition(pstate, exprLocation(ai->uidx))));
-		}
+		subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
+		subExpr = coerce_jsonpath_subscript_to_int4_or_text(pstate, subExpr);
 
 		upperIndexpr = lappend(upperIndexpr, subExpr);
 	}
@@ -161,10 +417,6 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->refupperindexpr = upperIndexpr;
 	sbsref->reflowerindexpr = NIL;
 
-	/* Determine the result type of the subscripting operation; always jsonb */
-	sbsref->refrestype = JSONBOID;
-	sbsref->reftypmod = -1;
-
 	/* Remove processed elements */
 	if (upperIndexpr)
 		*indirection = list_delete_first_n(*indirection, list_length(upperIndexpr));
@@ -219,7 +471,7 @@ jsonb_subscript_check_subscripts(ExprState *state,
 			 * For jsonb fetch and assign functions we need to provide path in
 			 * text format. Convert if it's not already text.
 			 */
-			if (workspace->indexOid[i] == INT4OID)
+			if (!workspace->jsonpath && workspace->indexOid[i] == INT4OID)
 			{
 				Datum		datum = sbsrefstate->upperindex[i];
 				char	   *cs = DatumGetCString(DirectFunctionCall1(int4out, datum));
@@ -247,17 +499,32 @@ jsonb_subscript_fetch(ExprState *state,
 {
 	SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
 	JsonbSubWorkspace *workspace = (JsonbSubWorkspace *) sbsrefstate->workspace;
-	Jsonb	   *jsonbSource;
 
 	/* Should not get here if source jsonb (or any subscript) is null */
 	Assert(!(*op->resnull));
 
-	jsonbSource = DatumGetJsonbP(*op->resvalue);
-	*op->resvalue = jsonb_get_element(jsonbSource,
-									  workspace->index,
-									  sbsrefstate->numupper,
-									  op->resnull,
-									  false);
+	if (workspace->jsonpath)
+	{
+		bool		empty = false;
+		bool		error = false;
+
+		*op->resvalue = JsonPathQuery(*op->resvalue, workspace->jsonpath,
+									  JSW_CONDITIONAL,
+									  &empty, &error, NULL,
+									  NULL);
+
+		*op->resnull = empty || error;
+	}
+	else
+	{
+		Jsonb	   *jsonbSource = DatumGetJsonbP(*op->resvalue);
+
+		*op->resvalue = jsonb_get_element(jsonbSource,
+										  workspace->index,
+										  sbsrefstate->numupper,
+										  op->resnull,
+										  false);
+	}
 }
 
 /*
@@ -365,7 +632,7 @@ jsonb_exec_setup(const SubscriptingRef *sbsref,
 {
 	JsonbSubWorkspace *workspace;
 	ListCell   *lc;
-	int			nupper = sbsref->refupperindexpr->length;
+	int			nupper = list_length(sbsref->refupperindexpr);
 	char	   *ptr;
 
 	/* Allocate type-specific workspace with space for per-subscript data */
@@ -374,6 +641,9 @@ jsonb_exec_setup(const SubscriptingRef *sbsref,
 	workspace->expectArray = false;
 	ptr = ((char *) workspace) + MAXALIGN(sizeof(JsonbSubWorkspace));
 
+	if (sbsref->refjsonbpath)
+		workspace->jsonpath = DatumGetJsonPathP(castNode(Const, sbsref->refjsonbpath)->constvalue);
+
 	/*
 	 * This coding assumes sizeof(Datum) >= sizeof(Oid), else we might
 	 * misalign the indexOid pointer
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 3d6e6bdbfd2..baa3ae97d57 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9329,10 +9329,13 @@ get_rule_expr(Node *node, deparse_context *context,
 				 * Parenthesize the argument unless it's a simple Var or a
 				 * FieldSelect.  (In particular, if it's another
 				 * SubscriptingRef, we *must* parenthesize to avoid
-				 * confusion.)
+				 * confusion.) Always add parenthesis if JSON simplified
+				 * accessor is used, for now.
 				 */
-				need_parens = !IsA(sbsref->refexpr, Var) &&
-					!IsA(sbsref->refexpr, FieldSelect);
+				need_parens = (!IsA(sbsref->refexpr, Var) &&
+					!IsA(sbsref->refexpr, FieldSelect)) ||
+						sbsref->refjsonbpath;
+
 				if (need_parens)
 					appendStringInfoChar(buf, '(');
 				get_rule_expr((Node *) sbsref->refexpr, context, showimplicit);
@@ -13005,17 +13008,35 @@ printSubscripts(SubscriptingRef *sbsref, deparse_context *context)
 	lowlist_item = list_head(sbsref->reflowerindexpr);	/* could be NULL */
 	foreach(uplist_item, sbsref->refupperindexpr)
 	{
-		appendStringInfoChar(buf, '[');
-		if (lowlist_item)
+		Node *upper = (Node *) lfirst(uplist_item);
+
+		if (upper && IsA(upper, FieldAccessorExpr))
 		{
+			FieldAccessorExpr *fae = (FieldAccessorExpr *) upper;
+
+			/* Use dot-notation for field access */
+			appendStringInfoChar(buf, '.');
+			appendStringInfoString(buf, quote_identifier(fae->fieldname));
+
+			/* Skip matching low index — field access doesn't use slices */
+			if (lowlist_item)
+				lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
+		}
+		else
+		{
+			/* Use JSONB array subscripting */
+			appendStringInfoChar(buf, '[');
+			if (lowlist_item)
+			{
+				/* If subexpression is NULL, get_rule_expr prints nothing */
+				get_rule_expr((Node *) lfirst(lowlist_item), context, false);
+				appendStringInfoChar(buf, ':');
+				lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
+			}
 			/* If subexpression is NULL, get_rule_expr prints nothing */
-			get_rule_expr((Node *) lfirst(lowlist_item), context, false);
-			appendStringInfoChar(buf, ':');
-			lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
+			get_rule_expr(upper, context, false);
+			appendStringInfoChar(buf, ']');
 		}
-		/* If subexpression is NULL, get_rule_expr prints nothing */
-		get_rule_expr((Node *) lfirst(uplist_item), context, false);
-		appendStringInfoChar(buf, ']');
 	}
 }
 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 6dfca3cb35b..7e89621bd65 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -708,18 +708,30 @@ typedef struct SubscriptingRef
 	int32		reftypmod pg_node_attr(query_jumble_ignore);
 	/* collation of result, or InvalidOid if none */
 	Oid			refcollid pg_node_attr(query_jumble_ignore);
-	/* expressions that evaluate to upper container indexes */
+
+	/*
+	 * expressions that evaluate to upper container indexes or expressions
+	 * that are collected but not evaluated when refjsonbpath is set.
+	 */
 	List	   *refupperindexpr;
 
 	/*
-	 * expressions that evaluate to lower container indexes, or NIL for single
-	 * container element.
+	 * expressions that evaluate to lower container indexes, or NIL for a
+	 * single container element, or expressions that are collected but not
+	 * evaluated when refjsonbpath is set.
 	 */
 	List	   *reflowerindexpr;
 	/* the expression that evaluates to a container value */
 	Expr	   *refexpr;
 	/* expression for the source value, or NULL if fetch */
 	Expr	   *refassgnexpr;
+
+	/*
+	 * container-specific extra information, currently used only by jsonb.
+	 * stores a JsonPath expression when jsonb dot notation is used. NULL for
+	 * simple subscripting.
+	 */
+	Node	   *refjsonbpath;
 } SubscriptingRef;
 
 /*
@@ -2371,4 +2383,40 @@ typedef struct OnConflictExpr
 	List	   *exclRelTlist;	/* tlist of the EXCLUDED pseudo relation */
 } OnConflictExpr;
 
+/*
+ * FieldAccessorExpr - represents a single object member access using dot-notation
+ *		in JSON simplified accessor syntax (e.g., jsonb_col.a).
+ *
+ * These nodes appear as list elements in SubscriptingRef.refupperindexpr to
+ * indicate JSON object key access. They are not evaluable expressions by
+ * themselves but serve as placeholders to preserve source-level syntax for
+ * rule rewriting and deparsing (e.g., in EXPLAIN and view definitions).
+ * Execution is handled by the enclosing SubscriptingRef.
+ *
+ * If dot-notation is used in a SubscriptingRef, the JSON path is represented
+ * as a flat list of FieldAccessorExpr nodes (for object field access), Const
+ * nodes (for array indexes), and NULLs (for omitted slice bounds), rather than
+ * through nested expression trees.
+ *
+ * Note: The flat representation avoids nested FieldAccessorExpr chains,
+ * simplifying evaluation and enabling standard-compliant behavior such as
+ * conditional array wrapping. This avoids the need for position-aware
+ * wrapping/unwrapping logic during execution.
+ *
+ * For example, in the expression:
+ *		('{"a": [{"b": 1}]}'::jsonb).a[0].b
+ * the SubscriptingRef will contain:
+ *		- refexpr: the base expression (the jsonb value)
+ *		- refupperindexpr: [FieldAccessorExpr("a"), Const(0),
+ *			FieldAccessorExpr("b")]
+ *		- reflowerindexpr: [NULL, NULL, NULL] (slice lower bounds not used here)
+ */
+typedef struct FieldAccessorExpr
+{
+	NodeTag		type;
+	char	   *fieldname;		/* name of the JSONB object field accessed via
+								 * dot notation */
+	Oid			faecollid pg_node_attr(query_jumble_ignore);
+}			FieldAccessorExpr;
+
 #endif							/* PRIMNODES_H */
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.c b/src/interfaces/ecpg/test/expected/sql-sqljson.c
index 39221f9ea5d..e6a7ece6dab 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.c
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.c
@@ -417,12 +417,122 @@ if (sqlca.sqlcode < 0) sqlprint();}
 	  for (int i = 0; i < sizeof(is_json); i++)
 		  printf("Found is_json[%d]: %s\n", i, is_json[i] ? "true" : "false");
 
-  { ECPGdisconnect(__LINE__, "CURRENT");
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . \"a\" )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 118 "sqljson.pgc"
 
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 118 "sqljson.pgc"
 
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb . \"a\" )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 121 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 121 "sqljson.pgc"
+
+	// error
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . a )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 124 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 124 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . a . b )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 127 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 127 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( coalesce ( json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . c ) , 'null' ) )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 130 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 130 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ 0 ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 133 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 133 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b . x )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 136 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 136 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) [ 'b' ] [ 0 ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 139 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 139 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 142 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 142 "sqljson.pgc"
+
+	// error
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . * )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 145 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 145 "sqljson.pgc"
+
+	// error
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 148 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 148 "sqljson.pgc"
+
+	// error
+
+  { ECPGdisconnect(__LINE__, "CURRENT");
+#line 151 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 151 "sqljson.pgc"
+
 
   return 0;
 }
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
index e55a95dd711..19f8c58af06 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
@@ -268,5 +268,105 @@ SQL error: cannot use type jsonb in RETURNING clause of JSON_SERIALIZE() on line
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_get_data on line 102: RESULT: f offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 118: query: select json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . "a" ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 118: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 118: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 118: RESULT: {"b": 1, "c": 2} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 121: query: select json ( '{"a": {"b": 1, "c": 2}}' :: jsonb . "a" ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 121: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 121: bad response - ERROR:  schema "jsonb" does not exist
+LINE 1: select json ( '{"a": {"b": 1, "c": 2}}' :: jsonb . "a" )
+                                                   ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 3F000 (sqlcode -400): schema "jsonb" does not exist on line 121
+[NO_PID]: sqlca: code: -400, state: 3F000
+SQL error: schema "jsonb" does not exist on line 121
+[NO_PID]: ecpg_execute on line 124: query: select json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . a ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 124: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 124: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 124: RESULT: {"b": 1, "c": 2} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 127: query: select json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . a . b ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 127: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 127: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 127: RESULT: 1 offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 130: query: select json ( coalesce ( json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . c ) , 'null' ) ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 130: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 130: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 130: RESULT: null offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 133: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 0 ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 133: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 133: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 133: RESULT: {"x": 1} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 136: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b . x ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 136: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 136: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 136: RESULT: [1, [12, {"y": 1}]] offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 139: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 139: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 139: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 139: RESULT: {"x": 1} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 142: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 142: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 142: bad response - ERROR:  jsonb subscript does not support slices
+LINE 1: ..., {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] )
+                                                                ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 42804 (sqlcode -400): jsonb subscript does not support slices on line 142
+[NO_PID]: sqlca: code: -400, state: 42804
+SQL error: jsonb subscript does not support slices on line 142
+[NO_PID]: ecpg_execute on line 145: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . * ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 145: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 145: bad response - ERROR:  row expansion via "*" is not supported here
+LINE 1: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x...
+                        ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 0A000 (sqlcode -400): row expansion via "*" is not supported here on line 145
+[NO_PID]: sqlca: code: -400, state: 0A000
+SQL error: row expansion via "*" is not supported here on line 145
+[NO_PID]: ecpg_execute on line 148: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 148: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 148: bad response - ERROR:  jsonb subscript does not support slices
+LINE 1: ...x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )
+                                                                ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 42804 (sqlcode -400): jsonb subscript does not support slices on line 148
+[NO_PID]: sqlca: code: -400, state: 42804
+SQL error: jsonb subscript does not support slices on line 148
 [NO_PID]: ecpg_finish: connection ecpg1_regression closed
 [NO_PID]: sqlca: code: 0, state: 00000
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
index 83f8df13e5a..442d36931f1 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
@@ -28,3 +28,10 @@ Found is_json[4]: false
 Found is_json[5]: false
 Found is_json[6]: true
 Found is_json[7]: false
+Found json={"b": 1, "c": 2}
+Found json={"b": 1, "c": 2}
+Found json=1
+Found json=null
+Found json={"x": 1}
+Found json=[1, [12, {"y": 1}]]
+Found json={"x": 1}
diff --git a/src/interfaces/ecpg/test/sql/sqljson.pgc b/src/interfaces/ecpg/test/sql/sqljson.pgc
index ddcbcc3b3cb..57a9bff424d 100644
--- a/src/interfaces/ecpg/test/sql/sqljson.pgc
+++ b/src/interfaces/ecpg/test/sql/sqljson.pgc
@@ -115,6 +115,39 @@ EXEC SQL END DECLARE SECTION;
 	  for (int i = 0; i < sizeof(is_json); i++)
 		  printf("Found is_json[%d]: %s\n", i, is_json[i] ? "true" : "false");
 
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}}'::jsonb)."a") INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON('{"a": {"b": 1, "c": 2}}'::jsonb."a") INTO :json;
+	// error
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}}'::jsonb).a) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}}'::jsonb).a.b) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(COALESCE(JSON(('{"a": {"b": 1, "c": 2}}'::jsonb).c), 'null')) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b[0]) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b.x) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb)['b'][0]) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b[1].x[0:]) INTO :json;
+	// error
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).*) INTO :json;
+	// error
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb)['b'][0:]) INTO :json;
+	// error
+
   EXEC SQL DISCONNECT;
 
   return 0;
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5a1eb18aba2..b054b8b5dd0 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -4989,6 +4989,12 @@ select ('123'::jsonb)['a'];
  
 (1 row)
 
+select ('123'::jsonb).a;
+ a 
+---
+ 
+(1 row)
+
 select ('123'::jsonb)[0];
  jsonb 
 -------
@@ -5001,12 +5007,24 @@ select ('123'::jsonb)[NULL];
  
 (1 row)
 
+select ('123'::jsonb).NULL;
+ null 
+------
+ 
+(1 row)
+
 select ('{"a": 1}'::jsonb)['a'];
  jsonb 
 -------
  1
 (1 row)
 
+select ('{"a": 1}'::jsonb).a;
+ a 
+---
+ 1
+(1 row)
+
 select ('{"a": 1}'::jsonb)[0];
  jsonb 
 -------
@@ -5019,6 +5037,12 @@ select ('{"a": 1}'::jsonb)['not_exist'];
  
 (1 row)
 
+select ('{"a": 1}'::jsonb)."not_exist";
+ not_exist 
+-----------
+ 
+(1 row)
+
 select ('{"a": 1}'::jsonb)[NULL];
  jsonb 
 -------
@@ -5031,6 +5055,12 @@ select ('[1, "2", null]'::jsonb)['a'];
  
 (1 row)
 
+select ('[1, "2", null]'::jsonb).a;
+ a 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[0];
  jsonb 
 -------
@@ -5043,6 +5073,12 @@ select ('[1, "2", null]'::jsonb)['1'];
  "2"
 (1 row)
 
+select ('[1, "2", null]'::jsonb)."1";
+ 1 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[1.0];
 ERROR:  subscript type numeric is not supported
 LINE 1: select ('[1, "2", null]'::jsonb)[1.0];
@@ -5072,6 +5108,12 @@ select ('[1, "2", null]'::jsonb)[1]['a'];
  
 (1 row)
 
+select ('[1, "2", null]'::jsonb)[1].a;
+ a 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[1][0];
  jsonb 
 -------
@@ -5084,56 +5126,127 @@ select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['b'];
  "c"
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).b;
+  b  
+-----
+ "c"
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'];
    jsonb   
 -----------
  [1, 2, 3]
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d;
+     d     
+-----------
+ [1, 2, 3]
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'][1];
  jsonb 
 -------
  2
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d[1];
+ d 
+---
+ 2
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d']['a'];
  jsonb 
 -------
  
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['a'];
+ d 
+---
+ 
+(1 row)
+
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d.a;
+ a 
+---
+ 
+(1 row)
+
+select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d['a'][0];
+ d 
+---
+ 1
+(1 row)
+
+select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d.a[0];
+ a 
+---
+ 1
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1'];
      jsonb     
 ---------------
  {"a2": "aaa"}
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1;
+      a1       
+---------------
+ {"a2": "aaa"}
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2'];
  jsonb 
 -------
  "aaa"
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2;
+  a2   
+-------
+ "aaa"
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']['a3'];
  jsonb 
 -------
  
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2.a3;
+ a3 
+----
+ 
+(1 row)
+
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'];
          jsonb         
 -----------------------
  ["aaa", "bbb", "ccc"]
 (1 row)
 
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1;
+          b1           
+-----------------------
+ ["aaa", "bbb", "ccc"]
+(1 row)
+
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'][2];
  jsonb 
 -------
  "ccc"
 (1 row)
 
--- slices are not supported
-select ('{"a": 1}'::jsonb)['a':'b'];
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1[2];
+  b1   
+-------
+ "ccc"
+(1 row)
+
+select ('{"a": 1}'::jsonb)['a':'b']; -- fails
 ERROR:  jsonb subscript does not support slices
 LINE 1: select ('{"a": 1}'::jsonb)['a':'b'];
                                        ^
@@ -5831,3 +5944,287 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
  12345
 (1 row)
 
+-- dot notation
+CREATE TABLE test_jsonb_dot_notation AS
+SELECT '{"a": [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], "b": [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]}'::jsonb jb;
+SELECT (jb).a FROM test_jsonb_dot_notation;
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+SELECT (jb)."a" FROM test_jsonb_dot_notation; -- double quote should work
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+SELECT (jb).'a' FROM test_jsonb_dot_notation; -- single quote should not work
+ERROR:  syntax error at or near "'a'"
+LINE 1: SELECT (jb).'a' FROM test_jsonb_dot_notation;
+                    ^
+SELECT (jb).b FROM test_jsonb_dot_notation;
+                         b                         
+---------------------------------------------------
+ [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]
+(1 row)
+
+SELECT (jb).c FROM test_jsonb_dot_notation;
+ c 
+---
+ 
+(1 row)
+
+SELECT (jb).a.b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT (jb).a[2].b FROM test_jsonb_dot_notation;
+  b  
+-----
+ "c"
+(1 row)
+
+SELECT (jb).a.x.y FROM test_jsonb_dot_notation;
+   y   
+-------
+ "yyy"
+(1 row)
+
+SELECT (jb).b.x.z FROM test_jsonb_dot_notation;
+   z   
+-------
+ "ZZZ"
+(1 row)
+
+SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
+ c 
+---
+ 
+(1 row)
+
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t; -- fails
+ERROR:  jsonb subscript does not support slices
+SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
+ERROR:  type jsonb is not composite
+-- explains should work
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+                  QUERY PLAN                  
+----------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation t
+   Output: (jb).a
+(2 rows)
+
+SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb).a[1]
+(2 rows)
+
+SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+ a 
+---
+ 2
+(1 row)
+
+-- views should work
+CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT (jb).a[3].x.y AS y
+   FROM test_jsonb_dot_notation
+-- mixed syntax
+DROP VIEW test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb['a'::text]).b
+(2 rows)
+
+SELECT (jb)['a'].b FROM test_jsonb_dot_notation; -- returns an array due to lax mode
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT (jb['a'::text]).b AS b
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: ((jb).a)['b'::text]
+(2 rows)
+
+SELECT (jb).a['b'] FROM test_jsonb_dot_notation; -- returns NULL due to strict mode
+ a 
+---
+ 
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT ((jb).a)['b'::text] AS a
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+ a 
+---
+ 
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: ((jb).b.x)['z'::text]
+(2 rows)
+
+SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation; -- warnings
+   x   
+-------
+ "ZZZ"
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT ((jb).b.x)['z'::text] AS x
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+   x   
+-------
+ "ZZZ"
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: ((jb['b'::text]).x)['z'::text]
+(2 rows)
+
+SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;  -- warnings
+   x   
+-------
+ "ZZZ"
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT ((jb['b'::text]).x)['z'::text] AS x
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+   x   
+-------
+ "ZZZ"
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (((jb).b)['x'::text]).z
+(2 rows)
+
+SELECT (jb).b['x'].z FROM test_jsonb_dot_notation; -- returns NULL
+ z 
+---
+ 
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT (((jb).b)['x'::text]).z AS z
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+ z 
+---
+ 
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: ((jb).b)['x'::text]['z'::text]
+(2 rows)
+
+SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation; -- returns NULL
+ b 
+---
+ 
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT ((jb).b)['x'::text]['z'::text] AS b
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+ b 
+---
+ 
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb['b'::text]['x'::text]).z
+(2 rows)
+
+SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation; -- returns NULL
+ z 
+---
+ 
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT (jb['b'::text]['x'::text]).z AS z
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+ z 
+---
+ 
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+-- clean up
+DROP TABLE test_jsonb_dot_notation;
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 57c11acddfe..4db16c47563 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1304,33 +1304,51 @@ select jsonb_insert('{"a": {"b": "value"}}', '{a, b}', '"new_value"', true);
 
 -- jsonb subscript
 select ('123'::jsonb)['a'];
+select ('123'::jsonb).a;
 select ('123'::jsonb)[0];
 select ('123'::jsonb)[NULL];
+select ('123'::jsonb).NULL;
 select ('{"a": 1}'::jsonb)['a'];
+select ('{"a": 1}'::jsonb).a;
 select ('{"a": 1}'::jsonb)[0];
 select ('{"a": 1}'::jsonb)['not_exist'];
+select ('{"a": 1}'::jsonb)."not_exist";
 select ('{"a": 1}'::jsonb)[NULL];
 select ('[1, "2", null]'::jsonb)['a'];
+select ('[1, "2", null]'::jsonb).a;
 select ('[1, "2", null]'::jsonb)[0];
 select ('[1, "2", null]'::jsonb)['1'];
+select ('[1, "2", null]'::jsonb)."1";
 select ('[1, "2", null]'::jsonb)[1.0];
 select ('[1, "2", null]'::jsonb)[2];
 select ('[1, "2", null]'::jsonb)[3];
 select ('[1, "2", null]'::jsonb)[-2];
 select ('[1, "2", null]'::jsonb)[1]['a'];
+select ('[1, "2", null]'::jsonb)[1].a;
 select ('[1, "2", null]'::jsonb)[1][0];
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['b'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).b;
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d;
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'][1];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d[1];
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d']['a'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['a'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d.a;
+select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d['a'][0];
+select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d.a[0];
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1;
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2;
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']['a3'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2.a3;
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'];
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1;
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'][2];
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1[2];
 
--- slices are not supported
-select ('{"a": 1}'::jsonb)['a':'b'];
+select ('{"a": 1}'::jsonb)['a':'b']; -- fails
 select ('[1, "2", null]'::jsonb)[1:2];
 select ('[1, "2", null]'::jsonb)[:2];
 select ('[1, "2", null]'::jsonb)[1:];
@@ -1590,3 +1608,92 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::float8;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int2;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int4;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
+
+-- dot notation
+CREATE TABLE test_jsonb_dot_notation AS
+SELECT '{"a": [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], "b": [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]}'::jsonb jb;
+
+SELECT (jb).a FROM test_jsonb_dot_notation;
+SELECT (jb)."a" FROM test_jsonb_dot_notation; -- double quote should work
+SELECT (jb).'a' FROM test_jsonb_dot_notation; -- single quote should not work
+SELECT (jb).b FROM test_jsonb_dot_notation;
+SELECT (jb).c FROM test_jsonb_dot_notation;
+SELECT (jb).a.b FROM test_jsonb_dot_notation;
+SELECT (jb).a[2].b FROM test_jsonb_dot_notation;
+SELECT (jb).a.x.y FROM test_jsonb_dot_notation;
+SELECT (jb).b.x.z FROM test_jsonb_dot_notation;
+SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t; -- fails
+SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
+
+-- explains should work
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+
+-- views should work
+CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+
+-- mixed syntax
+DROP VIEW test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
+SELECT (jb)['a'].b FROM test_jsonb_dot_notation; -- returns an array due to lax mode
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+SELECT (jb).a['b'] FROM test_jsonb_dot_notation; -- returns NULL due to strict mode
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation; -- warnings
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;  -- warnings
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+SELECT (jb).b['x'].z FROM test_jsonb_dot_notation; -- returns NULL
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation; -- returns NULL
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
+SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation; -- returns NULL
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+-- clean up
+DROP TABLE test_jsonb_dot_notation;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index a13e8162890..cc64642f399 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -805,6 +805,7 @@ FdwRoutine
 FetchDirection
 FetchDirectionKeywords
 FetchStmt
+FieldAccessorExpr
 FieldSelect
 FieldStore
 File
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v14-0007-Implement-jsonb-wildcard-member-accessor.patch (31.4K, ../../CAK98qZ35eF+9MZuqR4HNrmebyBFdNiNLiLZHpQPB7S7OUk-DDQ@mail.gmail.com/7-v14-0007-Implement-jsonb-wildcard-member-accessor.patch)
  download | inline diff:
From 1f6f6a435c9b3dbd45a628bcc4791b7bd83ac848 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v14 7/7] Implement jsonb wildcard member accessor

This commit adds support for wildcard member access in jsonb, as
specified by the JSON simplified accessor syntax in SQL:2023.

Co-authored-by: Nikita Glukhov <[email protected]>
Co-authored-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Mark Dilger <[email protected]>
Reviewed-by: Jian He <[email protected]>
Reviewed-by: Vik Fearing <[email protected]>
Reviewed-by: Nikita Malakhov <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Jelte Fennema-Nio <[email protected]>
Tested-by: Jelte Fennema-Nio <[email protected]>
---
 src/backend/nodes/nodeFuncs.c                 |   8 +
 src/backend/parser/gram.y                     |   2 +
 src/backend/parser/parse_expr.c               |  34 ++-
 src/backend/parser/parse_target.c             |  67 ++++--
 src/backend/utils/adt/jsonbsubs.c             |  12 +-
 src/backend/utils/adt/ruleutils.c             |   6 +-
 src/include/nodes/primnodes.h                 |  12 +
 src/include/parser/parse_expr.h               |   3 +
 .../ecpg/test/expected/sql-sqljson.c          |  18 +-
 .../ecpg/test/expected/sql-sqljson.stderr     |  23 +-
 .../ecpg/test/expected/sql-sqljson.stdout     |   2 +
 src/interfaces/ecpg/test/sql/sqljson.pgc      |   5 +-
 src/test/regress/expected/jsonb.out           | 222 +++++++++++++++++-
 src/test/regress/sql/jsonb.sql                |  42 +++-
 14 files changed, 405 insertions(+), 51 deletions(-)

diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index d1bd575d9bd..5f3038a1c26 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -291,6 +291,9 @@ exprType(const Node *expr)
 			 */
 			type = TEXTOID;
 			break;
+		case T_Star:
+			type = UNKNOWNOID;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			type = InvalidOid;	/* keep compiler quiet */
@@ -1156,6 +1159,9 @@ exprSetCollation(Node *expr, Oid collation)
 		case T_FieldAccessorExpr:
 			((FieldAccessorExpr *) expr)->faecollid = collation;
 			break;
+		case T_Star:
+			Assert(!OidIsValid(collation));
+			break;
 		case T_SubscriptingRef:
 			((SubscriptingRef *) expr)->refcollid = collation;
 			break;
@@ -2140,6 +2146,7 @@ expression_tree_walker_impl(Node *node,
 		case T_CTESearchClause:
 		case T_MergeSupportFunc:
 		case T_FieldAccessorExpr:
+		case T_Star:
 			/* primitive node types with no expression subnodes */
 			break;
 		case T_WithCheckOption:
@@ -3020,6 +3027,7 @@ expression_tree_mutator_impl(Node *node,
 		case T_CTESearchClause:
 		case T_MergeSupportFunc:
 		case T_FieldAccessorExpr:
+		case T_Star:
 			return copyObject(node);
 		case T_WithCheckOption:
 			{
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index db43034b9db..703c7416b0d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -18979,6 +18979,7 @@ check_func_name(List *names, core_yyscan_t yyscanner)
 static List *
 check_indirection(List *indirection, core_yyscan_t yyscanner)
 {
+#if 0
 	ListCell   *l;
 
 	foreach(l, indirection)
@@ -18989,6 +18990,7 @@ check_indirection(List *indirection, core_yyscan_t yyscanner)
 				parser_yyerror("improper use of \"*\"");
 		}
 	}
+#endif
 	return indirection;
 }
 
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index dc1c7e9b75c..7d5c56caa70 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -74,7 +74,6 @@ static Node *transformColumnRef(ParseState *pstate, ColumnRef *cref);
 static Node *transformWholeRowRef(ParseState *pstate,
 								  ParseNamespaceItem *nsitem,
 								  int sublevels_up, int location);
-static Node *transformIndirection(ParseState *pstate, A_Indirection *ind);
 static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
 static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
 static Node *transformJsonObjectConstructor(ParseState *pstate,
@@ -158,7 +157,7 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 			break;
 
 		case T_A_Indirection:
-			result = transformIndirection(pstate, (A_Indirection *) expr);
+			result = transformIndirection(pstate, (A_Indirection *) expr, NULL);
 			break;
 
 		case T_A_ArrayExpr:
@@ -432,8 +431,9 @@ unknown_attribute(ParseState *pstate, Node *relref, const char *attname,
 	}
 }
 
-static Node *
-transformIndirection(ParseState *pstate, A_Indirection *ind)
+Node *
+transformIndirection(ParseState *pstate, A_Indirection *ind,
+					 bool *trailing_star_expansion)
 {
 	Node	   *last_srf = pstate->p_last_srf;
 	Node	   *result = transformExprRecurse(pstate, ind->arg);
@@ -454,12 +454,7 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		if (IsA(n, A_Indices))
 			subscripts = lappend(subscripts, n);
 		else if (IsA(n, A_Star))
-		{
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("row expansion via \"*\" is not supported here"),
-					 parser_errposition(pstate, location)));
-		}
+			subscripts = lappend(subscripts, n);
 		else
 		{
 			Assert(IsA(n, String));
@@ -491,7 +486,21 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 
 			n = linitial(subscripts);
 
-			if (!IsA(n, String))
+			if (IsA(n, A_Star))
+			{
+				/* Success, if trailing star expansion is allowed */
+				if (trailing_star_expansion && list_length(subscripts) == 1)
+				{
+					*trailing_star_expansion = true;
+					return result;
+				}
+
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("row expansion via \"*\" is not supported here"),
+						 parser_errposition(pstate, location)));
+			}
+			else if (!IsA(n, String))
 				ereport(ERROR,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
 						 errmsg("cannot subscript type %s because it does not support subscripting",
@@ -517,6 +526,9 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		result = newresult;
 	}
 
+	if (trailing_star_expansion)
+		*trailing_star_expansion = false;
+
 	return result;
 }
 
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 3ef5897f2eb..141fc1dfeb1 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -48,7 +48,7 @@ static Node *transformAssignmentSubscripts(ParseState *pstate,
 static List *ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
 								 bool make_target_entry);
 static List *ExpandAllTables(ParseState *pstate, int location);
-static List *ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
+static Node *ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
 								   bool make_target_entry, ParseExprKind exprKind);
 static List *ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 							   int sublevels_up, int location,
@@ -134,6 +134,7 @@ transformTargetList(ParseState *pstate, List *targetlist,
 	foreach(o_target, targetlist)
 	{
 		ResTarget  *res = (ResTarget *) lfirst(o_target);
+		Node	   *transformed = NULL;
 
 		/*
 		 * Check for "something.*".  Depending on the complexity of the
@@ -162,13 +163,19 @@ transformTargetList(ParseState *pstate, List *targetlist,
 
 				if (IsA(llast(ind->indirection), A_Star))
 				{
-					/* It is something.*, expand into multiple items */
-					p_target = list_concat(p_target,
-										   ExpandIndirectionStar(pstate,
-																 ind,
-																 true,
-																 exprKind));
-					continue;
+					Node	   *columns = ExpandIndirectionStar(pstate,
+																ind,
+																true,
+																exprKind);
+
+					if (IsA(columns, List))
+					{
+						/* It is something.*, expand into multiple items */
+						p_target = list_concat(p_target, (List *) columns);
+						continue;
+					}
+
+					transformed = (Node *) columns;
 				}
 			}
 		}
@@ -180,7 +187,7 @@ transformTargetList(ParseState *pstate, List *targetlist,
 		p_target = lappend(p_target,
 						   transformTargetEntry(pstate,
 												res->val,
-												NULL,
+												transformed,
 												exprKind,
 												res->name,
 												false));
@@ -251,10 +258,15 @@ transformExpressionList(ParseState *pstate, List *exprlist,
 
 			if (IsA(llast(ind->indirection), A_Star))
 			{
-				/* It is something.*, expand into multiple items */
-				result = list_concat(result,
-									 ExpandIndirectionStar(pstate, ind,
-														   false, exprKind));
+				Node	   *cols = ExpandIndirectionStar(pstate, ind,
+														 false, exprKind);
+
+				if (!cols || IsA(cols, List))
+					/* It is something.*, expand into multiple items */
+					result = list_concat(result, (List *) cols);
+				else
+					result = lappend(result, cols);
+
 				continue;
 			}
 		}
@@ -1345,22 +1357,30 @@ ExpandAllTables(ParseState *pstate, int location)
  * For robustness, we use a separate "make_target_entry" flag to control
  * this rather than relying on exprKind.
  */
-static List *
+static Node *
 ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
 					  bool make_target_entry, ParseExprKind exprKind)
 {
 	Node	   *expr;
+	ParseExprKind sv_expr_kind;
+	bool		trailing_star_expansion = false;
+
+	/* Save and restore identity of expression type we're parsing */
+	Assert(exprKind != EXPR_KIND_NONE);
+	sv_expr_kind = pstate->p_expr_kind;
+	pstate->p_expr_kind = exprKind;
 
 	/* Strip off the '*' to create a reference to the rowtype object */
-	ind = copyObject(ind);
-	ind->indirection = list_truncate(ind->indirection,
-									 list_length(ind->indirection) - 1);
+	expr = transformIndirection(pstate, ind, &trailing_star_expansion);
+
+	pstate->p_expr_kind = sv_expr_kind;
 
-	/* And transform that */
-	expr = transformExpr(pstate, (Node *) ind, exprKind);
+	/* '*' was consumed by generic type subscripting */
+	if (!trailing_star_expansion)
+		return expr;
 
 	/* Expand the rowtype expression into individual fields */
-	return ExpandRowReference(pstate, expr, make_target_entry);
+	return (Node *) ExpandRowReference(pstate, expr, make_target_entry);
 }
 
 /*
@@ -1785,13 +1805,18 @@ FigureColnameInternal(Node *node, char **name)
 				char	   *fname = NULL;
 				ListCell   *l;
 
-				/* find last field name, if any, ignoring "*" and subscripts */
+				/*
+				 * find last field name, if any, ignoring subscripts, and use
+				 * '?column?' when there's a trailing '*'.
+				 */
 				foreach(l, ind->indirection)
 				{
 					Node	   *i = lfirst(l);
 
 					if (IsA(i, String))
 						fname = strVal(i);
+					else if (IsA(i, A_Star))
+						fname = "?column?";
 				}
 				if (fname)
 				{
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index 87e3029513a..cb72d12ca3f 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -124,7 +124,7 @@ jsonb_check_jsonpath_needed(List *indirection)
 	{
 		Node	   *accessor = lfirst(lc);
 
-		if (IsA(accessor, String))
+		if (IsA(accessor, String) || IsA(accessor, A_Star))
 			return true;
 		else
 		{
@@ -340,6 +340,16 @@ jsonb_subscript_make_jsonpath(ParseState *pstate, List **indirection, Subscripti
 				jpi->value.array.elems[0].to = NULL;
 			}
 		}
+		else if (IsA(accessor, A_Star))
+		{
+			Star *star_node;
+			jpi = make_jsonpath_item(jpiAnyKey);
+
+			star_node = makeNode(Star);
+			star_node->type = T_Star;
+
+			sbsref->refupperindexpr = lappend(sbsref->refupperindexpr, star_node);
+		}
 		else
 		{
 			/*
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index baa3ae97d57..ace0eff52e2 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -13010,7 +13010,11 @@ printSubscripts(SubscriptingRef *sbsref, deparse_context *context)
 	{
 		Node *upper = (Node *) lfirst(uplist_item);
 
-		if (upper && IsA(upper, FieldAccessorExpr))
+		if (upper && IsA(upper, Star))
+		{
+			appendStringInfoString(buf, ".*");
+		}
+		else if (upper && IsA(upper, FieldAccessorExpr))
 		{
 			FieldAccessorExpr *fae = (FieldAccessorExpr *) upper;
 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 7e89621bd65..7e418830f22 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -2419,4 +2419,16 @@ typedef struct FieldAccessorExpr
 	Oid			faecollid pg_node_attr(query_jumble_ignore);
 }			FieldAccessorExpr;
 
+/*
+ * Star - represents a wildcard member accessor (e.g., ".*") used in JSONB simplified accessor.
+ *
+ * This node serves as a syntactic placeholder in the expression tree and does not get evaluated, hence it
+ * has no associated type or collation.
+ */
+typedef struct Star
+{
+	NodeTag		type;
+}			Star;
+
+
 #endif							/* PRIMNODES_H */
diff --git a/src/include/parser/parse_expr.h b/src/include/parser/parse_expr.h
index efbaff8e710..c9f6a7724c0 100644
--- a/src/include/parser/parse_expr.h
+++ b/src/include/parser/parse_expr.h
@@ -22,4 +22,7 @@ extern Node *transformExpr(ParseState *pstate, Node *expr, ParseExprKind exprKin
 
 extern const char *ParseExprKindName(ParseExprKind exprKind);
 
+extern Node *transformIndirection(ParseState *pstate, A_Indirection *ind,
+								  bool *trailing_star_expansion);
+
 #endif							/* PARSE_EXPR_H */
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.c b/src/interfaces/ecpg/test/expected/sql-sqljson.c
index 935b47a3b9a..585d9b14445 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.c
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.c
@@ -515,9 +515,9 @@ if (sqlca.sqlcode < 0) sqlprint();}
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 145 "sqljson.pgc"
 
-	// error
+	printf("Found json=%s\n", json);
 
-	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )", ECPGt_EOIT, 
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . * . x )", ECPGt_EOIT, 
 	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
 	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 148 "sqljson.pgc"
@@ -527,7 +527,7 @@ if (sqlca.sqlcode < 0) sqlprint();}
 
 	printf("Found json=%s\n", json);
 
-	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ : ] )", ECPGt_EOIT, 
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )", ECPGt_EOIT, 
 	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
 	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 151 "sqljson.pgc"
@@ -537,12 +537,22 @@ if (sqlca.sqlcode < 0) sqlprint();}
 
 	printf("Found json=%s\n", json);
 
-  { ECPGdisconnect(__LINE__, "CURRENT");
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ : ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 154 "sqljson.pgc"
 
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 154 "sqljson.pgc"
 
+	printf("Found json=%s\n", json);
+
+  { ECPGdisconnect(__LINE__, "CURRENT");
+#line 157 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 157 "sqljson.pgc"
+
 
   return 0;
 }
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
index f3f899c6d87..4b9088545d6 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
@@ -347,22 +347,19 @@ SQL error: schema "jsonb" does not exist on line 121
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 145: using PQexec
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_check_PQresult on line 145: bad response - ERROR:  row expansion via "*" is not supported here
-LINE 1: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x...
-                        ^
+[NO_PID]: ecpg_process_output on line 145: correctly got 1 tuples with 1 fields
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: raising sqlstate 0A000 (sqlcode -400): row expansion via "*" is not supported here on line 145
-[NO_PID]: sqlca: code: -400, state: 0A000
-SQL error: row expansion via "*" is not supported here on line 145
-[NO_PID]: ecpg_execute on line 148: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: ecpg_get_data on line 145: RESULT: [{"b": 1, "c": 2}, [{"x": 1}, {"x": [12, {"y": 1}]}]] offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 148: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . * . x ); with 0 parameter(s) on connection ecpg1_regression
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 148: using PQexec
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_process_output on line 148: correctly got 1 tuples with 1 fields
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_get_data on line 148: RESULT: [{"x": 1}, {"x": [12, {"y": 1}]}] offset: -1; array: no
+[NO_PID]: ecpg_get_data on line 148: RESULT: [1, [12, {"y": 1}]] offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_execute on line 151: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: ecpg_execute on line 151: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] ); with 0 parameter(s) on connection ecpg1_regression
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 151: using PQexec
 [NO_PID]: sqlca: code: 0, state: 00000
@@ -370,5 +367,13 @@ SQL error: row expansion via "*" is not supported here on line 145
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_get_data on line 151: RESULT: [{"x": 1}, {"x": [12, {"y": 1}]}] offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 154: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 154: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 154: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 154: RESULT: [{"x": 1}, {"x": [12, {"y": 1}]}] offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_finish: connection ecpg1_regression closed
 [NO_PID]: sqlca: code: 0, state: 00000
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
index d01a8457f01..145dc95d430 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
@@ -36,5 +36,7 @@ Found json={"x": 1}
 Found json=[1, [12, {"y": 1}]]
 Found json={"x": 1}
 Found json=[12, {"y": 1}]
+Found json=[{"b": 1, "c": 2}, [{"x": 1}, {"x": [12, {"y": 1}]}]]
+Found json=[1, [12, {"y": 1}]]
 Found json=[{"x": 1}, {"x": [12, {"y": 1}]}]
 Found json=[{"x": 1}, {"x": [12, {"y": 1}]}]
diff --git a/src/interfaces/ecpg/test/sql/sqljson.pgc b/src/interfaces/ecpg/test/sql/sqljson.pgc
index 9423d25fd0b..2af50b5da4b 100644
--- a/src/interfaces/ecpg/test/sql/sqljson.pgc
+++ b/src/interfaces/ecpg/test/sql/sqljson.pgc
@@ -143,7 +143,10 @@ EXEC SQL END DECLARE SECTION;
 	printf("Found json=%s\n", json);
 
 	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).*) INTO :json;
-	// error
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).*.x) INTO :json;
+	printf("Found json=%s\n", json);
 
 	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb)['b'][0:]) INTO :json;
 	printf("Found json=%s\n", json);
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 9176927afc1..1ffa984feb4 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -6052,8 +6052,175 @@ SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
  {"y": "YYY", "z": "ZZZ"}
 (1 row)
 
-SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
-ERROR:  type jsonb is not composite
+/* wild card member access */
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+                 ?column?                  
+-------------------------------------------
+ ["c", "d", "f", {"y": "yyy", "z": "zzz"}]
+(1 row)
+
+SELECT (jb).* FROM test_jsonb_dot_notation;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (jb).* FROM test_jsonb_dot_notation t;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (jb).* FROM test_jsonb_dot_notation;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (t.jb).* FROM test_jsonb_dot_notation;
+ERROR:  missing FROM-clause entry for table "t"
+LINE 1: SELECT (t.jb).* FROM test_jsonb_dot_notation;
+                ^
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+                 ?column?                  
+-------------------------------------------
+ ["c", "d", "f", {"y": "yyy", "z": "zzz"}]
+(1 row)
+
+SELECT (jb).a.*.b FROM test_jsonb_dot_notation;
+ b 
+---
+ 
+(1 row)
+
+SELECT (jb).a.*.x FROM test_jsonb_dot_notation;
+ x 
+---
+ 
+(1 row)
+
+SELECT (jb).a.*.y FROM test_jsonb_dot_notation;
+   y   
+-------
+ "yyy"
+(1 row)
+
+SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+    ?column?    
+----------------
+ ["yyy", "zzz"]
+(1 row)
+
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT (jb).*.x FROM test_jsonb_dot_notation t;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+ x 
+---
+ 
+(1 row)
+
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+ x 
+---
+ 
+(1 row)
+
+SELECT ((jb).*)[:].x FROM test_jsonb_dot_notation t;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT (jb).*.x.* FROM test_jsonb_dot_notation;
+           ?column?           
+------------------------------
+ ["yyy", "zzz", "YYY", "ZZZ"]
+(1 row)
+
+SELECT (jb).*.x.y FROM test_jsonb_dot_notation;
+       y        
+----------------
+ ["yyy", "YYY"]
+(1 row)
+
+SELECT (jb).*.x.z FROM test_jsonb_dot_notation;
+       z        
+----------------
+ ["zzz", "ZZZ"]
+(1 row)
+
+SELECT (jb).*.*.y FROM test_jsonb_dot_notation;
+       y        
+----------------
+ ["yyy", "YYY"]
+(1 row)
+
+SELECT (jb).*.*.* FROM test_jsonb_dot_notation;
+           ?column?           
+------------------------------
+ ["yyy", "zzz", "YYY", "ZZZ"]
+(1 row)
+
+SELECT (jb).*.*.*.* FROM test_jsonb_dot_notation;
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT (jb).a.b.c.* FROM test_jsonb_dot_notation;
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+    ?column?    
+----------------
+ ["yyy", "zzz"]
+(1 row)
+
+SELECT (jb).a.*[:].* FROM test_jsonb_dot_notation;
+    ?column?    
+----------------
+ ["yyy", "zzz"]
+(1 row)
+
+SELECT (jb).a.*[*].* FROM test_jsonb_dot_notation; -- not supported
+ERROR:  syntax error at or near "*"
+LINE 1: SELECT (jb).a.*[*].* FROM test_jsonb_dot_notation;
+                        ^
+SELECT (jb).a.**.x FROM test_jsonb_dot_notation; -- not supported
+ERROR:  syntax error at or near "**"
+LINE 1: SELECT (jb).a.**.x FROM test_jsonb_dot_notation;
+                      ^
 -- explains should work
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
                   QUERY PLAN                  
@@ -6081,6 +6248,45 @@ SELECT (jb).a[1] FROM test_jsonb_dot_notation;
  2
 (1 row)
 
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.* FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb).a.*
+(2 rows)
+
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+                 ?column?                  
+-------------------------------------------
+ ["c", "d", "f", {"y": "yyy", "z": "zzz"}]
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:].* FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb).a.*[:].*
+(2 rows)
+
+SELECT (jb).a.*[1:].* FROM test_jsonb_dot_notation;
+ ?column? 
+----------
+ 
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb).a.*[:2].*.b
+(2 rows)
+
+SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
+ b 
+---
+ 
+(1 row)
+
 -- views should work
 CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
 \sv test_jsonb_dot_notation_v1
@@ -6109,6 +6315,17 @@ SELECT * from v3;
  "yyy"
 (1 row)
 
+CREATE VIEW v4 AS SELECT (jb).a.*[:].* FROM test_jsonb_dot_notation;
+\sv v4
+CREATE OR REPLACE VIEW public.v4 AS
+ SELECT (jb).a.*[:].* AS "?column?"
+   FROM test_jsonb_dot_notation
+SELECT * from v4;
+    ?column?    
+----------------
+ ["yyy", "zzz"]
+(1 row)
+
 -- mixed syntax
 DROP VIEW test_jsonb_dot_notation_v1;
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
@@ -6344,4 +6561,5 @@ NOTICE:  [2]
 -- clean up
 DROP VIEW v2;
 DROP VIEW v3;
+DROP VIEW v4;
 DROP TABLE test_jsonb_dot_notation;
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index bf9faf52cab..346576a085b 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1629,13 +1629,49 @@ SELECT (jb).a[2:].b FROM test_jsonb_dot_notation;
 SELECT (jb).a[:2].b FROM test_jsonb_dot_notation;
 SELECT (jb).a[:].b FROM test_jsonb_dot_notation;
 SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
-SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
+
+/* wild card member access */
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+SELECT (jb).* FROM test_jsonb_dot_notation;
+SELECT (jb).* FROM test_jsonb_dot_notation t;
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+SELECT (jb).* FROM test_jsonb_dot_notation;
+SELECT (t.jb).* FROM test_jsonb_dot_notation;
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.b FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.x FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.y FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+SELECT (jb).*.x FROM test_jsonb_dot_notation t;
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+SELECT ((jb).*)[:].x FROM test_jsonb_dot_notation t;
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+SELECT (jb).*.x.* FROM test_jsonb_dot_notation;
+SELECT (jb).*.x.y FROM test_jsonb_dot_notation;
+SELECT (jb).*.x.z FROM test_jsonb_dot_notation;
+SELECT (jb).*.*.y FROM test_jsonb_dot_notation;
+SELECT (jb).*.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).*.*.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.b.c.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*[:].* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*[*].* FROM test_jsonb_dot_notation; -- not supported
+SELECT (jb).a.**.x FROM test_jsonb_dot_notation; -- not supported
 
 -- explains should work
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
 SELECT (t.jb).a FROM test_jsonb_dot_notation t;
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
 SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:].* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*[1:].* FROM test_jsonb_dot_notation;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
+SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
 
 -- views should work
 CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
@@ -1646,6 +1682,9 @@ SELECT * from v2;
 CREATE VIEW v3 AS SELECT (jb).a[:3].x.y[-1:] FROM test_jsonb_dot_notation;
 \sv v3
 SELECT * from v3;
+CREATE VIEW v4 AS SELECT (jb).a.*[:].* FROM test_jsonb_dot_notation;
+\sv v4
+SELECT * from v4;
 
 -- mixed syntax
 DROP VIEW test_jsonb_dot_notation_v1;
@@ -1747,4 +1786,5 @@ $$ LANGUAGE plpgsql;
 -- clean up
 DROP VIEW v2;
 DROP VIEW v3;
+DROP VIEW v4;
 DROP TABLE test_jsonb_dot_notation;
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v14-0006-Implement-Jsonb-subscripting-with-slicing.patch (17.4K, ../../CAK98qZ35eF+9MZuqR4HNrmebyBFdNiNLiLZHpQPB7S7OUk-DDQ@mail.gmail.com/8-v14-0006-Implement-Jsonb-subscripting-with-slicing.patch)
  download | inline diff:
From 77a7f2437faca688a140a2f4662dff16ccfe9e44 Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v14 6/7] Implement Jsonb subscripting with slicing

Previously, slicing was not supported for jsonb subscripting. This commit
implements subscripting with slicing as part of the JSON simplified accessor
syntax specified in SQL:2023.

Co-authored-by: Nikita Glukhov <[email protected]>
Co-authored-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Mark Dilger <[email protected]>
Reviewed-by: Jian He <[email protected]>
Reviewed-by: Vik Fearing <[email protected]>
Reviewed-by: Nikita Malakhov <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Tested-by: Mark Dilger <[email protected]>
Tested-by: Jian He <[email protected]>
---
 src/backend/utils/adt/jsonbsubs.c             |  33 +++-
 .../ecpg/test/expected/sql-sqljson.c          |  16 +-
 .../ecpg/test/expected/sql-sqljson.stderr     |  26 ++--
 .../ecpg/test/expected/sql-sqljson.stdout     |   3 +
 src/interfaces/ecpg/test/sql/sqljson.pgc      |   7 +-
 src/test/regress/expected/jsonb.out           | 145 ++++++++++++++++--
 src/test/regress/sql/jsonb.sql                |  53 ++++++-
 7 files changed, 243 insertions(+), 40 deletions(-)

diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index ddf45dd73b2..87e3029513a 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -192,9 +192,10 @@ make_jsonpath_item_int(int32 val, List **exprs)
  * - pstate: parse state context
  * - expr: input expression node
  * - exprs: list of expression nodes (updated in place)
+ * - no_error: returns NULL when the data type doesn't match. Otherwise, emits an ERROR.
  */
 static JsonPathParseItem *
-make_jsonpath_item_expr(ParseState *pstate, Node *expr, List **exprs)
+make_jsonpath_item_expr(ParseState *pstate, Node *expr, List **exprs, bool no_error)
 {
 	Const	   *cnst;
 
@@ -207,7 +208,14 @@ make_jsonpath_item_expr(ParseState *pstate, Node *expr, List **exprs)
 			return make_jsonpath_item_int(DatumGetInt32(cnst->constvalue), exprs);
 	}
 
-	return NULL;
+	if (no_error)
+		return NULL;
+	else
+		ereport(ERROR,
+				errcode(ERRCODE_DATATYPE_MISMATCH),
+				errmsg("only non-null integer constants are supported for jsonb simplified accessor subscripting"),
+				errhint("use int data type for subscripting with slicing."),
+				parser_errposition(pstate, exprLocation(expr)));
 }
 
 /*
@@ -277,19 +285,28 @@ jsonb_subscript_make_jsonpath(ParseState *pstate, List **indirection, Subscripti
 
 			if (ai->is_slice)
 			{
-				Node	   *expr = ai->uidx ? ai->uidx : ai->lidx;
+				while (list_length(sbsref->reflowerindexpr) < list_length(sbsref->refupperindexpr))
+					sbsref->reflowerindexpr = lappend(sbsref->reflowerindexpr, NULL);
+
+				if (ai->lidx)
+					jpi->value.array.elems[0].from = make_jsonpath_item_expr(pstate, ai->lidx, &sbsref->reflowerindexpr, false);
+				else
+					jpi->value.array.elems[0].from = make_jsonpath_item_int(0, &sbsref->reflowerindexpr);
 
-				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("jsonb subscript does not support slices"),
-						 parser_errposition(pstate, exprLocation(expr))));
+				if (ai->uidx)
+					jpi->value.array.elems[0].to = make_jsonpath_item_expr(pstate, ai->uidx, &sbsref->refupperindexpr, false);
+				else
+				{
+					jpi->value.array.elems[0].to = make_jsonpath_item(jpiLast);
+					sbsref->refupperindexpr = lappend(sbsref->refupperindexpr, NULL);
+				}
 			}
 			else
 			{
 				JsonPathParseItem *jpi_from = NULL;
 
 				Assert(ai->uidx && !ai->lidx);
-				jpi_from = make_jsonpath_item_expr(pstate, ai->uidx, &sbsref->refupperindexpr);
+				jpi_from = make_jsonpath_item_expr(pstate, ai->uidx, &sbsref->refupperindexpr, true);
 				if (jpi_from == NULL)
 				{
 					/*
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.c b/src/interfaces/ecpg/test/expected/sql-sqljson.c
index e6a7ece6dab..935b47a3b9a 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.c
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.c
@@ -505,7 +505,7 @@ if (sqlca.sqlcode < 0) sqlprint();}
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 142 "sqljson.pgc"
 
-	// error
+	printf("Found json=%s\n", json);
 
 	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . * )", ECPGt_EOIT, 
 	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
@@ -525,14 +525,24 @@ if (sqlca.sqlcode < 0) sqlprint();}
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 148 "sqljson.pgc"
 
-	// error
+	printf("Found json=%s\n", json);
 
-  { ECPGdisconnect(__LINE__, "CURRENT");
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ : ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 151 "sqljson.pgc"
 
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 151 "sqljson.pgc"
 
+	printf("Found json=%s\n", json);
+
+  { ECPGdisconnect(__LINE__, "CURRENT");
+#line 154 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 154 "sqljson.pgc"
+
 
   return 0;
 }
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
index 19f8c58af06..f3f899c6d87 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
@@ -339,13 +339,10 @@ SQL error: schema "jsonb" does not exist on line 121
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 142: using PQexec
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_check_PQresult on line 142: bad response - ERROR:  jsonb subscript does not support slices
-LINE 1: ..., {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] )
-                                                                ^
+[NO_PID]: ecpg_process_output on line 142: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 142: RESULT: [12, {"y": 1}] offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: raising sqlstate 42804 (sqlcode -400): jsonb subscript does not support slices on line 142
-[NO_PID]: sqlca: code: -400, state: 42804
-SQL error: jsonb subscript does not support slices on line 142
 [NO_PID]: ecpg_execute on line 145: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . * ); with 0 parameter(s) on connection ecpg1_regression
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 145: using PQexec
@@ -361,12 +358,17 @@ SQL error: row expansion via "*" is not supported here on line 145
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 148: using PQexec
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_check_PQresult on line 148: bad response - ERROR:  jsonb subscript does not support slices
-LINE 1: ...x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )
-                                                                ^
+[NO_PID]: ecpg_process_output on line 148: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 148: RESULT: [{"x": 1}, {"x": [12, {"y": 1}]}] offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 151: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 151: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 151: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 151: RESULT: [{"x": 1}, {"x": [12, {"y": 1}]}] offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: raising sqlstate 42804 (sqlcode -400): jsonb subscript does not support slices on line 148
-[NO_PID]: sqlca: code: -400, state: 42804
-SQL error: jsonb subscript does not support slices on line 148
 [NO_PID]: ecpg_finish: connection ecpg1_regression closed
 [NO_PID]: sqlca: code: 0, state: 00000
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
index 442d36931f1..d01a8457f01 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
@@ -35,3 +35,6 @@ Found json=null
 Found json={"x": 1}
 Found json=[1, [12, {"y": 1}]]
 Found json={"x": 1}
+Found json=[12, {"y": 1}]
+Found json=[{"x": 1}, {"x": [12, {"y": 1}]}]
+Found json=[{"x": 1}, {"x": [12, {"y": 1}]}]
diff --git a/src/interfaces/ecpg/test/sql/sqljson.pgc b/src/interfaces/ecpg/test/sql/sqljson.pgc
index 57a9bff424d..9423d25fd0b 100644
--- a/src/interfaces/ecpg/test/sql/sqljson.pgc
+++ b/src/interfaces/ecpg/test/sql/sqljson.pgc
@@ -140,13 +140,16 @@ EXEC SQL END DECLARE SECTION;
 	printf("Found json=%s\n", json);
 
 	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b[1].x[0:]) INTO :json;
-	// error
+	printf("Found json=%s\n", json);
 
 	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).*) INTO :json;
 	// error
 
 	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb)['b'][0:]) INTO :json;
-	// error
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b[:]) INTO :json;
+	printf("Found json=%s\n", json);
 
   EXEC SQL DISCONNECT;
 
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index b054b8b5dd0..9176927afc1 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5247,23 +5247,34 @@ select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b
 (1 row)
 
 select ('{"a": 1}'::jsonb)['a':'b']; -- fails
-ERROR:  jsonb subscript does not support slices
+ERROR:  only non-null integer constants are supported for jsonb simplified accessor subscripting
 LINE 1: select ('{"a": 1}'::jsonb)['a':'b'];
-                                       ^
+                                   ^
+HINT:  use int data type for subscripting with slicing.
 select ('[1, "2", null]'::jsonb)[1:2];
-ERROR:  jsonb subscript does not support slices
-LINE 1: select ('[1, "2", null]'::jsonb)[1:2];
-                                           ^
+    jsonb    
+-------------
+ ["2", null]
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[:2];
-ERROR:  jsonb subscript does not support slices
-LINE 1: select ('[1, "2", null]'::jsonb)[:2];
-                                          ^
+     jsonb      
+----------------
+ [1, "2", null]
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[1:];
-ERROR:  jsonb subscript does not support slices
-LINE 1: select ('[1, "2", null]'::jsonb)[1:];
-                                         ^
+    jsonb    
+-------------
+ ["2", null]
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[:];
-ERROR:  jsonb subscript does not support slices
+     jsonb      
+----------------
+ [1, "2", null]
+(1 row)
+
 create TEMP TABLE test_jsonb_subscript (
        id int,
        test_json jsonb
@@ -6005,8 +6016,42 @@ SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
  
 (1 row)
 
-SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t; -- fails
-ERROR:  jsonb subscript does not support slices
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
+            x             
+--------------------------
+ {"y": "YYY", "z": "ZZZ"}
+(1 row)
+
+SELECT (jb).a[2:3].b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT (jb).a[2:].b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT (jb).a[:2].b FROM test_jsonb_dot_notation;
+  b  
+-----
+ "c"
+(1 row)
+
+SELECT (jb).a[:].b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
+            x             
+--------------------------
+ {"y": "YYY", "z": "ZZZ"}
+(1 row)
+
 SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
 ERROR:  type jsonb is not composite
 -- explains should work
@@ -6042,6 +6087,28 @@ CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_d
 CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
  SELECT (jb).a[3].x.y AS y
    FROM test_jsonb_dot_notation
+CREATE VIEW v2 AS SELECT (jb).a[3:].x.y[:-1] FROM test_jsonb_dot_notation;
+\sv v2
+CREATE OR REPLACE VIEW public.v2 AS
+ SELECT (jb).a[3:].x.y[0:'-1'::integer] AS y
+   FROM test_jsonb_dot_notation
+SELECT * from v2;
+ y 
+---
+ 
+(1 row)
+
+CREATE VIEW v3 AS SELECT (jb).a[:3].x.y[-1:] FROM test_jsonb_dot_notation;
+\sv v3
+CREATE OR REPLACE VIEW public.v3 AS
+ SELECT (jb).a[0:3].x.y['-1'::integer:] AS y
+   FROM test_jsonb_dot_notation
+SELECT * from v3;
+   y   
+-------
+ "yyy"
+(1 row)
+
 -- mixed syntax
 DROP VIEW test_jsonb_dot_notation_v1;
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
@@ -6226,5 +6293,55 @@ SELECT * from test_jsonb_dot_notation_v1;
 (1 row)
 
 DROP VIEW public.test_jsonb_dot_notation_v1;
+-- jsonb array access in plpgsql
+DO $$
+    DECLARE
+        a jsonb := '[1,2,3,4,5,6,7]'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := a[2:];
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+NOTICE:  [1, 2, 3, 4, 5, 6, 7]
+NOTICE:  [3, 4, 5, 6, 7]
+NOTICE:  [5, 6, 7]
+NOTICE:  7
+-- jsonb dot access in plpgsql
+DO $$
+    DECLARE
+        a jsonb := '{"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := COALESCE(a."NU", a[2]); -- fails
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+NOTICE:  {"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}
+ERROR:  missing FROM-clause entry for table "a"
+LINE 1: a := COALESCE(a."NU", a[2])
+                      ^
+QUERY:  a := COALESCE(a."NU", a[2])
+CONTEXT:  PL/pgSQL function inline_code_block line 8 at assignment
+DO $$
+    DECLARE
+        a jsonb := '{"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := COALESCE((a)."NU", a[2]); -- succeeds
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+NOTICE:  {"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}
+NOTICE:  [{"": [[3]]}, [6], [2], "bCi"]
+NOTICE:  [2]
 -- clean up
+DROP VIEW v2;
+DROP VIEW v3;
 DROP TABLE test_jsonb_dot_notation;
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 4db16c47563..bf9faf52cab 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1623,7 +1623,12 @@ SELECT (jb).a[2].b FROM test_jsonb_dot_notation;
 SELECT (jb).a.x.y FROM test_jsonb_dot_notation;
 SELECT (jb).b.x.z FROM test_jsonb_dot_notation;
 SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
-SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t; -- fails
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
+SELECT (jb).a[2:3].b FROM test_jsonb_dot_notation;
+SELECT (jb).a[2:].b FROM test_jsonb_dot_notation;
+SELECT (jb).a[:2].b FROM test_jsonb_dot_notation;
+SELECT (jb).a[:].b FROM test_jsonb_dot_notation;
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
 SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
 
 -- explains should work
@@ -1635,6 +1640,12 @@ SELECT (jb).a[1] FROM test_jsonb_dot_notation;
 -- views should work
 CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
 \sv test_jsonb_dot_notation_v1
+CREATE VIEW v2 AS SELECT (jb).a[3:].x.y[:-1] FROM test_jsonb_dot_notation;
+\sv v2
+SELECT * from v2;
+CREATE VIEW v3 AS SELECT (jb).a[:3].x.y[-1:] FROM test_jsonb_dot_notation;
+\sv v3
+SELECT * from v3;
 
 -- mixed syntax
 DROP VIEW test_jsonb_dot_notation_v1;
@@ -1695,5 +1706,45 @@ SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
 SELECT * from test_jsonb_dot_notation_v1;
 DROP VIEW public.test_jsonb_dot_notation_v1;
 
+-- jsonb array access in plpgsql
+DO $$
+    DECLARE
+        a jsonb := '[1,2,3,4,5,6,7]'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := a[2:];
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+
+-- jsonb dot access in plpgsql
+DO $$
+    DECLARE
+        a jsonb := '{"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := COALESCE(a."NU", a[2]); -- fails
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+
+DO $$
+    DECLARE
+        a jsonb := '{"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := COALESCE((a)."NU", a[2]); -- succeeds
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+
 -- clean up
+DROP VIEW v2;
+DROP VIEW v3;
 DROP TABLE test_jsonb_dot_notation;
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v14-0004-Extract-coerce_jsonpath_subscript.patch (5.8K, ../../CAK98qZ35eF+9MZuqR4HNrmebyBFdNiNLiLZHpQPB7S7OUk-DDQ@mail.gmail.com/9-v14-0004-Extract-coerce_jsonpath_subscript.patch)
  download | inline diff:
From 9cc44094d896768c6fce0a250efca674f2324291 Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v14 4/7] Extract coerce_jsonpath_subscript()

This is a preparation step for a future commit that will reuse the
aforementioned function.

Co-authored-by: Nikita Glukhov <[email protected]>
Co-authored-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Jian He <[email protected]>
---
 src/backend/utils/adt/jsonbsubs.c | 130 +++++++++++++++---------------
 1 file changed, 65 insertions(+), 65 deletions(-)

diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index a0d38a0fd80..f944d1544ca 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -32,6 +32,69 @@ typedef struct JsonbSubWorkspace
 	Datum	   *index;			/* Subscript values in Datum format */
 } JsonbSubWorkspace;
 
+static Node *
+coerce_jsonpath_subscript_to_int4_or_text(ParseState *pstate, Node *subExpr)
+{
+	Oid			subExprType = exprType(subExpr);
+	Oid			targetType = InvalidOid;
+
+	if (subExprType != UNKNOWNOID)
+	{
+		Oid			targets[2] = {INT4OID, TEXTOID};
+
+		/*
+		 * Jsonb can handle multiple subscript types, but cases when a
+		 * subscript could be coerced to multiple target types must be
+		 * avoided, similar to overloaded functions. It could be possibly
+		 * extend with jsonpath in the future.
+		 */
+		for (int i = 0; i < 2; i++)
+		{
+			if (can_coerce_type(1, &subExprType, &targets[i], COERCION_IMPLICIT))
+			{
+				/*
+				 * One type has already succeeded, it means there are two
+				 * coercion targets possible, failure.
+				 */
+				if (OidIsValid(targetType))
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
+							 errhint("jsonb subscript must be coercible to only one type, integer or text."),
+							 parser_errposition(pstate, exprLocation(subExpr))));
+
+				targetType = targets[i];
+			}
+		}
+
+		/*
+		 * No suitable types were found, failure.
+		 */
+		if (!OidIsValid(targetType))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
+					 errhint("jsonb subscript must be coercible to either integer or text."),
+					 parser_errposition(pstate, exprLocation(subExpr))));
+	}
+	else
+		targetType = TEXTOID;
+
+	/*
+	 * We known from can_coerce_type that coercion will succeed, so
+	 * coerce_type could be used. Note the implicit coercion context, which is
+	 * required to handle subscripts of different types, similar to overloaded
+	 * functions.
+	 */
+	subExpr = coerce_type(pstate,
+						  subExpr, subExprType,
+						  targetType, -1,
+						  COERCION_IMPLICIT,
+						  COERCE_IMPLICIT_CAST,
+						  -1);
+
+	return subExpr;
+}
 
 /*
  * Finish parse analysis of a SubscriptingRef expression for a jsonb.
@@ -51,7 +114,7 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 
 	/*
 	 * Transform and convert the subscript expressions. Jsonb subscripting
-	 * does not support slices, look only and the upper index.
+	 * does not support slices, look only at the upper index.
 	 */
 	foreach(idx, *indirection)
 	{
@@ -75,71 +138,8 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 
 		if (ai->uidx)
 		{
-			Oid			subExprType = InvalidOid,
-						targetType = UNKNOWNOID;
-
 			subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
-			subExprType = exprType(subExpr);
-
-			if (subExprType != UNKNOWNOID)
-			{
-				Oid			targets[2] = {INT4OID, TEXTOID};
-
-				/*
-				 * Jsonb can handle multiple subscript types, but cases when a
-				 * subscript could be coerced to multiple target types must be
-				 * avoided, similar to overloaded functions. It could be
-				 * possibly extend with jsonpath in the future.
-				 */
-				for (int i = 0; i < 2; i++)
-				{
-					if (can_coerce_type(1, &subExprType, &targets[i], COERCION_IMPLICIT))
-					{
-						/*
-						 * One type has already succeeded, it means there are
-						 * two coercion targets possible, failure.
-						 */
-						if (targetType != UNKNOWNOID)
-							ereport(ERROR,
-									(errcode(ERRCODE_DATATYPE_MISMATCH),
-									 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
-									 errhint("jsonb subscript must be coercible to only one type, integer or text."),
-									 parser_errposition(pstate, exprLocation(subExpr))));
-
-						targetType = targets[i];
-					}
-				}
-
-				/*
-				 * No suitable types were found, failure.
-				 */
-				if (targetType == UNKNOWNOID)
-					ereport(ERROR,
-							(errcode(ERRCODE_DATATYPE_MISMATCH),
-							 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
-							 errhint("jsonb subscript must be coercible to either integer or text."),
-							 parser_errposition(pstate, exprLocation(subExpr))));
-			}
-			else
-				targetType = TEXTOID;
-
-			/*
-			 * We known from can_coerce_type that coercion will succeed, so
-			 * coerce_type could be used. Note the implicit coercion context,
-			 * which is required to handle subscripts of different types,
-			 * similar to overloaded functions.
-			 */
-			subExpr = coerce_type(pstate,
-								  subExpr, subExprType,
-								  targetType, -1,
-								  COERCION_IMPLICIT,
-								  COERCE_IMPLICIT_CAST,
-								  -1);
-			if (subExpr == NULL)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("jsonb subscript must have text type"),
-						 parser_errposition(pstate, exprLocation(subExpr))));
+			subExpr = coerce_jsonpath_subscript_to_int4_or_text(pstate, subExpr);
 		}
 		else
 		{
-- 
2.39.5 (Apple Git-154)



^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-06-24 13:29                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-07-09 08:01                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-07-10 08:53                                 ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-21 04:53                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-22 08:19                                     ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-22 19:33                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-25 08:09                                         ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-26 03:52                                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
@ 2025-08-27 04:15                                             ` jian he <[email protected]>
  3 siblings, 0 replies; 67+ messages in thread

From: jian he @ 2025-08-27 04:15 UTC (permalink / raw)
  To: Alexandra Wang <[email protected]>; +Cc: Nikita Malakhov <[email protected]>; Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>

On Tue, Aug 26, 2025 at 11:53 AM Alexandra Wang
<[email protected]> wrote:
>
> Hi Jian,
>
> I’ve attached v14, which includes only indentation and comment changes
> from v13.
>

hi.
still reviewing v14-0001 to v14-0005.

I am confused by the comments in jsonb_subscript_transform
""
     * (b) jsonb_subscript_make_jsonpath() examined the first indirection
     * element but could not turn it into a JsonPath component (for example,
     * ['a']).
"""
CREATE TABLE ts2 AS SELECT '{"a": "b"}' ::jsonb jb;
SELECT (jb)['a'] FROM ts2;
SELECT (jb).a['a'] FROM ts2;
in these two cases, ['a'], it won't reach jsonb_subscript_make_jsonpath,
because jsonb_check_jsonpath_needed will return false.

maybe I am missing something,
for the above point b, can you use some SQL example to explain it?


make_jsonpath_item_expr(ParseState *pstate, Node *expr, List **exprs)
if expr satisfies the condition, then append the transformed expr to List exprs
if not returns NULL.
The list append logic is within make_jsonpath_item_int.
seems not that intuitive (another level of indirection)
maybe we don't need make_jsonpath_item_int.

Another reason would be in make_jsonpath_item_int it's not easy found
out that exprs actually refers to SubscriptingRef->refupperindexpr

also v14-0006-Implement-Jsonb-subscripting-with-slicing.patch also doesn't use
make_jsonpath_item_int that much frequently.
--------------------------------
in v14-0005-Implement-read-only-dot-notation-for-jsonb.patch

+ * In addition to building the JsonPath expression, this function populates
+ * the following fields of the given SubscriptingRef:
+ * - refjsonbpath: the generated JsonPath
+ * - refupperindexpr: upper index expressions (object keys or array indexes)
+ * - reflowerindexpr: lower index expressions, remains NIL as slices
are not yet supported.

"reflowerindexpr" changed in v14-0006, so "reflowerindexpr" comments
in v14-0006 need to change.
but "reflowerindexpr" we didn't touch in v14-0005, so the
"reflowerindexpr" comments look weird.


+SELECT (jb).a['b'] FROM test_jsonb_dot_notation; -- returns NULL due
to strict mode
we can not specify strict just like,  ``select 'strict $'::jsonpath;``
all the patches in here are default to lax mode.
so I am confused by this comment. ("strict mode").





^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-06-24 13:29                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-07-09 08:01                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-07-10 08:53                                 ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-21 04:53                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-22 08:19                                     ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-22 19:33                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-25 08:09                                         ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-26 03:52                                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
@ 2025-08-27 07:26                                             ` Chao Li <[email protected]>
  2025-08-29 03:29                                               ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-02 02:46                                               ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  3 siblings, 2 replies; 67+ messages in thread

From: Chao Li @ 2025-08-27 07:26 UTC (permalink / raw)
  To: Alexandra Wang <[email protected]>; +Cc: jian he <[email protected]>; Nikita Malakhov <[email protected]>; Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>



> On Aug 26, 2025, at 11:52, Alexandra Wang <[email protected]> wrote:
> 
> Best,
> Alex
> <v14-0002-Allow-Generic-Type-Subscripting-to-Accept-Dot-No.patch><v14-0003-Export-jsonPathFromParseResult.patch><v14-0001-Allow-transformation-of-only-a-sublist-of-subscr.patch><v14-0005-Implement-read-only-dot-notation-for-jsonb.patch><v14-0007-Implement-jsonb-wildcard-member-accessor.patch><v14-0006-Implement-Jsonb-subscripting-with-slicing.patch><v14-0004-Extract-coerce_jsonpath_subscript.patch>


I just started with 0001, I just got a comment:

diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index f7d07c84542..58a4b9df157 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -361,7 +361,7 @@ extern SubscriptingRef *transformContainerSubscripts(ParseState *pstate,
                                                                                                         Node *containerBase,
                                                                                                         Oid containerType,
                                                                                                         int32 containerTypMod,
-                                                                                                        List *indirection,
+                                                                                                        List **indirection,
                                                                                                         bool isAssignment);

Here we change “indirection” from * to **, I guess the reason is to indicate if the indirection list is fully processed. In case of fully processed, set it to NULL, then caller gets NULL via **.

As “indirection” is of type “List *”, can we just set “indirection->length = 0”? I checked pg_list.h, there is not a function or macro to cleanup a list (free elements and reset length to 0). There is a “list_free(List *list)”, but it will free “list” itself as well. Maybe we can add a new function, say “list_reset(List *list)” or “list_cleanup(List *list)”. This way will keep the function interface unchanged, and less changes would be involved.

diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index d66276801c6..e1565e11d09 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -466,14 +466,13 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
                        Assert(IsA(n, String));

                        /* process subscripts before this field selection */
-                       if (subscripts)
+                       while (subscripts)
                                result = (Node *) transformContainerSubscripts(pstate,

Changing “if” to “while” in 0001 is a little confusing. Because the impression is that, transform will stop when it cannot handle next indirection, so that transform again will also fail. Maybe my impression is wrong, can you add some comments here to explain why change “if” to “while”.

Regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/



^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-06-24 13:29                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-07-09 08:01                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-07-10 08:53                                 ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-21 04:53                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-22 08:19                                     ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-22 19:33                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-25 08:09                                         ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-26 03:52                                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-27 07:26                                             ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
@ 2025-08-29 03:29                                               ` Chao Li <[email protected]>
  2025-08-30 00:53                                                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  1 sibling, 1 reply; 67+ messages in thread

From: Chao Li @ 2025-08-29 03:29 UTC (permalink / raw)
  To: Alexandra Wang <[email protected]>; +Cc: jian he <[email protected]>; Nikita Malakhov <[email protected]>; Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>


>> On Aug 26, 2025, at 11:52, Alexandra Wang <[email protected]> wrote:
>> 
>> Best,
>> Alex
>> <v14-0002-Allow-Generic-Type-Subscripting-to-Accept-Dot-No.patch><v14-0003-Export-jsonPathFromParseResult.patch><v14-0001-Allow-transformation-of-only-a-sublist-of-subscr.patch><v14-0005-Implement-read-only-dot-notation-for-jsonb.patch><v14-0007-Implement-jsonb-wildcard-member-accessor.patch><v14-0006-Implement-Jsonb-subscripting-with-slicing.patch><v14-0004-Extract-coerce_jsonpath_subscript.patch>
> 
> 


I found a bug.

```
INSERT INTO test_jsonb_types (data) VALUES
('[1, 2, "three"]'),
('{"con": {"a": [{"b": {"c": {"d": 99}}}, {"b": {"c": {"d": 100}}}]}}’);
```

If I use a index following a slice, it doesn’t work:

```
evantest=# select data[0] from test_jsonb_types;
 data
------

 1

(2 rows)

evantest=# select data[0:2][1] from test_jsonb_types; # This should return “2"
 data
------


(2 rows)

evantest=# select (t.data)['con']['a'][0:1] from test_jsonb_types t; # returned the slice properly
                        data
-----------------------------------------------------

 [{"b": {"c": {"d": 99}}}, {"b": {"c": {"d": 100}}}]
(2 rows)

evantest=# select (t.data)['con']['a'][0:1][0] from test_jsonb_types t; # also returned the slice, which is wrong
                        data
-----------------------------------------------------

 [{"b": {"c": {"d": 99}}}, {"b": {"c": {"d": 100}}}]
(2 rows)
```

We should consider a slice as a container, so the fix is simple. My quick unpolished fix is:

```
chaol@ChaodeMacBook-Air postgresql % git diff
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index cb72d12ca3f..8845dcf239a 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -247,6 +247,7 @@ jsonb_subscript_make_jsonpath(ParseState *pstate, List **indirection, Subscripti
        ListCell   *lc;
        Datum           jsp;
        int                     pathlen = 0;
+       bool            isSlice = false;

        sbsref->refupperindexpr = NIL;
        sbsref->reflowerindexpr = NIL;
@@ -285,6 +286,7 @@ jsonb_subscript_make_jsonpath(ParseState *pstate, List **indirection, Subscripti

                        if (ai->is_slice)
                        {
+                               isSlice = true;
                                while (list_length(sbsref->reflowerindexpr) < list_length(sbsref->refupperindexpr))
                                        sbsref->reflowerindexpr = lappend(sbsref->reflowerindexpr, NULL);

@@ -369,6 +371,9 @@ jsonb_subscript_make_jsonpath(ParseState *pstate, List **indirection, Subscripti
                path->next = jpi;
                path = jpi;
                pathlen++;
+
+               if (isSlice)
+                       break;
        }

        if (pathlen == 0)
```

After the fix, let’s test again:

```
evantest=# select data[0:2][1] from test_jsonb_types; # good result
 data
------
 2

(2 rows)

evantest=# select (t.data)['con']['a'][0:1][0] from test_jsonb_types t; # good result
          data
-------------------------

 {"b": {"c": {"d": 99}}}
(2 rows)
```

Regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/






^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-06-24 13:29                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-07-09 08:01                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-07-10 08:53                                 ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-21 04:53                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-22 08:19                                     ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-22 19:33                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-25 08:09                                         ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-26 03:52                                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-27 07:26                                             ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-08-29 03:29                                               ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
@ 2025-08-30 00:53                                                 ` Alexandra Wang <[email protected]>
  2025-09-02 02:43                                                   ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  0 siblings, 1 reply; 67+ messages in thread

From: Alexandra Wang @ 2025-08-30 00:53 UTC (permalink / raw)
  To: Chao Li <[email protected]>; +Cc: jian he <[email protected]>; Nikita Malakhov <[email protected]>; Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>

Hi Chao,

Thanks for reviewing!

On Thu, Aug 28, 2025 at 8:29 PM Chao Li <[email protected]> wrote:

>
> On Aug 26, 2025, at 11:52, Alexandra Wang <[email protected]>
> wrote:
>
> Best,
> Alex
> <v14-0002-Allow-Generic-Type-Subscripting-to-Accept-Dot-No.patch>
> <v14-0003-Export-jsonPathFromParseResult.patch>
> <v14-0001-Allow-transformation-of-only-a-sublist-of-subscr.patch>
> <v14-0005-Implement-read-only-dot-notation-for-jsonb.patch>
> <v14-0007-Implement-jsonb-wildcard-member-accessor.patch>
> <v14-0006-Implement-Jsonb-subscripting-with-slicing.patch>
> <v14-0004-Extract-coerce_jsonpath_subscript.patch>
>
>
>
> I found a bug.
>
```
> INSERT INTO test_jsonb_types (data) VALUES
> ('[1, 2, "three"]'),
> ('{"con": {"a": [{"b": {"c": {"d": 99}}}, {"b": {"c": {"d": 100}}}]}}’);
> ```
>
> If I use a index following a slice, it doesn’t work:
>
> ```
> evantest=# select data[0] from test_jsonb_types;
>  data
> ------
>
>  1
>
> (2 rows)
>
> evantest=# select data[0:2][1] from test_jsonb_types; # This should return
> “2"
>  data
> ------
>
>
> (2 rows)
>
> evantest=# select (t.data)['con']['a'][0:1] from test_jsonb_types t; #
> returned the slice properly
>                         data
> -----------------------------------------------------
>
>  [{"b": {"c": {"d": 99}}}, {"b": {"c": {"d": 100}}}]
> (2 rows)
>
> evantest=# select (t.data)['con']['a'][0:1][0] from test_jsonb_types t; #
> also returned the slice, which is wrong
>                         data
> -----------------------------------------------------
>
>  [{"b": {"c": {"d": 99}}}, {"b": {"c": {"d": 100}}}]
> (2 rows)
> ```
>
> We should consider a slice as a container, so the fix is simple. My quick
> unpolished fix is:
>
> ```
> chaol@ChaodeMacBook-Air postgresql % git diff
> diff --git a/src/backend/utils/adt/jsonbsubs.c
> b/src/backend/utils/adt/jsonbsubs.c
> index cb72d12ca3f..8845dcf239a 100644
> --- a/src/backend/utils/adt/jsonbsubs.c
> +++ b/src/backend/utils/adt/jsonbsubs.c
> @@ -247,6 +247,7 @@ jsonb_subscript_make_jsonpath(ParseState *pstate, List
> **indirection, Subscripti
>         ListCell   *lc;
>         Datum           jsp;
>         int                     pathlen = 0;
> +       bool            isSlice = false;
>
>         sbsref->refupperindexpr = NIL;
>         sbsref->reflowerindexpr = NIL;
> @@ -285,6 +286,7 @@ jsonb_subscript_make_jsonpath(ParseState *pstate, List
> **indirection, Subscripti
>
>                         if (ai->is_slice)
>                         {
> +                               isSlice = true;
>                                 while
> (list_length(sbsref->reflowerindexpr) <
> list_length(sbsref->refupperindexpr))
>                                         sbsref->reflowerindexpr =
> lappend(sbsref->reflowerindexpr, NULL);
>
> @@ -369,6 +371,9 @@ jsonb_subscript_make_jsonpath(ParseState *pstate, List
> **indirection, Subscripti
>                 path->next = jpi;
>                 path = jpi;
>                 pathlen++;
> +
> +               if (isSlice)
> +                       break;
>         }
>
>         if (pathlen == 0)
> ```
>
> After the fix, let’s test again:
>
> ```
> evantest=# select data[0:2][1] from test_jsonb_types; # good result
>  data
> ------
>  2
>
> (2 rows)
>
> evantest=# select (t.data)['con']['a'][0:1][0] from test_jsonb_types t; #
> good result
>           data
> -------------------------
>
>  {"b": {"c": {"d": 99}}}
> (2 rows)
> ```
>

TL;DR: It is a feature, not a bug.

See longer explanation below:

This behavior aligns with the SQL:2023 standard. While the result you
expected is more intuitive in my opinion, it is incorrect according to
the spec.

As I mentioned in the commit message of patch v14-0005:






*    The SQL standard states that simplified access is equivalent to:
JSON_QUERY (VEP, 'lax $.JC' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY
NULL ON ERROR)    where:      VEP = <value expression primary>      JC =
<JSON simplified accessor op chain>*

The most relevant detail that I want to highlight is "WITH CONDITIONAL
ARRAY WRAPPER". The documentation[1] says:









*> If the path expression may return multiple values, it might be>
necessary to wrap those values using the WITH WRAPPER clause to make> it a
valid JSON string, because the default behavior is to not wrap> them, as if
WITHOUT WRAPPER were specified. The WITH WRAPPER clause is> by default
taken to mean WITH UNCONDITIONAL WRAPPER, which means that> even a single
result value will be wrapped. To apply the wrapper only> when multiple
values are present, specify WITH CONDITIONAL WRAPPER.> Getting multiple
values in result will be treated as an error if> WITHOUT WRAPPER is
specified.*

So, for your test queries:

select data[0:2] from test_jsonb_types;
select data[0:2][1] from test_jsonb_types;
select (t.data)['con']['a'][0:1] from test_jsonb_types t;
select (t.data)['con']['a'][0:1][0] from test_jsonb_types t;

We have these equivalents using json_query():

select json_query(data, 'lax $[0 to 2]' WITH CONDITIONAL ARRAY WRAPPER NULL
ON EMPTY NULL ON ERROR) from test_jsonb_types;
select json_query(data, 'lax $[0 to 2][1]' WITH CONDITIONAL ARRAY WRAPPER
NULL ON EMPTY NULL ON ERROR) from test_jsonb_types;
select json_query(data, 'lax $.con.a[0 to 1]' WITH CONDITIONAL ARRAY
WRAPPER NULL ON EMPTY NULL ON ERROR) from test_jsonb_types; -- **[NOTE]**
select json_query(data, 'lax $.con.a[0 to 1][0]' WITH CONDITIONAL ARRAY
WRAPPER NULL ON EMPTY NULL ON ERROR) from test_jsonb_types; -- **[NOTE]**

**[NOTE]**: .a and ['a'] (as well as .con and ['con']) are not
syntactically equivalent, as the dot-notation .a is in "lax" mode,
whereas the pre-standard subscript ['a'] is in "strict" mode. I will
discuss this more in a separate reply to your other comment. However,
for the specific data we inserted in your example table, they happen
to return the same results. Since our focus here is not dot-notation,
we won’t go further into it here.

You can verify correctness with:

test=# select data[0:2] = json_query(data, 'lax $[0 to 2]' WITH CONDITIONAL
ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from test_jsonb_types;
 ?column?
----------
 t
 t
(2 rows)

test=# select (data[0:2][1] is NULL) AND (json_query(data, 'lax $[0 to
2][1]' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) is NULL)
from test_jsonb_types;
 ?column?
----------
 t
 t
(2 rows)

test=# select (((t.data)['con']['a'][0:1] IS NULL) AND (json_query(data,
'lax $.con.a[0 to 1]' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON
ERROR) IS NULL)) OR ((t.data)['con']['a'][0:1] = json_query(data, 'lax
$.con.a[0 to 1]' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON
ERROR)) from test_jsonb_types t;
 ?column?
----------
 t
 t
(2 rows)

test=# select (((t.data)['con']['a'][0:1][0] IS NULL) AND (json_query(data,
'lax $.con.a[0 to 1][0]' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL
ON ERROR) IS NULL)) OR ((t.data)['con']['a'][0:1][0] = json_query(data,
'lax $.con.a[0 to 1][0]' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL
ON ERROR)) from test_jsonb_types t;
 ?column?
----------
 t
 t
(2 rows)

All tests show "t", confirming the current results are all correct.

I think the root of your confusion is the meaning of CONDITIONAL ARRAY
WRAPPER. So let’s try more examples:

-- keep your previous setup
drop table test_jsonb_types;
create table test_jsonb_types (data jsonb);
INSERT INTO test_jsonb_types (data) VALUES ('[1, 2, "three"]'), ('{"con":
{"a": [{"b": {"c": {"d": 99}}}, {"b": {"c": {"d": 100}}}]}}');

Let's start with:

select data[0:2] from test_jsonb_types;

It is equivalent to:

select json_query(data, 'lax $[0 to 2]' WITH CONDITIONAL ARRAY WRAPPER NULL
ON EMPTY NULL ON ERROR) from test_jsonb_types;

They all have the following output:

test=# select json_query(data, 'lax $[0 to 2]' WITH CONDITIONAL ARRAY
WRAPPER NULL ON EMPTY NULL ON ERROR) from test_jsonb_types;
                             json_query
---------------------------------------------------------------------
 [1, 2, "three"]
 {"con": {"a": [{"b": {"c": {"d": 99}}}, {"b": {"c": {"d": 100}}}]}}
(2 rows)

To find out what WITH CONDITIONAL ARRAY WRAPPER does, let's toggle it
to WITHOUT ARRAY WRAPPER:

test=# select json_query(data, 'lax $[0 to 2]' WITHOUT ARRAY WRAPPER NULL
ON EMPTY NULL ON ERROR) from test_jsonb_types;
                             json_query
---------------------------------------------------------------------

 {"con": {"a": [{"b": {"c": {"d": 99}}}, {"b": {"c": {"d": 100}}}]}}
(2 rows)

The first row return NULL, because we've specified NULL ON ERROR, so
let's toggle that as well to ERROR ON ERROR:

test=# select json_query(data, 'lax $[0 to 2]' WITHOUT ARRAY WRAPPER NULL
ON EMPTY ERROR ON ERROR) from test_jsonb_types;
ERROR:  22034: JSON path expression in JSON_QUERY must return single item
when no wrapper is requested
HINT:  Use the WITH WRAPPER clause to wrap SQL/JSON items into an array.
LOCATION:  JsonPathQuery, jsonpath_exec.c:3987

As shown, without ARRAY WRAPPER, the query produces a sequence of JSON
value items, not a JSON array.

Note that array wrapping is only applied to the final result of a
jsonpath, not to each intermediate result in the chain. See the
following example:

-- new setup
truncate test_jsonb_types;
INSERT INTO test_jsonb_types (data) VALUES ('[1, 2, "three"]'), ('[1, [2,
22], "three"]');

test=# select json_query(data, 'lax $[0 to 2][1]' WITHOUT ARRAY WRAPPER
NULL ON EMPTY ERROR ON ERROR) from test_jsonb_types;
 json_query
------------

 22
(2 rows)

In this case, you can see more clearly that "[0 to 2]" fetches three
individual jsonb array elements, and "[1]" treats each of the three
jsonb values as independent jsonb arrays, reading the first element of
each. This is different from wrapping the intermediate result into an
array and accessing the first element of that wrapped array.

Another thing I want to point out is that there is a trivial case for
"lax" mode when accessing a jsonb object (not a jsonb array) using a
JSON array accessor "[0]". For example:

-- another setup, still use your data
truncate test_jsonb_types;
insert into test_jsonb_types VALUES ('{"con": {"a": [{"b": {"c": {"d":
99}}}, {"b": {"c": {"d": 100}}}]}}');

test=# select (data).con[0] from test_jsonb_types;
                            con
------------------------------------------------------------
 {"a": [{"b": {"c": {"d": 99}}}, {"b": {"c": {"d": 100}}}]}
(1 row)

This is equivalent to:

test=# select json_query(data, 'lax $.con[0]' WITH CONDITIONAL ARRAY
WRAPPER NULL ON EMPTY NULL ON ERROR) from test_jsonb_types;
                         json_query
------------------------------------------------------------
 {"a": [{"b": {"c": {"d": 99}}}, {"b": {"c": {"d": 100}}}]}
(1 row)

which is different from the result if we use "strict" mode:

test=# select json_query(data, 'strict $.con[0]' WITH CONDITIONAL ARRAY
WRAPPER NULL ON EMPTY NULL ON ERROR) from test_jsonb_types;
 json_query
------------

(1 row)

According to SQL:2023:




*In lax mode:— If an operation requires an SQL/JSON array but the operand
is not an SQL/JSON array, then the operand is first “wrapped” in an
SQL/JSON array prior to performing the operation.— If an operation requires
something other than an SQL/JSON array, but the operand is an SQL/JSON
array, then the operand is “unwrapped” by converting its elements into an
SQL/JSON sequence prior to performing the operation.— After applying the
preceding resolutions to structural errors, if there is still a structural
error , the result is an empty SQL/JSON sequence.*

Please refer to the first point to understand the example queries.

Because "lax" mode wraps a jsonb object into an array of a single
element, accessing it with [0] will always return the same jsonb
object. In fact, you can access it with a chain of [0]s and still get
the same jsonb object:

test=# select json_query(data, 'lax $.con[0][0][0][0][0][0]' WITH
CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from
test_jsonb_types;
                         json_query
------------------------------------------------------------
 {"a": [{"b": {"c": {"d": 99}}}, {"b": {"c": {"d": 100}}}]}
(1 row)

test=# select (data).con[0][0][0][0][0][0] from test_jsonb_types;
                            con
------------------------------------------------------------
 {"a": [{"b": {"c": {"d": 99}}}, {"b": {"c": {"d": 100}}}]}
(1 row)

I hope this long explanation helps!

[1]
https://www.postgresql.org/docs/current/functions-json.html#SQLJSON-QUERY-FUNCTIONS

Best,
Alex


^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-06-24 13:29                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-07-09 08:01                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-07-10 08:53                                 ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-21 04:53                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-22 08:19                                     ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-22 19:33                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-25 08:09                                         ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-26 03:52                                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-27 07:26                                             ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-08-29 03:29                                               ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-08-30 00:53                                                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
@ 2025-09-02 02:43                                                   ` Chao Li <[email protected]>
  0 siblings, 0 replies; 67+ messages in thread

From: Chao Li @ 2025-09-02 02:43 UTC (permalink / raw)
  To: Alexandra Wang <[email protected]>; +Cc: jian he <[email protected]>; Nikita Malakhov <[email protected]>; Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>

Hi Alex,

Thank you so much for such a detailed explanation.

> On Aug 30, 2025, at 08:53, Alexandra Wang <[email protected]> wrote:
> 
> 
> 
> I hope this long explanation helps!
> 
> [1] https://www.postgresql.org/docs/current/functions-json.html#SQLJSON-QUERY-FUNCTIONS
> 
> 


I used to only use the “->” and “->>” accessors for jsonb statements. After some research, I think my misunderstanding was about the slice operator [x:y]. I thought it could return a new array, however it actually returns a sequence.

I revisited the patch diffs again, and got a few other comments. Let me raise them in a separate email.

Regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/






^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-06-24 13:29                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-07-09 08:01                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-07-10 08:53                                 ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-21 04:53                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-22 08:19                                     ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-22 19:33                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-25 08:09                                         ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-26 03:52                                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-27 07:26                                             ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
@ 2025-09-02 02:46                                               ` Chao Li <[email protected]>
  1 sibling, 0 replies; 67+ messages in thread

From: Chao Li @ 2025-09-02 02:46 UTC (permalink / raw)
  To: Alexandra Wang <[email protected]>; +Cc: jian he <[email protected]>; Nikita Malakhov <[email protected]>; Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>



> On Aug 27, 2025, at 15:26, Chao Li <[email protected]> wrote:
> 
> diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
> index f7d07c84542..58a4b9df157 100644
> --- a/src/include/parser/parse_node.h
> +++ b/src/include/parser/parse_node.h
> @@ -361,7 +361,7 @@ extern SubscriptingRef *transformContainerSubscripts(ParseState *pstate,
>                                                                                                          Node *containerBase,
>                                                                                                          Oid containerType,
>                                                                                                          int32 containerTypMod,
> -                                                                                                        List *indirection,
> +                                                                                                        List **indirection,
>                                                                                                          bool isAssignment);
> 
> Here we change “indirection” from * to **, I guess the reason is to indicate if the indirection list is fully processed. In case of fully processed, set it to NULL, then caller gets NULL via **.
> 
> As “indirection” is of type “List *”, can we just set “indirection->length = 0”? I checked pg_list.h, there is not a function or macro to cleanup a list (free elements and reset length to 0). There is a “list_free(List *list)”, but it will free “list” itself as well. Maybe we can add a new function, say “list_reset(List *list)” or “list_cleanup(List *list)”. This way will keep the function interface unchanged, and less changes would be involved.


After revisiting the changes, I take back this comment. Given the nature that List are always used in pointer form, my comment would just make things much more complicated.

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/






^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-06-24 13:29                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-07-09 08:01                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-07-10 08:53                                 ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-21 04:53                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-22 08:19                                     ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-22 19:33                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-25 08:09                                         ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-26 03:52                                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
@ 2025-08-29 03:42                                             ` Chao Li <[email protected]>
  2025-09-01 04:11                                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  3 siblings, 1 reply; 67+ messages in thread

From: Chao Li @ 2025-08-29 03:42 UTC (permalink / raw)
  To: Alexandra Wang <[email protected]>; +Cc: jian he <[email protected]>; Nikita Malakhov <[email protected]>; Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>

> 
> Best,
> Alex
> <v14-0002-Allow-Generic-Type-Subscripting-to-Accept-Dot-No.patch><v14-0003-Export-jsonPathFromParseResult.patch><v14-0001-Allow-transformation-of-only-a-sublist-of-subscr.patch><v14-0005-Implement-read-only-dot-notation-for-jsonb.patch><v14-0007-Implement-jsonb-wildcard-member-accessor.patch><v14-0006-Implement-Jsonb-subscripting-with-slicing.patch><v14-0004-Extract-coerce_jsonpath_subscript.patch>


I am trying to split different topics to different email to keep every issue to be focused.

I also have a suggestion.

If I do:

```
— s1
select (t.data)['con']['a'][1]['b']['c']['d'] from test_jsonb_types t;

—s2
select (t.data).con.a[1].b['c'].d from test_jsonb_types t;
```

The two statements are actually identical. But they generate quite different rewritten query trees. S1’s rewritten tree is much simpler than s2’s. However, their plan trees are the same.

I think we can convert string indirections to indices indirection first before transform indirections, so that the two SQLs will generate the same rewritten query tree, which would simplify the code logic of rewriting query tree and the planner.


Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/






^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-06-24 13:29                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-07-09 08:01                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-07-10 08:53                                 ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-21 04:53                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-22 08:19                                     ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-22 19:33                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-25 08:09                                         ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-26 03:52                                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-29 03:42                                             ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
@ 2025-09-01 04:11                                               ` Alexandra Wang <[email protected]>
  0 siblings, 0 replies; 67+ messages in thread

From: Alexandra Wang @ 2025-09-01 04:11 UTC (permalink / raw)
  To: Chao Li <[email protected]>; +Cc: jian he <[email protected]>; Nikita Malakhov <[email protected]>; Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>

Hi Chao,

On Thu, Aug 28, 2025 at 8:42 PM Chao Li <[email protected]> wrote:

> I am trying to split different topics to different email to keep every
> issue to be focused.
>

Sure!

On Thu, Aug 28, 2025 at 8:42 PM Chao Li <[email protected]> wrote:

> I also have a suggestion.
>
> If I do:
>
> ```
> — s1
> select (t.data)['con']['a'][1]['b']['c']['d'] from test_jsonb_types t;
>
> —s2
> select (t.data).con.a[1].b['c'].d from test_jsonb_types t;
> ```
>
> The two statements are actually identical. But they generate quite
> different rewritten query trees. S1’s rewritten tree is much simpler than
> s2’s. However, their plan trees are the same.
>

The above two statements are NOT identical. Specifically, dot-notation
(e.g., .con) and pre-standard jsonb subscripting (e.g., ['con']) are
NOT semantically the same.

Here's an example:

-- setup
create table t (jb jsonb);
insert into t SELECT '{"con": 1}'::jsonb;
insert into t SELECT '[{"con": 1}, {"con": {"a": 2}}]'::jsonb;

-- queries
test=# select (t.jb).con from t;
      con
---------------
 1
 [1, {"a": 2}]
(2 rows)

test=# select (t.jb)['con'] from t;
 jb
----
 1

(2 rows)

As you can see, dot-notation returns different results from jsonb
subscripting.

As I mentioned in the previous reply:

    The SQL standard states that simplified access is equivalent to:
>     JSON_QUERY (VEP, 'lax $.JC' WITH CONDITIONAL ARRAY WRAPPER NULL ON
> EMPTY NULL ON ERROR
> )


>     where:
>       VEP = <value expression primary>
>       JC = <JSON simplified accessor op chain>


And


> *In lax mode:*
> *— If an operation requires an SQL/JSON array but the operand is not an
> SQL/JSON array, then the operand is first “wrapped” in an SQL/JSON array
> prior to performing the operation.*
> *— If an operation requires something other than an SQL/JSON array, but
> the operand is an SQL/JSON array, then the operand is “unwrapped” by
> converting its elements into an SQL/JSON sequence prior to performing the
> operation.**— After applying the preceding resolutions to structural
> errors, if there is still a structural error , the result is an empty
> SQL/JSON sequence.*


The example query demonstrates the second point above. The
dot-notation attempts to access a member field (."con") of a JSON
object, while the operand is a JSON array ([{"con": 1}, {"con": {"a":
2}}]). In "lax" mode, the operand is "unwrapped" into a JSON sequence
(two elements: {"con": 1} and {"con": {"a": 2}}), and the member field
access is performed on each element. The multiple results are then
wrapped into a JSON array ([1, {"a": 2}]) due to WITH CONDITIONAL
ARRAY WRAPPER. I’ve already explained what "ARRAY WRAPPER" does in my
previous reply, so I won't repeat it here.

Best,
Alex


^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-06-24 13:29                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-07-09 08:01                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-07-10 08:53                                 ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-21 04:53                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-22 08:19                                     ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-22 19:33                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-25 08:09                                         ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-26 03:52                                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
@ 2025-08-29 07:22                                             ` Chao Li <[email protected]>
  2025-09-02 02:59                                               ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  3 siblings, 1 reply; 67+ messages in thread

From: Chao Li @ 2025-08-29 07:22 UTC (permalink / raw)
  To: Alexandra Wang <[email protected]>; +Cc: jian he <[email protected]>; Nikita Malakhov <[email protected]>; Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>



> On Aug 26, 2025, at 11:52, Alexandra Wang <[email protected]> wrote:

> <v14-0002-Allow-Generic-Type-Subscripting-to-Accept-Dot-No.patch><v14-0003-Export-jsonPathFromParseResult.patch><v14-0001-Allow-transformation-of-only-a-sublist-of-subscr.patch><v14-0005-Implement-read-only-dot-notation-for-jsonb.patch><v14-0007-Implement-jsonb-wildcard-member-accessor.patch><v14-0006-Implement-Jsonb-subscripting-with-slicing.patch><v14-0004-Extract-coerce_jsonpath_subscript.patch>


A few other comments:

-static Node *
-transformIndirection(ParseState *pstate, A_Indirection *ind)
+Node *
+transformIndirection(ParseState *pstate, A_Indirection *ind,
+                                        bool *trailing_star_expansion)
 {
        Node       *last_srf = pstate->p_last_srf;
        Node       *result = transformExprRecurse(pstate, ind->arg);
@@ -454,12 +454,7 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
                if (IsA(n, A_Indices))
                        subscripts = lappend(subscripts, n);
                else if (IsA(n, A_Star))
-               {
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                        errmsg("row expansion via \"*\" is not supported here"),
-                                        parser_errposition(pstate, location)));
-               }
+                       subscripts = lappend(subscripts, n);
                else
                {
                        Assert(IsA(n, String));

Now, the 3 clauses are quite similar, the if-elseif-else can be simplified as:

     If (!IsA(n, A_Indices) && !Is_A(n, A_Start))
          Assert(IsA(n, String));
     Subscripts = lappend(subscripts, n)

+static bool
+jsonb_check_jsonpath_needed(List *indirection)
+{
+       ListCell   *lc;
+
+       foreach(lc, indirection)
+       {
+               Node       *accessor = lfirst(lc);
+
+               if (IsA(accessor, String))
+                       return true;

Like I mentioned in my previous comment, if we convert String (dot notation) to Indices in rewritten phase, then jsonpath might be no longer needed, and the code logic is much simplified.


--- a/src/backend/parser/parse_node.c
+++ b/src/backend/parser/parse_node.c
@@ -244,7 +244,7 @@ transformContainerSubscripts(ParseState *pstate,
                                                         Node *containerBase,
                                                         Oid containerType,
                                                         int32 containerTypMod,
-                                                        List *indirection,
+                                                        List **indirection,
                                                         bool isAssignment)
 {
        SubscriptingRef *sbsref;
@@ -280,7 +280,7 @@ transformContainerSubscripts(ParseState *pstate,
         * element.  If any of the items are slice specifiers (lower:upper), then
         * the subscript expression means a container slice operation.
         */
-       foreach(idx, indirection)
+       foreach(idx, *indirection)
        {
                A_Indices  *ai = lfirst_node(A_Indices, idx);

Should this foreach be pull up to out of transformContainerSubscripts()? Originally transformContainerSubscripts() runs only once, but now it’s placed in a while loop, and every time this foreach needs to go through the entire indirection list, which are duplicated computation.

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/






^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-06-24 13:29                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-07-09 08:01                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-07-10 08:53                                 ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-21 04:53                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-22 08:19                                     ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-22 19:33                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-25 08:09                                         ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-26 03:52                                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-29 07:22                                             ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
@ 2025-09-02 02:59                                               ` Chao Li <[email protected]>
  2025-09-02 03:32                                                 ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-03 02:16                                                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  0 siblings, 2 replies; 67+ messages in thread

From: Chao Li @ 2025-09-02 02:59 UTC (permalink / raw)
  To: Alexandra Wang <[email protected]>; +Cc: jian he <[email protected]>; Nikita Malakhov <[email protected]>; Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>



> On Aug 29, 2025, at 15:22, Chao Li <[email protected]> wrote:
> 
> +static bool
> +jsonb_check_jsonpath_needed(List *indirection)
> +{
> +       ListCell   *lc;
> +
> +       foreach(lc, indirection)
> +       {
> +               Node       *accessor = lfirst(lc);
> +
> +               if (IsA(accessor, String))
> +                       return true;
> 
> Like I mentioned in my previous comment, if we convert String (dot notation) to Indices in rewritten phase, then jsonpath might be no longer needed, and the code logic is much simplified.
> 

Taking back this comment. As Alex explained in the other email, dot notation and indices accessor are different.

> 
> --- a/src/backend/parser/parse_node.c
> +++ b/src/backend/parser/parse_node.c
> @@ -244,7 +244,7 @@ transformContainerSubscripts(ParseState *pstate,
>                                                          Node *containerBase,
>                                                          Oid containerType,
>                                                          int32 containerTypMod,
> -                                                        List *indirection,
> +                                                        List **indirection,
>                                                          bool isAssignment)
>  {
>         SubscriptingRef *sbsref;
> @@ -280,7 +280,7 @@ transformContainerSubscripts(ParseState *pstate,
>          * element.  If any of the items are slice specifiers (lower:upper), then
>          * the subscript expression means a container slice operation.
>          */
> -       foreach(idx, indirection)
> +       foreach(idx, *indirection)
>         {
>                 A_Indices  *ai = lfirst_node(A_Indices, idx);
> 
> Should this foreach be pull up to out of transformContainerSubscripts()? Originally transformContainerSubscripts() runs only once, but now it’s placed in a while loop, and every time this foreach needs to go through the entire indirection list, which are duplicated computation.


After revisiting the code, I think this loop of checking is_slice should be removed here.

It seems only arrsubs cares about this is_slice flag.

hstore_subs can only take a single indirection, so it can do a simple check like:

```
ai = initial_node(A_Indices, *indirection);
If (list_length(*indirection) != 1 || ai->is_slice)
{
    ereport(…)
}
```

jsonbsubs doesn’t need to check is_slice flag as well, I will explain that in my next email tougher with my new comments.

So that, “bool isSlace” can be removed from SubscriptTransform, and let every individual subs check is_slice.

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/





^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-06-24 13:29                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-07-09 08:01                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-07-10 08:53                                 ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-21 04:53                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-22 08:19                                     ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-22 19:33                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-25 08:09                                         ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-26 03:52                                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-29 07:22                                             ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-02 02:59                                               ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
@ 2025-09-02 03:32                                                 ` Chao Li <[email protected]>
  2025-09-02 03:53                                                   ` Re: SQL:2023 JSON simplified accessor support David G. Johnston <[email protected]>
  2025-09-03 02:20                                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  1 sibling, 2 replies; 67+ messages in thread

From: Chao Li @ 2025-09-02 03:32 UTC (permalink / raw)
  To: Alexandra Wang <[email protected]>; Nikita Glukhov <[email protected]>; +Cc: jian he <[email protected]>; Nikita Malakhov <[email protected]>; Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>



> On Sep 2, 2025, at 10:59, Chao Li <[email protected]> wrote:
> 
> jsonbsubs doesn’t need to check is_slice flag as well, I will explain that in my next email tougher with my new comments.



Now jsonb_check_json_needed() loops over all indirections:

static bool
jsonb_check_jsonpath_needed(List *indirection)
{
    ListCell   *lc;

    foreach(lc, indirection)
    {
        Node       *accessor = lfirst(lc);

        if (IsA(accessor, String))
            return true;
        else
        {
            Assert(IsA(accessor, A_Indices));

            if (castNode(A_Indices, accessor)->is_slice)
                return true;
        }
    }

    return false;
}

Let’s consider this statement: select data[‘a’][‘b’].c from jsonb_table.

For this indirection list, first two elements are non-slice indices, and the third is a string accessor. So json_check_jsonpath_needed() will return true.

Then:

static void
jsonb_subscript_transform(SubscriptingRef *sbsref,
                          List **indirection,
                          ParseState *pstate,
                          bool isSlice,
                          bool isAssignment)
{
    List       *upperIndexpr = NIL;
    ListCell   *idx;

    /* Determine the result type of the subscripting operation; always jsonb */
    sbsref->refrestype = JSONBOID;
    sbsref->reftypmod = -1;

    if (isSlice || jsonb_check_jsonpath_needed(*indirection))
    {
        jsonb_subscript_make_jsonpath(pstate, indirection, sbsref);
        if (sbsref->refjsonbpath)
            return;
    }

It will fall into the call to json_subscript_make_jsonpath(), and it cannot process [‘a’], so sbsref->refjsonbpath will be NULL, then it will fall through down to the logic of processing [‘a’].

That’s actually a waste of looping over the entire indirection list and making an unnecessary call to jsonb_subscript_make_jsonpath().

In json_check_jsonpath_needed(), we can only check the first item. Also, as jsonb_check_jsonpath_needed() already has the logic of checking is_slice, here in jsonb_subscript_transform(), we don’t need to check “isSlice” in the “if” condition at all.

I made the change locally, and “make check” passed.

My dirty change is like:
```
@@ -118,11 +118,11 @@ coerce_jsonpath_subscript_to_int4_or_text(ParseState *pstate, Node *subExpr)
 static bool
 jsonb_check_jsonpath_needed(List *indirection)
 {
-       ListCell   *lc;
+       //ListCell   *lc;

-       foreach(lc, indirection)
-       {
-               Node       *accessor = lfirst(lc);
+       //foreach(lc, indirection)
+       //{
+               Node       *accessor = lfirst(list_head(indirection));

                if (IsA(accessor, String) || IsA(accessor, A_Star))
                        return true;
@@ -133,7 +133,8 @@ jsonb_check_jsonpath_needed(List *indirection)
                        if (castNode(A_Indices, accessor)->is_slice)
                                return true;
                }
-       }
+               //break;
+       //}

        return false;
 }

@@ -401,7 +435,7 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
        sbsref->refrestype = JSONBOID;
        sbsref->reftypmod = -1;

-       if (isSlice || jsonb_check_jsonpath_needed(*indirection))
+       if (jsonb_check_jsonpath_needed(*indirection))
        {
                jsonb_subscript_make_jsonpath(pstate, indirection, sbsref);
                if (sbsref->refjsonbpath)
```

The other comment is that jsonb_subscript_make_jsonpath() can be optimized a little bit. When indices and dot notations are mixed, it will always hit the clause “if (api_from == NULL)”. In this case, memory of jpi has been allocated, and with the “if” we need to free the memory. So, we can pull up calculating jpi_from, if it is NULL, then we don’t need to allocate and free memory. My dirty change is like:

```
                else if (IsA(accessor, A_Indices))
                {
+                       JsonPathParseItem *jpi_from = NULL;
                        A_Indices  *ai = castNode(A_Indices, accessor);

+                       if (!ai->is_slice)
+                       {
+                               Assert(ai->uidx && !ai->lidx);
+                               jpi_from = make_jsonpath_item_expr(pstate, ai->uidx, &sbsref->refupperindexpr, true);
+                               if (jpi_from == NULL)
+                               {
+                                       /*
+                                        * Break out of the loop if the subscript is not a
+                                        * non-null integer constant, so that we can fall back to
+                                        * jsonb subscripting logic.
+                                        *
+                                        * This is needed to handle cases with mixed usage of SQL
+                                        * standard json simplified accessor syntax and PostgreSQL
+                                        * jsonb subscripting syntax, e.g:
+                                        *
+                                        * select (jb).a['b'].c from jsonb_table;
+                                        *
+                                        * where dot-notation (.a and .c) is the SQL standard json
+                                        * simplified accessor syntax, and the ['b'] subscript is
+                                        * the PostgreSQL jsonb subscripting syntax, because 'b'
+                                        * is not a non-null constant integer and cannot be used
+                                        * for json array access.
+                                        *
+                                        * In this case, we cannot create a JsonPath item, so we
+                                        * break out of the loop and let
+                                        * jsonb_subscript_transform() handle this indirection as
+                                        * a PostgreSQL jsonb subscript.
+                                        */
+                                       break;
+                               }
+                       }
+
                        jpi = make_jsonpath_item(jpiIndexArray);
                        jpi->value.array.nelems = 1;
                        jpi->value.array.elems = palloc(sizeof(jpi->value.array.elems[0]));
@@ -303,12 +337,12 @@ jsonb_subscript_make_jsonpath(ParseState *pstate, List **indirection, Subscripti
                        }
                        else
                        {
-                               JsonPathParseItem *jpi_from = NULL;
+                               //JsonPathParseItem *jpi_from = NULL;

-                               Assert(ai->uidx && !ai->lidx);
-                               jpi_from = make_jsonpath_item_expr(pstate, ai->uidx, &sbsref->refupperindexpr, true);
-                               if (jpi_from == NULL)
-                               {
+                               //Assert(ai->uidx && !ai->lidx);
+                               //jpi_from = make_jsonpath_item_expr(pstate, ai->uidx, &sbsref->refupperindexpr, true);
+                               //if (jpi_from == NULL)
+                               //{
                                        /*
                                         * Break out of the loop if the subscript is not a
                                         * non-null integer constant, so that we can fall back to
@@ -331,10 +365,10 @@ jsonb_subscript_make_jsonpath(ParseState *pstate, List **indirection, Subscripti
                                         * jsonb_subscript_transform() handle this indirection as
                                         * a PostgreSQL jsonb subscript.
                                         */
-                                       pfree(jpi->value.array.elems);
-                                       pfree(jpi);
-                                       break;
-                               }
+                               //      pfree(jpi->value.array.elems);
+                               //      pfree(jpi);
+                               //      break;
+                               //}

                                jpi->value.array.elems[0].from = jpi_from;
                                jpi->value.array.elems[0].to = NULL;
```

With this change, “make check” also passed.

The third comment is about test cases. I only see tests for indices following dot notation, like data.a[‘b’], but not the reverse. Let’s add a few test cases for dot notation following indices, like data[‘a’].b.

The last comment is about error message:

```
evantest=# select data.a from test_jsonb_types;
ERROR:  missing FROM-clause entry for table "data"
LINE 1: select data.a from test_jsonb_types;
```

“Missing FROM-clause entry” is quite confusing and not providing much useful hint to resolve the problem. When I first saw this error, I was stuck. Only after read through the commit comments, I figured out the problem. For end users, we should provide some more meaningful error messages.


Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/






^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-06-24 13:29                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-07-09 08:01                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-07-10 08:53                                 ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-21 04:53                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-22 08:19                                     ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-22 19:33                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-25 08:09                                         ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-26 03:52                                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-29 07:22                                             ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-02 02:59                                               ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-02 03:32                                                 ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
@ 2025-09-02 03:53                                                   ` David G. Johnston <[email protected]>
  2025-09-02 05:27                                                     ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  1 sibling, 1 reply; 67+ messages in thread

From: David G. Johnston @ 2025-09-02 03:53 UTC (permalink / raw)
  To: Chao Li <[email protected]>; +Cc: Alexandra Wang <[email protected]>; Nikita Glukhov <[email protected]>; jian he <[email protected]>; Nikita Malakhov <[email protected]>; Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>

On Monday, September 1, 2025, Chao Li <[email protected]> wrote:

>
> The last comment is about error message:
>
> ```
> evantest=# select data.a from test_jsonb_types;
> ERROR:  missing FROM-clause entry for table "data"
> LINE 1: select data.a from test_jsonb_types;
> ```
>
> “Missing FROM-clause entry” is quite confusing and not providing much
> useful hint to resolve the problem. When I first saw this error, I was
> stuck. Only after read through the commit comments, I figured out the
> problem. For end users, we should provide some more meaningful error
> messages.
>
>
I don’t think it’s fair to blame this patch set for this.  If you want to
reference a component of a composite column you need to write
([tbl.]col).field otherwise the parser gets confused because it wants the
thing prior to “field” to be a table name if you don’t use the
parentheses.  This comes up all the time if one uses composite typed
columns.  I’ll agree this isn’t the greatest error message but I’d suggest
starting a new thread if you want to see about improving it.

David J.


^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-06-24 13:29                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-07-09 08:01                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-07-10 08:53                                 ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-21 04:53                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-22 08:19                                     ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-22 19:33                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-25 08:09                                         ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-26 03:52                                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-29 07:22                                             ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-02 02:59                                               ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-02 03:32                                                 ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-02 03:53                                                   ` Re: SQL:2023 JSON simplified accessor support David G. Johnston <[email protected]>
@ 2025-09-02 05:27                                                     ` Chao Li <[email protected]>
  0 siblings, 0 replies; 67+ messages in thread

From: Chao Li @ 2025-09-02 05:27 UTC (permalink / raw)
  To: David G. Johnston <[email protected]>; +Cc: Alexandra Wang <[email protected]>; Nikita Glukhov <[email protected]>; jian he <[email protected]>; Nikita Malakhov <[email protected]>; Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>



> On Sep 2, 2025, at 11:53, David G. Johnston <[email protected]> wrote:
> 
> On Monday, September 1, 2025, Chao Li <[email protected] <mailto:[email protected]>> wrote:
>> 
>> The last comment is about error message:
>> 
>> ```
>> evantest=# select data.a from test_jsonb_types;
>> ERROR:  missing FROM-clause entry for table "data"
>> LINE 1: select data.a from test_jsonb_types;
>> ```
>> 
>> “Missing FROM-clause entry” is quite confusing and not providing much useful hint to resolve the problem. When I first saw this error, I was stuck. Only after read through the commit comments, I figured out the problem. For end users, we should provide some more meaningful error messages.
>> 
> 
> I don’t think it’s fair to blame this patch set for this.  If you want to reference a component of a composite column you need to write ([tbl.]col).field otherwise the parser gets confused because it wants the thing prior to “field” to be a table name if you don’t use the parentheses.  This comes up all the time if one uses composite typed columns.  I’ll agree this isn’t the greatest error message but I’d suggest starting a new thread if you want to see about improving it.
> 
> David J.
> 


Sure, we can discuss the topic separately.

--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/






^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-06-24 13:29                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-07-09 08:01                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-07-10 08:53                                 ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-21 04:53                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-22 08:19                                     ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-22 19:33                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-25 08:09                                         ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-26 03:52                                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-29 07:22                                             ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-02 02:59                                               ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-02 03:32                                                 ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
@ 2025-09-03 02:20                                                   ` Alexandra Wang <[email protected]>
  2025-09-03 06:56                                                     ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  1 sibling, 1 reply; 67+ messages in thread

From: Alexandra Wang @ 2025-09-03 02:20 UTC (permalink / raw)
  To: Chao Li <[email protected]>; +Cc: Nikita Glukhov <[email protected]>; jian he <[email protected]>; Nikita Malakhov <[email protected]>; Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>

Hi Chao,

Continue from my previous email.

On Mon, Sep 1, 2025 at 8:32 PM Chao Li <[email protected]> wrote:

>
>
> On Sep 2, 2025, at 10:59, Chao Li <[email protected]> wrote:
>
> jsonbsubs doesn’t need to check is_slice flag as well, I will explain that
> in my next email tougher with my new comments.
>
>
>
> Now jsonb_check_json_needed() loops over all indirections:
>
> static bool
> jsonb_check_jsonpath_needed(List *indirection)
> {
> ListCell *lc;
>
> foreach(lc, indirection)
> {
> Node *accessor = lfirst(lc);
>
> if (IsA(accessor, String))
> return true;
> else
> {
> Assert(IsA(accessor, A_Indices));
>
> if (castNode(A_Indices, accessor)->is_slice)
> return true;
> }
> }
>
> return false;
> }
>
> Let’s consider this statement: select data[‘a’][‘b’].c from jsonb_table.
>
> For this indirection list, first two elements are non-slice indices, and
> the third is a string accessor. So json_check_jsonpath_needed() will return
> true.
>
> Then:
>
> static void
> jsonb_subscript_transform(SubscriptingRef *sbsref,
> List **indirection,
> ParseState *pstate,
> bool isSlice,
> bool isAssignment)
> {
> List *upperIndexpr = NIL;
> ListCell *idx;
>
> /* Determine the result type of the subscripting operation; always jsonb */
> sbsref->refrestype = JSONBOID;
> sbsref->reftypmod = -1;
>
> if (isSlice || jsonb_check_jsonpath_needed(*indirection))
> {
> jsonb_subscript_make_jsonpath(pstate, indirection, sbsref);
> if (sbsref->refjsonbpath)
> return;
> }
>
> It will fall into the call to json_subscript_make_jsonpath(), and it
> cannot process [‘a’], so sbsref->refjsonbpath will be NULL, then it will
> fall through down to the logic of processing [‘a’].
>
> That’s actually a waste of looping over the entire indirection list and
> making an unnecessary call to jsonb_subscript_make_jsonpath().
>
> In json_check_jsonpath_needed(), we can only check the first item. Also,
> as jsonb_check_jsonpath_needed() already has the logic of checking
> is_slice, here in jsonb_subscript_transform(), we don’t need to check
> “isSlice” in the “if” condition at all.
>
> I made the change locally, and “make check” passed.
>
> My dirty change is like:
> ```
> @@ -118,11 +118,11 @@ coerce_jsonpath_subscript_to_int4_or_text(ParseState
> *pstate, Node *subExpr)
>  static bool
>  jsonb_check_jsonpath_needed(List *indirection)
>  {
> -       ListCell   *lc;
> +       //ListCell   *lc;
>
> -       foreach(lc, indirection)
> -       {
> -               Node       *accessor = lfirst(lc);
> +       //foreach(lc, indirection)
> +       //{
> +               Node       *accessor = lfirst(list_head(indirection));
>
>                 if (IsA(accessor, String) || IsA(accessor, A_Star))
>                         return true;
> @@ -133,7 +133,8 @@ jsonb_check_jsonpath_needed(List *indirection)
>                         if (castNode(A_Indices, accessor)->is_slice)
>                                 return true;
>                 }
> -       }
> +               //break;
> +       //}
>
>         return false;
>  }
>
> @@ -401,7 +435,7 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
>         sbsref->refrestype = JSONBOID;
>         sbsref->reftypmod = -1;
>
> -       if (isSlice || jsonb_check_jsonpath_needed(*indirection))
> +       if (jsonb_check_jsonpath_needed(*indirection))
>         {
>                 jsonb_subscript_make_jsonpath(pstate, indirection, sbsref);
>                 if (sbsref->refjsonbpath)
> ```
>

This change would give an incorrect result for an accessor like
"[0].a" when the jsonb column data is a jsonb object instead of a
jsonb array.  I've added two new test cases to cover this scenario:

In jsonb.sql, the newly added tests are:
select (jb)[0].a from test_jsonb_dot_notation; -- returns same result as
(jb).a
select (jb)[1].a from test_jsonb_dot_notation; -- returns NULL

In v5, I included isSlice as an argument of
jsonb_check_jsonpath_needed(), so we no longer need to check the
is_slice field of each indirection element. That should make the check
more efficient. In jsonb_subscript_make_jsonpath(), I also moved the
check for cases like ['a'] earlier, as you suggested. These changes
should eliminate some unnecessary comparisons and allocate then
immediately free.

I also want to point out that we should not recommend users mix the
standard simplified accessor syntax with the pre-standard jsonb
subscripting, because:

1. It is confusing. I would assume that a user who uses dot-notation
understands its definition. As we discussed earlier, (jb).a and
(jb)['a'] can return different results: the former is in "lax" mode
and with a conditional array wrapper, while the latter has neither. If
a user queries something like (jb)['a'].b['c'], I think most likely
she wants either (jb)['a']['b']['c'] or (jb).a.b.c.

2. As you said, it hurts performance. When a pre-standard non-integer
subscript appears before a dot-notation, we bail out of creating a
jsonpath. Alternatively, if a user really wants  (jb)['a'].b['c'], it
could be explicitly written as (((jb)['a']).b)['c']. This avoids
unnecessary looping and bailout.

In my earlier revisions (up to v12), I actually emitted warnings when
users mixed syntaxes. Starting from v13, I removed the warning based
on Jian’s feedback. Here's the context:

On Wed, Aug 20, 2025 at 9:53 PM Alexandra Wang <[email protected]>
wrote:

> On Thu, Jul 10, 2025 at 1:54 AM jian he <[email protected]>
> wrote:
>
>> after applied v12-0001 to v12-0006
>> + /* emit warning conditionally to minimize duplicate warnings */
>> + if (list_length(*indirection) > 0)
>> + ereport(WARNING,
>> + errcode(ERRCODE_WARNING),
>> + errmsg("mixed usage of jsonb simplified accessor syntax and jsonb
>> subscripting."),
>> + errhint("use dot-notation for member access, or use non-null integer
>> constants subscripting for array access."),
>> + parser_errposition(pstate, warning_location));
>>
>> src7=# select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['1'::int8];
>> WARNING:  mixed usage of jsonb simplified accessor syntax and jsonb
>> subscripting.
>> LINE 1: ...t ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['1'::int8]...
>>                                                              ^
>> HINT:  use dot-notation for member access, or use non-null integer
>> constants subscripting for array access.
>> ERROR:  subscript type bigint is not supported
>> LINE 1: ...t ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['1'::int8]...
>>                                                              ^
>> HINT:  jsonb subscript must be coercible to either integer or text.
>>
>> The above example looks very bad. location printed twice, hint message
>> is different.
>> two messages level (ERROR, WARNING).
>> ... omitting some irelavent comments...
>
>

I see your confusion about the WARNING/ERROR messages. My intent was
> to encourage users to use consistent syntax flavors rather than mixing
> the SQL standard JSON simplified accessor with the pre-standard
> PostgreSQL jsonb subscripting. In the meantime, I want to support
> mixed syntax flavors as much as possible. However, your feedback makes
> it clear that users don’t need that level of details. So, in v13 I’ve
> removed the WARNING message and instead added a comment explaining how
> we support mixed syntaxes.
>
> Here's the relevant diff between v12 and v13, hope it helps:
>
> --- a/src/backend/utils/adt/jsonbsubs.c
> +++ b/src/backend/utils/adt/jsonbsubs.c
> @@ -247,7 +247,6 @@ jsonb_subscript_make_jsonpath(ParseState *pstate, List
> **indirection, Subscripti
>         ListCell   *lc;
>         Datum           jsp;
>         int                     pathlen = 0;
> -       int                     warning_location = -1;
>
>         sbsref->refupperindexpr = NIL;
>         sbsref->reflowerindexpr = NIL;
> @@ -311,10 +310,27 @@ jsonb_subscript_make_jsonpath(ParseState *pstate,
> List **indirection, Subscripti
>                                 if (jpi_from == NULL)
>                                 {
>                                         /*
> -                                        * postpone emitting the warning
> until the end of this
> -                                        * function
> +                                        * Break out of the loop if the
> subscript is not a
> +                                        * non-null integer constant, so
> that we can fall back to
> +                                        * jsonb subscripting logic.
> +                                        *
> +                                        * This is needed to handle cases
> with mixed usage of SQL
> +                                        * standard json simplified
> accessor syntax and PostgreSQL
> +                                        * jsonb subscripting syntax, e.g:
> +                                        *
> +                                        * select (jb).a['b'].c from
> jsonb_table;
> +                                        *
> +                                        * where dot-notation (.a and .c)
> is the SQL standard json
> +                                        * simplified accessor syntax, and
> the ['b'] subscript is
> +                                        * the PostgreSQL jsonb
> subscripting syntax, because 'b'
> +                                        * is not a non-null constant
> integer and cannot be used
> +                                        * for json array access.
> +                                        *
> +                                        * In this case, we cannot create
> a JsonPath item, so we
> +                                        * break out of the loop and let
> +                                        * jsonb_subscript_transform()
> handle this indirection as
> +                                        * a PostgreSQL jsonb subscript.
>                                          */
> -                                       warning_location =
> exprLocation(ai->uidx);
>                                         pfree(jpi->value.array.elems);
>                                         pfree(jpi);
>                                         break;
> @@ -360,14 +376,6 @@ jsonb_subscript_make_jsonpath(ParseState *pstate,
> List **indirection, Subscripti
>
>         *indirection = list_delete_first_n(*indirection, pathlen);
>
> -       /* emit warning conditionally to minimize duplicate warnings */
> -       if (list_length(*indirection) > 0)
> -               ereport(WARNING,
> -                               errcode(ERRCODE_WARNING),
> -                               errmsg("mixed usage of jsonb simplified
> accessor syntax and jsonb subscripting."),
> -                               errhint("use dot-notation for member
> access, or use non-null integer constants subscripting for array access."),
> -                               parser_errposition(pstate,
> warning_location));
> -
>         jsp = jsonPathFromParseResult(&jpres, 0, NULL);
>

I think we should definitely add detailed documentation about mixed
syntax. However, I'm not convinced it's worth putting more effort into
optimizing performance for those cases. Alternatively, we could fail
outright in those cases, but I don't think that would be the best user
experience. Thoughts?

 On Mon, Sep 1, 2025 at 8:32 PM Chao Li <[email protected]> wrote:

> The other comment is that jsonb_subscript_make_jsonpath() can be optimized
> a little bit. When indices and dot notations are mixed, it will always hit
> the clause “if (api_from == NULL)”. In this case, memory of jpi has been
> allocated, and with the “if” we need to free the memory. So, we can pull up
> calculating jpi_from, if it is NULL, then we don’t need to allocate and
> free memory. My dirty change is like:
>
> ```
>                 else if (IsA(accessor, A_Indices))
>                 {
> +                       JsonPathParseItem *jpi_from = NULL;
>                         A_Indices  *ai = castNode(A_Indices, accessor);
>
> +                       if (!ai->is_slice)
> +                       {
> +                               Assert(ai->uidx && !ai->lidx);
> +                               jpi_from = make_jsonpath_item_expr(pstate,
> ai->uidx, &sbsref->refupperindexpr, true);
> +                               if (jpi_from == NULL)
> +                               {
> +                                       /*
> +                                        * Break out of the loop if the
> subscript is not a
> +                                        * non-null integer constant, so
> that we can fall back to
> +                                        * jsonb subscripting logic.
> +                                        *
> +                                        * This is needed to handle cases
> with mixed usage of SQL
> +                                        * standard json simplified
> accessor syntax and PostgreSQL
> +                                        * jsonb subscripting syntax, e.g:
> +                                        *
> +                                        * select (jb).a['b'].c from
> jsonb_table;
> +                                        *
> +                                        * where dot-notation (.a and .c)
> is the SQL standard json
> +                                        * simplified accessor syntax, and
> the ['b'] subscript is
> +                                        * the PostgreSQL jsonb
> subscripting syntax, because 'b'
> +                                        * is not a non-null constant
> integer and cannot be used
> +                                        * for json array access.
> +                                        *
> +                                        * In this case, we cannot create
> a JsonPath item, so we
> +                                        * break out of the loop and let
> +                                        * jsonb_subscript_transform()
> handle this indirection as
> +                                        * a PostgreSQL jsonb subscript.
> +                                        */
> +                                       break;
> +                               }
> +                       }
> +
>                         jpi = make_jsonpath_item(jpiIndexArray);
>                         jpi->value.array.nelems = 1;
>                         jpi->value.array.elems =
> palloc(sizeof(jpi->value.array.elems[0]));
> @@ -303,12 +337,12 @@ jsonb_subscript_make_jsonpath(ParseState *pstate,
> List **indirection, Subscripti
>                         }
>                         else
>                         {
> -                               JsonPathParseItem *jpi_from = NULL;
> +                               //JsonPathParseItem *jpi_from = NULL;
>
> -                               Assert(ai->uidx && !ai->lidx);
> -                               jpi_from = make_jsonpath_item_expr(pstate,
> ai->uidx, &sbsref->refupperindexpr, true);
> -                               if (jpi_from == NULL)
> -                               {
> +                               //Assert(ai->uidx && !ai->lidx);
> +                               //jpi_from =
> make_jsonpath_item_expr(pstate, ai->uidx, &sbsref->refupperindexpr, true);
> +                               //if (jpi_from == NULL)
> +                               //{
>                                         /*
>                                          * Break out of the loop if the
> subscript is not a
>                                          * non-null integer constant, so
> that we can fall back to
> @@ -331,10 +365,10 @@ jsonb_subscript_make_jsonpath(ParseState *pstate,
> List **indirection, Subscripti
>                                          * jsonb_subscript_transform()
> handle this indirection as
>                                          * a PostgreSQL jsonb subscript.
>                                          */
> -                                       pfree(jpi->value.array.elems);
> -                                       pfree(jpi);
> -                                       break;
> -                               }
> +                               //      pfree(jpi->value.array.elems);
> +                               //      pfree(jpi);
> +                               //      break;
> +                               //}
>
>                                 jpi->value.array.elems[0].from = jpi_from;
>                                 jpi->value.array.elems[0].to = NULL;
> ```
>
> With this change, “make check” also passed.
>

Fixed. Thank you!

 On Mon, Sep 1, 2025 at 8:32 PM Chao Li <[email protected]> wrote:

> The third comment is about test cases. I only see tests for indices
> following dot notation, like data.a[‘b’], but not the reverse. Let’s add a
> few test cases for dot notation following indices, like data[‘a’].b.
>

I believe both cases are already covered. Please take a look at
src/test/regress/jsonb.sql; there's a "-- mixed syntax" section.

Best,
Alex


^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-06-24 13:29                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-07-09 08:01                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-07-10 08:53                                 ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-21 04:53                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-22 08:19                                     ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-22 19:33                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-25 08:09                                         ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-26 03:52                                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-29 07:22                                             ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-02 02:59                                               ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-02 03:32                                                 ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-03 02:20                                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
@ 2025-09-03 06:56                                                     ` Chao Li <[email protected]>
  2025-09-09 23:53                                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  0 siblings, 1 reply; 67+ messages in thread

From: Chao Li @ 2025-09-03 06:56 UTC (permalink / raw)
  To: Alexandra Wang <[email protected]>; +Cc: Nikita Glukhov <[email protected]>; jian he <[email protected]>; Nikita Malakhov <[email protected]>; Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>



> On Sep 3, 2025, at 10:20, Alexandra Wang <[email protected]> wrote:
> 
> This change would give an incorrect result for an accessor like
> "[0].a" when the jsonb column data is a jsonb object instead of a
> jsonb array.  I've added two new test cases to cover this scenario:
> 
> In jsonb.sql, the newly added tests are:
> select (jb)[0].a from test_jsonb_dot_notation; -- returns same result as (jb).a
> select (jb)[1].a from test_jsonb_dot_notation; -- returns NULL


I think the latest patch is still wrong. Ultimately, dot notation “.a", index “[0] and slice "[1:4]” rely on jsonpath, and subscript [“a”] relies on the rest logic in jsonb_subscript_transform() in “foreach”.

Now “jsonb_check_jsonpath_needed()” checks only dot nation and slice, but not checking index case. So that reason why your case “select (jb)[0].a” works is because the second indirection is a dot nation. However, “select (jb)[0][‘a’]” will not work. See my test:

```
evantest=# select ('{"name": "Alice", "age": 30}'::jsonb)[0].name;
  name
---------
 "Alice"
(1 row)

evantest=# select ('{"name": "Alice", "age": 30}'::jsonb)[0]['name'];
 jsonb
-------

(1 row)
```

In my test, (jsonb)[0][’name’] should also return “Alice”.

So, end up, “jsonb_check_jsonpath_needed()” only does incomplete and inaccurate checks, only jsonb_subscript_make_jsonpath() can make an accurate decision and return a null jsonpath upon subscript “[‘a’]”.

As a result, “json_check_jsonpath_needed()” feels not needed at all. In jsonb_subscript_transform(), just go ahead to run jsonb_subscript_make_jsonpath() first, if returned jsonpath is NULL, then run rest of logic.

With my dirty change of removing json_check_jsonpath_needed:
```
chaol@ChaodeMacBook-Air postgresql % git diff
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index 374040b3b4e..d9faab5c799 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -416,12 +416,12 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
        sbsref->refrestype = JSONBOID;
        sbsref->reftypmod = -1;

-       if (jsonb_check_jsonpath_needed(*indirection, isSlice))
-       {
+       //if (jsonb_check_jsonpath_needed(*indirection, isSlice))
+       //{
                jsonb_subscript_make_jsonpath(pstate, indirection, sbsref);
                if (sbsref->refjsonbpath)
                        return;
-       }
+       //}

        /*
         * We reach here only in two cases: (a) the JSON simplified accessor is
```

You can see:
```
evantest=# select ('{"name": "Alice", "age": 30}'::jsonb)[0]['name'];
  jsonb
---------
 "Alice"
(1 row)

evantest=# select ('{"name": "Alice", "age": 30}'::jsonb)[0].name;
  name
---------
 "Alice"
(1 row)
```

Then I found “make check” failed. For example the first broken test case:

```
@@ -4998,7 +4998,7 @@
 select ('123'::jsonb)[0];
  jsonb
 -------
-
+ 123
 (1 row)
```

The test expected an empty result, which implies “strict” mode.

But the problem is, which mode should be the default? JSON_QUERY() uses “lax” as default mode. And from Peter Eisentraut’s blog: https://peter.eisentraut.org/blog/2023/04/04/sql-2023-is-finished-here-is-whats-new

```
The semantics of this are defined in terms of JSON_QUERY and JSON_VALUE constructions (which have been available since SQL:2016), so this really just syntactic sugar.
```

Also feels like “lax” should be the default mode. If that is true, then my dirty change of removing “json_check_jsonpath_need()” works properly.

The current logic with this patch sounds strange. Because “json_check_jsonpath_need()” iterate through unprocessed indirections to decide if jsonpath is needed (lax mode). With this logic:

1) if index [0] directly following dot notation, like (data).a[0], it’s lax mode
2) if index [0] directly following subscript [‘a’], like (data)[‘a’][0], it’s strict mode
3) if index [0] directly following the data column, then if there is a dot nation in indirection list, use lax mode, otherwise strict mode. For the failed test case, as there is no more indirection following [0], so it expected strict mode.

I wonder where this behavior is defined?

With my change, 1) and 2) are the same, for 3), if index [0] directly following the data column, regardless what indirections are followed, it’s by default lax mode.

So, I think this is a design decision. Maybe I missed something from your previous design, but I don’t find anything about that from the commit comments. I feel this would be better aligned with 1) and 2).

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/






^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-06-24 13:29                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-07-09 08:01                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-07-10 08:53                                 ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-21 04:53                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-22 08:19                                     ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-22 19:33                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-25 08:09                                         ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-26 03:52                                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-29 07:22                                             ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-02 02:59                                               ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-02 03:32                                                 ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-03 02:20                                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-09-03 06:56                                                     ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
@ 2025-09-09 23:53                                                       ` Alexandra Wang <[email protected]>
  2025-09-10 04:03                                                         ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  0 siblings, 1 reply; 67+ messages in thread

From: Alexandra Wang @ 2025-09-09 23:53 UTC (permalink / raw)
  To: Chao Li <[email protected]>; +Cc: Nikita Glukhov <[email protected]>; jian he <[email protected]>; Nikita Malakhov <[email protected]>; Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>

Hi Chao,

On Tue, Sep 2, 2025 at 11:57 PM Chao Li <[email protected]> wrote:

> On Sep 3, 2025, at 10:20, Alexandra Wang <[email protected]>
> wrote:
>
> This change would give an incorrect result for an accessor like
> "[0].a" when the jsonb column data is a jsonb object instead of a
> jsonb array.  I've added two new test cases to cover this scenario:
>
> In jsonb.sql, the newly added tests are:
> select (jb)[0].a from test_jsonb_dot_notation; -- returns same result as
> (jb).a
> select (jb)[1].a from test_jsonb_dot_notation; -- returns NULL
>
>
> I think the latest patch is still wrong. Ultimately, dot notation “.a",
> index “[0] and slice "[1:4]” rely on jsonpath, and subscript [“a”] relies
> on the rest logic in jsonb_subscript_transform() in “foreach”.
>
> Now “jsonb_check_jsonpath_needed()” checks only dot nation and slice, but
> not checking index case. So that reason why your case “select (jb)[0].a”
> works is because the second indirection is a dot nation. However, “select
> (jb)[0][‘a’]” will not work. See my test:
>
> ```
> evantest=# select ('{"name": "Alice", "age": 30}'::jsonb)[0].name;
>   name
> ---------
>  "Alice"
> (1 row)
>
> evantest=# select ('{"name": "Alice", "age": 30}'::jsonb)[0]['name'];
>  jsonb
> -------
>
> (1 row)
> ```
>
> In my test, (jsonb)[0][’name’] should also return “Alice”.
>
> So, end up, “jsonb_check_jsonpath_needed()” only does incomplete and
> inaccurate checks, only jsonb_subscript_make_jsonpath() can make an
> accurate decision and return a null jsonpath upon subscript “[‘a’]”.
>
> As a result, “json_check_jsonpath_needed()” feels not needed at all. In
> jsonb_subscript_transform(), just go ahead to run
> jsonb_subscript_make_jsonpath() first, if returned jsonpath is NULL, then
> run rest of logic.
>
> With my dirty change of removing json_check_jsonpath_needed:
> ```
> chaol@ChaodeMacBook-Air postgresql % git diff
> diff --git a/src/backend/utils/adt/jsonbsubs.c
> b/src/backend/utils/adt/jsonbsubs.c
> index 374040b3b4e..d9faab5c799 100644
> --- a/src/backend/utils/adt/jsonbsubs.c
> +++ b/src/backend/utils/adt/jsonbsubs.c
> @@ -416,12 +416,12 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
>         sbsref->refrestype = JSONBOID;
>         sbsref->reftypmod = -1;
>
> -       if (jsonb_check_jsonpath_needed(*indirection, isSlice))
> -       {
> +       //if (jsonb_check_jsonpath_needed(*indirection, isSlice))
> +       //{
>                 jsonb_subscript_make_jsonpath(pstate, indirection, sbsref);
>                 if (sbsref->refjsonbpath)
>                         return;
> -       }
> +       //}
>
>         /*
>          * We reach here only in two cases: (a) the JSON simplified
> accessor is
> ```
>
> You can see:
> ```
> evantest=# select ('{"name": "Alice", "age": 30}'::jsonb)[0]['name'];
>   jsonb
> ---------
>  "Alice"
> (1 row)
>
> evantest=# select ('{"name": "Alice", "age": 30}'::jsonb)[0].name;
>   name
> ---------
>  "Alice"
> (1 row)
> ```
>
> Then I found “make check” failed. For example the first broken test case:
>
> ```
> @@ -4998,7 +4998,7 @@
>  select ('123'::jsonb)[0];
>   jsonb
>  -------
> -
> + 123
>  (1 row)
> ```
>
> The test expected an empty result, which implies “strict” mode.
>
> But the problem is, which mode should be the default? JSON_QUERY() uses
> “lax” as default mode. And from Peter Eisentraut’s blog:
> https://peter.eisentraut.org/blog/2023/04/04/sql-2023-is-finished-here-is-whats-new
>
> ```
> The semantics of this are defined in terms of JSON_QUERY and JSON_VALUE constructions
> (which have been available since SQL:2016), so this really just syntactic
> sugar.
> ```
>
> Also feels like “lax” should be the default mode. If that is true, then my
> dirty change of removing “json_check_jsonpath_need()” works properly.
>
> The current logic with this patch sounds strange. Because
> “json_check_jsonpath_need()” iterate through unprocessed indirections to
> decide if jsonpath is needed (lax mode). With this logic:
>
> 1) if index [0] directly following dot notation, like (data).a[0], it’s
> lax mode
> 2) if index [0] directly following subscript [‘a’], like (data)[‘a’][0],
> it’s strict mode
> 3) if index [0] directly following the data column, then if there is a dot
> nation in indirection list, use lax mode, otherwise strict mode. For the
> failed test case, as there is no more indirection following [0], so it
> expected strict mode.
>
> I wonder where this behavior is defined?
>
> With my change, 1) and 2) are the same, for 3), if index [0] directly
> following the data column, regardless what indirections are followed, it’s
> by default lax mode.
>
> So, I think this is a design decision. Maybe I missed something from your
> previous design, but I don’t find anything about that from the commit
> comments. I feel this would be better aligned with 1) and 2).
>

Thanks for investigating this, and for the great questions!

Here’s a bit of background. The pre-standard jsonb subscripting
feature [1] was introduced in Postgres 14. Specifically, support for
queries like (jb)[0] and (jb)[0]['a'] was added by this commit:

commit 676887a3b0b8e3c0348ac3f82ab0d16e9a24bd43 (HEAD)
Author: Alexander Korotkov <[email protected]>
Date:   Sun Jan 31 23:50:40 2021 +0300

    Implementation of subscripting for jsonb

Without my patch, from version 14 through the current master branch,
all supported versions of Postgres return the following results:

test=# select ('123'::jsonb)[0];
 jsonb
-------

(1 row)

test=# select ('{"name": "Alice", "age": 30}'::jsonb)[0];
 jsonb
-------

(1 row)

test=# select ('{"name": "Alice", "age": 30}'::jsonb)[0]['age'];
 jsonb
-------

(1 row)

The simplified accessor syntax defined in the SQL standard includes
dot-notation access as well as integer-based subscripts. It does not
include text-based subscripts such as ['a']. Therefore, the only
overlap between the pre-standard jsonb subscripting and the standard
simplified accessor is non-slicing integer-based subscripts. (Since
the pre-standard jsonb subscripting never supported slicing, we don’t
need to consider that case either when comparing the two syntaxes.)

When we compare the two syntaxes for non-slicing integer-based
subscripts, the only discrepancy is that for a jsonb object jb,
(jb)[0] in the pre-standard syntax returns NULL, while (jb)[0] in the
standard syntax returns (jb) itself. As long as the integer subscript
is non-zero, both syntaxes return the same results.

I think this (jb)[0] case is rather trivial. However, since it's been
behaving the pre-standard way since PG14, I hesitate to change the
existing behavior as part of my patches, and I feel it could be a
bikeshedding kind of discussion in a separate thread.

The main goal of my patch is to add dot-notation. For now, my
intention is for cases like (jb)[0].a to work in the standard way,
because if a user chooses dot-notation instead of text-based
subscripting, they are likely expecting the standard behavior.

Thoughts?

[1]
https://www.postgresql.org/docs/current/datatype-json.html#JSONB-SUBSCRIPTING


^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-06-24 13:29                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-07-09 08:01                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-07-10 08:53                                 ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-21 04:53                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-22 08:19                                     ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-22 19:33                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-25 08:09                                         ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-26 03:52                                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-29 07:22                                             ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-02 02:59                                               ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-02 03:32                                                 ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-03 02:20                                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-09-03 06:56                                                     ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-09 23:53                                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
@ 2025-09-10 04:03                                                         ` Chao Li <[email protected]>
  2025-09-10 23:56                                                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  0 siblings, 1 reply; 67+ messages in thread

From: Chao Li @ 2025-09-10 04:03 UTC (permalink / raw)
  To: Alexandra Wang <[email protected]>; +Cc: Nikita Glukhov <[email protected]>; jian he <[email protected]>; Nikita Malakhov <[email protected]>; Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>



> On Sep 10, 2025, at 07:53, Alexandra Wang <[email protected]> wrote:
> 
> I think this (jb)[0] case is rather trivial. However, since it's been
> behaving the pre-standard way since PG14, I hesitate to change the
> existing behavior as part of my patches, and I feel it could be a
> bikeshedding kind of discussion in a separate thread. 


I am fine to retain the PG14 behavior. But the confusion part is:

```
evantest=# select ('{"name": "Alice", "birthday": {"year": 2000, "month": 8}}'::jsonb)[0]['birthday']['year'];
 jsonb
-------

(1 row)

evantest=# select ('{"name": "Alice", "birthday": {"year": 2000, "month": 8}}'::jsonb)[0]['birthday'].year;
 year
------
 2000
(1 row)
```

As long as the expression containing a dot notation, “[0]” behaves differently from PG14.

I agree we should not recommend the mixed usages of `[`a`]` and `.a`, but we haven’t prevented from that. Given they are different meanings, `[‘a’]`  implies strict mode and `.a` implies lax mode, for a complex JSON object, users may intentionally mix use both, which sounds reasonable.

I don’t see you have updated any documentation yet. I don’t want to block you because of this issue. As long as you state the behavior clearly in the document, I am happy to let it go.

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/






^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-06-24 13:29                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-07-09 08:01                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-07-10 08:53                                 ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-21 04:53                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-22 08:19                                     ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-22 19:33                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-25 08:09                                         ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-26 03:52                                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-29 07:22                                             ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-02 02:59                                               ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-02 03:32                                                 ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-03 02:20                                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-09-03 06:56                                                     ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-09 23:53                                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-09-10 04:03                                                         ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
@ 2025-09-10 23:56                                                           ` Alexandra Wang <[email protected]>
  2025-09-15 18:32                                                             ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  0 siblings, 1 reply; 67+ messages in thread

From: Alexandra Wang @ 2025-09-10 23:56 UTC (permalink / raw)
  To: Chao Li <[email protected]>; +Cc: Nikita Glukhov <[email protected]>; jian he <[email protected]>; Nikita Malakhov <[email protected]>; Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>

Hi Chao,

On Tue, Sep 9, 2025 at 9:03 PM Chao Li <[email protected]> wrote:

> I don’t see you have updated any documentation yet. I don’t want to block
> you because of this issue. As long as you state the behavior clearly in the
> document, I am happy to let it go.
>

Thanks for the reminder for documentation! I've attached v18. v18 adds
documentation in 0005. I've also updated the commit messages for 0003,
0004, and 0007 to include reviewers' names.

For everyone: my immediate goal is to move 0001 - 0005 forward, as I
feel more confident about them now. In the meantime, I'd also
appreciate code review for 0006 - 0007.

Best,
Alex


Attachments:

  [application/octet-stream] v18-0006-Implement-Jsonb-subscripting-with-slicing.patch (21.5K, ../../CAK98qZ0v5dkgMZNK-ogC8Cio-6OPSnNVM-4BWa=Jh=JnvZpB-Q@mail.gmail.com/3-v18-0006-Implement-Jsonb-subscripting-with-slicing.patch)
  download | inline diff:
From 73833bd1c6cf2935a6727512ea3a03906c6fade5 Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v18 6/7] Implement Jsonb subscripting with slicing

Previously, slicing was not supported for jsonb subscripting. This commit
implements subscripting with slicing as part of the JSON simplified accessor
syntax specified in SQL:2023.

Co-authored-by: Nikita Glukhov <[email protected]>
Co-authored-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Mark Dilger <[email protected]>
Reviewed-by: Jian He <[email protected]>
Reviewed-by: Vik Fearing <[email protected]>
Reviewed-by: Nikita Malakhov <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Tested-by: Mark Dilger <[email protected]>
Tested-by: Jian He <[email protected]>
---
 src/backend/utils/adt/jsonbsubs.c             | 119 ++++++++------
 .../ecpg/test/expected/sql-sqljson.c          |  16 +-
 .../ecpg/test/expected/sql-sqljson.stderr     |  26 ++--
 .../ecpg/test/expected/sql-sqljson.stdout     |   3 +
 src/interfaces/ecpg/test/sql/sqljson.pgc      |   7 +-
 src/test/regress/expected/jsonb.out           | 145 ++++++++++++++++--
 src/test/regress/sql/jsonb.sql                |  53 ++++++-
 7 files changed, 286 insertions(+), 83 deletions(-)

diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index ccc5f3c6e94..4762f6bd11c 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -111,7 +111,7 @@ coerce_jsonpath_subscript_to_int4_or_text(ParseState *pstate, Node *subExpr)
  *
  * JsonPath is needed if the indirection list includes:
  * - String-based access (dot notation)
- * - Slice-based subscripting (when isSlice is true)
+ * - Slice-based subscripting
  *
  * Otherwise, simple jsonb subscripting is enough.
  */
@@ -127,7 +127,11 @@ jsonb_check_jsonpath_needed(List *indirection)
 		if (IsA(accessor, String))
 			return true;
 		else
+		{
 			Assert(IsA(accessor, A_Indices));
+			if (castNode(A_Indices, accessor)->is_slice)
+				return true;
+		}
 	}
 
 	return false;
@@ -152,20 +156,45 @@ make_jsonpath_item(JsonPathItemType type)
 	return v;
 }
 
+/*
+ * Build a JsonPathParseItem for a constant integer value.
+ *
+ * This function constructs a jpiNumeric item for use in JsonPath,
+ * and appends a matching Const(INT4) node to the given expression list
+ * for use in EXPLAIN, views, etc.
+ *
+ * Parameters:
+ * - val: integer constant value
+ * - exprs: list of expression nodes (updated in place)
+ */
+static JsonPathParseItem *
+make_jsonpath_item_int(int32 val, List **exprs)
+{
+	JsonPathParseItem *jpi = make_jsonpath_item(jpiNumeric);
+
+	jpi->value.numeric =
+		DatumGetNumeric(DirectFunctionCall1(int4_numeric, Int32GetDatum(val)));
+
+	*exprs = lappend(*exprs, makeConst(INT4OID, -1, InvalidOid, 4,
+									   Int32GetDatum(val), false, true));
+
+	return jpi;
+}
+
 /*
  * Convert a constant integer expression into a JsonPathParseItem.
  *
  * The input expression must be a non-null constant of type INT4. Returns NULL otherwise.
- * This function constructs a jpiNumeric item for use in JsonPath and appends a matching
- * Const(INT4) node to the given expression list for use in EXPLAIN, views, etc.
+ * The function extracts the value and delegates it to make_jsonpath_item_int().
  *
  * Parameters:
  * - pstate: parse state context
  * - expr: input expression node
  * - exprs: list of expression nodes (updated in place)
+ * - no_error: returns NULL when the data type doesn't match. Otherwise, emits an ERROR.
  */
 static JsonPathParseItem *
-make_jsonpath_item_expr(ParseState *pstate, Node *expr, List **exprs)
+make_jsonpath_item_expr(ParseState *pstate, Node *expr, List **exprs, bool no_error)
 {
 	Const	   *cnst;
 
@@ -175,20 +204,17 @@ make_jsonpath_item_expr(ParseState *pstate, Node *expr, List **exprs)
 	{
 		cnst = (Const *) expr;
 		if (cnst->consttype == INT4OID && !(cnst->constisnull))
-		{
-			JsonPathParseItem *jpi = make_jsonpath_item(jpiNumeric);
-
-			jpi->value.numeric =
-				DatumGetNumeric(DirectFunctionCall1(int4_numeric, Int32GetDatum(cnst->constvalue)));
-
-			*exprs = lappend(*exprs, makeConst(INT4OID, -1, InvalidOid, 4,
-											   Int32GetDatum(cnst->constvalue), false, true));
-
-			return jpi;
-		}
+			return make_jsonpath_item_int(DatumGetInt32(cnst->constvalue), exprs);
 	}
 
-	return NULL;
+	if (no_error)
+		return NULL;
+	else
+		ereport(ERROR,
+				errcode(ERRCODE_DATATYPE_MISMATCH),
+				errmsg("only non-null integer constants are supported for jsonb simplified accessor subscripting"),
+				errhint("use int data type for subscripting with slicing."),
+				parser_errposition(pstate, exprLocation(expr)));
 }
 
 /*
@@ -251,13 +277,12 @@ jsonb_subscript_make_jsonpath(ParseState *pstate, List **indirection, Subscripti
 		else if (IsA(accessor, A_Indices))
 		{
 			A_Indices  *ai = castNode(A_Indices, accessor);
+			JsonPathParseItem *jpi_from = NULL;
 
 			if (!ai->is_slice)
 			{
-				JsonPathParseItem *jpi_from = NULL;
-
 				Assert(ai->uidx && !ai->lidx);
-				jpi_from = make_jsonpath_item_expr(pstate, ai->uidx, &sbsref->refupperindexpr);
+				jpi_from = make_jsonpath_item_expr(pstate, ai->uidx, &sbsref->refupperindexpr, true);
 				if (jpi_from == NULL)
 				{
 					/*
@@ -284,22 +309,34 @@ jsonb_subscript_make_jsonpath(ParseState *pstate, List **indirection, Subscripti
 					 */
 					break;
 				}
+			}
 
-				jpi = make_jsonpath_item(jpiIndexArray);
-				jpi->value.array.nelems = 1;
-				jpi->value.array.elems = palloc(sizeof(jpi->value.array.elems[0]));
+			jpi = make_jsonpath_item(jpiIndexArray);
+			jpi->value.array.nelems = 1;
+			jpi->value.array.elems = palloc(sizeof(jpi->value.array.elems[0]));
 
+			if (!ai->is_slice)
+			{
 				jpi->value.array.elems[0].from = jpi_from;
 				jpi->value.array.elems[0].to = NULL;
 			}
 			else
 			{
-				Node	   *expr = ai->uidx ? ai->uidx : ai->lidx;
+				while (list_length(sbsref->reflowerindexpr) < list_length(sbsref->refupperindexpr))
+					sbsref->reflowerindexpr = lappend(sbsref->reflowerindexpr, NULL);
+
+				if (ai->lidx)
+					jpi->value.array.elems[0].from = make_jsonpath_item_expr(pstate, ai->lidx, &sbsref->reflowerindexpr, false);
+				else
+					jpi->value.array.elems[0].from = make_jsonpath_item_int(0, &sbsref->reflowerindexpr);
 
-				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("jsonb subscript does not support slices"),
-						 parser_errposition(pstate, exprLocation(expr))));
+				if (ai->uidx)
+					jpi->value.array.elems[0].to = make_jsonpath_item_expr(pstate, ai->uidx, &sbsref->refupperindexpr, false);
+				else
+				{
+					jpi->value.array.elems[0].to = make_jsonpath_item(jpiLast);
+					sbsref->refupperindexpr = lappend(sbsref->refupperindexpr, NULL);
+				}
 			}
 		}
 		else
@@ -381,32 +418,12 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 		ai = lfirst_node(A_Indices, idx);
 
 		if (ai->is_slice)
-		{
-			Node	   *expr = ai->uidx ? ai->uidx : ai->lidx;
+			break;
 
-			ereport(ERROR,
-					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("jsonb subscript does not support slices"),
-					 parser_errposition(pstate, exprLocation(expr))));
-		}
+		Assert(!ai->lidx && ai->uidx);
 
-		if (ai->uidx)
-		{
-			subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
-			subExpr = coerce_jsonpath_subscript_to_int4_or_text(pstate, subExpr);
-		}
-		else
-		{
-			/*
-			 * Slice with omitted upper bound. Should not happen as we already
-			 * errored out on slice earlier, but handle this just in case.
-			 */
-			Assert(ai->is_slice);
-			ereport(ERROR,
-					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("jsonb subscript does not support slices"),
-					 parser_errposition(pstate, exprLocation(ai->uidx))));
-		}
+		subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
+		subExpr = coerce_jsonpath_subscript_to_int4_or_text(pstate, subExpr);
 
 		upperIndexpr = lappend(upperIndexpr, subExpr);
 	}
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.c b/src/interfaces/ecpg/test/expected/sql-sqljson.c
index e6a7ece6dab..935b47a3b9a 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.c
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.c
@@ -505,7 +505,7 @@ if (sqlca.sqlcode < 0) sqlprint();}
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 142 "sqljson.pgc"
 
-	// error
+	printf("Found json=%s\n", json);
 
 	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . * )", ECPGt_EOIT, 
 	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
@@ -525,14 +525,24 @@ if (sqlca.sqlcode < 0) sqlprint();}
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 148 "sqljson.pgc"
 
-	// error
+	printf("Found json=%s\n", json);
 
-  { ECPGdisconnect(__LINE__, "CURRENT");
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ : ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 151 "sqljson.pgc"
 
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 151 "sqljson.pgc"
 
+	printf("Found json=%s\n", json);
+
+  { ECPGdisconnect(__LINE__, "CURRENT");
+#line 154 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 154 "sqljson.pgc"
+
 
   return 0;
 }
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
index 19f8c58af06..f3f899c6d87 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
@@ -339,13 +339,10 @@ SQL error: schema "jsonb" does not exist on line 121
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 142: using PQexec
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_check_PQresult on line 142: bad response - ERROR:  jsonb subscript does not support slices
-LINE 1: ..., {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] )
-                                                                ^
+[NO_PID]: ecpg_process_output on line 142: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 142: RESULT: [12, {"y": 1}] offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: raising sqlstate 42804 (sqlcode -400): jsonb subscript does not support slices on line 142
-[NO_PID]: sqlca: code: -400, state: 42804
-SQL error: jsonb subscript does not support slices on line 142
 [NO_PID]: ecpg_execute on line 145: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . * ); with 0 parameter(s) on connection ecpg1_regression
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 145: using PQexec
@@ -361,12 +358,17 @@ SQL error: row expansion via "*" is not supported here on line 145
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 148: using PQexec
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_check_PQresult on line 148: bad response - ERROR:  jsonb subscript does not support slices
-LINE 1: ...x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )
-                                                                ^
+[NO_PID]: ecpg_process_output on line 148: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 148: RESULT: [{"x": 1}, {"x": [12, {"y": 1}]}] offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 151: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 151: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 151: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 151: RESULT: [{"x": 1}, {"x": [12, {"y": 1}]}] offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: raising sqlstate 42804 (sqlcode -400): jsonb subscript does not support slices on line 148
-[NO_PID]: sqlca: code: -400, state: 42804
-SQL error: jsonb subscript does not support slices on line 148
 [NO_PID]: ecpg_finish: connection ecpg1_regression closed
 [NO_PID]: sqlca: code: 0, state: 00000
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
index 442d36931f1..d01a8457f01 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
@@ -35,3 +35,6 @@ Found json=null
 Found json={"x": 1}
 Found json=[1, [12, {"y": 1}]]
 Found json={"x": 1}
+Found json=[12, {"y": 1}]
+Found json=[{"x": 1}, {"x": [12, {"y": 1}]}]
+Found json=[{"x": 1}, {"x": [12, {"y": 1}]}]
diff --git a/src/interfaces/ecpg/test/sql/sqljson.pgc b/src/interfaces/ecpg/test/sql/sqljson.pgc
index 57a9bff424d..9423d25fd0b 100644
--- a/src/interfaces/ecpg/test/sql/sqljson.pgc
+++ b/src/interfaces/ecpg/test/sql/sqljson.pgc
@@ -140,13 +140,16 @@ EXEC SQL END DECLARE SECTION;
 	printf("Found json=%s\n", json);
 
 	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b[1].x[0:]) INTO :json;
-	// error
+	printf("Found json=%s\n", json);
 
 	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).*) INTO :json;
 	// error
 
 	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb)['b'][0:]) INTO :json;
-	// error
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b[:]) INTO :json;
+	printf("Found json=%s\n", json);
 
   EXEC SQL DISCONNECT;
 
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 37d71b23d5d..2702edc9705 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5247,23 +5247,34 @@ select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b
 (1 row)
 
 select ('{"a": 1}'::jsonb)['a':'b']; -- fails
-ERROR:  jsonb subscript does not support slices
+ERROR:  only non-null integer constants are supported for jsonb simplified accessor subscripting
 LINE 1: select ('{"a": 1}'::jsonb)['a':'b'];
-                                       ^
+                                   ^
+HINT:  use int data type for subscripting with slicing.
 select ('[1, "2", null]'::jsonb)[1:2];
-ERROR:  jsonb subscript does not support slices
-LINE 1: select ('[1, "2", null]'::jsonb)[1:2];
-                                           ^
+    jsonb    
+-------------
+ ["2", null]
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[:2];
-ERROR:  jsonb subscript does not support slices
-LINE 1: select ('[1, "2", null]'::jsonb)[:2];
-                                          ^
+     jsonb      
+----------------
+ [1, "2", null]
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[1:];
-ERROR:  jsonb subscript does not support slices
-LINE 1: select ('[1, "2", null]'::jsonb)[1:];
-                                         ^
+    jsonb    
+-------------
+ ["2", null]
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[:];
-ERROR:  jsonb subscript does not support slices
+     jsonb      
+----------------
+ [1, "2", null]
+(1 row)
+
 create TEMP TABLE test_jsonb_subscript (
        id int,
        test_json jsonb
@@ -6017,8 +6028,42 @@ SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
  
 (1 row)
 
-SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t; -- fails
-ERROR:  jsonb subscript does not support slices
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
+            x             
+--------------------------
+ {"y": "YYY", "z": "ZZZ"}
+(1 row)
+
+SELECT (jb).a[2:3].b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT (jb).a[2:].b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT (jb).a[:2].b FROM test_jsonb_dot_notation;
+  b  
+-----
+ "c"
+(1 row)
+
+SELECT (jb).a[:].b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
+            x             
+--------------------------
+ {"y": "YYY", "z": "ZZZ"}
+(1 row)
+
 SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
 ERROR:  type jsonb is not composite
 -- explains should work
@@ -6054,6 +6099,28 @@ CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_d
 CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
  SELECT (jb).a[3].x.y AS y
    FROM test_jsonb_dot_notation
+CREATE VIEW v2 AS SELECT (jb).a[3:].x.y[:-1] FROM test_jsonb_dot_notation;
+\sv v2
+CREATE OR REPLACE VIEW public.v2 AS
+ SELECT (jb).a[3:].x.y[0:'-1'::integer] AS y
+   FROM test_jsonb_dot_notation
+SELECT * from v2;
+ y 
+---
+ 
+(1 row)
+
+CREATE VIEW v3 AS SELECT (jb).a[:3].x.y[-1:] FROM test_jsonb_dot_notation;
+\sv v3
+CREATE OR REPLACE VIEW public.v3 AS
+ SELECT (jb).a[0:3].x.y['-1'::integer:] AS y
+   FROM test_jsonb_dot_notation
+SELECT * from v3;
+   y   
+-------
+ "yyy"
+(1 row)
+
 -- mixed syntax
 DROP VIEW test_jsonb_dot_notation_v1;
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
@@ -6238,5 +6305,55 @@ SELECT * from test_jsonb_dot_notation_v1;
 (1 row)
 
 DROP VIEW public.test_jsonb_dot_notation_v1;
+-- jsonb array access in plpgsql
+DO $$
+    DECLARE
+        a jsonb := '[1,2,3,4,5,6,7]'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := a[2:];
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+NOTICE:  [1, 2, 3, 4, 5, 6, 7]
+NOTICE:  [3, 4, 5, 6, 7]
+NOTICE:  [5, 6, 7]
+NOTICE:  7
+-- jsonb dot access in plpgsql
+DO $$
+    DECLARE
+        a jsonb := '{"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := COALESCE(a."NU", a[2]); -- fails
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+NOTICE:  {"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}
+ERROR:  missing FROM-clause entry for table "a"
+LINE 1: a := COALESCE(a."NU", a[2])
+                      ^
+QUERY:  a := COALESCE(a."NU", a[2])
+CONTEXT:  PL/pgSQL function inline_code_block line 8 at assignment
+DO $$
+    DECLARE
+        a jsonb := '{"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := COALESCE((a)."NU", a[2]); -- succeeds
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+NOTICE:  {"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}
+NOTICE:  [{"": [[3]]}, [6], [2], "bCi"]
+NOTICE:  [2]
 -- clean up
+DROP VIEW v2;
+DROP VIEW v3;
 DROP TABLE test_jsonb_dot_notation;
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 5fc53e1c9aa..de2c9cda094 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1625,7 +1625,12 @@ SELECT (jb).a[2].b FROM test_jsonb_dot_notation;
 SELECT (jb).a.x.y FROM test_jsonb_dot_notation;
 SELECT (jb).b.x.z FROM test_jsonb_dot_notation;
 SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
-SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t; -- fails
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
+SELECT (jb).a[2:3].b FROM test_jsonb_dot_notation;
+SELECT (jb).a[2:].b FROM test_jsonb_dot_notation;
+SELECT (jb).a[:2].b FROM test_jsonb_dot_notation;
+SELECT (jb).a[:].b FROM test_jsonb_dot_notation;
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
 SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
 
 -- explains should work
@@ -1637,6 +1642,12 @@ SELECT (jb).a[1] FROM test_jsonb_dot_notation;
 -- views should work
 CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
 \sv test_jsonb_dot_notation_v1
+CREATE VIEW v2 AS SELECT (jb).a[3:].x.y[:-1] FROM test_jsonb_dot_notation;
+\sv v2
+SELECT * from v2;
+CREATE VIEW v3 AS SELECT (jb).a[:3].x.y[-1:] FROM test_jsonb_dot_notation;
+\sv v3
+SELECT * from v3;
 
 -- mixed syntax
 DROP VIEW test_jsonb_dot_notation_v1;
@@ -1697,5 +1708,45 @@ SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
 SELECT * from test_jsonb_dot_notation_v1;
 DROP VIEW public.test_jsonb_dot_notation_v1;
 
+-- jsonb array access in plpgsql
+DO $$
+    DECLARE
+        a jsonb := '[1,2,3,4,5,6,7]'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := a[2:];
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+
+-- jsonb dot access in plpgsql
+DO $$
+    DECLARE
+        a jsonb := '{"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := COALESCE(a."NU", a[2]); -- fails
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+
+DO $$
+    DECLARE
+        a jsonb := '{"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := COALESCE((a)."NU", a[2]); -- succeeds
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+
 -- clean up
+DROP VIEW v2;
+DROP VIEW v3;
 DROP TABLE test_jsonb_dot_notation;
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v18-0001-Allow-transformation-of-only-a-sublist-of-subscr.patch (9.0K, ../../CAK98qZ0v5dkgMZNK-ogC8Cio-6OPSnNVM-4BWa=Jh=JnvZpB-Q@mail.gmail.com/4-v18-0001-Allow-transformation-of-only-a-sublist-of-subscr.patch)
  download | inline diff:
From 140b45ed86bc7a4e9498194a777250ec4643fd06 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v18 1/7] Allow transformation of only a sublist of subscripts

This is a preparation step for allowing subscripting containers to
transform only a prefix of an indirection list and modify the list
in-place by removing the processed elements. Currently, all elements
are consumed, and the list is set to NIL after transformation.

In the following commit, subscripting containers will gain the
flexibility to stop transformation when encountering an unsupported
indirection and return the remaining indirections to the caller.

Reviewed-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Jian He <[email protected]>
Reviewed-by: Chao Li <[email protected]>
---
 contrib/hstore/hstore_subs.c      | 10 ++++++----
 src/backend/parser/parse_expr.c   |  9 ++++-----
 src/backend/parser/parse_node.c   |  4 ++--
 src/backend/parser/parse_target.c |  2 +-
 src/backend/utils/adt/arraysubs.c |  6 ++++--
 src/backend/utils/adt/jsonbsubs.c |  6 ++++--
 src/include/nodes/subscripting.h  |  7 ++++++-
 src/include/parser/parse_node.h   |  2 +-
 8 files changed, 28 insertions(+), 18 deletions(-)

diff --git a/contrib/hstore/hstore_subs.c b/contrib/hstore/hstore_subs.c
index 3d03f66fa0d..1b29543ab67 100644
--- a/contrib/hstore/hstore_subs.c
+++ b/contrib/hstore/hstore_subs.c
@@ -40,7 +40,7 @@
  */
 static void
 hstore_subscript_transform(SubscriptingRef *sbsref,
-						   List *indirection,
+						   List **indirection,
 						   ParseState *pstate,
 						   bool isSlice,
 						   bool isAssignment)
@@ -49,15 +49,15 @@ hstore_subscript_transform(SubscriptingRef *sbsref,
 	Node	   *subexpr;
 
 	/* We support only single-subscript, non-slice cases */
-	if (isSlice || list_length(indirection) != 1)
+	if (isSlice || list_length(*indirection) != 1)
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("hstore allows only one subscript"),
 				 parser_errposition(pstate,
-									exprLocation((Node *) indirection))));
+									exprLocation((Node *) *indirection))));
 
 	/* Transform the subscript expression to type text */
-	ai = linitial_node(A_Indices, indirection);
+	ai = linitial_node(A_Indices, *indirection);
 	Assert(ai->uidx != NULL && ai->lidx == NULL && !ai->is_slice);
 
 	subexpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
@@ -81,6 +81,8 @@ hstore_subscript_transform(SubscriptingRef *sbsref,
 	/* Determine the result type of the subscripting operation; always text */
 	sbsref->refrestype = TEXTOID;
 	sbsref->reftypmod = -1;
+
+	*indirection = NIL;
 }
 
 /*
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index e1979a80c19..6e8fd42c612 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -465,14 +465,13 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 			Assert(IsA(n, String));
 
 			/* process subscripts before this field selection */
-			if (subscripts)
+			while (subscripts)
 				result = (Node *) transformContainerSubscripts(pstate,
 															   result,
 															   exprType(result),
 															   exprTypmod(result),
-															   subscripts,
+															   &subscripts,
 															   false);
-			subscripts = NIL;
 
 			newresult = ParseFuncOrColumn(pstate,
 										  list_make1(n),
@@ -487,12 +486,12 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		}
 	}
 	/* process trailing subscripts, if any */
-	if (subscripts)
+	while (subscripts)
 		result = (Node *) transformContainerSubscripts(pstate,
 													   result,
 													   exprType(result),
 													   exprTypmod(result),
-													   subscripts,
+													   &subscripts,
 													   false);
 
 	return result;
diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c
index 203b7a32178..f05baa50a15 100644
--- a/src/backend/parser/parse_node.c
+++ b/src/backend/parser/parse_node.c
@@ -244,7 +244,7 @@ transformContainerSubscripts(ParseState *pstate,
 							 Node *containerBase,
 							 Oid containerType,
 							 int32 containerTypMod,
-							 List *indirection,
+							 List **indirection,
 							 bool isAssignment)
 {
 	SubscriptingRef *sbsref;
@@ -280,7 +280,7 @@ transformContainerSubscripts(ParseState *pstate,
 	 * element.  If any of the items are slice specifiers (lower:upper), then
 	 * the subscript expression means a container slice operation.
 	 */
-	foreach(idx, indirection)
+	foreach(idx, *indirection)
 	{
 		A_Indices  *ai = lfirst_node(A_Indices, idx);
 
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 905c975d83b..a097736229a 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -935,7 +935,7 @@ transformAssignmentSubscripts(ParseState *pstate,
 										  basenode,
 										  containerType,
 										  containerTypMod,
-										  subscripts,
+										  &subscripts,
 										  true);
 
 	typeNeeded = sbsref->refrestype;
diff --git a/src/backend/utils/adt/arraysubs.c b/src/backend/utils/adt/arraysubs.c
index 2940fb8e8d7..234c2c278c1 100644
--- a/src/backend/utils/adt/arraysubs.c
+++ b/src/backend/utils/adt/arraysubs.c
@@ -54,7 +54,7 @@ typedef struct ArraySubWorkspace
  */
 static void
 array_subscript_transform(SubscriptingRef *sbsref,
-						  List *indirection,
+						  List **indirection,
 						  ParseState *pstate,
 						  bool isSlice,
 						  bool isAssignment)
@@ -71,7 +71,7 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	 * indirection items to slices by treating the single subscript as the
 	 * upper bound and supplying an assumed lower bound of 1.
 	 */
-	foreach(idx, indirection)
+	foreach(idx, *indirection)
 	{
 		A_Indices  *ai = lfirst_node(A_Indices, idx);
 		Node	   *subexpr;
@@ -152,6 +152,8 @@ array_subscript_transform(SubscriptingRef *sbsref,
 						list_length(upperIndexpr), MAXDIM)));
 	/* We need not check lowerIndexpr separately */
 
+	*indirection = NIL;
+
 	/*
 	 * Determine the result type of the subscripting operation.  It's the same
 	 * as the array type if we're slicing, else it's the element type.  In
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index e8626d3b4fc..5ead693a3b2 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -41,7 +41,7 @@ typedef struct JsonbSubWorkspace
  */
 static void
 jsonb_subscript_transform(SubscriptingRef *sbsref,
-						  List *indirection,
+						  List **indirection,
 						  ParseState *pstate,
 						  bool isSlice,
 						  bool isAssignment)
@@ -53,7 +53,7 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	 * Transform and convert the subscript expressions. Jsonb subscripting
 	 * does not support slices, look only at the upper index.
 	 */
-	foreach(idx, indirection)
+	foreach(idx, *indirection)
 	{
 		A_Indices  *ai = lfirst_node(A_Indices, idx);
 		Node	   *subExpr;
@@ -159,6 +159,8 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	/* Determine the result type of the subscripting operation; always jsonb */
 	sbsref->refrestype = JSONBOID;
 	sbsref->reftypmod = -1;
+
+	*indirection = NIL;
 }
 
 /*
diff --git a/src/include/nodes/subscripting.h b/src/include/nodes/subscripting.h
index 234e8ad8012..df988a85fad 100644
--- a/src/include/nodes/subscripting.h
+++ b/src/include/nodes/subscripting.h
@@ -71,6 +71,11 @@ struct SubscriptExecSteps;
  * does not care to support slicing, it can just throw an error if isSlice.)
  * See array_subscript_transform() for sample code.
  *
+ * The transform method receives a pointer to a list of raw indirections.
+ * It may parse a sublist (typically the prefix) of these indirections and
+ * modify the original list in place, allowing the caller to handle any
+ * remaining indirections differently or to raise an error as needed.
+ *
  * The transform method is also responsible for identifying the result type
  * of the subscripting operation.  At call, refcontainertype and reftypmod
  * describe the container type (this will be a base type not a domain), and
@@ -93,7 +98,7 @@ struct SubscriptExecSteps;
  * assignment must return.
  */
 typedef void (*SubscriptTransform) (SubscriptingRef *sbsref,
-									List *indirection,
+									List **indirection,
 									struct ParseState *pstate,
 									bool isSlice,
 									bool isAssignment);
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index f7d07c84542..58a4b9df157 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -361,7 +361,7 @@ extern SubscriptingRef *transformContainerSubscripts(ParseState *pstate,
 													 Node *containerBase,
 													 Oid containerType,
 													 int32 containerTypMod,
-													 List *indirection,
+													 List **indirection,
 													 bool isAssignment);
 extern Const *make_const(ParseState *pstate, A_Const *aconst);
 
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v18-0004-Extract-coerce_jsonpath_subscript.patch (5.5K, ../../CAK98qZ0v5dkgMZNK-ogC8Cio-6OPSnNVM-4BWa=Jh=JnvZpB-Q@mail.gmail.com/5-v18-0004-Extract-coerce_jsonpath_subscript.patch)
  download | inline diff:
From 864e870271d033ab61f08238f3a60f8a60aebb8e Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v18 4/7] Extract coerce_jsonpath_subscript()

This is a preparation step for a future commit that will reuse the
aforementioned function.

Co-authored-by: Nikita Glukhov <[email protected]>
Co-authored-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Jian He <[email protected]>
Reviewed-by: Chao Li <[email protected]>
---
 src/backend/utils/adt/jsonbsubs.c | 128 +++++++++++++++---------------
 1 file changed, 64 insertions(+), 64 deletions(-)

diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index 2aa410f605b..8852c27a198 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -32,6 +32,69 @@ typedef struct JsonbSubWorkspace
 	Datum	   *index;			/* Subscript values in Datum format */
 } JsonbSubWorkspace;
 
+static Node *
+coerce_jsonpath_subscript_to_int4_or_text(ParseState *pstate, Node *subExpr)
+{
+	Oid			subExprType = exprType(subExpr);
+	Oid			targetType = InvalidOid;
+
+	if (subExprType != UNKNOWNOID)
+	{
+		const Oid	targets[] = {INT4OID, TEXTOID};
+
+		/*
+		 * Jsonb can handle multiple subscript types, but cases when a
+		 * subscript could be coerced to multiple target types must be
+		 * avoided, similar to overloaded functions. It could be possibly
+		 * extend with jsonpath in the future.
+		 */
+		for (int i = 0; i < lengthof(targets); i++)
+		{
+			if (can_coerce_type(1, &subExprType, &targets[i], COERCION_IMPLICIT))
+			{
+				/*
+				 * One type has already succeeded, it means there are two
+				 * coercion targets possible, failure.
+				 */
+				if (OidIsValid(targetType))
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
+							 errhint("jsonb subscript must be coercible to only one type, integer or text."),
+							 parser_errposition(pstate, exprLocation(subExpr))));
+
+				targetType = targets[i];
+			}
+		}
+
+		/*
+		 * No suitable types were found, failure.
+		 */
+		if (!OidIsValid(targetType))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
+					 errhint("jsonb subscript must be coercible to either integer or text."),
+					 parser_errposition(pstate, exprLocation(subExpr))));
+	}
+	else
+		targetType = TEXTOID;
+
+	/*
+	 * We known from can_coerce_type that coercion will succeed, so
+	 * coerce_type could be used. Note the implicit coercion context, which is
+	 * required to handle subscripts of different types, similar to overloaded
+	 * functions.
+	 */
+	subExpr = coerce_type(pstate,
+						  subExpr, subExprType,
+						  targetType, -1,
+						  COERCION_IMPLICIT,
+						  COERCE_IMPLICIT_CAST,
+						  -1);
+
+	return subExpr;
+}
 
 /*
  * Finish parse analysis of a SubscriptingRef expression for a jsonb.
@@ -74,71 +137,8 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 
 		if (ai->uidx)
 		{
-			Oid			subExprType = InvalidOid,
-						targetType = UNKNOWNOID;
-
 			subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
-			subExprType = exprType(subExpr);
-
-			if (subExprType != UNKNOWNOID)
-			{
-				Oid			targets[2] = {INT4OID, TEXTOID};
-
-				/*
-				 * Jsonb can handle multiple subscript types, but cases when a
-				 * subscript could be coerced to multiple target types must be
-				 * avoided, similar to overloaded functions. It could be
-				 * possibly extend with jsonpath in the future.
-				 */
-				for (int i = 0; i < 2; i++)
-				{
-					if (can_coerce_type(1, &subExprType, &targets[i], COERCION_IMPLICIT))
-					{
-						/*
-						 * One type has already succeeded, it means there are
-						 * two coercion targets possible, failure.
-						 */
-						if (targetType != UNKNOWNOID)
-							ereport(ERROR,
-									(errcode(ERRCODE_DATATYPE_MISMATCH),
-									 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
-									 errhint("jsonb subscript must be coercible to only one type, integer or text."),
-									 parser_errposition(pstate, exprLocation(subExpr))));
-
-						targetType = targets[i];
-					}
-				}
-
-				/*
-				 * No suitable types were found, failure.
-				 */
-				if (targetType == UNKNOWNOID)
-					ereport(ERROR,
-							(errcode(ERRCODE_DATATYPE_MISMATCH),
-							 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
-							 errhint("jsonb subscript must be coercible to either integer or text."),
-							 parser_errposition(pstate, exprLocation(subExpr))));
-			}
-			else
-				targetType = TEXTOID;
-
-			/*
-			 * We known from can_coerce_type that coercion will succeed, so
-			 * coerce_type could be used. Note the implicit coercion context,
-			 * which is required to handle subscripts of different types,
-			 * similar to overloaded functions.
-			 */
-			subExpr = coerce_type(pstate,
-								  subExpr, subExprType,
-								  targetType, -1,
-								  COERCION_IMPLICIT,
-								  COERCE_IMPLICIT_CAST,
-								  -1);
-			if (subExpr == NULL)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("jsonb subscript must have text type"),
-						 parser_errposition(pstate, exprLocation(subExpr))));
+			subExpr = coerce_jsonpath_subscript_to_int4_or_text(pstate, subExpr);
 		}
 		else
 		{
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v18-0007-Implement-jsonb-wildcard-member-accessor.patch (31.5K, ../../CAK98qZ0v5dkgMZNK-ogC8Cio-6OPSnNVM-4BWa=Jh=JnvZpB-Q@mail.gmail.com/6-v18-0007-Implement-jsonb-wildcard-member-accessor.patch)
  download | inline diff:
From d39a8a5149b376e4e8e9d4176ff51cbf3318982b Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v18 7/7] Implement jsonb wildcard member accessor

This commit adds support for wildcard member access in jsonb, as
specified by the JSON simplified accessor syntax in SQL:2023.

Co-authored-by: Nikita Glukhov <[email protected]>
Co-authored-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Mark Dilger <[email protected]>
Reviewed-by: Jian He <[email protected]>
Reviewed-by: Vik Fearing <[email protected]>
Reviewed-by: Nikita Malakhov <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Jelte Fennema-Nio <[email protected]>
Tested-by: Jelte Fennema-Nio <[email protected]>
Tested-by: Chao Li <[email protected]>
---
 src/backend/nodes/nodeFuncs.c                 |   8 +
 src/backend/parser/gram.y                     |   2 +
 src/backend/parser/parse_expr.c               |  39 +--
 src/backend/parser/parse_target.c             |  67 ++++--
 src/backend/utils/adt/jsonbsubs.c             |  12 +-
 src/backend/utils/adt/ruleutils.c             |   6 +-
 src/include/nodes/primnodes.h                 |  12 +
 src/include/parser/parse_expr.h               |   3 +
 .../ecpg/test/expected/sql-sqljson.c          |  18 +-
 .../ecpg/test/expected/sql-sqljson.stderr     |  23 +-
 .../ecpg/test/expected/sql-sqljson.stdout     |   2 +
 src/interfaces/ecpg/test/sql/sqljson.pgc      |   5 +-
 src/test/regress/expected/jsonb.out           | 222 +++++++++++++++++-
 src/test/regress/sql/jsonb.sql                |  42 +++-
 14 files changed, 406 insertions(+), 55 deletions(-)

diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index d1bd575d9bd..5f3038a1c26 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -291,6 +291,9 @@ exprType(const Node *expr)
 			 */
 			type = TEXTOID;
 			break;
+		case T_Star:
+			type = UNKNOWNOID;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			type = InvalidOid;	/* keep compiler quiet */
@@ -1156,6 +1159,9 @@ exprSetCollation(Node *expr, Oid collation)
 		case T_FieldAccessorExpr:
 			((FieldAccessorExpr *) expr)->faecollid = collation;
 			break;
+		case T_Star:
+			Assert(!OidIsValid(collation));
+			break;
 		case T_SubscriptingRef:
 			((SubscriptingRef *) expr)->refcollid = collation;
 			break;
@@ -2140,6 +2146,7 @@ expression_tree_walker_impl(Node *node,
 		case T_CTESearchClause:
 		case T_MergeSupportFunc:
 		case T_FieldAccessorExpr:
+		case T_Star:
 			/* primitive node types with no expression subnodes */
 			break;
 		case T_WithCheckOption:
@@ -3020,6 +3027,7 @@ expression_tree_mutator_impl(Node *node,
 		case T_CTESearchClause:
 		case T_MergeSupportFunc:
 		case T_FieldAccessorExpr:
+		case T_Star:
 			return copyObject(node);
 		case T_WithCheckOption:
 			{
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9fd48acb1f8..c86ab6fd512 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -18980,6 +18980,7 @@ check_func_name(List *names, core_yyscan_t yyscanner)
 static List *
 check_indirection(List *indirection, core_yyscan_t yyscanner)
 {
+#if 0
 	ListCell   *l;
 
 	foreach(l, indirection)
@@ -18990,6 +18991,7 @@ check_indirection(List *indirection, core_yyscan_t yyscanner)
 				parser_yyerror("improper use of \"*\"");
 		}
 	}
+#endif
 	return indirection;
 }
 
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index f8a0617f823..38ac6503a22 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -73,7 +73,6 @@ static Node *transformColumnRef(ParseState *pstate, ColumnRef *cref);
 static Node *transformWholeRowRef(ParseState *pstate,
 								  ParseNamespaceItem *nsitem,
 								  int sublevels_up, int location);
-static Node *transformIndirection(ParseState *pstate, A_Indirection *ind);
 static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
 static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
 static Node *transformJsonObjectConstructor(ParseState *pstate,
@@ -157,7 +156,7 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 			break;
 
 		case T_A_Indirection:
-			result = transformIndirection(pstate, (A_Indirection *) expr);
+			result = transformIndirection(pstate, (A_Indirection *) expr, NULL);
 			break;
 
 		case T_A_ArrayExpr:
@@ -431,8 +430,9 @@ unknown_attribute(ParseState *pstate, Node *relref, const char *attname,
 	}
 }
 
-static Node *
-transformIndirection(ParseState *pstate, A_Indirection *ind)
+Node *
+transformIndirection(ParseState *pstate, A_Indirection *ind,
+					 bool *trailing_star_expansion)
 {
 	Node	   *last_srf = pstate->p_last_srf;
 	Node	   *result = transformExprRecurse(pstate, ind->arg);
@@ -450,16 +450,8 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 	{
 		Node	   *n = lfirst(i);
 
-		if (IsA(n, A_Indices) || IsA(n, String))
-			subscripts = lappend(subscripts, n);
-		else
-		{
-			Assert(IsA(n, A_Star));
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("row expansion via \"*\" is not supported here"),
-					 parser_errposition(pstate, location)));
-		}
+		Assert (IsA(n, A_Indices) || IsA(n, String) || IsA(n, A_Star));
+		subscripts = lappend(subscripts, n);
 	}
 
 	while (subscripts)
@@ -486,7 +478,21 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 
 			n = linitial(subscripts);
 
-			if (!IsA(n, String))
+			if (IsA(n, A_Star))
+			{
+				/* Success, if trailing star expansion is allowed */
+				if (trailing_star_expansion && list_length(subscripts) == 1)
+				{
+					*trailing_star_expansion = true;
+					return result;
+				}
+
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("row expansion via \"*\" is not supported here"),
+						 parser_errposition(pstate, location)));
+			}
+			else if (!IsA(n, String))
 				ereport(ERROR,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
 						 errmsg("cannot subscript type %s because it does not support subscripting",
@@ -512,6 +518,9 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		result = newresult;
 	}
 
+	if (trailing_star_expansion)
+		*trailing_star_expansion = false;
+
 	return result;
 }
 
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index b89736ff1ea..85c05c7434c 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -47,7 +47,7 @@ static Node *transformAssignmentSubscripts(ParseState *pstate,
 static List *ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
 								 bool make_target_entry);
 static List *ExpandAllTables(ParseState *pstate, int location);
-static List *ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
+static Node *ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
 								   bool make_target_entry, ParseExprKind exprKind);
 static List *ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 							   int sublevels_up, int location,
@@ -133,6 +133,7 @@ transformTargetList(ParseState *pstate, List *targetlist,
 	foreach(o_target, targetlist)
 	{
 		ResTarget  *res = (ResTarget *) lfirst(o_target);
+		Node	   *transformed = NULL;
 
 		/*
 		 * Check for "something.*".  Depending on the complexity of the
@@ -161,13 +162,19 @@ transformTargetList(ParseState *pstate, List *targetlist,
 
 				if (IsA(llast(ind->indirection), A_Star))
 				{
-					/* It is something.*, expand into multiple items */
-					p_target = list_concat(p_target,
-										   ExpandIndirectionStar(pstate,
-																 ind,
-																 true,
-																 exprKind));
-					continue;
+					Node	   *columns = ExpandIndirectionStar(pstate,
+																ind,
+																true,
+																exprKind);
+
+					if (IsA(columns, List))
+					{
+						/* It is something.*, expand into multiple items */
+						p_target = list_concat(p_target, (List *) columns);
+						continue;
+					}
+
+					transformed = (Node *) columns;
 				}
 			}
 		}
@@ -179,7 +186,7 @@ transformTargetList(ParseState *pstate, List *targetlist,
 		p_target = lappend(p_target,
 						   transformTargetEntry(pstate,
 												res->val,
-												NULL,
+												transformed,
 												exprKind,
 												res->name,
 												false));
@@ -250,10 +257,15 @@ transformExpressionList(ParseState *pstate, List *exprlist,
 
 			if (IsA(llast(ind->indirection), A_Star))
 			{
-				/* It is something.*, expand into multiple items */
-				result = list_concat(result,
-									 ExpandIndirectionStar(pstate, ind,
-														   false, exprKind));
+				Node	   *cols = ExpandIndirectionStar(pstate, ind,
+														 false, exprKind);
+
+				if (!cols || IsA(cols, List))
+					/* It is something.*, expand into multiple items */
+					result = list_concat(result, (List *) cols);
+				else
+					result = lappend(result, cols);
+
 				continue;
 			}
 		}
@@ -1344,22 +1356,30 @@ ExpandAllTables(ParseState *pstate, int location)
  * For robustness, we use a separate "make_target_entry" flag to control
  * this rather than relying on exprKind.
  */
-static List *
+static Node *
 ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
 					  bool make_target_entry, ParseExprKind exprKind)
 {
 	Node	   *expr;
+	ParseExprKind sv_expr_kind;
+	bool		trailing_star_expansion = false;
+
+	/* Save and restore identity of expression type we're parsing */
+	Assert(exprKind != EXPR_KIND_NONE);
+	sv_expr_kind = pstate->p_expr_kind;
+	pstate->p_expr_kind = exprKind;
 
 	/* Strip off the '*' to create a reference to the rowtype object */
-	ind = copyObject(ind);
-	ind->indirection = list_truncate(ind->indirection,
-									 list_length(ind->indirection) - 1);
+	expr = transformIndirection(pstate, ind, &trailing_star_expansion);
+
+	pstate->p_expr_kind = sv_expr_kind;
 
-	/* And transform that */
-	expr = transformExpr(pstate, (Node *) ind, exprKind);
+	/* '*' was consumed by generic type subscripting */
+	if (!trailing_star_expansion)
+		return expr;
 
 	/* Expand the rowtype expression into individual fields */
-	return ExpandRowReference(pstate, expr, make_target_entry);
+	return (Node *) ExpandRowReference(pstate, expr, make_target_entry);
 }
 
 /*
@@ -1784,13 +1804,18 @@ FigureColnameInternal(Node *node, char **name)
 				char	   *fname = NULL;
 				ListCell   *l;
 
-				/* find last field name, if any, ignoring "*" and subscripts */
+				/*
+				 * find last field name, if any, ignoring subscripts, and use
+				 * '?column?' when there's a trailing '*'.
+				 */
 				foreach(l, ind->indirection)
 				{
 					Node	   *i = lfirst(l);
 
 					if (IsA(i, String))
 						fname = strVal(i);
+					else if (IsA(i, A_Star))
+						fname = "?column?";
 				}
 				if (fname)
 				{
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index 4762f6bd11c..1dcde658739 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -124,7 +124,7 @@ jsonb_check_jsonpath_needed(List *indirection)
 	{
 		Node	   *accessor = lfirst(lc);
 
-		if (IsA(accessor, String))
+		if (IsA(accessor, String) || IsA(accessor, A_Star))
 			return true;
 		else
 		{
@@ -339,6 +339,16 @@ jsonb_subscript_make_jsonpath(ParseState *pstate, List **indirection, Subscripti
 				}
 			}
 		}
+		else if (IsA(accessor, A_Star))
+		{
+			Star *star_node;
+			jpi = make_jsonpath_item(jpiAnyKey);
+
+			star_node = makeNode(Star);
+			star_node->type = T_Star;
+
+			sbsref->refupperindexpr = lappend(sbsref->refupperindexpr, star_node);
+		}
 		else
 		{
 			/*
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index baa3ae97d57..ace0eff52e2 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -13010,7 +13010,11 @@ printSubscripts(SubscriptingRef *sbsref, deparse_context *context)
 	{
 		Node *upper = (Node *) lfirst(uplist_item);
 
-		if (upper && IsA(upper, FieldAccessorExpr))
+		if (upper && IsA(upper, Star))
+		{
+			appendStringInfoString(buf, ".*");
+		}
+		else if (upper && IsA(upper, FieldAccessorExpr))
 		{
 			FieldAccessorExpr *fae = (FieldAccessorExpr *) upper;
 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 7e89621bd65..7e418830f22 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -2419,4 +2419,16 @@ typedef struct FieldAccessorExpr
 	Oid			faecollid pg_node_attr(query_jumble_ignore);
 }			FieldAccessorExpr;
 
+/*
+ * Star - represents a wildcard member accessor (e.g., ".*") used in JSONB simplified accessor.
+ *
+ * This node serves as a syntactic placeholder in the expression tree and does not get evaluated, hence it
+ * has no associated type or collation.
+ */
+typedef struct Star
+{
+	NodeTag		type;
+}			Star;
+
+
 #endif							/* PRIMNODES_H */
diff --git a/src/include/parser/parse_expr.h b/src/include/parser/parse_expr.h
index efbaff8e710..c9f6a7724c0 100644
--- a/src/include/parser/parse_expr.h
+++ b/src/include/parser/parse_expr.h
@@ -22,4 +22,7 @@ extern Node *transformExpr(ParseState *pstate, Node *expr, ParseExprKind exprKin
 
 extern const char *ParseExprKindName(ParseExprKind exprKind);
 
+extern Node *transformIndirection(ParseState *pstate, A_Indirection *ind,
+								  bool *trailing_star_expansion);
+
 #endif							/* PARSE_EXPR_H */
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.c b/src/interfaces/ecpg/test/expected/sql-sqljson.c
index 935b47a3b9a..585d9b14445 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.c
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.c
@@ -515,9 +515,9 @@ if (sqlca.sqlcode < 0) sqlprint();}
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 145 "sqljson.pgc"
 
-	// error
+	printf("Found json=%s\n", json);
 
-	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )", ECPGt_EOIT, 
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . * . x )", ECPGt_EOIT, 
 	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
 	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 148 "sqljson.pgc"
@@ -527,7 +527,7 @@ if (sqlca.sqlcode < 0) sqlprint();}
 
 	printf("Found json=%s\n", json);
 
-	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ : ] )", ECPGt_EOIT, 
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )", ECPGt_EOIT, 
 	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
 	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 151 "sqljson.pgc"
@@ -537,12 +537,22 @@ if (sqlca.sqlcode < 0) sqlprint();}
 
 	printf("Found json=%s\n", json);
 
-  { ECPGdisconnect(__LINE__, "CURRENT");
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ : ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 154 "sqljson.pgc"
 
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 154 "sqljson.pgc"
 
+	printf("Found json=%s\n", json);
+
+  { ECPGdisconnect(__LINE__, "CURRENT");
+#line 157 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 157 "sqljson.pgc"
+
 
   return 0;
 }
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
index f3f899c6d87..4b9088545d6 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
@@ -347,22 +347,19 @@ SQL error: schema "jsonb" does not exist on line 121
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 145: using PQexec
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_check_PQresult on line 145: bad response - ERROR:  row expansion via "*" is not supported here
-LINE 1: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x...
-                        ^
+[NO_PID]: ecpg_process_output on line 145: correctly got 1 tuples with 1 fields
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: raising sqlstate 0A000 (sqlcode -400): row expansion via "*" is not supported here on line 145
-[NO_PID]: sqlca: code: -400, state: 0A000
-SQL error: row expansion via "*" is not supported here on line 145
-[NO_PID]: ecpg_execute on line 148: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: ecpg_get_data on line 145: RESULT: [{"b": 1, "c": 2}, [{"x": 1}, {"x": [12, {"y": 1}]}]] offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 148: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . * . x ); with 0 parameter(s) on connection ecpg1_regression
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 148: using PQexec
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_process_output on line 148: correctly got 1 tuples with 1 fields
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_get_data on line 148: RESULT: [{"x": 1}, {"x": [12, {"y": 1}]}] offset: -1; array: no
+[NO_PID]: ecpg_get_data on line 148: RESULT: [1, [12, {"y": 1}]] offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_execute on line 151: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: ecpg_execute on line 151: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] ); with 0 parameter(s) on connection ecpg1_regression
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 151: using PQexec
 [NO_PID]: sqlca: code: 0, state: 00000
@@ -370,5 +367,13 @@ SQL error: row expansion via "*" is not supported here on line 145
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_get_data on line 151: RESULT: [{"x": 1}, {"x": [12, {"y": 1}]}] offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 154: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 154: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 154: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 154: RESULT: [{"x": 1}, {"x": [12, {"y": 1}]}] offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_finish: connection ecpg1_regression closed
 [NO_PID]: sqlca: code: 0, state: 00000
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
index d01a8457f01..145dc95d430 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
@@ -36,5 +36,7 @@ Found json={"x": 1}
 Found json=[1, [12, {"y": 1}]]
 Found json={"x": 1}
 Found json=[12, {"y": 1}]
+Found json=[{"b": 1, "c": 2}, [{"x": 1}, {"x": [12, {"y": 1}]}]]
+Found json=[1, [12, {"y": 1}]]
 Found json=[{"x": 1}, {"x": [12, {"y": 1}]}]
 Found json=[{"x": 1}, {"x": [12, {"y": 1}]}]
diff --git a/src/interfaces/ecpg/test/sql/sqljson.pgc b/src/interfaces/ecpg/test/sql/sqljson.pgc
index 9423d25fd0b..2af50b5da4b 100644
--- a/src/interfaces/ecpg/test/sql/sqljson.pgc
+++ b/src/interfaces/ecpg/test/sql/sqljson.pgc
@@ -143,7 +143,10 @@ EXEC SQL END DECLARE SECTION;
 	printf("Found json=%s\n", json);
 
 	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).*) INTO :json;
-	// error
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).*.x) INTO :json;
+	printf("Found json=%s\n", json);
 
 	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb)['b'][0:]) INTO :json;
 	printf("Found json=%s\n", json);
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 2702edc9705..86b7f7677fc 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -6064,8 +6064,175 @@ SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
  {"y": "YYY", "z": "ZZZ"}
 (1 row)
 
-SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
-ERROR:  type jsonb is not composite
+/* wild card member access */
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+                 ?column?                  
+-------------------------------------------
+ ["c", "d", "f", {"y": "yyy", "z": "zzz"}]
+(1 row)
+
+SELECT (jb).* FROM test_jsonb_dot_notation;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (jb).* FROM test_jsonb_dot_notation t;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (jb).* FROM test_jsonb_dot_notation;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (t.jb).* FROM test_jsonb_dot_notation;
+ERROR:  missing FROM-clause entry for table "t"
+LINE 1: SELECT (t.jb).* FROM test_jsonb_dot_notation;
+                ^
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+                 ?column?                  
+-------------------------------------------
+ ["c", "d", "f", {"y": "yyy", "z": "zzz"}]
+(1 row)
+
+SELECT (jb).a.*.b FROM test_jsonb_dot_notation;
+ b 
+---
+ 
+(1 row)
+
+SELECT (jb).a.*.x FROM test_jsonb_dot_notation;
+ x 
+---
+ 
+(1 row)
+
+SELECT (jb).a.*.y FROM test_jsonb_dot_notation;
+   y   
+-------
+ "yyy"
+(1 row)
+
+SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+    ?column?    
+----------------
+ ["yyy", "zzz"]
+(1 row)
+
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT (jb).*.x FROM test_jsonb_dot_notation t;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+ x 
+---
+ 
+(1 row)
+
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+ x 
+---
+ 
+(1 row)
+
+SELECT ((jb).*)[:].x FROM test_jsonb_dot_notation t;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT (jb).*.x.* FROM test_jsonb_dot_notation;
+           ?column?           
+------------------------------
+ ["yyy", "zzz", "YYY", "ZZZ"]
+(1 row)
+
+SELECT (jb).*.x.y FROM test_jsonb_dot_notation;
+       y        
+----------------
+ ["yyy", "YYY"]
+(1 row)
+
+SELECT (jb).*.x.z FROM test_jsonb_dot_notation;
+       z        
+----------------
+ ["zzz", "ZZZ"]
+(1 row)
+
+SELECT (jb).*.*.y FROM test_jsonb_dot_notation;
+       y        
+----------------
+ ["yyy", "YYY"]
+(1 row)
+
+SELECT (jb).*.*.* FROM test_jsonb_dot_notation;
+           ?column?           
+------------------------------
+ ["yyy", "zzz", "YYY", "ZZZ"]
+(1 row)
+
+SELECT (jb).*.*.*.* FROM test_jsonb_dot_notation;
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT (jb).a.b.c.* FROM test_jsonb_dot_notation;
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+    ?column?    
+----------------
+ ["yyy", "zzz"]
+(1 row)
+
+SELECT (jb).a.*[:].* FROM test_jsonb_dot_notation;
+    ?column?    
+----------------
+ ["yyy", "zzz"]
+(1 row)
+
+SELECT (jb).a.*[*].* FROM test_jsonb_dot_notation; -- not supported
+ERROR:  syntax error at or near "*"
+LINE 1: SELECT (jb).a.*[*].* FROM test_jsonb_dot_notation;
+                        ^
+SELECT (jb).a.**.x FROM test_jsonb_dot_notation; -- not supported
+ERROR:  syntax error at or near "**"
+LINE 1: SELECT (jb).a.**.x FROM test_jsonb_dot_notation;
+                      ^
 -- explains should work
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
                   QUERY PLAN                  
@@ -6093,6 +6260,45 @@ SELECT (jb).a[1] FROM test_jsonb_dot_notation;
  2
 (1 row)
 
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.* FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb).a.*
+(2 rows)
+
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+                 ?column?                  
+-------------------------------------------
+ ["c", "d", "f", {"y": "yyy", "z": "zzz"}]
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:].* FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb).a.*[:].*
+(2 rows)
+
+SELECT (jb).a.*[1:].* FROM test_jsonb_dot_notation;
+ ?column? 
+----------
+ 
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb).a.*[:2].*.b
+(2 rows)
+
+SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
+ b 
+---
+ 
+(1 row)
+
 -- views should work
 CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
 \sv test_jsonb_dot_notation_v1
@@ -6121,6 +6327,17 @@ SELECT * from v3;
  "yyy"
 (1 row)
 
+CREATE VIEW v4 AS SELECT (jb).a.*[:].* FROM test_jsonb_dot_notation;
+\sv v4
+CREATE OR REPLACE VIEW public.v4 AS
+ SELECT (jb).a.*[:].* AS "?column?"
+   FROM test_jsonb_dot_notation
+SELECT * from v4;
+    ?column?    
+----------------
+ ["yyy", "zzz"]
+(1 row)
+
 -- mixed syntax
 DROP VIEW test_jsonb_dot_notation_v1;
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
@@ -6356,4 +6573,5 @@ NOTICE:  [2]
 -- clean up
 DROP VIEW v2;
 DROP VIEW v3;
+DROP VIEW v4;
 DROP TABLE test_jsonb_dot_notation;
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index de2c9cda094..07facc280d7 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1631,13 +1631,49 @@ SELECT (jb).a[2:].b FROM test_jsonb_dot_notation;
 SELECT (jb).a[:2].b FROM test_jsonb_dot_notation;
 SELECT (jb).a[:].b FROM test_jsonb_dot_notation;
 SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
-SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
+
+/* wild card member access */
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+SELECT (jb).* FROM test_jsonb_dot_notation;
+SELECT (jb).* FROM test_jsonb_dot_notation t;
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+SELECT (jb).* FROM test_jsonb_dot_notation;
+SELECT (t.jb).* FROM test_jsonb_dot_notation;
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.b FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.x FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.y FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+SELECT (jb).*.x FROM test_jsonb_dot_notation t;
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+SELECT ((jb).*)[:].x FROM test_jsonb_dot_notation t;
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+SELECT (jb).*.x.* FROM test_jsonb_dot_notation;
+SELECT (jb).*.x.y FROM test_jsonb_dot_notation;
+SELECT (jb).*.x.z FROM test_jsonb_dot_notation;
+SELECT (jb).*.*.y FROM test_jsonb_dot_notation;
+SELECT (jb).*.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).*.*.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.b.c.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*[:].* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*[*].* FROM test_jsonb_dot_notation; -- not supported
+SELECT (jb).a.**.x FROM test_jsonb_dot_notation; -- not supported
 
 -- explains should work
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
 SELECT (t.jb).a FROM test_jsonb_dot_notation t;
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
 SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:].* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*[1:].* FROM test_jsonb_dot_notation;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
+SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
 
 -- views should work
 CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
@@ -1648,6 +1684,9 @@ SELECT * from v2;
 CREATE VIEW v3 AS SELECT (jb).a[:3].x.y[-1:] FROM test_jsonb_dot_notation;
 \sv v3
 SELECT * from v3;
+CREATE VIEW v4 AS SELECT (jb).a.*[:].* FROM test_jsonb_dot_notation;
+\sv v4
+SELECT * from v4;
 
 -- mixed syntax
 DROP VIEW test_jsonb_dot_notation_v1;
@@ -1749,4 +1788,5 @@ $$ LANGUAGE plpgsql;
 -- clean up
 DROP VIEW v2;
 DROP VIEW v3;
+DROP VIEW v4;
 DROP TABLE test_jsonb_dot_notation;
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v18-0005-Implement-read-only-dot-notation-for-jsonb.patch (72.8K, ../../CAK98qZ0v5dkgMZNK-ogC8Cio-6OPSnNVM-4BWa=Jh=JnvZpB-Q@mail.gmail.com/7-v18-0005-Implement-read-only-dot-notation-for-jsonb.patch)
  download | inline diff:
From 6bc92e6dbadd5e41a8df3bf39d885377b18e331b Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v18 5/7] Implement read-only dot notation for jsonb

This patch introduces JSONB member access using dot notation that
aligns with the JSON simplified accessor specified in SQL:2023.

Examples:

-- Setup
create table t(x int, y jsonb);
insert into t select 1, '{"a": 1, "b": 42}'::jsonb;
insert into t select 1, '{"a": 2, "b": {"c": 42}}'::jsonb;
insert into t select 1, '{"a": 3, "b": {"c": "42"}, "d":[11, 12]}'::jsonb;

-- Existing syntax in PostgreSQL that predates the SQL standard:
select (t.y)->'b' from t;
select (t.y)->'b'->'c' from t;
select (t.y)->'d'->0 from t;

-- JSON simplified accessor specified by the SQL standard:
select (t.y).b from t;
select (t.y).b.c from t;
select (t.y).d[0] from t;

The SQL standard states that simplified access is equivalent to:
JSON_QUERY (VEP, 'lax $.JC' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR)

where:
  VEP = <value expression primary>
  JC = <JSON simplified accessor op chain>

For example, the JSON_QUERY equivalents of the above queries are:

select json_query(y, 'lax $.b' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t;
select json_query(y, 'lax $.b.c' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t;
select json_query(y, 'lax $.d[0]' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t;

Implementation details:

This patch extends the existing container subscripting interface to
support container-specific information, namely a JSONPath expression
for jsonb.

During query transformation, if dot-notation is present, a JSONPath
expression is constructed to represent the access chain.

Then during execution, if a JSONPath expression is present in
JsonbSubWorkspace, executes it via JsonPathQuery().

Note that we cannot simply rewrite the accessors into JSON_QUERY()
during transformation, because the original query structure must be
preserved for EXPLAIN and CREATE VIEW.

Co-authored-by: Nikita Glukhov <[email protected]>
Co-authored-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Mark Dilger <[email protected]>
Reviewed-by: Jian He <[email protected]>
Reviewed-by: Vik Fearing <[email protected]>
Reviewed-by: Nikita Malakhov <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Tested-by: Jelte Fennema-Nio <[email protected]>
---
 doc/src/sgml/json.sgml                        | 301 +++++++++++++
 src/backend/catalog/sql_features.txt          |   4 +-
 src/backend/executor/execExpr.c               |  81 ++--
 src/backend/nodes/nodeFuncs.c                 |  12 +
 src/backend/utils/adt/jsonbsubs.c             | 301 ++++++++++++-
 src/backend/utils/adt/ruleutils.c             |  43 +-
 src/include/nodes/primnodes.h                 |  54 ++-
 .../ecpg/test/expected/sql-sqljson.c          | 112 ++++-
 .../ecpg/test/expected/sql-sqljson.stderr     | 100 +++++
 .../ecpg/test/expected/sql-sqljson.stdout     |   7 +
 src/interfaces/ecpg/test/sql/sqljson.pgc      |  33 ++
 src/test/regress/expected/jsonb.out           | 413 +++++++++++++++++-
 src/test/regress/sql/jsonb.sql                | 113 ++++-
 src/tools/pgindent/typedefs.list              |   1 +
 14 files changed, 1502 insertions(+), 73 deletions(-)

diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 206eadb8f7b..4e77e9e9512 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -712,6 +712,307 @@ UPDATE table_name SET jsonb_field[1]['a'] = '1';
   </para>
  </sect2>
 
+ <sect2 id="jsonb-simplified-accessor">
+  <title>JSON Simplified Accessor</title>
+  <para>
+   PostgreSQL implements the JSON simplified accessor as specified in SQL:2023.
+   The SQL standard defines the simplified accessor as a chain of operations
+   that can include JSON member accessors (dot notation for object fields)
+   and JSON array accessors (integer subscripts for array elements).
+   This provides a standardized way to access JSON data that complements
+   PostgreSQL's pre-standard subscripting and operator-based access methods.
+  </para>
+
+  <para>
+   The SQL:2023 simplified accessor syntax includes:
+   <itemizedlist>
+    <listitem>
+     <para>
+      <emphasis>JSON member accessor:</emphasis> <literal>jsonb_column.field_name</literal> 
+      for accessing object fields
+     </para>
+    </listitem>
+    <listitem>
+     <para>
+      <emphasis>JSON array accessor:</emphasis> <literal>jsonb_column[index]</literal> 
+      for accessing array elements by integer index
+     </para>
+    </listitem>
+   </itemizedlist>
+   These can be chained together: <literal>jsonb_column.field_name[0].nested_field</literal>.
+   When dot notation is present in the accessor chain, the entire chain follows
+   SQL:2023 semantics with lax mode behavior and conditional array wrapper.
+  </para>
+
+  <para>
+   The JSON simplified accessor is semantically equivalent to using
+   <function>JSON_QUERY</function> with the <literal>lax</literal> mode and
+   <literal>WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR</literal>
+   options. For example, <literal>json_col.field</literal> is equivalent to
+   <literal>JSON_QUERY(json_col, 'lax $.field' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR)</literal>.
+   The conditional array wrapper wraps multiple results in an array
+   but leaves a single result as-is without wrapping.
+  </para>
+
+  <para>
+   Examples of JSON simplified accessor syntax:
+
+<programlisting>
+
+-- Basic field access
+SELECT ('{"color": "red", "rgb": [255, 0, 0]}'::jsonb).color;
+
+-- Nested field access
+SELECT ('{"user": {"profile": {"settings": {"theme": "dark"}}}}'::jsonb).user.profile.settings.theme;
+
+-- JSON member accessor followed by JSON array accessor (both part of simplified accessor)
+SELECT ('{"repertoire": [{"title": "Swan Lake"}, {"title": "The Nutcracker"}]}'::jsonb).repertoire[1].title;
+
+-- In WHERE clauses
+SELECT * FROM users WHERE profile.preferences.theme = '"dark"';
+
+-- Comparison with other access methods (NOT equivalent - different semantics):
+SELECT json_col['address']['city'];           -- Subscripting
+SELECT json_col->'address'->'city';           -- Operator  
+SELECT json_col.address.city;                 -- Simplified accessor (different behavior)
+</programlisting>
+  </para>
+
+  <sect3 id="jsonb-access-method-comparison">
+   <title>Comparison of JSON Access Methods</title>
+   <para>
+    PostgreSQL provides three different approaches for accessing JSON data, each with
+    distinct semantics and behaviors:
+   </para>
+
+   <para>
+    <emphasis>SQL:2023 JSON Simplified Accessor:</emphasis>
+    <itemizedlist>
+     <listitem>
+      <para>
+       Syntax: <literal>json_col.field_name</literal> (member accessor) and 
+       <literal>json_col[index]</literal> (array accessor when used with dot notation)
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Standard SQL:2023 behavior with <literal>lax</literal> mode semantics
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Automatic array wrapping/unwrapping as specified in the SQL standard
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       When accessing a field from an array, operates on each array element and wraps results in an array;
+       when accessing an array index from a non-array, wraps the value as an array first
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Triggered when dot notation is present anywhere in the accessor chain
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Read-only access
+      </para>
+     </listitem>
+    </itemizedlist>
+   </para>
+
+   <para>
+    <emphasis>Pre-standard JSONB Subscripting:</emphasis>
+    <itemizedlist>
+     <listitem>
+      <para>
+       Syntax: <literal>json_col['field_name']</literal> (text-based) and 
+       <literal>json_col[index]</literal> (integer-based when no dot notation present)
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       PostgreSQL's original JSONB subscripting behavior (available since version 14)
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Direct object field and array element access without array wrapping/unwrapping
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Supports both read and write operations
+      </para>
+     </listitem>
+    </itemizedlist>
+   </para>
+
+   <para>
+    <emphasis>Arrow Operators:</emphasis>
+    <itemizedlist>
+     <listitem>
+      <para>
+       Syntax: <literal>json_col->'field_name'</literal> and <literal>json_col->>'field_name'</literal>
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       PostgreSQL's JSON operators that work with both <literal>json</literal> and <literal>jsonb</literal> types
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Direct object field and array element access without array wrapping/unwrapping
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       <literal>-></literal> returns jsonb, <literal>->></literal> returns text
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Read-only access
+      </para>
+     </listitem>
+    </itemizedlist>
+   </para>
+
+   <para>
+    <emphasis>Key Semantic Differences:</emphasis> The most important distinctions are
+    how these methods handle member access from arrays and array access from non-array values.
+   </para>
+
+   <para>
+    <emphasis>Member Access from Arrays:</emphasis>
+<programlisting>
+-- Setup data
+INSERT INTO test_table VALUES 
+  ('{"brightness": 80}'),                      -- Object case
+  ('[{"brightness": 45}, {"brightness": 90}]'); -- Array case
+
+-- Different behaviors:
+SELECT data.brightness FROM test_table;         -- Simplified accessor
+-- Results: 80, [45, 90]  (array elements unwrapped, results wrapped)
+
+SELECT data['brightness'] FROM test_table;      -- Pre-standard subscripting  
+-- Results: 80, NULL    (no array handling)
+
+SELECT data->'brightness' FROM test_table;      -- Arrow operator
+-- Results: 80, NULL    (no array handling)
+</programlisting>
+
+    In the array case, the simplified accessor applies the field access to each array element
+    (unwrapping) and conditionally wraps the results in an array, while subscripting and arrow operators
+    attempt direct field access on the array itself (which returns NULL since
+    arrays don't have named fields).
+   </para>
+
+   <para>
+    <emphasis>Array Access from Objects (Lax Mode Behavior):</emphasis>
+<programlisting>
+-- Setup data
+INSERT INTO test_table VALUES ('{"weather": "sunny", "temperature": "72F"}');
+
+-- Different behaviors when accessing [0] on a non-array value:
+SELECT data[0] FROM test_table;                 -- Simplified accessor (lax mode, if dots present elsewhere)
+-- Result: {"weather": "sunny", "temperature": "72F"}  (object wrapped as array, [0] returns entire object)
+
+SELECT data[0] FROM test_table;                 -- Pre-standard subscripting (strict mode, no dots)
+-- Result: NULL  (no wrapping, direct array access on object fails)
+
+SELECT data->0 FROM test_table;                 -- Arrow operator (strict mode)
+-- Result: NULL  (no wrapping, direct array access on object fails)
+</programlisting>
+
+    In lax mode (simplified accessor), when an array operation is performed on a non-array value,
+    the value is first wrapped in an array, then the operation proceeds. In strict mode 
+    (pre-standard methods), the operation fails and returns NULL.
+   </para>
+
+   <para>
+    <emphasis>When to Use Each Method:</emphasis>
+    <itemizedlist>
+     <listitem>
+      <para>
+       Use <emphasis>SQL:2023 simplified accessor</emphasis> for standard compliance and when you want lax mode and conditional array wrapper
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Use <emphasis>pre-standard subscripting</emphasis> for write operations or when you need direct field access without array processing
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Use <emphasis>arrow operators</emphasis> when you need text output (<literal>->></literal>) or when working with both <literal>json</literal> and <literal>jsonb</literal> types
+      </para>
+     </listitem>
+    </itemizedlist>
+   </para>
+  </sect3>
+
+  <sect3 id="jsonb-accessor-best-practices">
+   <title>Best Practices: Avoid Mixing Access Methods</title>
+   <para>
+    <emphasis>Important:</emphasis> Do not mix SQL:2023 simplified accessor syntax 
+    with pre-standard subscripting syntax in the same accessor chain. These 
+    methods have subtly different semantics and are not interchangeable aliases.
+    Mixing them can lead to confusion and code that is difficult to understand.
+   </para>
+
+   <para>
+    <emphasis>Recommended - Consistent simplified accessor:</emphasis>
+<programlisting>
+-- All parts use simplified accessor (standard behavior)
+SELECT data.location.coordinates.latitude FROM table;  -- Good
+SELECT data.repertoire[0].title FROM table;           -- Good  
+SELECT data.users[1].profile.email FROM table;        -- Good
+</programlisting>
+   </para>
+
+   <para>
+    <emphasis>Recommended - Consistent pre-standard subscripting:</emphasis>
+<programlisting>
+-- All parts use pre-standard subscripting
+SELECT data['location']['coordinates']['latitude'] FROM table; -- Good
+SELECT data[0]['title'] FROM table;                    -- Good (when no dots present)
+SELECT data['users'][1]['profile']['email'] FROM table; -- Good
+</programlisting>
+   </para>
+
+   <para>
+    <emphasis>Not recommended - Mixed syntax:</emphasis>
+<programlisting>
+-- Mixing simplified accessor with pre-standard subscripting
+SELECT data.location['latitude'] FROM table;       -- Avoid
+SELECT data['repertoire'][0].title FROM table;    -- Avoid
+</programlisting>
+    While these mixed forms work as designed, they can be very confusing
+    because the simplified accessor cannot handle text-based subscripts like `['field']`.
+    This forces a fallback to pre-standard semantics for those specific parts,
+    creating a chain that switches between lax mode (for dot notation) and 
+    strict mode (for text subscripts) within the same accessor expression.
+   </para>
+
+   <para>
+    Choose one approach consistently throughout your accessor chain to ensure
+    predictable and maintainable code.
+   </para>
+
+   <para>
+    <emphasis>Current Implementation:</emphasis> The current implementation supports
+    JSON member accessors (dot notation) and JSON array accessors (integer subscripts)
+    as defined in the SQL:2023 simplified accessor specification.
+    Advanced features from the SQL:2023 specification, such as wildcard member
+    accessors and item method accessors, are not yet implemented.
+   </para>
+  </sect3>
+ </sect2>
+
  <sect2 id="datatype-json-transforms">
   <title>Transforms</title>
 
diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_features.txt
index ebe85337c28..457e993305e 100644
--- a/src/backend/catalog/sql_features.txt
+++ b/src/backend/catalog/sql_features.txt
@@ -568,8 +568,8 @@ T838	JSON_TABLE: PLAN DEFAULT clause			NO
 T839	Formatted cast of datetimes to/from character strings			NO	
 T840	Hex integer literals in SQL/JSON path language			YES	
 T851	SQL/JSON: optional keywords for default syntax			YES	
-T860	SQL/JSON simplified accessor: column reference only			NO	
-T861	SQL/JSON simplified accessor: case-sensitive JSON member accessor			NO	
+T860	SQL/JSON simplified accessor: column reference only			YES	
+T861	SQL/JSON simplified accessor: case-sensitive JSON member accessor			YES	
 T862	SQL/JSON simplified accessor: wildcard member accessor			NO	
 T863	SQL/JSON simplified accessor: single-quoted string literal as member accessor			NO	
 T864	SQL/JSON simplified accessor			NO	
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index f1569879b52..385c8d0cefe 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -3320,50 +3320,59 @@ ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref,
 								   state->steps_len - 1);
 	}
 
-	/* Evaluate upper subscripts */
-	i = 0;
-	foreach(lc, sbsref->refupperindexpr)
+	/* Evaluate upper subscripts, unless refjsonbpath is used for execution */
+	if (!sbsref->refjsonbpath)
 	{
-		Expr	   *e = (Expr *) lfirst(lc);
-
-		/* When slicing, individual subscript bounds can be omitted */
-		if (!e)
-		{
-			sbsrefstate->upperprovided[i] = false;
-			sbsrefstate->upperindexnull[i] = true;
-		}
-		else
+		i = 0;
+		foreach(lc, sbsref->refupperindexpr)
 		{
-			sbsrefstate->upperprovided[i] = true;
-			/* Each subscript is evaluated into appropriate array entry */
-			ExecInitExprRec(e, state,
-							&sbsrefstate->upperindex[i],
-							&sbsrefstate->upperindexnull[i]);
+			Expr *e = (Expr *) lfirst(lc);
+
+			/* When slicing, individual subscript bounds can be omitted */
+			if (!e)
+			{
+				sbsrefstate->upperprovided[i] = false;
+				sbsrefstate->upperindexnull[i] = true;
+			}
+			else
+			{
+				sbsrefstate->upperprovided[i] = true;
+				/* Each subscript is evaluated into appropriate array entry */
+				ExecInitExprRec(e, state,
+								&sbsrefstate->upperindex[i],
+								&sbsrefstate->upperindexnull[i]);
+			}
+			i++;
 		}
-		i++;
 	}
 
-	/* Evaluate lower subscripts similarly */
-	i = 0;
-	foreach(lc, sbsref->reflowerindexpr)
+	/*
+	 * Evaluate lower subscripts similarly, unless refjsonbpath is used for
+	 * execution
+	 */
+	if (!sbsref->refjsonbpath)
 	{
-		Expr	   *e = (Expr *) lfirst(lc);
-
-		/* When slicing, individual subscript bounds can be omitted */
-		if (!e)
-		{
-			sbsrefstate->lowerprovided[i] = false;
-			sbsrefstate->lowerindexnull[i] = true;
-		}
-		else
+		i = 0;
+		foreach(lc, sbsref->reflowerindexpr)
 		{
-			sbsrefstate->lowerprovided[i] = true;
-			/* Each subscript is evaluated into appropriate array entry */
-			ExecInitExprRec(e, state,
-							&sbsrefstate->lowerindex[i],
-							&sbsrefstate->lowerindexnull[i]);
+			Expr	   *e = (Expr *) lfirst(lc);
+
+			/* When slicing, individual subscript bounds can be omitted */
+			if (!e)
+			{
+				sbsrefstate->lowerprovided[i] = false;
+				sbsrefstate->lowerindexnull[i] = true;
+			}
+			else
+			{
+				sbsrefstate->lowerprovided[i] = true;
+				/* Each subscript is evaluated into appropriate array entry */
+				ExecInitExprRec(e, state,
+								&sbsrefstate->lowerindex[i],
+								&sbsrefstate->lowerindexnull[i]);
+			}
+			i++;
 		}
-		i++;
 	}
 
 	/* SBSREF_SUBSCRIPTS checks and converts all the subscripts at once */
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 7bc823507f1..d1bd575d9bd 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -284,6 +284,13 @@ exprType(const Node *expr)
 		case T_PlaceHolderVar:
 			type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_FieldAccessorExpr:
+			/*
+			 * FieldAccessorExpr is not evaluable. Treat it as TEXT for collation,
+			 * deparsing, and similar purposes, since it represents a JSON field name.
+			 */
+			type = TEXTOID;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			type = InvalidOid;	/* keep compiler quiet */
@@ -1146,6 +1153,9 @@ exprSetCollation(Node *expr, Oid collation)
 		case T_MergeSupportFunc:
 			((MergeSupportFunc *) expr)->msfcollid = collation;
 			break;
+		case T_FieldAccessorExpr:
+			((FieldAccessorExpr *) expr)->faecollid = collation;
+			break;
 		case T_SubscriptingRef:
 			((SubscriptingRef *) expr)->refcollid = collation;
 			break;
@@ -2129,6 +2139,7 @@ expression_tree_walker_impl(Node *node,
 		case T_SortGroupClause:
 		case T_CTESearchClause:
 		case T_MergeSupportFunc:
+		case T_FieldAccessorExpr:
 			/* primitive node types with no expression subnodes */
 			break;
 		case T_WithCheckOption:
@@ -3008,6 +3019,7 @@ expression_tree_mutator_impl(Node *node,
 		case T_SortGroupClause:
 		case T_CTESearchClause:
 		case T_MergeSupportFunc:
+		case T_FieldAccessorExpr:
 			return copyObject(node);
 		case T_WithCheckOption:
 			{
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index 8852c27a198..ccc5f3c6e94 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -15,21 +15,30 @@
 #include "postgres.h"
 
 #include "executor/execExpr.h"
+#include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "nodes/subscripting.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_expr.h"
 #include "utils/builtins.h"
 #include "utils/jsonb.h"
+#include "utils/jsonpath.h"
 
 
-/* SubscriptingRefState.workspace for jsonb subscripting execution */
+/*
+ * SubscriptingRefState.workspace for generic jsonb subscripting execution.
+ *
+ * Stores state for both jsonb simple subscripting and dot notation access.
+ * Dot notation additionally uses `jsonpath` for JsonPath evaluation.
+ */
 typedef struct JsonbSubWorkspace
 {
 	bool		expectArray;	/* jsonb root is expected to be an array */
 	Oid		   *indexOid;		/* OID of coerced subscript expression, could
 								 * be only integer or text */
 	Datum	   *index;			/* Subscript values in Datum format */
+	JsonPath   *jsonpath;		/* JsonPath for dot notation execution via
+								 * JsonPathQuery() */
 } JsonbSubWorkspace;
 
 static Node *
@@ -96,6 +105,234 @@ coerce_jsonpath_subscript_to_int4_or_text(ParseState *pstate, Node *subExpr)
 	return subExpr;
 }
 
+/*
+ * During transformation, determine whether to build a JsonPath
+ * for JsonPathQuery() execution.
+ *
+ * JsonPath is needed if the indirection list includes:
+ * - String-based access (dot notation)
+ * - Slice-based subscripting (when isSlice is true)
+ *
+ * Otherwise, simple jsonb subscripting is enough.
+ */
+static bool
+jsonb_check_jsonpath_needed(List *indirection)
+{
+	ListCell   *lc;
+
+	foreach(lc, indirection)
+	{
+		Node	   *accessor = lfirst(lc);
+
+		if (IsA(accessor, String))
+			return true;
+		else
+			Assert(IsA(accessor, A_Indices));
+	}
+
+	return false;
+}
+
+/*
+ * Helper functions for constructing JsonPath expressions.
+ *
+ * The make_jsonpath_item_* functions create various types of JsonPathParseItem
+ * nodes, which are used to build JsonPath expressions for jsonb simplified
+ * accessor.
+ */
+
+static JsonPathParseItem *
+make_jsonpath_item(JsonPathItemType type)
+{
+	JsonPathParseItem *v = palloc(sizeof(*v));
+
+	v->type = type;
+	v->next = NULL;
+
+	return v;
+}
+
+/*
+ * Convert a constant integer expression into a JsonPathParseItem.
+ *
+ * The input expression must be a non-null constant of type INT4. Returns NULL otherwise.
+ * This function constructs a jpiNumeric item for use in JsonPath and appends a matching
+ * Const(INT4) node to the given expression list for use in EXPLAIN, views, etc.
+ *
+ * Parameters:
+ * - pstate: parse state context
+ * - expr: input expression node
+ * - exprs: list of expression nodes (updated in place)
+ */
+static JsonPathParseItem *
+make_jsonpath_item_expr(ParseState *pstate, Node *expr, List **exprs)
+{
+	Const	   *cnst;
+
+	expr = transformExpr(pstate, expr, pstate->p_expr_kind);
+
+	if (IsA(expr, Const))
+	{
+		cnst = (Const *) expr;
+		if (cnst->consttype == INT4OID && !(cnst->constisnull))
+		{
+			JsonPathParseItem *jpi = make_jsonpath_item(jpiNumeric);
+
+			jpi->value.numeric =
+				DatumGetNumeric(DirectFunctionCall1(int4_numeric, Int32GetDatum(cnst->constvalue)));
+
+			*exprs = lappend(*exprs, makeConst(INT4OID, -1, InvalidOid, 4,
+											   Int32GetDatum(cnst->constvalue), false, true));
+
+			return jpi;
+		}
+	}
+
+	return NULL;
+}
+
+/*
+ * Constructs a JsonPath expression from a list of indirections.
+ * This function is used when jsonb subscripting involves dot notation,
+ * requiring JsonPath-based evaluation.
+ *
+ * The function modifies the indirection list in place, removing processed
+ * elements as it converts them into JsonPath components, as follows:
+ * - String keys (dot notation) -> jpiKey items.
+ * - Array indices -> jpiIndexArray items.
+ *
+ * In addition to building the JsonPath expression, this function populates
+ * the following fields of the given SubscriptingRef:
+ * - refjsonbpath: the generated JsonPath
+ * - refupperindexpr: upper index expressions (object keys or array indexes)
+ * - reflowerindexpr: lower index expressions, remains NIL as slices are not supported.
+ *
+ * Parameters:
+ * - pstate: Parse state context.
+ * - indirection: List of subscripting expressions (modified in-place).
+ * - sbsref: SubscriptingRef node to update
+ */
+static void
+jsonb_subscript_make_jsonpath(ParseState *pstate, List **indirection, SubscriptingRef *sbsref)
+{
+	JsonPathParseResult jpres;
+	JsonPathParseItem *path = make_jsonpath_item(jpiRoot);
+	ListCell   *lc;
+	Datum		jsp;
+	int			pathlen = 0;
+
+	sbsref->refupperindexpr = NIL;
+	sbsref->reflowerindexpr = NIL;
+	sbsref->refjsonbpath = NULL;
+
+	jpres.expr = path;
+	jpres.lax = true;
+
+	foreach(lc, *indirection)
+	{
+		Node	   *accessor = lfirst(lc);
+		JsonPathParseItem *jpi;
+
+		if (IsA(accessor, String))
+		{
+			char	   *field = strVal(accessor);
+			FieldAccessorExpr *accessor_expr;
+
+			jpi = make_jsonpath_item(jpiKey);
+			jpi->value.string.val = field;
+			jpi->value.string.len = strlen(field);
+
+			accessor_expr = makeNode(FieldAccessorExpr);
+			accessor_expr->type = T_FieldAccessorExpr;
+			accessor_expr->fieldname = field;
+
+			sbsref->refupperindexpr = lappend(sbsref->refupperindexpr, accessor_expr);
+		}
+		else if (IsA(accessor, A_Indices))
+		{
+			A_Indices  *ai = castNode(A_Indices, accessor);
+
+			if (!ai->is_slice)
+			{
+				JsonPathParseItem *jpi_from = NULL;
+
+				Assert(ai->uidx && !ai->lidx);
+				jpi_from = make_jsonpath_item_expr(pstate, ai->uidx, &sbsref->refupperindexpr);
+				if (jpi_from == NULL)
+				{
+					/*
+					 * Break out of the loop if the subscript is not a
+					 * non-null integer constant, so that we can fall back to
+					 * jsonb subscripting logic.
+					 *
+					 * This is needed to handle cases with mixed usage of SQL
+					 * standard json simplified accessor syntax and PostgreSQL
+					 * jsonb subscripting syntax, e.g:
+					 *
+					 * select (jb).a['b'].c from jsonb_table;
+					 *
+					 * where dot-notation (.a and .c) is the SQL standard json
+					 * simplified accessor syntax, and the ['b'] subscript is
+					 * the PostgreSQL jsonb subscripting syntax, because 'b'
+					 * is not a non-null constant integer and cannot be used
+					 * for json array access.
+					 *
+					 * In this case, we cannot create a JsonPath item, so we
+					 * break out of the loop and let
+					 * jsonb_subscript_transform() handle this indirection as
+					 * a PostgreSQL jsonb subscript.
+					 */
+					break;
+				}
+
+				jpi = make_jsonpath_item(jpiIndexArray);
+				jpi->value.array.nelems = 1;
+				jpi->value.array.elems = palloc(sizeof(jpi->value.array.elems[0]));
+
+				jpi->value.array.elems[0].from = jpi_from;
+				jpi->value.array.elems[0].to = NULL;
+			}
+			else
+			{
+				Node	   *expr = ai->uidx ? ai->uidx : ai->lidx;
+
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("jsonb subscript does not support slices"),
+						 parser_errposition(pstate, exprLocation(expr))));
+			}
+		}
+		else
+		{
+			/*
+			 * Unexpected node type in indirection list. This should not
+			 * happen with current grammar, but we handle it defensively by
+			 * breaking out of the loop rather than crashing. In case of
+			 * future grammar changes that might introduce new node types,
+			 * this allows us to create a jsonpath from as many indirection
+			 * elements as we can and let transformIndirection() fallback to
+			 * alternative logic to handle the remaining indirection elements.
+			 */
+			Assert(false);		/* not reachable */
+			break;
+		}
+
+		/* append path item */
+		path->next = jpi;
+		path = jpi;
+		pathlen++;
+	}
+
+	if (pathlen == 0)
+		return;
+
+	*indirection = list_delete_first_n(*indirection, pathlen);
+
+	jsp = jsonPathFromParseResult(&jpres, 0, NULL);
+
+	sbsref->refjsonbpath = (Node *) makeConst(JSONPATHOID, -1, InvalidOid, -1, jsp, false, false);
+}
+
 /*
  * Finish parse analysis of a SubscriptingRef expression for a jsonb.
  *
@@ -111,9 +348,27 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	List	   *upperIndexpr = NIL;
 	ListCell   *idx;
 
+	/* Determine the result type of the subscripting operation; always jsonb */
+	sbsref->refrestype = JSONBOID;
+	sbsref->reftypmod = -1;
+
+	if (jsonb_check_jsonpath_needed(*indirection))
+	{
+		jsonb_subscript_make_jsonpath(pstate, indirection, sbsref);
+		if (sbsref->refjsonbpath)
+			return;
+	}
+
 	/*
-	 * Transform and convert the subscript expressions. Jsonb subscripting
-	 * does not support slices, look only at the upper index.
+	 * We reach here only in two cases: (a) the JSON simplified accessor is
+	 * not needed at all (for example, a plain array subscript like [1] or
+	 * object key access like ['a']), or (b) jsonb_subscript_make_jsonpath()
+	 * was called but could not complete the JsonPath construction (for
+	 * example, when mixing dot notation with non-integer subscripts like
+	 * (jb)['a'].b where 'a' is not a constant integer).
+	 *
+	 * In both cases we fall back to pre-standard jsonb subscripting, coercing
+	 * each subscript to array index or object key as needed.
 	 */
 	foreach(idx, *indirection)
 	{
@@ -160,10 +415,6 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->refupperindexpr = upperIndexpr;
 	sbsref->reflowerindexpr = NIL;
 
-	/* Determine the result type of the subscripting operation; always jsonb */
-	sbsref->refrestype = JSONBOID;
-	sbsref->reftypmod = -1;
-
 	/* Remove processed elements */
 	if (upperIndexpr)
 		*indirection = list_delete_first_n(*indirection, list_length(upperIndexpr));
@@ -218,7 +469,7 @@ jsonb_subscript_check_subscripts(ExprState *state,
 			 * For jsonb fetch and assign functions we need to provide path in
 			 * text format. Convert if it's not already text.
 			 */
-			if (workspace->indexOid[i] == INT4OID)
+			if (!workspace->jsonpath && workspace->indexOid[i] == INT4OID)
 			{
 				Datum		datum = sbsrefstate->upperindex[i];
 				char	   *cs = DatumGetCString(DirectFunctionCall1(int4out, datum));
@@ -246,17 +497,32 @@ jsonb_subscript_fetch(ExprState *state,
 {
 	SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
 	JsonbSubWorkspace *workspace = (JsonbSubWorkspace *) sbsrefstate->workspace;
-	Jsonb	   *jsonbSource;
 
 	/* Should not get here if source jsonb (or any subscript) is null */
 	Assert(!(*op->resnull));
 
-	jsonbSource = DatumGetJsonbP(*op->resvalue);
-	*op->resvalue = jsonb_get_element(jsonbSource,
-									  workspace->index,
-									  sbsrefstate->numupper,
-									  op->resnull,
-									  false);
+	if (workspace->jsonpath)
+	{
+		bool		empty = false;
+		bool		error = false;
+
+		*op->resvalue = JsonPathQuery(*op->resvalue, workspace->jsonpath,
+									  JSW_CONDITIONAL,
+									  &empty, &error, NULL,
+									  NULL);
+
+		*op->resnull = empty || error;
+	}
+	else
+	{
+		Jsonb	   *jsonbSource = DatumGetJsonbP(*op->resvalue);
+
+		*op->resvalue = jsonb_get_element(jsonbSource,
+										  workspace->index,
+										  sbsrefstate->numupper,
+										  op->resnull,
+										  false);
+	}
 }
 
 /*
@@ -364,7 +630,7 @@ jsonb_exec_setup(const SubscriptingRef *sbsref,
 {
 	JsonbSubWorkspace *workspace;
 	ListCell   *lc;
-	int			nupper = sbsref->refupperindexpr->length;
+	int			nupper = list_length(sbsref->refupperindexpr);
 	char	   *ptr;
 
 	/* Allocate type-specific workspace with space for per-subscript data */
@@ -373,6 +639,9 @@ jsonb_exec_setup(const SubscriptingRef *sbsref,
 	workspace->expectArray = false;
 	ptr = ((char *) workspace) + MAXALIGN(sizeof(JsonbSubWorkspace));
 
+	if (sbsref->refjsonbpath)
+		workspace->jsonpath = DatumGetJsonPathP(castNode(Const, sbsref->refjsonbpath)->constvalue);
+
 	/*
 	 * This coding assumes sizeof(Datum) >= sizeof(Oid), else we might
 	 * misalign the indexOid pointer
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 3d6e6bdbfd2..baa3ae97d57 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9329,10 +9329,13 @@ get_rule_expr(Node *node, deparse_context *context,
 				 * Parenthesize the argument unless it's a simple Var or a
 				 * FieldSelect.  (In particular, if it's another
 				 * SubscriptingRef, we *must* parenthesize to avoid
-				 * confusion.)
+				 * confusion.) Always add parenthesis if JSON simplified
+				 * accessor is used, for now.
 				 */
-				need_parens = !IsA(sbsref->refexpr, Var) &&
-					!IsA(sbsref->refexpr, FieldSelect);
+				need_parens = (!IsA(sbsref->refexpr, Var) &&
+					!IsA(sbsref->refexpr, FieldSelect)) ||
+						sbsref->refjsonbpath;
+
 				if (need_parens)
 					appendStringInfoChar(buf, '(');
 				get_rule_expr((Node *) sbsref->refexpr, context, showimplicit);
@@ -13005,17 +13008,35 @@ printSubscripts(SubscriptingRef *sbsref, deparse_context *context)
 	lowlist_item = list_head(sbsref->reflowerindexpr);	/* could be NULL */
 	foreach(uplist_item, sbsref->refupperindexpr)
 	{
-		appendStringInfoChar(buf, '[');
-		if (lowlist_item)
+		Node *upper = (Node *) lfirst(uplist_item);
+
+		if (upper && IsA(upper, FieldAccessorExpr))
 		{
+			FieldAccessorExpr *fae = (FieldAccessorExpr *) upper;
+
+			/* Use dot-notation for field access */
+			appendStringInfoChar(buf, '.');
+			appendStringInfoString(buf, quote_identifier(fae->fieldname));
+
+			/* Skip matching low index — field access doesn't use slices */
+			if (lowlist_item)
+				lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
+		}
+		else
+		{
+			/* Use JSONB array subscripting */
+			appendStringInfoChar(buf, '[');
+			if (lowlist_item)
+			{
+				/* If subexpression is NULL, get_rule_expr prints nothing */
+				get_rule_expr((Node *) lfirst(lowlist_item), context, false);
+				appendStringInfoChar(buf, ':');
+				lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
+			}
 			/* If subexpression is NULL, get_rule_expr prints nothing */
-			get_rule_expr((Node *) lfirst(lowlist_item), context, false);
-			appendStringInfoChar(buf, ':');
-			lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
+			get_rule_expr(upper, context, false);
+			appendStringInfoChar(buf, ']');
 		}
-		/* If subexpression is NULL, get_rule_expr prints nothing */
-		get_rule_expr((Node *) lfirst(uplist_item), context, false);
-		appendStringInfoChar(buf, ']');
 	}
 }
 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 6dfca3cb35b..7e89621bd65 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -708,18 +708,30 @@ typedef struct SubscriptingRef
 	int32		reftypmod pg_node_attr(query_jumble_ignore);
 	/* collation of result, or InvalidOid if none */
 	Oid			refcollid pg_node_attr(query_jumble_ignore);
-	/* expressions that evaluate to upper container indexes */
+
+	/*
+	 * expressions that evaluate to upper container indexes or expressions
+	 * that are collected but not evaluated when refjsonbpath is set.
+	 */
 	List	   *refupperindexpr;
 
 	/*
-	 * expressions that evaluate to lower container indexes, or NIL for single
-	 * container element.
+	 * expressions that evaluate to lower container indexes, or NIL for a
+	 * single container element, or expressions that are collected but not
+	 * evaluated when refjsonbpath is set.
 	 */
 	List	   *reflowerindexpr;
 	/* the expression that evaluates to a container value */
 	Expr	   *refexpr;
 	/* expression for the source value, or NULL if fetch */
 	Expr	   *refassgnexpr;
+
+	/*
+	 * container-specific extra information, currently used only by jsonb.
+	 * stores a JsonPath expression when jsonb dot notation is used. NULL for
+	 * simple subscripting.
+	 */
+	Node	   *refjsonbpath;
 } SubscriptingRef;
 
 /*
@@ -2371,4 +2383,40 @@ typedef struct OnConflictExpr
 	List	   *exclRelTlist;	/* tlist of the EXCLUDED pseudo relation */
 } OnConflictExpr;
 
+/*
+ * FieldAccessorExpr - represents a single object member access using dot-notation
+ *		in JSON simplified accessor syntax (e.g., jsonb_col.a).
+ *
+ * These nodes appear as list elements in SubscriptingRef.refupperindexpr to
+ * indicate JSON object key access. They are not evaluable expressions by
+ * themselves but serve as placeholders to preserve source-level syntax for
+ * rule rewriting and deparsing (e.g., in EXPLAIN and view definitions).
+ * Execution is handled by the enclosing SubscriptingRef.
+ *
+ * If dot-notation is used in a SubscriptingRef, the JSON path is represented
+ * as a flat list of FieldAccessorExpr nodes (for object field access), Const
+ * nodes (for array indexes), and NULLs (for omitted slice bounds), rather than
+ * through nested expression trees.
+ *
+ * Note: The flat representation avoids nested FieldAccessorExpr chains,
+ * simplifying evaluation and enabling standard-compliant behavior such as
+ * conditional array wrapping. This avoids the need for position-aware
+ * wrapping/unwrapping logic during execution.
+ *
+ * For example, in the expression:
+ *		('{"a": [{"b": 1}]}'::jsonb).a[0].b
+ * the SubscriptingRef will contain:
+ *		- refexpr: the base expression (the jsonb value)
+ *		- refupperindexpr: [FieldAccessorExpr("a"), Const(0),
+ *			FieldAccessorExpr("b")]
+ *		- reflowerindexpr: [NULL, NULL, NULL] (slice lower bounds not used here)
+ */
+typedef struct FieldAccessorExpr
+{
+	NodeTag		type;
+	char	   *fieldname;		/* name of the JSONB object field accessed via
+								 * dot notation */
+	Oid			faecollid pg_node_attr(query_jumble_ignore);
+}			FieldAccessorExpr;
+
 #endif							/* PRIMNODES_H */
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.c b/src/interfaces/ecpg/test/expected/sql-sqljson.c
index 39221f9ea5d..e6a7ece6dab 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.c
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.c
@@ -417,12 +417,122 @@ if (sqlca.sqlcode < 0) sqlprint();}
 	  for (int i = 0; i < sizeof(is_json); i++)
 		  printf("Found is_json[%d]: %s\n", i, is_json[i] ? "true" : "false");
 
-  { ECPGdisconnect(__LINE__, "CURRENT");
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . \"a\" )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 118 "sqljson.pgc"
 
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 118 "sqljson.pgc"
 
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb . \"a\" )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 121 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 121 "sqljson.pgc"
+
+	// error
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . a )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 124 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 124 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . a . b )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 127 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 127 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( coalesce ( json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . c ) , 'null' ) )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 130 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 130 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ 0 ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 133 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 133 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b . x )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 136 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 136 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) [ 'b' ] [ 0 ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 139 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 139 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 142 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 142 "sqljson.pgc"
+
+	// error
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . * )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 145 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 145 "sqljson.pgc"
+
+	// error
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 148 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 148 "sqljson.pgc"
+
+	// error
+
+  { ECPGdisconnect(__LINE__, "CURRENT");
+#line 151 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 151 "sqljson.pgc"
+
 
   return 0;
 }
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
index e55a95dd711..19f8c58af06 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
@@ -268,5 +268,105 @@ SQL error: cannot use type jsonb in RETURNING clause of JSON_SERIALIZE() on line
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_get_data on line 102: RESULT: f offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 118: query: select json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . "a" ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 118: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 118: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 118: RESULT: {"b": 1, "c": 2} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 121: query: select json ( '{"a": {"b": 1, "c": 2}}' :: jsonb . "a" ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 121: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 121: bad response - ERROR:  schema "jsonb" does not exist
+LINE 1: select json ( '{"a": {"b": 1, "c": 2}}' :: jsonb . "a" )
+                                                   ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 3F000 (sqlcode -400): schema "jsonb" does not exist on line 121
+[NO_PID]: sqlca: code: -400, state: 3F000
+SQL error: schema "jsonb" does not exist on line 121
+[NO_PID]: ecpg_execute on line 124: query: select json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . a ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 124: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 124: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 124: RESULT: {"b": 1, "c": 2} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 127: query: select json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . a . b ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 127: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 127: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 127: RESULT: 1 offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 130: query: select json ( coalesce ( json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . c ) , 'null' ) ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 130: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 130: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 130: RESULT: null offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 133: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 0 ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 133: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 133: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 133: RESULT: {"x": 1} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 136: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b . x ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 136: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 136: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 136: RESULT: [1, [12, {"y": 1}]] offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 139: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 139: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 139: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 139: RESULT: {"x": 1} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 142: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 142: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 142: bad response - ERROR:  jsonb subscript does not support slices
+LINE 1: ..., {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] )
+                                                                ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 42804 (sqlcode -400): jsonb subscript does not support slices on line 142
+[NO_PID]: sqlca: code: -400, state: 42804
+SQL error: jsonb subscript does not support slices on line 142
+[NO_PID]: ecpg_execute on line 145: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . * ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 145: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 145: bad response - ERROR:  row expansion via "*" is not supported here
+LINE 1: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x...
+                        ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 0A000 (sqlcode -400): row expansion via "*" is not supported here on line 145
+[NO_PID]: sqlca: code: -400, state: 0A000
+SQL error: row expansion via "*" is not supported here on line 145
+[NO_PID]: ecpg_execute on line 148: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 148: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 148: bad response - ERROR:  jsonb subscript does not support slices
+LINE 1: ...x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )
+                                                                ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 42804 (sqlcode -400): jsonb subscript does not support slices on line 148
+[NO_PID]: sqlca: code: -400, state: 42804
+SQL error: jsonb subscript does not support slices on line 148
 [NO_PID]: ecpg_finish: connection ecpg1_regression closed
 [NO_PID]: sqlca: code: 0, state: 00000
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
index 83f8df13e5a..442d36931f1 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
@@ -28,3 +28,10 @@ Found is_json[4]: false
 Found is_json[5]: false
 Found is_json[6]: true
 Found is_json[7]: false
+Found json={"b": 1, "c": 2}
+Found json={"b": 1, "c": 2}
+Found json=1
+Found json=null
+Found json={"x": 1}
+Found json=[1, [12, {"y": 1}]]
+Found json={"x": 1}
diff --git a/src/interfaces/ecpg/test/sql/sqljson.pgc b/src/interfaces/ecpg/test/sql/sqljson.pgc
index ddcbcc3b3cb..57a9bff424d 100644
--- a/src/interfaces/ecpg/test/sql/sqljson.pgc
+++ b/src/interfaces/ecpg/test/sql/sqljson.pgc
@@ -115,6 +115,39 @@ EXEC SQL END DECLARE SECTION;
 	  for (int i = 0; i < sizeof(is_json); i++)
 		  printf("Found is_json[%d]: %s\n", i, is_json[i] ? "true" : "false");
 
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}}'::jsonb)."a") INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON('{"a": {"b": 1, "c": 2}}'::jsonb."a") INTO :json;
+	// error
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}}'::jsonb).a) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}}'::jsonb).a.b) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(COALESCE(JSON(('{"a": {"b": 1, "c": 2}}'::jsonb).c), 'null')) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b[0]) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b.x) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb)['b'][0]) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b[1].x[0:]) INTO :json;
+	// error
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).*) INTO :json;
+	// error
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb)['b'][0:]) INTO :json;
+	// error
+
   EXEC SQL DISCONNECT;
 
   return 0;
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5a1eb18aba2..37d71b23d5d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -4989,6 +4989,12 @@ select ('123'::jsonb)['a'];
  
 (1 row)
 
+select ('123'::jsonb).a;
+ a 
+---
+ 
+(1 row)
+
 select ('123'::jsonb)[0];
  jsonb 
 -------
@@ -5001,12 +5007,24 @@ select ('123'::jsonb)[NULL];
  
 (1 row)
 
+select ('123'::jsonb).NULL;
+ null 
+------
+ 
+(1 row)
+
 select ('{"a": 1}'::jsonb)['a'];
  jsonb 
 -------
  1
 (1 row)
 
+select ('{"a": 1}'::jsonb).a;
+ a 
+---
+ 1
+(1 row)
+
 select ('{"a": 1}'::jsonb)[0];
  jsonb 
 -------
@@ -5019,6 +5037,12 @@ select ('{"a": 1}'::jsonb)['not_exist'];
  
 (1 row)
 
+select ('{"a": 1}'::jsonb)."not_exist";
+ not_exist 
+-----------
+ 
+(1 row)
+
 select ('{"a": 1}'::jsonb)[NULL];
  jsonb 
 -------
@@ -5031,6 +5055,12 @@ select ('[1, "2", null]'::jsonb)['a'];
  
 (1 row)
 
+select ('[1, "2", null]'::jsonb).a;
+ a 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[0];
  jsonb 
 -------
@@ -5043,6 +5073,12 @@ select ('[1, "2", null]'::jsonb)['1'];
  "2"
 (1 row)
 
+select ('[1, "2", null]'::jsonb)."1";
+ 1 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[1.0];
 ERROR:  subscript type numeric is not supported
 LINE 1: select ('[1, "2", null]'::jsonb)[1.0];
@@ -5072,6 +5108,12 @@ select ('[1, "2", null]'::jsonb)[1]['a'];
  
 (1 row)
 
+select ('[1, "2", null]'::jsonb)[1].a;
+ a 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[1][0];
  jsonb 
 -------
@@ -5084,56 +5126,127 @@ select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['b'];
  "c"
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).b;
+  b  
+-----
+ "c"
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'];
    jsonb   
 -----------
  [1, 2, 3]
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d;
+     d     
+-----------
+ [1, 2, 3]
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'][1];
  jsonb 
 -------
  2
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d[1];
+ d 
+---
+ 2
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d']['a'];
  jsonb 
 -------
  
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['a'];
+ d 
+---
+ 
+(1 row)
+
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d.a;
+ a 
+---
+ 
+(1 row)
+
+select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d['a'][0];
+ d 
+---
+ 1
+(1 row)
+
+select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d.a[0];
+ a 
+---
+ 1
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1'];
      jsonb     
 ---------------
  {"a2": "aaa"}
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1;
+      a1       
+---------------
+ {"a2": "aaa"}
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2'];
  jsonb 
 -------
  "aaa"
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2;
+  a2   
+-------
+ "aaa"
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']['a3'];
  jsonb 
 -------
  
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2.a3;
+ a3 
+----
+ 
+(1 row)
+
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'];
          jsonb         
 -----------------------
  ["aaa", "bbb", "ccc"]
 (1 row)
 
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1;
+          b1           
+-----------------------
+ ["aaa", "bbb", "ccc"]
+(1 row)
+
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'][2];
  jsonb 
 -------
  "ccc"
 (1 row)
 
--- slices are not supported
-select ('{"a": 1}'::jsonb)['a':'b'];
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1[2];
+  b1   
+-------
+ "ccc"
+(1 row)
+
+select ('{"a": 1}'::jsonb)['a':'b']; -- fails
 ERROR:  jsonb subscript does not support slices
 LINE 1: select ('{"a": 1}'::jsonb)['a':'b'];
                                        ^
@@ -5831,3 +5944,299 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
  12345
 (1 row)
 
+-- dot notation
+CREATE TABLE test_jsonb_dot_notation AS
+SELECT '{"a": [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], "b": [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]}'::jsonb jb;
+SELECT (jb).a FROM test_jsonb_dot_notation;
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+SELECT (jb)."a" FROM test_jsonb_dot_notation; -- double quote should work
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+SELECT (jb).'a' FROM test_jsonb_dot_notation; -- single quote should not work
+ERROR:  syntax error at or near "'a'"
+LINE 1: SELECT (jb).'a' FROM test_jsonb_dot_notation;
+                    ^
+select (jb)[0].a from test_jsonb_dot_notation; -- returns same result as (jb).a
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+select (jb)[1].a from test_jsonb_dot_notation; -- returns NULL
+ a 
+---
+ 
+(1 row)
+
+SELECT (jb).b FROM test_jsonb_dot_notation;
+                         b                         
+---------------------------------------------------
+ [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]
+(1 row)
+
+SELECT (jb).c FROM test_jsonb_dot_notation;
+ c 
+---
+ 
+(1 row)
+
+SELECT (jb).a.b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT (jb).a[2].b FROM test_jsonb_dot_notation;
+  b  
+-----
+ "c"
+(1 row)
+
+SELECT (jb).a.x.y FROM test_jsonb_dot_notation;
+   y   
+-------
+ "yyy"
+(1 row)
+
+SELECT (jb).b.x.z FROM test_jsonb_dot_notation;
+   z   
+-------
+ "ZZZ"
+(1 row)
+
+SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
+ c 
+---
+ 
+(1 row)
+
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t; -- fails
+ERROR:  jsonb subscript does not support slices
+SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
+ERROR:  type jsonb is not composite
+-- explains should work
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+                  QUERY PLAN                  
+----------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation t
+   Output: (jb).a
+(2 rows)
+
+SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb).a[1]
+(2 rows)
+
+SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+ a 
+---
+ 2
+(1 row)
+
+-- views should work
+CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT (jb).a[3].x.y AS y
+   FROM test_jsonb_dot_notation
+-- mixed syntax
+DROP VIEW test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb['a'::text]).b
+(2 rows)
+
+SELECT (jb)['a'].b FROM test_jsonb_dot_notation; -- returns an array due to lax mode
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT (jb['a'::text]).b AS b
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: ((jb).a)['b'::text]
+(2 rows)
+
+SELECT (jb).a['b'] FROM test_jsonb_dot_notation; -- returns NULL because ['b'] looks for strict match in an object
+ a 
+---
+ 
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT ((jb).a)['b'::text] AS a
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+ a 
+---
+ 
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: ((jb).b.x)['z'::text]
+(2 rows)
+
+SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation; -- warnings
+   x   
+-------
+ "ZZZ"
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT ((jb).b.x)['z'::text] AS x
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+   x   
+-------
+ "ZZZ"
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: ((jb['b'::text]).x)['z'::text]
+(2 rows)
+
+SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;  -- warnings
+   x   
+-------
+ "ZZZ"
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT ((jb['b'::text]).x)['z'::text] AS x
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+   x   
+-------
+ "ZZZ"
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (((jb).b)['x'::text]).z
+(2 rows)
+
+SELECT (jb).b['x'].z FROM test_jsonb_dot_notation; -- returns NULL
+ z 
+---
+ 
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT (((jb).b)['x'::text]).z AS z
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+ z 
+---
+ 
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: ((jb).b)['x'::text]['z'::text]
+(2 rows)
+
+SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation; -- returns NULL
+ b 
+---
+ 
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT ((jb).b)['x'::text]['z'::text] AS b
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+ b 
+---
+ 
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb['b'::text]['x'::text]).z
+(2 rows)
+
+SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation; -- returns NULL
+ z 
+---
+ 
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT (jb['b'::text]['x'::text]).z AS z
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+ z 
+---
+ 
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+-- clean up
+DROP TABLE test_jsonb_dot_notation;
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 57c11acddfe..5fc53e1c9aa 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1304,33 +1304,51 @@ select jsonb_insert('{"a": {"b": "value"}}', '{a, b}', '"new_value"', true);
 
 -- jsonb subscript
 select ('123'::jsonb)['a'];
+select ('123'::jsonb).a;
 select ('123'::jsonb)[0];
 select ('123'::jsonb)[NULL];
+select ('123'::jsonb).NULL;
 select ('{"a": 1}'::jsonb)['a'];
+select ('{"a": 1}'::jsonb).a;
 select ('{"a": 1}'::jsonb)[0];
 select ('{"a": 1}'::jsonb)['not_exist'];
+select ('{"a": 1}'::jsonb)."not_exist";
 select ('{"a": 1}'::jsonb)[NULL];
 select ('[1, "2", null]'::jsonb)['a'];
+select ('[1, "2", null]'::jsonb).a;
 select ('[1, "2", null]'::jsonb)[0];
 select ('[1, "2", null]'::jsonb)['1'];
+select ('[1, "2", null]'::jsonb)."1";
 select ('[1, "2", null]'::jsonb)[1.0];
 select ('[1, "2", null]'::jsonb)[2];
 select ('[1, "2", null]'::jsonb)[3];
 select ('[1, "2", null]'::jsonb)[-2];
 select ('[1, "2", null]'::jsonb)[1]['a'];
+select ('[1, "2", null]'::jsonb)[1].a;
 select ('[1, "2", null]'::jsonb)[1][0];
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['b'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).b;
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d;
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'][1];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d[1];
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d']['a'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['a'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d.a;
+select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d['a'][0];
+select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d.a[0];
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1;
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2;
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']['a3'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2.a3;
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'];
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1;
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'][2];
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1[2];
 
--- slices are not supported
-select ('{"a": 1}'::jsonb)['a':'b'];
+select ('{"a": 1}'::jsonb)['a':'b']; -- fails
 select ('[1, "2", null]'::jsonb)[1:2];
 select ('[1, "2", null]'::jsonb)[:2];
 select ('[1, "2", null]'::jsonb)[1:];
@@ -1590,3 +1608,94 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::float8;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int2;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int4;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
+
+-- dot notation
+CREATE TABLE test_jsonb_dot_notation AS
+SELECT '{"a": [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], "b": [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]}'::jsonb jb;
+
+SELECT (jb).a FROM test_jsonb_dot_notation;
+SELECT (jb)."a" FROM test_jsonb_dot_notation; -- double quote should work
+SELECT (jb).'a' FROM test_jsonb_dot_notation; -- single quote should not work
+select (jb)[0].a from test_jsonb_dot_notation; -- returns same result as (jb).a
+select (jb)[1].a from test_jsonb_dot_notation; -- returns NULL
+SELECT (jb).b FROM test_jsonb_dot_notation;
+SELECT (jb).c FROM test_jsonb_dot_notation;
+SELECT (jb).a.b FROM test_jsonb_dot_notation;
+SELECT (jb).a[2].b FROM test_jsonb_dot_notation;
+SELECT (jb).a.x.y FROM test_jsonb_dot_notation;
+SELECT (jb).b.x.z FROM test_jsonb_dot_notation;
+SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t; -- fails
+SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
+
+-- explains should work
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+
+-- views should work
+CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+
+-- mixed syntax
+DROP VIEW test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
+SELECT (jb)['a'].b FROM test_jsonb_dot_notation; -- returns an array due to lax mode
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+SELECT (jb).a['b'] FROM test_jsonb_dot_notation; -- returns NULL because ['b'] looks for strict match in an object
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation; -- warnings
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;  -- warnings
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+SELECT (jb).b['x'].z FROM test_jsonb_dot_notation; -- returns NULL
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation; -- returns NULL
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
+SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation; -- returns NULL
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+-- clean up
+DROP TABLE test_jsonb_dot_notation;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index a13e8162890..cc64642f399 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -805,6 +805,7 @@ FdwRoutine
 FetchDirection
 FetchDirectionKeywords
 FetchStmt
+FieldAccessorExpr
 FieldSelect
 FieldStore
 File
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v18-0003-Export-jsonPathFromParseResult.patch (2.8K, ../../CAK98qZ0v5dkgMZNK-ogC8Cio-6OPSnNVM-4BWa=Jh=JnvZpB-Q@mail.gmail.com/8-v18-0003-Export-jsonPathFromParseResult.patch)
  download | inline diff:
From cae40708106646a2e27a705cf4540887ab5f32ab Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v18 3/7] Export jsonPathFromParseResult()

This is a preparation step for a future commit that will reuse the
aforementioned function.

Authored-by: Nikita Glukhov <[email protected]>
Reviewed-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Jian He <[email protected]>
Reviewed-by: Chao Li <[email protected]>
---
 src/backend/utils/adt/jsonpath.c | 19 +++++++++++++++----
 src/include/utils/jsonpath.h     |  4 ++++
 2 files changed, 19 insertions(+), 4 deletions(-)

diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index 762f7e8a09d..c83774b2a16 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -166,15 +166,13 @@ jsonpath_send(PG_FUNCTION_ARGS)
  * Converts C-string to a jsonpath value.
  *
  * Uses jsonpath parser to turn string into an AST, then
- * flattenJsonPathParseItem() does second pass turning AST into binary
+ * jsonPathFromParseResult() does second pass turning AST into binary
  * representation of jsonpath.
  */
 static Datum
 jsonPathFromCstring(char *in, int len, struct Node *escontext)
 {
 	JsonPathParseResult *jsonpath = parsejsonpath(in, len, escontext);
-	JsonPath   *res;
-	StringInfoData buf;
 
 	if (SOFT_ERROR_OCCURRED(escontext))
 		return (Datum) 0;
@@ -185,8 +183,21 @@ jsonPathFromCstring(char *in, int len, struct Node *escontext)
 				 errmsg("invalid input syntax for type %s: \"%s\"", "jsonpath",
 						in)));
 
+	return jsonPathFromParseResult(jsonpath, 4 * len, escontext);
+}
+
+/*
+ * Converts jsonpath Abstract Syntax Tree (AST) into jsonpath value in binary.
+ */
+Datum
+jsonPathFromParseResult(const JsonPathParseResult *jsonpath, int estimated_len,
+						struct Node *escontext)
+{
+	JsonPath   *res;
+	StringInfoData buf;
+
 	initStringInfo(&buf);
-	enlargeStringInfo(&buf, 4 * len /* estimation */ );
+	enlargeStringInfo(&buf, estimated_len);
 
 	appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
 
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index 23a76d233e9..0958bc22bb6 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -281,6 +281,10 @@ extern JsonPathParseResult *parsejsonpath(const char *str, int len,
 extern bool jspConvertRegexFlags(uint32 xflags, int *result,
 								 struct Node *escontext);
 
+extern Datum jsonPathFromParseResult(const JsonPathParseResult *jsonpath,
+									 int estimated_len,
+									 struct Node *escontext);
+
 /*
  * Struct for details about external variables passed into jsonpath executor
  */
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v18-0002-Allow-Generic-Type-Subscripting-to-Accept-Dot-No.patch (18.2K, ../../CAK98qZ0v5dkgMZNK-ogC8Cio-6OPSnNVM-4BWa=Jh=JnvZpB-Q@mail.gmail.com/9-v18-0002-Allow-Generic-Type-Subscripting-to-Accept-Dot-No.patch)
  download | inline diff:
From 820862795fff46158c9931f2f64cc3fab8a4e004 Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Fri, 5 Sep 2025 14:17:37 -0700
Subject: [PATCH v18 2/7] Allow Generic Type Subscripting to Accept Dot
 Notation (.) as Input
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This change extends generic type subscripting to recognize dot
notation (.) in addition to bracket notation ([]). While this does not
yet provide full support for dot notation, it enables subscripting
containers to process it in the future.

For now, container-specific transform functions only handle
subscripting indices and stop processing when encountering dot
notation. It is up to individual containers to decide how to transform
dot notation in subsequent updates.

This change also updates the SubscriptTransform() API to remove the
"bool isSlice" argument. Each container’s transform function now
determines slice-ness itself, since it may only process a prefix of
the indirection list. For example, array_subscript_transform() stops
at dot notation, so "isSlice" should not depend on slice specifiers
that appear afterward.

Authored-by: Nikita Glukhov <[email protected]>
Authored-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Jian He <[email protected]>
Reviewed-by: Chao Li <[email protected]>
---
 contrib/hstore/hstore_subs.c         | 11 +++--
 src/backend/parser/parse_expr.c      | 68 ++++++++++++++++++----------
 src/backend/parser/parse_node.c      | 57 ++++++++++++++---------
 src/backend/parser/parse_target.c    |  3 +-
 src/backend/utils/adt/arraysubs.c    | 40 ++++++++++++++--
 src/backend/utils/adt/jsonbsubs.c    | 16 +++++--
 src/include/nodes/subscripting.h     |  3 +-
 src/include/parser/parse_node.h      |  3 +-
 src/test/regress/expected/arrays.out | 26 +++++++++--
 src/test/regress/sql/arrays.sql      |  7 ++-
 10 files changed, 163 insertions(+), 71 deletions(-)

diff --git a/contrib/hstore/hstore_subs.c b/contrib/hstore/hstore_subs.c
index 1b29543ab67..f008e5faf03 100644
--- a/contrib/hstore/hstore_subs.c
+++ b/contrib/hstore/hstore_subs.c
@@ -42,14 +42,16 @@ static void
 hstore_subscript_transform(SubscriptingRef *sbsref,
 						   List **indirection,
 						   ParseState *pstate,
-						   bool isSlice,
 						   bool isAssignment)
 {
 	A_Indices  *ai;
 	Node	   *subexpr;
 
+	Assert(*indirection != NIL);
+	ai = linitial_node(A_Indices, *indirection);
+
 	/* We support only single-subscript, non-slice cases */
-	if (isSlice || list_length(*indirection) != 1)
+	if (list_length(*indirection) != 1 || ai->is_slice)
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("hstore allows only one subscript"),
@@ -57,8 +59,7 @@ hstore_subscript_transform(SubscriptingRef *sbsref,
 									exprLocation((Node *) *indirection))));
 
 	/* Transform the subscript expression to type text */
-	ai = linitial_node(A_Indices, *indirection);
-	Assert(ai->uidx != NULL && ai->lidx == NULL && !ai->is_slice);
+	Assert(ai->uidx != NULL && ai->lidx == NULL);
 
 	subexpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
 	/* If it's not text already, try to coerce */
@@ -82,7 +83,7 @@ hstore_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->refrestype = TEXTOID;
 	sbsref->reftypmod = -1;
 
-	*indirection = NIL;
+	*indirection = list_delete_first_n(*indirection, 1);
 }
 
 /*
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 6e8fd42c612..f8a0617f823 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -441,38 +441,59 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 	ListCell   *i;
 
 	/*
-	 * We have to split any field-selection operations apart from
-	 * subscripting.  Adjacent A_Indices nodes have to be treated as a single
+	 * Combine field names and subscripts into a single indirection list, as
+	 * some subscripting containers, such as jsonb, support field access using
+	 * dot notation. Adjacent A_Indices nodes have to be treated as a single
 	 * multidimensional subscript operation.
 	 */
 	foreach(i, ind->indirection)
 	{
 		Node	   *n = lfirst(i);
 
-		if (IsA(n, A_Indices))
+		if (IsA(n, A_Indices) || IsA(n, String))
 			subscripts = lappend(subscripts, n);
-		else if (IsA(n, A_Star))
+		else
 		{
+			Assert(IsA(n, A_Star));
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("row expansion via \"*\" is not supported here"),
 					 parser_errposition(pstate, location)));
 		}
-		else
+	}
+
+	while (subscripts)
+	{
+		/* try processing container subscripts first */
+		Node	   *newresult = (Node *)
+			transformContainerSubscripts(pstate,
+										 result,
+										 exprType(result),
+										 exprTypmod(result),
+										 &subscripts,
+										 false,
+										 true);
+
+		if (!newresult)
 		{
-			Node	   *newresult;
+			/*
+			 * generic subscripting failed; falling back to field selection
+			 * for a composite type.
+			 */
+			Node	   *n;
+
+			Assert(subscripts);
 
-			Assert(IsA(n, String));
+			n = linitial(subscripts);
 
-			/* process subscripts before this field selection */
-			while (subscripts)
-				result = (Node *) transformContainerSubscripts(pstate,
-															   result,
-															   exprType(result),
-															   exprTypmod(result),
-															   &subscripts,
-															   false);
+			if (!IsA(n, String))
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("cannot subscript type %s because it does not support subscripting",
+								format_type_be(exprType(result))),
+						 parser_errposition(pstate, exprLocation(result))));
 
+			/* try to find function for field selection */
 			newresult = ParseFuncOrColumn(pstate,
 										  list_make1(n),
 										  list_make1(result),
@@ -480,19 +501,16 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 										  NULL,
 										  false,
 										  location);
-			if (newresult == NULL)
+
+			if (!newresult)
 				unknown_attribute(pstate, result, strVal(n), location);
-			result = newresult;
+
+			/* consume field select */
+			subscripts = list_delete_first(subscripts);
 		}
+
+		result = newresult;
 	}
-	/* process trailing subscripts, if any */
-	while (subscripts)
-		result = (Node *) transformContainerSubscripts(pstate,
-													   result,
-													   exprType(result),
-													   exprTypmod(result),
-													   &subscripts,
-													   false);
 
 	return result;
 }
diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c
index f05baa50a15..d7b23688b9b 100644
--- a/src/backend/parser/parse_node.c
+++ b/src/backend/parser/parse_node.c
@@ -238,6 +238,8 @@ transformContainerType(Oid *containerType, int32 *containerTypmod)
  * containerTypMod	typmod for the container
  * indirection		Untransformed list of subscripts (must not be NIL)
  * isAssignment		True if this will become a container assignment.
+ * noError			True for return NULL with no error, if the container type
+ * 					is not subscriptable.
  */
 SubscriptingRef *
 transformContainerSubscripts(ParseState *pstate,
@@ -245,13 +247,13 @@ transformContainerSubscripts(ParseState *pstate,
 							 Oid containerType,
 							 int32 containerTypMod,
 							 List **indirection,
-							 bool isAssignment)
+							 bool isAssignment,
+							 bool noError)
 {
 	SubscriptingRef *sbsref;
 	const SubscriptRoutines *sbsroutines;
 	Oid			elementType;
-	bool		isSlice = false;
-	ListCell   *idx;
+	int			indirection_length = list_length(*indirection);
 
 	/*
 	 * Determine the actual container type, smashing any domain.  In the
@@ -267,28 +269,15 @@ transformContainerSubscripts(ParseState *pstate,
 	 */
 	sbsroutines = getSubscriptingRoutines(containerType, &elementType);
 	if (!sbsroutines)
+	{
+		if (noError)
+			return NULL;
+
 		ereport(ERROR,
 				(errcode(ERRCODE_DATATYPE_MISMATCH),
 				 errmsg("cannot subscript type %s because it does not support subscripting",
 						format_type_be(containerType)),
 				 parser_errposition(pstate, exprLocation(containerBase))));
-
-	/*
-	 * Detect whether any of the indirection items are slice specifiers.
-	 *
-	 * A list containing only simple subscripts refers to a single container
-	 * element.  If any of the items are slice specifiers (lower:upper), then
-	 * the subscript expression means a container slice operation.
-	 */
-	foreach(idx, *indirection)
-	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
-
-		if (ai->is_slice)
-		{
-			isSlice = true;
-			break;
-		}
 	}
 
 	/*
@@ -310,7 +299,33 @@ transformContainerSubscripts(ParseState *pstate,
 	 * determine the subscripting result type.
 	 */
 	sbsroutines->transform(sbsref, indirection, pstate,
-						   isSlice, isAssignment);
+						   isAssignment);
+
+	/*
+	 * Error out, if datatype failed to consume any indirection elements.
+	 */
+	if (list_length(*indirection) == indirection_length)
+	{
+		Node	   *ind = linitial(*indirection);
+
+		if (noError)
+			return NULL;
+
+		if (IsA(ind, String))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("type %s does not support dot notation",
+							format_type_be(containerType)),
+					 parser_errposition(pstate, exprLocation(containerBase))));
+		else if (IsA(ind, A_Indices))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("type %s does not support array subscripting",
+							format_type_be(containerType)),
+					 parser_errposition(pstate, exprLocation(containerBase))));
+		else
+			elog(ERROR, "invalid indirection operation: %d", nodeTag(ind));
+	}
 
 	/*
 	 * Verify we got a valid type (this defends, for example, against someone
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index a097736229a..b89736ff1ea 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -936,7 +936,8 @@ transformAssignmentSubscripts(ParseState *pstate,
 										  containerType,
 										  containerTypMod,
 										  &subscripts,
-										  true);
+										  true,
+										  false);
 
 	typeNeeded = sbsref->refrestype;
 	typmodNeeded = sbsref->reftypmod;
diff --git a/src/backend/utils/adt/arraysubs.c b/src/backend/utils/adt/arraysubs.c
index 234c2c278c1..378b6bf16cb 100644
--- a/src/backend/utils/adt/arraysubs.c
+++ b/src/backend/utils/adt/arraysubs.c
@@ -56,12 +56,38 @@ static void
 array_subscript_transform(SubscriptingRef *sbsref,
 						  List **indirection,
 						  ParseState *pstate,
-						  bool isSlice,
 						  bool isAssignment)
 {
 	List	   *upperIndexpr = NIL;
 	List	   *lowerIndexpr = NIL;
 	ListCell   *idx;
+	int			ndim;
+	bool		isSlice = false;
+
+	/*
+	 * Detect whether any of the indirection items are slice specifiers.
+	 *
+	 * A list containing only simple subscripts refers to a single container
+	 * element.  If any of the items are slice specifiers (lower:upper), then
+	 * the subscript expression means a container slice operation.
+	 */
+	foreach(idx, *indirection)
+	{
+		Node	   *ai = lfirst(idx);
+
+		/*
+		 * We should not inspect slice specifiers beyond an indirection node
+		 * type that we don't support.
+		 */
+		if (!IsA(ai, A_Indices))
+			break;
+
+		if (castNode(A_Indices, ai)->is_slice)
+		{
+			isSlice = true;
+			break;
+		}
+	}
 
 	/*
 	 * Transform the subscript expressions, and separate upper and lower
@@ -73,9 +99,14 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	 */
 	foreach(idx, *indirection)
 	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
+		A_Indices  *ai;
 		Node	   *subexpr;
 
+		if (!IsA(lfirst(idx), A_Indices))
+			break;
+
+		ai = lfirst_node(A_Indices, idx);
+
 		if (isSlice)
 		{
 			if (ai->lidx)
@@ -145,14 +176,15 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->reflowerindexpr = lowerIndexpr;
 
 	/* Verify subscript list lengths are within implementation limit */
-	if (list_length(upperIndexpr) > MAXDIM)
+	ndim = list_length(upperIndexpr);
+	if (ndim > MAXDIM)
 		ereport(ERROR,
 				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 				 errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
 						list_length(upperIndexpr), MAXDIM)));
 	/* We need not check lowerIndexpr separately */
 
-	*indirection = NIL;
+	*indirection = list_delete_first_n(*indirection, ndim);
 
 	/*
 	 * Determine the result type of the subscripting operation.  It's the same
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index 5ead693a3b2..2aa410f605b 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -43,7 +43,6 @@ static void
 jsonb_subscript_transform(SubscriptingRef *sbsref,
 						  List **indirection,
 						  ParseState *pstate,
-						  bool isSlice,
 						  bool isAssignment)
 {
 	List	   *upperIndexpr = NIL;
@@ -55,10 +54,15 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	 */
 	foreach(idx, *indirection)
 	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
+		A_Indices  *ai;
 		Node	   *subExpr;
 
-		if (isSlice)
+		if (!IsA(lfirst(idx), A_Indices))
+			break;
+
+		ai = lfirst_node(A_Indices, idx);
+
+		if (ai->is_slice)
 		{
 			Node	   *expr = ai->uidx ? ai->uidx : ai->lidx;
 
@@ -142,7 +146,7 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 			 * Slice with omitted upper bound. Should not happen as we already
 			 * errored out on slice earlier, but handle this just in case.
 			 */
-			Assert(isSlice && ai->is_slice);
+			Assert(ai->is_slice);
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
 					 errmsg("jsonb subscript does not support slices"),
@@ -160,7 +164,9 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->refrestype = JSONBOID;
 	sbsref->reftypmod = -1;
 
-	*indirection = NIL;
+	/* Remove processed elements */
+	if (upperIndexpr)
+		*indirection = list_delete_first_n(*indirection, list_length(upperIndexpr));
 }
 
 /*
diff --git a/src/include/nodes/subscripting.h b/src/include/nodes/subscripting.h
index df988a85fad..14ef414a180 100644
--- a/src/include/nodes/subscripting.h
+++ b/src/include/nodes/subscripting.h
@@ -68,7 +68,7 @@ struct SubscriptExecSteps;
  * same length as refupperindexpr for a slice operation.  Insert NULLs
  * (that is, an empty parse tree, not a null Const node) for any omitted
  * subscripts in a slice operation.  (Of course, if the transform method
- * does not care to support slicing, it can just throw an error if isSlice.)
+ * does not care to support slicing, it can just throw an error.)
  * See array_subscript_transform() for sample code.
  *
  * The transform method receives a pointer to a list of raw indirections.
@@ -100,7 +100,6 @@ struct SubscriptExecSteps;
 typedef void (*SubscriptTransform) (SubscriptingRef *sbsref,
 									List **indirection,
 									struct ParseState *pstate,
-									bool isSlice,
 									bool isAssignment);
 
 /*
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 58a4b9df157..5cc3ce58c30 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -362,7 +362,8 @@ extern SubscriptingRef *transformContainerSubscripts(ParseState *pstate,
 													 Oid containerType,
 													 int32 containerTypMod,
 													 List **indirection,
-													 bool isAssignment);
+													 bool isAssignment,
+													 bool noError);
 extern Const *make_const(ParseState *pstate, A_Const *aconst);
 
 #endif							/* PARSE_NODE_H */
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index b815473f414..78dd9e71bf3 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -1782,17 +1782,17 @@ SELECT max(f1), min(f1), max(f2), min(f2), max(f3), min(f3) FROM arraggtest;
 (1 row)
 
 -- A few simple tests for arrays of composite types
-create type comptype as (f1 int, f2 text);
+create type comptype as (f1 int, f2 text, f3 int[]);
 create table comptable (c1 comptype, c2 comptype[]);
 -- XXX would like to not have to specify row() construct types here ...
 insert into comptable
-  values (row(1,'foo'), array[row(2,'bar')::comptype, row(3,'baz')::comptype]);
+  values (row(1,'foo',array[10,20]), array[row(2,'bar',array[30,40])::comptype, row(3,'baz',array[50,60])::comptype]);
 -- check that implicitly named array type _comptype isn't a problem
 create type _comptype as enum('fooey');
 select * from comptable;
-   c1    |          c2           
----------+-----------------------
- (1,foo) | {"(2,bar)","(3,baz)"}
+        c1         |                      c2                       
+-------------------+-----------------------------------------------
+ (1,foo,"{10,20}") | {"(2,bar,\"{30,40}\")","(3,baz,\"{50,60}\")"}
 (1 row)
 
 select c2[2].f2 from comptable;
@@ -1801,6 +1801,22 @@ select c2[2].f2 from comptable;
  baz
 (1 row)
 
+select c2[2].f3 from comptable;
+   f3    
+---------
+ {50,60}
+(1 row)
+
+select c2[2].f3[1:2] from comptable;
+   f3    
+---------
+ {50,60}
+(1 row)
+
+select c2[1:2].f3[1:2] from comptable;
+ERROR:  column notation .f3 applied to type comptype[], which is not a composite type
+LINE 1: select c2[1:2].f3[1:2] from comptable;
+               ^
 drop type _comptype;
 drop table comptable;
 drop type comptype;
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 47d62c1d38d..450389831a0 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -555,19 +555,22 @@ SELECT max(f1), min(f1), max(f2), min(f2), max(f3), min(f3) FROM arraggtest;
 
 -- A few simple tests for arrays of composite types
 
-create type comptype as (f1 int, f2 text);
+create type comptype as (f1 int, f2 text, f3 int[]);
 
 create table comptable (c1 comptype, c2 comptype[]);
 
 -- XXX would like to not have to specify row() construct types here ...
 insert into comptable
-  values (row(1,'foo'), array[row(2,'bar')::comptype, row(3,'baz')::comptype]);
+  values (row(1,'foo',array[10,20]), array[row(2,'bar',array[30,40])::comptype, row(3,'baz',array[50,60])::comptype]);
 
 -- check that implicitly named array type _comptype isn't a problem
 create type _comptype as enum('fooey');
 
 select * from comptable;
 select c2[2].f2 from comptable;
+select c2[2].f3 from comptable;
+select c2[2].f3[1:2] from comptable;
+select c2[1:2].f3[1:2] from comptable;
 
 drop type _comptype;
 drop table comptable;
-- 
2.39.5 (Apple Git-154)



^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-06-24 13:29                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-07-09 08:01                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-07-10 08:53                                 ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-21 04:53                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-22 08:19                                     ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-22 19:33                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-25 08:09                                         ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-26 03:52                                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-29 07:22                                             ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-02 02:59                                               ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-02 03:32                                                 ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-03 02:20                                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-09-03 06:56                                                     ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-09 23:53                                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-09-10 04:03                                                         ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-10 23:56                                                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
@ 2025-09-15 18:32                                                             ` Alexandra Wang <[email protected]>
  2025-09-19 20:40                                                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  0 siblings, 1 reply; 67+ messages in thread

From: Alexandra Wang @ 2025-09-15 18:32 UTC (permalink / raw)
  To: Chao Li <[email protected]>; +Cc: Nikita Glukhov <[email protected]>; jian he <[email protected]>; Nikita Malakhov <[email protected]>; Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>

Hi,

I’ve rebased the patch set, no changes since v18.

Best,
Alex


Attachments:

  [application/octet-stream] v19-0005-Implement-read-only-dot-notation-for-jsonb.patch (72.8K, ../../CAK98qZ2nezKeQFqPV9GX80mDiLqS2Fh6MRBGz6ox_HkQF86asA@mail.gmail.com/3-v19-0005-Implement-read-only-dot-notation-for-jsonb.patch)
  download | inline diff:
From ae6b30bcdf4f0c2b847b5846fbc1b647267d7aa5 Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v19 5/7] Implement read-only dot notation for jsonb

This patch introduces JSONB member access using dot notation that
aligns with the JSON simplified accessor specified in SQL:2023.

Examples:

-- Setup
create table t(x int, y jsonb);
insert into t select 1, '{"a": 1, "b": 42}'::jsonb;
insert into t select 1, '{"a": 2, "b": {"c": 42}}'::jsonb;
insert into t select 1, '{"a": 3, "b": {"c": "42"}, "d":[11, 12]}'::jsonb;

-- Existing syntax in PostgreSQL that predates the SQL standard:
select (t.y)->'b' from t;
select (t.y)->'b'->'c' from t;
select (t.y)->'d'->0 from t;

-- JSON simplified accessor specified by the SQL standard:
select (t.y).b from t;
select (t.y).b.c from t;
select (t.y).d[0] from t;

The SQL standard states that simplified access is equivalent to:
JSON_QUERY (VEP, 'lax $.JC' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR)

where:
  VEP = <value expression primary>
  JC = <JSON simplified accessor op chain>

For example, the JSON_QUERY equivalents of the above queries are:

select json_query(y, 'lax $.b' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t;
select json_query(y, 'lax $.b.c' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t;
select json_query(y, 'lax $.d[0]' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t;

Implementation details:

This patch extends the existing container subscripting interface to
support container-specific information, namely a JSONPath expression
for jsonb.

During query transformation, if dot-notation is present, a JSONPath
expression is constructed to represent the access chain.

Then during execution, if a JSONPath expression is present in
JsonbSubWorkspace, executes it via JsonPathQuery().

Note that we cannot simply rewrite the accessors into JSON_QUERY()
during transformation, because the original query structure must be
preserved for EXPLAIN and CREATE VIEW.

Co-authored-by: Nikita Glukhov <[email protected]>
Co-authored-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Mark Dilger <[email protected]>
Reviewed-by: Jian He <[email protected]>
Reviewed-by: Vik Fearing <[email protected]>
Reviewed-by: Nikita Malakhov <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Tested-by: Jelte Fennema-Nio <[email protected]>
---
 doc/src/sgml/json.sgml                        | 301 +++++++++++++
 src/backend/catalog/sql_features.txt          |   4 +-
 src/backend/executor/execExpr.c               |  81 ++--
 src/backend/nodes/nodeFuncs.c                 |  12 +
 src/backend/utils/adt/jsonbsubs.c             | 301 ++++++++++++-
 src/backend/utils/adt/ruleutils.c             |  43 +-
 src/include/nodes/primnodes.h                 |  54 ++-
 .../ecpg/test/expected/sql-sqljson.c          | 112 ++++-
 .../ecpg/test/expected/sql-sqljson.stderr     | 100 +++++
 .../ecpg/test/expected/sql-sqljson.stdout     |   7 +
 src/interfaces/ecpg/test/sql/sqljson.pgc      |  33 ++
 src/test/regress/expected/jsonb.out           | 413 +++++++++++++++++-
 src/test/regress/sql/jsonb.sql                | 113 ++++-
 src/tools/pgindent/typedefs.list              |   1 +
 14 files changed, 1502 insertions(+), 73 deletions(-)

diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 206eadb8f7b..4e77e9e9512 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -712,6 +712,307 @@ UPDATE table_name SET jsonb_field[1]['a'] = '1';
   </para>
  </sect2>
 
+ <sect2 id="jsonb-simplified-accessor">
+  <title>JSON Simplified Accessor</title>
+  <para>
+   PostgreSQL implements the JSON simplified accessor as specified in SQL:2023.
+   The SQL standard defines the simplified accessor as a chain of operations
+   that can include JSON member accessors (dot notation for object fields)
+   and JSON array accessors (integer subscripts for array elements).
+   This provides a standardized way to access JSON data that complements
+   PostgreSQL's pre-standard subscripting and operator-based access methods.
+  </para>
+
+  <para>
+   The SQL:2023 simplified accessor syntax includes:
+   <itemizedlist>
+    <listitem>
+     <para>
+      <emphasis>JSON member accessor:</emphasis> <literal>jsonb_column.field_name</literal> 
+      for accessing object fields
+     </para>
+    </listitem>
+    <listitem>
+     <para>
+      <emphasis>JSON array accessor:</emphasis> <literal>jsonb_column[index]</literal> 
+      for accessing array elements by integer index
+     </para>
+    </listitem>
+   </itemizedlist>
+   These can be chained together: <literal>jsonb_column.field_name[0].nested_field</literal>.
+   When dot notation is present in the accessor chain, the entire chain follows
+   SQL:2023 semantics with lax mode behavior and conditional array wrapper.
+  </para>
+
+  <para>
+   The JSON simplified accessor is semantically equivalent to using
+   <function>JSON_QUERY</function> with the <literal>lax</literal> mode and
+   <literal>WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR</literal>
+   options. For example, <literal>json_col.field</literal> is equivalent to
+   <literal>JSON_QUERY(json_col, 'lax $.field' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR)</literal>.
+   The conditional array wrapper wraps multiple results in an array
+   but leaves a single result as-is without wrapping.
+  </para>
+
+  <para>
+   Examples of JSON simplified accessor syntax:
+
+<programlisting>
+
+-- Basic field access
+SELECT ('{"color": "red", "rgb": [255, 0, 0]}'::jsonb).color;
+
+-- Nested field access
+SELECT ('{"user": {"profile": {"settings": {"theme": "dark"}}}}'::jsonb).user.profile.settings.theme;
+
+-- JSON member accessor followed by JSON array accessor (both part of simplified accessor)
+SELECT ('{"repertoire": [{"title": "Swan Lake"}, {"title": "The Nutcracker"}]}'::jsonb).repertoire[1].title;
+
+-- In WHERE clauses
+SELECT * FROM users WHERE profile.preferences.theme = '"dark"';
+
+-- Comparison with other access methods (NOT equivalent - different semantics):
+SELECT json_col['address']['city'];           -- Subscripting
+SELECT json_col->'address'->'city';           -- Operator  
+SELECT json_col.address.city;                 -- Simplified accessor (different behavior)
+</programlisting>
+  </para>
+
+  <sect3 id="jsonb-access-method-comparison">
+   <title>Comparison of JSON Access Methods</title>
+   <para>
+    PostgreSQL provides three different approaches for accessing JSON data, each with
+    distinct semantics and behaviors:
+   </para>
+
+   <para>
+    <emphasis>SQL:2023 JSON Simplified Accessor:</emphasis>
+    <itemizedlist>
+     <listitem>
+      <para>
+       Syntax: <literal>json_col.field_name</literal> (member accessor) and 
+       <literal>json_col[index]</literal> (array accessor when used with dot notation)
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Standard SQL:2023 behavior with <literal>lax</literal> mode semantics
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Automatic array wrapping/unwrapping as specified in the SQL standard
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       When accessing a field from an array, operates on each array element and wraps results in an array;
+       when accessing an array index from a non-array, wraps the value as an array first
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Triggered when dot notation is present anywhere in the accessor chain
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Read-only access
+      </para>
+     </listitem>
+    </itemizedlist>
+   </para>
+
+   <para>
+    <emphasis>Pre-standard JSONB Subscripting:</emphasis>
+    <itemizedlist>
+     <listitem>
+      <para>
+       Syntax: <literal>json_col['field_name']</literal> (text-based) and 
+       <literal>json_col[index]</literal> (integer-based when no dot notation present)
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       PostgreSQL's original JSONB subscripting behavior (available since version 14)
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Direct object field and array element access without array wrapping/unwrapping
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Supports both read and write operations
+      </para>
+     </listitem>
+    </itemizedlist>
+   </para>
+
+   <para>
+    <emphasis>Arrow Operators:</emphasis>
+    <itemizedlist>
+     <listitem>
+      <para>
+       Syntax: <literal>json_col->'field_name'</literal> and <literal>json_col->>'field_name'</literal>
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       PostgreSQL's JSON operators that work with both <literal>json</literal> and <literal>jsonb</literal> types
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Direct object field and array element access without array wrapping/unwrapping
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       <literal>-></literal> returns jsonb, <literal>->></literal> returns text
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Read-only access
+      </para>
+     </listitem>
+    </itemizedlist>
+   </para>
+
+   <para>
+    <emphasis>Key Semantic Differences:</emphasis> The most important distinctions are
+    how these methods handle member access from arrays and array access from non-array values.
+   </para>
+
+   <para>
+    <emphasis>Member Access from Arrays:</emphasis>
+<programlisting>
+-- Setup data
+INSERT INTO test_table VALUES 
+  ('{"brightness": 80}'),                      -- Object case
+  ('[{"brightness": 45}, {"brightness": 90}]'); -- Array case
+
+-- Different behaviors:
+SELECT data.brightness FROM test_table;         -- Simplified accessor
+-- Results: 80, [45, 90]  (array elements unwrapped, results wrapped)
+
+SELECT data['brightness'] FROM test_table;      -- Pre-standard subscripting  
+-- Results: 80, NULL    (no array handling)
+
+SELECT data->'brightness' FROM test_table;      -- Arrow operator
+-- Results: 80, NULL    (no array handling)
+</programlisting>
+
+    In the array case, the simplified accessor applies the field access to each array element
+    (unwrapping) and conditionally wraps the results in an array, while subscripting and arrow operators
+    attempt direct field access on the array itself (which returns NULL since
+    arrays don't have named fields).
+   </para>
+
+   <para>
+    <emphasis>Array Access from Objects (Lax Mode Behavior):</emphasis>
+<programlisting>
+-- Setup data
+INSERT INTO test_table VALUES ('{"weather": "sunny", "temperature": "72F"}');
+
+-- Different behaviors when accessing [0] on a non-array value:
+SELECT data[0] FROM test_table;                 -- Simplified accessor (lax mode, if dots present elsewhere)
+-- Result: {"weather": "sunny", "temperature": "72F"}  (object wrapped as array, [0] returns entire object)
+
+SELECT data[0] FROM test_table;                 -- Pre-standard subscripting (strict mode, no dots)
+-- Result: NULL  (no wrapping, direct array access on object fails)
+
+SELECT data->0 FROM test_table;                 -- Arrow operator (strict mode)
+-- Result: NULL  (no wrapping, direct array access on object fails)
+</programlisting>
+
+    In lax mode (simplified accessor), when an array operation is performed on a non-array value,
+    the value is first wrapped in an array, then the operation proceeds. In strict mode 
+    (pre-standard methods), the operation fails and returns NULL.
+   </para>
+
+   <para>
+    <emphasis>When to Use Each Method:</emphasis>
+    <itemizedlist>
+     <listitem>
+      <para>
+       Use <emphasis>SQL:2023 simplified accessor</emphasis> for standard compliance and when you want lax mode and conditional array wrapper
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Use <emphasis>pre-standard subscripting</emphasis> for write operations or when you need direct field access without array processing
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Use <emphasis>arrow operators</emphasis> when you need text output (<literal>->></literal>) or when working with both <literal>json</literal> and <literal>jsonb</literal> types
+      </para>
+     </listitem>
+    </itemizedlist>
+   </para>
+  </sect3>
+
+  <sect3 id="jsonb-accessor-best-practices">
+   <title>Best Practices: Avoid Mixing Access Methods</title>
+   <para>
+    <emphasis>Important:</emphasis> Do not mix SQL:2023 simplified accessor syntax 
+    with pre-standard subscripting syntax in the same accessor chain. These 
+    methods have subtly different semantics and are not interchangeable aliases.
+    Mixing them can lead to confusion and code that is difficult to understand.
+   </para>
+
+   <para>
+    <emphasis>Recommended - Consistent simplified accessor:</emphasis>
+<programlisting>
+-- All parts use simplified accessor (standard behavior)
+SELECT data.location.coordinates.latitude FROM table;  -- Good
+SELECT data.repertoire[0].title FROM table;           -- Good  
+SELECT data.users[1].profile.email FROM table;        -- Good
+</programlisting>
+   </para>
+
+   <para>
+    <emphasis>Recommended - Consistent pre-standard subscripting:</emphasis>
+<programlisting>
+-- All parts use pre-standard subscripting
+SELECT data['location']['coordinates']['latitude'] FROM table; -- Good
+SELECT data[0]['title'] FROM table;                    -- Good (when no dots present)
+SELECT data['users'][1]['profile']['email'] FROM table; -- Good
+</programlisting>
+   </para>
+
+   <para>
+    <emphasis>Not recommended - Mixed syntax:</emphasis>
+<programlisting>
+-- Mixing simplified accessor with pre-standard subscripting
+SELECT data.location['latitude'] FROM table;       -- Avoid
+SELECT data['repertoire'][0].title FROM table;    -- Avoid
+</programlisting>
+    While these mixed forms work as designed, they can be very confusing
+    because the simplified accessor cannot handle text-based subscripts like `['field']`.
+    This forces a fallback to pre-standard semantics for those specific parts,
+    creating a chain that switches between lax mode (for dot notation) and 
+    strict mode (for text subscripts) within the same accessor expression.
+   </para>
+
+   <para>
+    Choose one approach consistently throughout your accessor chain to ensure
+    predictable and maintainable code.
+   </para>
+
+   <para>
+    <emphasis>Current Implementation:</emphasis> The current implementation supports
+    JSON member accessors (dot notation) and JSON array accessors (integer subscripts)
+    as defined in the SQL:2023 simplified accessor specification.
+    Advanced features from the SQL:2023 specification, such as wildcard member
+    accessors and item method accessors, are not yet implemented.
+   </para>
+  </sect3>
+ </sect2>
+
  <sect2 id="datatype-json-transforms">
   <title>Transforms</title>
 
diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_features.txt
index ebe85337c28..457e993305e 100644
--- a/src/backend/catalog/sql_features.txt
+++ b/src/backend/catalog/sql_features.txt
@@ -568,8 +568,8 @@ T838	JSON_TABLE: PLAN DEFAULT clause			NO
 T839	Formatted cast of datetimes to/from character strings			NO	
 T840	Hex integer literals in SQL/JSON path language			YES	
 T851	SQL/JSON: optional keywords for default syntax			YES	
-T860	SQL/JSON simplified accessor: column reference only			NO	
-T861	SQL/JSON simplified accessor: case-sensitive JSON member accessor			NO	
+T860	SQL/JSON simplified accessor: column reference only			YES	
+T861	SQL/JSON simplified accessor: case-sensitive JSON member accessor			YES	
 T862	SQL/JSON simplified accessor: wildcard member accessor			NO	
 T863	SQL/JSON simplified accessor: single-quoted string literal as member accessor			NO	
 T864	SQL/JSON simplified accessor			NO	
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index f1569879b52..385c8d0cefe 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -3320,50 +3320,59 @@ ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref,
 								   state->steps_len - 1);
 	}
 
-	/* Evaluate upper subscripts */
-	i = 0;
-	foreach(lc, sbsref->refupperindexpr)
+	/* Evaluate upper subscripts, unless refjsonbpath is used for execution */
+	if (!sbsref->refjsonbpath)
 	{
-		Expr	   *e = (Expr *) lfirst(lc);
-
-		/* When slicing, individual subscript bounds can be omitted */
-		if (!e)
-		{
-			sbsrefstate->upperprovided[i] = false;
-			sbsrefstate->upperindexnull[i] = true;
-		}
-		else
+		i = 0;
+		foreach(lc, sbsref->refupperindexpr)
 		{
-			sbsrefstate->upperprovided[i] = true;
-			/* Each subscript is evaluated into appropriate array entry */
-			ExecInitExprRec(e, state,
-							&sbsrefstate->upperindex[i],
-							&sbsrefstate->upperindexnull[i]);
+			Expr *e = (Expr *) lfirst(lc);
+
+			/* When slicing, individual subscript bounds can be omitted */
+			if (!e)
+			{
+				sbsrefstate->upperprovided[i] = false;
+				sbsrefstate->upperindexnull[i] = true;
+			}
+			else
+			{
+				sbsrefstate->upperprovided[i] = true;
+				/* Each subscript is evaluated into appropriate array entry */
+				ExecInitExprRec(e, state,
+								&sbsrefstate->upperindex[i],
+								&sbsrefstate->upperindexnull[i]);
+			}
+			i++;
 		}
-		i++;
 	}
 
-	/* Evaluate lower subscripts similarly */
-	i = 0;
-	foreach(lc, sbsref->reflowerindexpr)
+	/*
+	 * Evaluate lower subscripts similarly, unless refjsonbpath is used for
+	 * execution
+	 */
+	if (!sbsref->refjsonbpath)
 	{
-		Expr	   *e = (Expr *) lfirst(lc);
-
-		/* When slicing, individual subscript bounds can be omitted */
-		if (!e)
-		{
-			sbsrefstate->lowerprovided[i] = false;
-			sbsrefstate->lowerindexnull[i] = true;
-		}
-		else
+		i = 0;
+		foreach(lc, sbsref->reflowerindexpr)
 		{
-			sbsrefstate->lowerprovided[i] = true;
-			/* Each subscript is evaluated into appropriate array entry */
-			ExecInitExprRec(e, state,
-							&sbsrefstate->lowerindex[i],
-							&sbsrefstate->lowerindexnull[i]);
+			Expr	   *e = (Expr *) lfirst(lc);
+
+			/* When slicing, individual subscript bounds can be omitted */
+			if (!e)
+			{
+				sbsrefstate->lowerprovided[i] = false;
+				sbsrefstate->lowerindexnull[i] = true;
+			}
+			else
+			{
+				sbsrefstate->lowerprovided[i] = true;
+				/* Each subscript is evaluated into appropriate array entry */
+				ExecInitExprRec(e, state,
+								&sbsrefstate->lowerindex[i],
+								&sbsrefstate->lowerindexnull[i]);
+			}
+			i++;
 		}
-		i++;
 	}
 
 	/* SBSREF_SUBSCRIPTS checks and converts all the subscripts at once */
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 7bc823507f1..d1bd575d9bd 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -284,6 +284,13 @@ exprType(const Node *expr)
 		case T_PlaceHolderVar:
 			type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_FieldAccessorExpr:
+			/*
+			 * FieldAccessorExpr is not evaluable. Treat it as TEXT for collation,
+			 * deparsing, and similar purposes, since it represents a JSON field name.
+			 */
+			type = TEXTOID;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			type = InvalidOid;	/* keep compiler quiet */
@@ -1146,6 +1153,9 @@ exprSetCollation(Node *expr, Oid collation)
 		case T_MergeSupportFunc:
 			((MergeSupportFunc *) expr)->msfcollid = collation;
 			break;
+		case T_FieldAccessorExpr:
+			((FieldAccessorExpr *) expr)->faecollid = collation;
+			break;
 		case T_SubscriptingRef:
 			((SubscriptingRef *) expr)->refcollid = collation;
 			break;
@@ -2129,6 +2139,7 @@ expression_tree_walker_impl(Node *node,
 		case T_SortGroupClause:
 		case T_CTESearchClause:
 		case T_MergeSupportFunc:
+		case T_FieldAccessorExpr:
 			/* primitive node types with no expression subnodes */
 			break;
 		case T_WithCheckOption:
@@ -3008,6 +3019,7 @@ expression_tree_mutator_impl(Node *node,
 		case T_SortGroupClause:
 		case T_CTESearchClause:
 		case T_MergeSupportFunc:
+		case T_FieldAccessorExpr:
 			return copyObject(node);
 		case T_WithCheckOption:
 			{
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index 8852c27a198..ccc5f3c6e94 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -15,21 +15,30 @@
 #include "postgres.h"
 
 #include "executor/execExpr.h"
+#include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "nodes/subscripting.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_expr.h"
 #include "utils/builtins.h"
 #include "utils/jsonb.h"
+#include "utils/jsonpath.h"
 
 
-/* SubscriptingRefState.workspace for jsonb subscripting execution */
+/*
+ * SubscriptingRefState.workspace for generic jsonb subscripting execution.
+ *
+ * Stores state for both jsonb simple subscripting and dot notation access.
+ * Dot notation additionally uses `jsonpath` for JsonPath evaluation.
+ */
 typedef struct JsonbSubWorkspace
 {
 	bool		expectArray;	/* jsonb root is expected to be an array */
 	Oid		   *indexOid;		/* OID of coerced subscript expression, could
 								 * be only integer or text */
 	Datum	   *index;			/* Subscript values in Datum format */
+	JsonPath   *jsonpath;		/* JsonPath for dot notation execution via
+								 * JsonPathQuery() */
 } JsonbSubWorkspace;
 
 static Node *
@@ -96,6 +105,234 @@ coerce_jsonpath_subscript_to_int4_or_text(ParseState *pstate, Node *subExpr)
 	return subExpr;
 }
 
+/*
+ * During transformation, determine whether to build a JsonPath
+ * for JsonPathQuery() execution.
+ *
+ * JsonPath is needed if the indirection list includes:
+ * - String-based access (dot notation)
+ * - Slice-based subscripting (when isSlice is true)
+ *
+ * Otherwise, simple jsonb subscripting is enough.
+ */
+static bool
+jsonb_check_jsonpath_needed(List *indirection)
+{
+	ListCell   *lc;
+
+	foreach(lc, indirection)
+	{
+		Node	   *accessor = lfirst(lc);
+
+		if (IsA(accessor, String))
+			return true;
+		else
+			Assert(IsA(accessor, A_Indices));
+	}
+
+	return false;
+}
+
+/*
+ * Helper functions for constructing JsonPath expressions.
+ *
+ * The make_jsonpath_item_* functions create various types of JsonPathParseItem
+ * nodes, which are used to build JsonPath expressions for jsonb simplified
+ * accessor.
+ */
+
+static JsonPathParseItem *
+make_jsonpath_item(JsonPathItemType type)
+{
+	JsonPathParseItem *v = palloc(sizeof(*v));
+
+	v->type = type;
+	v->next = NULL;
+
+	return v;
+}
+
+/*
+ * Convert a constant integer expression into a JsonPathParseItem.
+ *
+ * The input expression must be a non-null constant of type INT4. Returns NULL otherwise.
+ * This function constructs a jpiNumeric item for use in JsonPath and appends a matching
+ * Const(INT4) node to the given expression list for use in EXPLAIN, views, etc.
+ *
+ * Parameters:
+ * - pstate: parse state context
+ * - expr: input expression node
+ * - exprs: list of expression nodes (updated in place)
+ */
+static JsonPathParseItem *
+make_jsonpath_item_expr(ParseState *pstate, Node *expr, List **exprs)
+{
+	Const	   *cnst;
+
+	expr = transformExpr(pstate, expr, pstate->p_expr_kind);
+
+	if (IsA(expr, Const))
+	{
+		cnst = (Const *) expr;
+		if (cnst->consttype == INT4OID && !(cnst->constisnull))
+		{
+			JsonPathParseItem *jpi = make_jsonpath_item(jpiNumeric);
+
+			jpi->value.numeric =
+				DatumGetNumeric(DirectFunctionCall1(int4_numeric, Int32GetDatum(cnst->constvalue)));
+
+			*exprs = lappend(*exprs, makeConst(INT4OID, -1, InvalidOid, 4,
+											   Int32GetDatum(cnst->constvalue), false, true));
+
+			return jpi;
+		}
+	}
+
+	return NULL;
+}
+
+/*
+ * Constructs a JsonPath expression from a list of indirections.
+ * This function is used when jsonb subscripting involves dot notation,
+ * requiring JsonPath-based evaluation.
+ *
+ * The function modifies the indirection list in place, removing processed
+ * elements as it converts them into JsonPath components, as follows:
+ * - String keys (dot notation) -> jpiKey items.
+ * - Array indices -> jpiIndexArray items.
+ *
+ * In addition to building the JsonPath expression, this function populates
+ * the following fields of the given SubscriptingRef:
+ * - refjsonbpath: the generated JsonPath
+ * - refupperindexpr: upper index expressions (object keys or array indexes)
+ * - reflowerindexpr: lower index expressions, remains NIL as slices are not supported.
+ *
+ * Parameters:
+ * - pstate: Parse state context.
+ * - indirection: List of subscripting expressions (modified in-place).
+ * - sbsref: SubscriptingRef node to update
+ */
+static void
+jsonb_subscript_make_jsonpath(ParseState *pstate, List **indirection, SubscriptingRef *sbsref)
+{
+	JsonPathParseResult jpres;
+	JsonPathParseItem *path = make_jsonpath_item(jpiRoot);
+	ListCell   *lc;
+	Datum		jsp;
+	int			pathlen = 0;
+
+	sbsref->refupperindexpr = NIL;
+	sbsref->reflowerindexpr = NIL;
+	sbsref->refjsonbpath = NULL;
+
+	jpres.expr = path;
+	jpres.lax = true;
+
+	foreach(lc, *indirection)
+	{
+		Node	   *accessor = lfirst(lc);
+		JsonPathParseItem *jpi;
+
+		if (IsA(accessor, String))
+		{
+			char	   *field = strVal(accessor);
+			FieldAccessorExpr *accessor_expr;
+
+			jpi = make_jsonpath_item(jpiKey);
+			jpi->value.string.val = field;
+			jpi->value.string.len = strlen(field);
+
+			accessor_expr = makeNode(FieldAccessorExpr);
+			accessor_expr->type = T_FieldAccessorExpr;
+			accessor_expr->fieldname = field;
+
+			sbsref->refupperindexpr = lappend(sbsref->refupperindexpr, accessor_expr);
+		}
+		else if (IsA(accessor, A_Indices))
+		{
+			A_Indices  *ai = castNode(A_Indices, accessor);
+
+			if (!ai->is_slice)
+			{
+				JsonPathParseItem *jpi_from = NULL;
+
+				Assert(ai->uidx && !ai->lidx);
+				jpi_from = make_jsonpath_item_expr(pstate, ai->uidx, &sbsref->refupperindexpr);
+				if (jpi_from == NULL)
+				{
+					/*
+					 * Break out of the loop if the subscript is not a
+					 * non-null integer constant, so that we can fall back to
+					 * jsonb subscripting logic.
+					 *
+					 * This is needed to handle cases with mixed usage of SQL
+					 * standard json simplified accessor syntax and PostgreSQL
+					 * jsonb subscripting syntax, e.g:
+					 *
+					 * select (jb).a['b'].c from jsonb_table;
+					 *
+					 * where dot-notation (.a and .c) is the SQL standard json
+					 * simplified accessor syntax, and the ['b'] subscript is
+					 * the PostgreSQL jsonb subscripting syntax, because 'b'
+					 * is not a non-null constant integer and cannot be used
+					 * for json array access.
+					 *
+					 * In this case, we cannot create a JsonPath item, so we
+					 * break out of the loop and let
+					 * jsonb_subscript_transform() handle this indirection as
+					 * a PostgreSQL jsonb subscript.
+					 */
+					break;
+				}
+
+				jpi = make_jsonpath_item(jpiIndexArray);
+				jpi->value.array.nelems = 1;
+				jpi->value.array.elems = palloc(sizeof(jpi->value.array.elems[0]));
+
+				jpi->value.array.elems[0].from = jpi_from;
+				jpi->value.array.elems[0].to = NULL;
+			}
+			else
+			{
+				Node	   *expr = ai->uidx ? ai->uidx : ai->lidx;
+
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("jsonb subscript does not support slices"),
+						 parser_errposition(pstate, exprLocation(expr))));
+			}
+		}
+		else
+		{
+			/*
+			 * Unexpected node type in indirection list. This should not
+			 * happen with current grammar, but we handle it defensively by
+			 * breaking out of the loop rather than crashing. In case of
+			 * future grammar changes that might introduce new node types,
+			 * this allows us to create a jsonpath from as many indirection
+			 * elements as we can and let transformIndirection() fallback to
+			 * alternative logic to handle the remaining indirection elements.
+			 */
+			Assert(false);		/* not reachable */
+			break;
+		}
+
+		/* append path item */
+		path->next = jpi;
+		path = jpi;
+		pathlen++;
+	}
+
+	if (pathlen == 0)
+		return;
+
+	*indirection = list_delete_first_n(*indirection, pathlen);
+
+	jsp = jsonPathFromParseResult(&jpres, 0, NULL);
+
+	sbsref->refjsonbpath = (Node *) makeConst(JSONPATHOID, -1, InvalidOid, -1, jsp, false, false);
+}
+
 /*
  * Finish parse analysis of a SubscriptingRef expression for a jsonb.
  *
@@ -111,9 +348,27 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	List	   *upperIndexpr = NIL;
 	ListCell   *idx;
 
+	/* Determine the result type of the subscripting operation; always jsonb */
+	sbsref->refrestype = JSONBOID;
+	sbsref->reftypmod = -1;
+
+	if (jsonb_check_jsonpath_needed(*indirection))
+	{
+		jsonb_subscript_make_jsonpath(pstate, indirection, sbsref);
+		if (sbsref->refjsonbpath)
+			return;
+	}
+
 	/*
-	 * Transform and convert the subscript expressions. Jsonb subscripting
-	 * does not support slices, look only at the upper index.
+	 * We reach here only in two cases: (a) the JSON simplified accessor is
+	 * not needed at all (for example, a plain array subscript like [1] or
+	 * object key access like ['a']), or (b) jsonb_subscript_make_jsonpath()
+	 * was called but could not complete the JsonPath construction (for
+	 * example, when mixing dot notation with non-integer subscripts like
+	 * (jb)['a'].b where 'a' is not a constant integer).
+	 *
+	 * In both cases we fall back to pre-standard jsonb subscripting, coercing
+	 * each subscript to array index or object key as needed.
 	 */
 	foreach(idx, *indirection)
 	{
@@ -160,10 +415,6 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->refupperindexpr = upperIndexpr;
 	sbsref->reflowerindexpr = NIL;
 
-	/* Determine the result type of the subscripting operation; always jsonb */
-	sbsref->refrestype = JSONBOID;
-	sbsref->reftypmod = -1;
-
 	/* Remove processed elements */
 	if (upperIndexpr)
 		*indirection = list_delete_first_n(*indirection, list_length(upperIndexpr));
@@ -218,7 +469,7 @@ jsonb_subscript_check_subscripts(ExprState *state,
 			 * For jsonb fetch and assign functions we need to provide path in
 			 * text format. Convert if it's not already text.
 			 */
-			if (workspace->indexOid[i] == INT4OID)
+			if (!workspace->jsonpath && workspace->indexOid[i] == INT4OID)
 			{
 				Datum		datum = sbsrefstate->upperindex[i];
 				char	   *cs = DatumGetCString(DirectFunctionCall1(int4out, datum));
@@ -246,17 +497,32 @@ jsonb_subscript_fetch(ExprState *state,
 {
 	SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
 	JsonbSubWorkspace *workspace = (JsonbSubWorkspace *) sbsrefstate->workspace;
-	Jsonb	   *jsonbSource;
 
 	/* Should not get here if source jsonb (or any subscript) is null */
 	Assert(!(*op->resnull));
 
-	jsonbSource = DatumGetJsonbP(*op->resvalue);
-	*op->resvalue = jsonb_get_element(jsonbSource,
-									  workspace->index,
-									  sbsrefstate->numupper,
-									  op->resnull,
-									  false);
+	if (workspace->jsonpath)
+	{
+		bool		empty = false;
+		bool		error = false;
+
+		*op->resvalue = JsonPathQuery(*op->resvalue, workspace->jsonpath,
+									  JSW_CONDITIONAL,
+									  &empty, &error, NULL,
+									  NULL);
+
+		*op->resnull = empty || error;
+	}
+	else
+	{
+		Jsonb	   *jsonbSource = DatumGetJsonbP(*op->resvalue);
+
+		*op->resvalue = jsonb_get_element(jsonbSource,
+										  workspace->index,
+										  sbsrefstate->numupper,
+										  op->resnull,
+										  false);
+	}
 }
 
 /*
@@ -364,7 +630,7 @@ jsonb_exec_setup(const SubscriptingRef *sbsref,
 {
 	JsonbSubWorkspace *workspace;
 	ListCell   *lc;
-	int			nupper = sbsref->refupperindexpr->length;
+	int			nupper = list_length(sbsref->refupperindexpr);
 	char	   *ptr;
 
 	/* Allocate type-specific workspace with space for per-subscript data */
@@ -373,6 +639,9 @@ jsonb_exec_setup(const SubscriptingRef *sbsref,
 	workspace->expectArray = false;
 	ptr = ((char *) workspace) + MAXALIGN(sizeof(JsonbSubWorkspace));
 
+	if (sbsref->refjsonbpath)
+		workspace->jsonpath = DatumGetJsonPathP(castNode(Const, sbsref->refjsonbpath)->constvalue);
+
 	/*
 	 * This coding assumes sizeof(Datum) >= sizeof(Oid), else we might
 	 * misalign the indexOid pointer
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 3d6e6bdbfd2..baa3ae97d57 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9329,10 +9329,13 @@ get_rule_expr(Node *node, deparse_context *context,
 				 * Parenthesize the argument unless it's a simple Var or a
 				 * FieldSelect.  (In particular, if it's another
 				 * SubscriptingRef, we *must* parenthesize to avoid
-				 * confusion.)
+				 * confusion.) Always add parenthesis if JSON simplified
+				 * accessor is used, for now.
 				 */
-				need_parens = !IsA(sbsref->refexpr, Var) &&
-					!IsA(sbsref->refexpr, FieldSelect);
+				need_parens = (!IsA(sbsref->refexpr, Var) &&
+					!IsA(sbsref->refexpr, FieldSelect)) ||
+						sbsref->refjsonbpath;
+
 				if (need_parens)
 					appendStringInfoChar(buf, '(');
 				get_rule_expr((Node *) sbsref->refexpr, context, showimplicit);
@@ -13005,17 +13008,35 @@ printSubscripts(SubscriptingRef *sbsref, deparse_context *context)
 	lowlist_item = list_head(sbsref->reflowerindexpr);	/* could be NULL */
 	foreach(uplist_item, sbsref->refupperindexpr)
 	{
-		appendStringInfoChar(buf, '[');
-		if (lowlist_item)
+		Node *upper = (Node *) lfirst(uplist_item);
+
+		if (upper && IsA(upper, FieldAccessorExpr))
 		{
+			FieldAccessorExpr *fae = (FieldAccessorExpr *) upper;
+
+			/* Use dot-notation for field access */
+			appendStringInfoChar(buf, '.');
+			appendStringInfoString(buf, quote_identifier(fae->fieldname));
+
+			/* Skip matching low index — field access doesn't use slices */
+			if (lowlist_item)
+				lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
+		}
+		else
+		{
+			/* Use JSONB array subscripting */
+			appendStringInfoChar(buf, '[');
+			if (lowlist_item)
+			{
+				/* If subexpression is NULL, get_rule_expr prints nothing */
+				get_rule_expr((Node *) lfirst(lowlist_item), context, false);
+				appendStringInfoChar(buf, ':');
+				lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
+			}
 			/* If subexpression is NULL, get_rule_expr prints nothing */
-			get_rule_expr((Node *) lfirst(lowlist_item), context, false);
-			appendStringInfoChar(buf, ':');
-			lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
+			get_rule_expr(upper, context, false);
+			appendStringInfoChar(buf, ']');
 		}
-		/* If subexpression is NULL, get_rule_expr prints nothing */
-		get_rule_expr((Node *) lfirst(uplist_item), context, false);
-		appendStringInfoChar(buf, ']');
 	}
 }
 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 6dfca3cb35b..7e89621bd65 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -708,18 +708,30 @@ typedef struct SubscriptingRef
 	int32		reftypmod pg_node_attr(query_jumble_ignore);
 	/* collation of result, or InvalidOid if none */
 	Oid			refcollid pg_node_attr(query_jumble_ignore);
-	/* expressions that evaluate to upper container indexes */
+
+	/*
+	 * expressions that evaluate to upper container indexes or expressions
+	 * that are collected but not evaluated when refjsonbpath is set.
+	 */
 	List	   *refupperindexpr;
 
 	/*
-	 * expressions that evaluate to lower container indexes, or NIL for single
-	 * container element.
+	 * expressions that evaluate to lower container indexes, or NIL for a
+	 * single container element, or expressions that are collected but not
+	 * evaluated when refjsonbpath is set.
 	 */
 	List	   *reflowerindexpr;
 	/* the expression that evaluates to a container value */
 	Expr	   *refexpr;
 	/* expression for the source value, or NULL if fetch */
 	Expr	   *refassgnexpr;
+
+	/*
+	 * container-specific extra information, currently used only by jsonb.
+	 * stores a JsonPath expression when jsonb dot notation is used. NULL for
+	 * simple subscripting.
+	 */
+	Node	   *refjsonbpath;
 } SubscriptingRef;
 
 /*
@@ -2371,4 +2383,40 @@ typedef struct OnConflictExpr
 	List	   *exclRelTlist;	/* tlist of the EXCLUDED pseudo relation */
 } OnConflictExpr;
 
+/*
+ * FieldAccessorExpr - represents a single object member access using dot-notation
+ *		in JSON simplified accessor syntax (e.g., jsonb_col.a).
+ *
+ * These nodes appear as list elements in SubscriptingRef.refupperindexpr to
+ * indicate JSON object key access. They are not evaluable expressions by
+ * themselves but serve as placeholders to preserve source-level syntax for
+ * rule rewriting and deparsing (e.g., in EXPLAIN and view definitions).
+ * Execution is handled by the enclosing SubscriptingRef.
+ *
+ * If dot-notation is used in a SubscriptingRef, the JSON path is represented
+ * as a flat list of FieldAccessorExpr nodes (for object field access), Const
+ * nodes (for array indexes), and NULLs (for omitted slice bounds), rather than
+ * through nested expression trees.
+ *
+ * Note: The flat representation avoids nested FieldAccessorExpr chains,
+ * simplifying evaluation and enabling standard-compliant behavior such as
+ * conditional array wrapping. This avoids the need for position-aware
+ * wrapping/unwrapping logic during execution.
+ *
+ * For example, in the expression:
+ *		('{"a": [{"b": 1}]}'::jsonb).a[0].b
+ * the SubscriptingRef will contain:
+ *		- refexpr: the base expression (the jsonb value)
+ *		- refupperindexpr: [FieldAccessorExpr("a"), Const(0),
+ *			FieldAccessorExpr("b")]
+ *		- reflowerindexpr: [NULL, NULL, NULL] (slice lower bounds not used here)
+ */
+typedef struct FieldAccessorExpr
+{
+	NodeTag		type;
+	char	   *fieldname;		/* name of the JSONB object field accessed via
+								 * dot notation */
+	Oid			faecollid pg_node_attr(query_jumble_ignore);
+}			FieldAccessorExpr;
+
 #endif							/* PRIMNODES_H */
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.c b/src/interfaces/ecpg/test/expected/sql-sqljson.c
index 39221f9ea5d..e6a7ece6dab 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.c
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.c
@@ -417,12 +417,122 @@ if (sqlca.sqlcode < 0) sqlprint();}
 	  for (int i = 0; i < sizeof(is_json); i++)
 		  printf("Found is_json[%d]: %s\n", i, is_json[i] ? "true" : "false");
 
-  { ECPGdisconnect(__LINE__, "CURRENT");
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . \"a\" )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 118 "sqljson.pgc"
 
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 118 "sqljson.pgc"
 
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb . \"a\" )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 121 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 121 "sqljson.pgc"
+
+	// error
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . a )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 124 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 124 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . a . b )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 127 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 127 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( coalesce ( json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . c ) , 'null' ) )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 130 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 130 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ 0 ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 133 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 133 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b . x )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 136 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 136 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) [ 'b' ] [ 0 ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 139 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 139 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 142 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 142 "sqljson.pgc"
+
+	// error
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . * )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 145 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 145 "sqljson.pgc"
+
+	// error
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 148 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 148 "sqljson.pgc"
+
+	// error
+
+  { ECPGdisconnect(__LINE__, "CURRENT");
+#line 151 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 151 "sqljson.pgc"
+
 
   return 0;
 }
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
index e55a95dd711..19f8c58af06 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
@@ -268,5 +268,105 @@ SQL error: cannot use type jsonb in RETURNING clause of JSON_SERIALIZE() on line
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_get_data on line 102: RESULT: f offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 118: query: select json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . "a" ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 118: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 118: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 118: RESULT: {"b": 1, "c": 2} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 121: query: select json ( '{"a": {"b": 1, "c": 2}}' :: jsonb . "a" ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 121: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 121: bad response - ERROR:  schema "jsonb" does not exist
+LINE 1: select json ( '{"a": {"b": 1, "c": 2}}' :: jsonb . "a" )
+                                                   ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 3F000 (sqlcode -400): schema "jsonb" does not exist on line 121
+[NO_PID]: sqlca: code: -400, state: 3F000
+SQL error: schema "jsonb" does not exist on line 121
+[NO_PID]: ecpg_execute on line 124: query: select json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . a ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 124: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 124: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 124: RESULT: {"b": 1, "c": 2} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 127: query: select json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . a . b ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 127: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 127: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 127: RESULT: 1 offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 130: query: select json ( coalesce ( json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . c ) , 'null' ) ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 130: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 130: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 130: RESULT: null offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 133: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 0 ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 133: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 133: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 133: RESULT: {"x": 1} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 136: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b . x ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 136: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 136: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 136: RESULT: [1, [12, {"y": 1}]] offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 139: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 139: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 139: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 139: RESULT: {"x": 1} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 142: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 142: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 142: bad response - ERROR:  jsonb subscript does not support slices
+LINE 1: ..., {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] )
+                                                                ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 42804 (sqlcode -400): jsonb subscript does not support slices on line 142
+[NO_PID]: sqlca: code: -400, state: 42804
+SQL error: jsonb subscript does not support slices on line 142
+[NO_PID]: ecpg_execute on line 145: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . * ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 145: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 145: bad response - ERROR:  row expansion via "*" is not supported here
+LINE 1: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x...
+                        ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 0A000 (sqlcode -400): row expansion via "*" is not supported here on line 145
+[NO_PID]: sqlca: code: -400, state: 0A000
+SQL error: row expansion via "*" is not supported here on line 145
+[NO_PID]: ecpg_execute on line 148: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 148: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 148: bad response - ERROR:  jsonb subscript does not support slices
+LINE 1: ...x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )
+                                                                ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 42804 (sqlcode -400): jsonb subscript does not support slices on line 148
+[NO_PID]: sqlca: code: -400, state: 42804
+SQL error: jsonb subscript does not support slices on line 148
 [NO_PID]: ecpg_finish: connection ecpg1_regression closed
 [NO_PID]: sqlca: code: 0, state: 00000
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
index 83f8df13e5a..442d36931f1 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
@@ -28,3 +28,10 @@ Found is_json[4]: false
 Found is_json[5]: false
 Found is_json[6]: true
 Found is_json[7]: false
+Found json={"b": 1, "c": 2}
+Found json={"b": 1, "c": 2}
+Found json=1
+Found json=null
+Found json={"x": 1}
+Found json=[1, [12, {"y": 1}]]
+Found json={"x": 1}
diff --git a/src/interfaces/ecpg/test/sql/sqljson.pgc b/src/interfaces/ecpg/test/sql/sqljson.pgc
index ddcbcc3b3cb..57a9bff424d 100644
--- a/src/interfaces/ecpg/test/sql/sqljson.pgc
+++ b/src/interfaces/ecpg/test/sql/sqljson.pgc
@@ -115,6 +115,39 @@ EXEC SQL END DECLARE SECTION;
 	  for (int i = 0; i < sizeof(is_json); i++)
 		  printf("Found is_json[%d]: %s\n", i, is_json[i] ? "true" : "false");
 
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}}'::jsonb)."a") INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON('{"a": {"b": 1, "c": 2}}'::jsonb."a") INTO :json;
+	// error
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}}'::jsonb).a) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}}'::jsonb).a.b) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(COALESCE(JSON(('{"a": {"b": 1, "c": 2}}'::jsonb).c), 'null')) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b[0]) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b.x) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb)['b'][0]) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b[1].x[0:]) INTO :json;
+	// error
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).*) INTO :json;
+	// error
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb)['b'][0:]) INTO :json;
+	// error
+
   EXEC SQL DISCONNECT;
 
   return 0;
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5a1eb18aba2..37d71b23d5d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -4989,6 +4989,12 @@ select ('123'::jsonb)['a'];
  
 (1 row)
 
+select ('123'::jsonb).a;
+ a 
+---
+ 
+(1 row)
+
 select ('123'::jsonb)[0];
  jsonb 
 -------
@@ -5001,12 +5007,24 @@ select ('123'::jsonb)[NULL];
  
 (1 row)
 
+select ('123'::jsonb).NULL;
+ null 
+------
+ 
+(1 row)
+
 select ('{"a": 1}'::jsonb)['a'];
  jsonb 
 -------
  1
 (1 row)
 
+select ('{"a": 1}'::jsonb).a;
+ a 
+---
+ 1
+(1 row)
+
 select ('{"a": 1}'::jsonb)[0];
  jsonb 
 -------
@@ -5019,6 +5037,12 @@ select ('{"a": 1}'::jsonb)['not_exist'];
  
 (1 row)
 
+select ('{"a": 1}'::jsonb)."not_exist";
+ not_exist 
+-----------
+ 
+(1 row)
+
 select ('{"a": 1}'::jsonb)[NULL];
  jsonb 
 -------
@@ -5031,6 +5055,12 @@ select ('[1, "2", null]'::jsonb)['a'];
  
 (1 row)
 
+select ('[1, "2", null]'::jsonb).a;
+ a 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[0];
  jsonb 
 -------
@@ -5043,6 +5073,12 @@ select ('[1, "2", null]'::jsonb)['1'];
  "2"
 (1 row)
 
+select ('[1, "2", null]'::jsonb)."1";
+ 1 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[1.0];
 ERROR:  subscript type numeric is not supported
 LINE 1: select ('[1, "2", null]'::jsonb)[1.0];
@@ -5072,6 +5108,12 @@ select ('[1, "2", null]'::jsonb)[1]['a'];
  
 (1 row)
 
+select ('[1, "2", null]'::jsonb)[1].a;
+ a 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[1][0];
  jsonb 
 -------
@@ -5084,56 +5126,127 @@ select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['b'];
  "c"
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).b;
+  b  
+-----
+ "c"
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'];
    jsonb   
 -----------
  [1, 2, 3]
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d;
+     d     
+-----------
+ [1, 2, 3]
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'][1];
  jsonb 
 -------
  2
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d[1];
+ d 
+---
+ 2
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d']['a'];
  jsonb 
 -------
  
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['a'];
+ d 
+---
+ 
+(1 row)
+
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d.a;
+ a 
+---
+ 
+(1 row)
+
+select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d['a'][0];
+ d 
+---
+ 1
+(1 row)
+
+select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d.a[0];
+ a 
+---
+ 1
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1'];
      jsonb     
 ---------------
  {"a2": "aaa"}
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1;
+      a1       
+---------------
+ {"a2": "aaa"}
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2'];
  jsonb 
 -------
  "aaa"
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2;
+  a2   
+-------
+ "aaa"
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']['a3'];
  jsonb 
 -------
  
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2.a3;
+ a3 
+----
+ 
+(1 row)
+
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'];
          jsonb         
 -----------------------
  ["aaa", "bbb", "ccc"]
 (1 row)
 
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1;
+          b1           
+-----------------------
+ ["aaa", "bbb", "ccc"]
+(1 row)
+
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'][2];
  jsonb 
 -------
  "ccc"
 (1 row)
 
--- slices are not supported
-select ('{"a": 1}'::jsonb)['a':'b'];
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1[2];
+  b1   
+-------
+ "ccc"
+(1 row)
+
+select ('{"a": 1}'::jsonb)['a':'b']; -- fails
 ERROR:  jsonb subscript does not support slices
 LINE 1: select ('{"a": 1}'::jsonb)['a':'b'];
                                        ^
@@ -5831,3 +5944,299 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
  12345
 (1 row)
 
+-- dot notation
+CREATE TABLE test_jsonb_dot_notation AS
+SELECT '{"a": [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], "b": [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]}'::jsonb jb;
+SELECT (jb).a FROM test_jsonb_dot_notation;
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+SELECT (jb)."a" FROM test_jsonb_dot_notation; -- double quote should work
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+SELECT (jb).'a' FROM test_jsonb_dot_notation; -- single quote should not work
+ERROR:  syntax error at or near "'a'"
+LINE 1: SELECT (jb).'a' FROM test_jsonb_dot_notation;
+                    ^
+select (jb)[0].a from test_jsonb_dot_notation; -- returns same result as (jb).a
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+select (jb)[1].a from test_jsonb_dot_notation; -- returns NULL
+ a 
+---
+ 
+(1 row)
+
+SELECT (jb).b FROM test_jsonb_dot_notation;
+                         b                         
+---------------------------------------------------
+ [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]
+(1 row)
+
+SELECT (jb).c FROM test_jsonb_dot_notation;
+ c 
+---
+ 
+(1 row)
+
+SELECT (jb).a.b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT (jb).a[2].b FROM test_jsonb_dot_notation;
+  b  
+-----
+ "c"
+(1 row)
+
+SELECT (jb).a.x.y FROM test_jsonb_dot_notation;
+   y   
+-------
+ "yyy"
+(1 row)
+
+SELECT (jb).b.x.z FROM test_jsonb_dot_notation;
+   z   
+-------
+ "ZZZ"
+(1 row)
+
+SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
+ c 
+---
+ 
+(1 row)
+
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t; -- fails
+ERROR:  jsonb subscript does not support slices
+SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
+ERROR:  type jsonb is not composite
+-- explains should work
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+                  QUERY PLAN                  
+----------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation t
+   Output: (jb).a
+(2 rows)
+
+SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb).a[1]
+(2 rows)
+
+SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+ a 
+---
+ 2
+(1 row)
+
+-- views should work
+CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT (jb).a[3].x.y AS y
+   FROM test_jsonb_dot_notation
+-- mixed syntax
+DROP VIEW test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb['a'::text]).b
+(2 rows)
+
+SELECT (jb)['a'].b FROM test_jsonb_dot_notation; -- returns an array due to lax mode
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT (jb['a'::text]).b AS b
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: ((jb).a)['b'::text]
+(2 rows)
+
+SELECT (jb).a['b'] FROM test_jsonb_dot_notation; -- returns NULL because ['b'] looks for strict match in an object
+ a 
+---
+ 
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT ((jb).a)['b'::text] AS a
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+ a 
+---
+ 
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: ((jb).b.x)['z'::text]
+(2 rows)
+
+SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation; -- warnings
+   x   
+-------
+ "ZZZ"
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT ((jb).b.x)['z'::text] AS x
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+   x   
+-------
+ "ZZZ"
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: ((jb['b'::text]).x)['z'::text]
+(2 rows)
+
+SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;  -- warnings
+   x   
+-------
+ "ZZZ"
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT ((jb['b'::text]).x)['z'::text] AS x
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+   x   
+-------
+ "ZZZ"
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (((jb).b)['x'::text]).z
+(2 rows)
+
+SELECT (jb).b['x'].z FROM test_jsonb_dot_notation; -- returns NULL
+ z 
+---
+ 
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT (((jb).b)['x'::text]).z AS z
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+ z 
+---
+ 
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: ((jb).b)['x'::text]['z'::text]
+(2 rows)
+
+SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation; -- returns NULL
+ b 
+---
+ 
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT ((jb).b)['x'::text]['z'::text] AS b
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+ b 
+---
+ 
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb['b'::text]['x'::text]).z
+(2 rows)
+
+SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation; -- returns NULL
+ z 
+---
+ 
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT (jb['b'::text]['x'::text]).z AS z
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+ z 
+---
+ 
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+-- clean up
+DROP TABLE test_jsonb_dot_notation;
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 57c11acddfe..5fc53e1c9aa 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1304,33 +1304,51 @@ select jsonb_insert('{"a": {"b": "value"}}', '{a, b}', '"new_value"', true);
 
 -- jsonb subscript
 select ('123'::jsonb)['a'];
+select ('123'::jsonb).a;
 select ('123'::jsonb)[0];
 select ('123'::jsonb)[NULL];
+select ('123'::jsonb).NULL;
 select ('{"a": 1}'::jsonb)['a'];
+select ('{"a": 1}'::jsonb).a;
 select ('{"a": 1}'::jsonb)[0];
 select ('{"a": 1}'::jsonb)['not_exist'];
+select ('{"a": 1}'::jsonb)."not_exist";
 select ('{"a": 1}'::jsonb)[NULL];
 select ('[1, "2", null]'::jsonb)['a'];
+select ('[1, "2", null]'::jsonb).a;
 select ('[1, "2", null]'::jsonb)[0];
 select ('[1, "2", null]'::jsonb)['1'];
+select ('[1, "2", null]'::jsonb)."1";
 select ('[1, "2", null]'::jsonb)[1.0];
 select ('[1, "2", null]'::jsonb)[2];
 select ('[1, "2", null]'::jsonb)[3];
 select ('[1, "2", null]'::jsonb)[-2];
 select ('[1, "2", null]'::jsonb)[1]['a'];
+select ('[1, "2", null]'::jsonb)[1].a;
 select ('[1, "2", null]'::jsonb)[1][0];
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['b'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).b;
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d;
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'][1];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d[1];
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d']['a'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['a'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d.a;
+select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d['a'][0];
+select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d.a[0];
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1;
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2;
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']['a3'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2.a3;
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'];
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1;
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'][2];
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1[2];
 
--- slices are not supported
-select ('{"a": 1}'::jsonb)['a':'b'];
+select ('{"a": 1}'::jsonb)['a':'b']; -- fails
 select ('[1, "2", null]'::jsonb)[1:2];
 select ('[1, "2", null]'::jsonb)[:2];
 select ('[1, "2", null]'::jsonb)[1:];
@@ -1590,3 +1608,94 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::float8;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int2;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int4;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
+
+-- dot notation
+CREATE TABLE test_jsonb_dot_notation AS
+SELECT '{"a": [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], "b": [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]}'::jsonb jb;
+
+SELECT (jb).a FROM test_jsonb_dot_notation;
+SELECT (jb)."a" FROM test_jsonb_dot_notation; -- double quote should work
+SELECT (jb).'a' FROM test_jsonb_dot_notation; -- single quote should not work
+select (jb)[0].a from test_jsonb_dot_notation; -- returns same result as (jb).a
+select (jb)[1].a from test_jsonb_dot_notation; -- returns NULL
+SELECT (jb).b FROM test_jsonb_dot_notation;
+SELECT (jb).c FROM test_jsonb_dot_notation;
+SELECT (jb).a.b FROM test_jsonb_dot_notation;
+SELECT (jb).a[2].b FROM test_jsonb_dot_notation;
+SELECT (jb).a.x.y FROM test_jsonb_dot_notation;
+SELECT (jb).b.x.z FROM test_jsonb_dot_notation;
+SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t; -- fails
+SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
+
+-- explains should work
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+
+-- views should work
+CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+
+-- mixed syntax
+DROP VIEW test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
+SELECT (jb)['a'].b FROM test_jsonb_dot_notation; -- returns an array due to lax mode
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+SELECT (jb).a['b'] FROM test_jsonb_dot_notation; -- returns NULL because ['b'] looks for strict match in an object
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation; -- warnings
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;  -- warnings
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+SELECT (jb).b['x'].z FROM test_jsonb_dot_notation; -- returns NULL
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation; -- returns NULL
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
+SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation; -- returns NULL
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+-- clean up
+DROP TABLE test_jsonb_dot_notation;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index e90af5b2ad3..23e38c163c5 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -805,6 +805,7 @@ FdwRoutine
 FetchDirection
 FetchDirectionKeywords
 FetchStmt
+FieldAccessorExpr
 FieldSelect
 FieldStore
 File
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v19-0006-Implement-Jsonb-subscripting-with-slicing.patch (21.5K, ../../CAK98qZ2nezKeQFqPV9GX80mDiLqS2Fh6MRBGz6ox_HkQF86asA@mail.gmail.com/4-v19-0006-Implement-Jsonb-subscripting-with-slicing.patch)
  download | inline diff:
From fbc380b0404bfc29e4e2cbc7ddaee1de9fe383fa Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v19 6/7] Implement Jsonb subscripting with slicing

Previously, slicing was not supported for jsonb subscripting. This commit
implements subscripting with slicing as part of the JSON simplified accessor
syntax specified in SQL:2023.

Co-authored-by: Nikita Glukhov <[email protected]>
Co-authored-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Mark Dilger <[email protected]>
Reviewed-by: Jian He <[email protected]>
Reviewed-by: Vik Fearing <[email protected]>
Reviewed-by: Nikita Malakhov <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Tested-by: Mark Dilger <[email protected]>
Tested-by: Jian He <[email protected]>
---
 src/backend/utils/adt/jsonbsubs.c             | 119 ++++++++------
 .../ecpg/test/expected/sql-sqljson.c          |  16 +-
 .../ecpg/test/expected/sql-sqljson.stderr     |  26 ++--
 .../ecpg/test/expected/sql-sqljson.stdout     |   3 +
 src/interfaces/ecpg/test/sql/sqljson.pgc      |   7 +-
 src/test/regress/expected/jsonb.out           | 145 ++++++++++++++++--
 src/test/regress/sql/jsonb.sql                |  53 ++++++-
 7 files changed, 286 insertions(+), 83 deletions(-)

diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index ccc5f3c6e94..4762f6bd11c 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -111,7 +111,7 @@ coerce_jsonpath_subscript_to_int4_or_text(ParseState *pstate, Node *subExpr)
  *
  * JsonPath is needed if the indirection list includes:
  * - String-based access (dot notation)
- * - Slice-based subscripting (when isSlice is true)
+ * - Slice-based subscripting
  *
  * Otherwise, simple jsonb subscripting is enough.
  */
@@ -127,7 +127,11 @@ jsonb_check_jsonpath_needed(List *indirection)
 		if (IsA(accessor, String))
 			return true;
 		else
+		{
 			Assert(IsA(accessor, A_Indices));
+			if (castNode(A_Indices, accessor)->is_slice)
+				return true;
+		}
 	}
 
 	return false;
@@ -152,20 +156,45 @@ make_jsonpath_item(JsonPathItemType type)
 	return v;
 }
 
+/*
+ * Build a JsonPathParseItem for a constant integer value.
+ *
+ * This function constructs a jpiNumeric item for use in JsonPath,
+ * and appends a matching Const(INT4) node to the given expression list
+ * for use in EXPLAIN, views, etc.
+ *
+ * Parameters:
+ * - val: integer constant value
+ * - exprs: list of expression nodes (updated in place)
+ */
+static JsonPathParseItem *
+make_jsonpath_item_int(int32 val, List **exprs)
+{
+	JsonPathParseItem *jpi = make_jsonpath_item(jpiNumeric);
+
+	jpi->value.numeric =
+		DatumGetNumeric(DirectFunctionCall1(int4_numeric, Int32GetDatum(val)));
+
+	*exprs = lappend(*exprs, makeConst(INT4OID, -1, InvalidOid, 4,
+									   Int32GetDatum(val), false, true));
+
+	return jpi;
+}
+
 /*
  * Convert a constant integer expression into a JsonPathParseItem.
  *
  * The input expression must be a non-null constant of type INT4. Returns NULL otherwise.
- * This function constructs a jpiNumeric item for use in JsonPath and appends a matching
- * Const(INT4) node to the given expression list for use in EXPLAIN, views, etc.
+ * The function extracts the value and delegates it to make_jsonpath_item_int().
  *
  * Parameters:
  * - pstate: parse state context
  * - expr: input expression node
  * - exprs: list of expression nodes (updated in place)
+ * - no_error: returns NULL when the data type doesn't match. Otherwise, emits an ERROR.
  */
 static JsonPathParseItem *
-make_jsonpath_item_expr(ParseState *pstate, Node *expr, List **exprs)
+make_jsonpath_item_expr(ParseState *pstate, Node *expr, List **exprs, bool no_error)
 {
 	Const	   *cnst;
 
@@ -175,20 +204,17 @@ make_jsonpath_item_expr(ParseState *pstate, Node *expr, List **exprs)
 	{
 		cnst = (Const *) expr;
 		if (cnst->consttype == INT4OID && !(cnst->constisnull))
-		{
-			JsonPathParseItem *jpi = make_jsonpath_item(jpiNumeric);
-
-			jpi->value.numeric =
-				DatumGetNumeric(DirectFunctionCall1(int4_numeric, Int32GetDatum(cnst->constvalue)));
-
-			*exprs = lappend(*exprs, makeConst(INT4OID, -1, InvalidOid, 4,
-											   Int32GetDatum(cnst->constvalue), false, true));
-
-			return jpi;
-		}
+			return make_jsonpath_item_int(DatumGetInt32(cnst->constvalue), exprs);
 	}
 
-	return NULL;
+	if (no_error)
+		return NULL;
+	else
+		ereport(ERROR,
+				errcode(ERRCODE_DATATYPE_MISMATCH),
+				errmsg("only non-null integer constants are supported for jsonb simplified accessor subscripting"),
+				errhint("use int data type for subscripting with slicing."),
+				parser_errposition(pstate, exprLocation(expr)));
 }
 
 /*
@@ -251,13 +277,12 @@ jsonb_subscript_make_jsonpath(ParseState *pstate, List **indirection, Subscripti
 		else if (IsA(accessor, A_Indices))
 		{
 			A_Indices  *ai = castNode(A_Indices, accessor);
+			JsonPathParseItem *jpi_from = NULL;
 
 			if (!ai->is_slice)
 			{
-				JsonPathParseItem *jpi_from = NULL;
-
 				Assert(ai->uidx && !ai->lidx);
-				jpi_from = make_jsonpath_item_expr(pstate, ai->uidx, &sbsref->refupperindexpr);
+				jpi_from = make_jsonpath_item_expr(pstate, ai->uidx, &sbsref->refupperindexpr, true);
 				if (jpi_from == NULL)
 				{
 					/*
@@ -284,22 +309,34 @@ jsonb_subscript_make_jsonpath(ParseState *pstate, List **indirection, Subscripti
 					 */
 					break;
 				}
+			}
 
-				jpi = make_jsonpath_item(jpiIndexArray);
-				jpi->value.array.nelems = 1;
-				jpi->value.array.elems = palloc(sizeof(jpi->value.array.elems[0]));
+			jpi = make_jsonpath_item(jpiIndexArray);
+			jpi->value.array.nelems = 1;
+			jpi->value.array.elems = palloc(sizeof(jpi->value.array.elems[0]));
 
+			if (!ai->is_slice)
+			{
 				jpi->value.array.elems[0].from = jpi_from;
 				jpi->value.array.elems[0].to = NULL;
 			}
 			else
 			{
-				Node	   *expr = ai->uidx ? ai->uidx : ai->lidx;
+				while (list_length(sbsref->reflowerindexpr) < list_length(sbsref->refupperindexpr))
+					sbsref->reflowerindexpr = lappend(sbsref->reflowerindexpr, NULL);
+
+				if (ai->lidx)
+					jpi->value.array.elems[0].from = make_jsonpath_item_expr(pstate, ai->lidx, &sbsref->reflowerindexpr, false);
+				else
+					jpi->value.array.elems[0].from = make_jsonpath_item_int(0, &sbsref->reflowerindexpr);
 
-				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("jsonb subscript does not support slices"),
-						 parser_errposition(pstate, exprLocation(expr))));
+				if (ai->uidx)
+					jpi->value.array.elems[0].to = make_jsonpath_item_expr(pstate, ai->uidx, &sbsref->refupperindexpr, false);
+				else
+				{
+					jpi->value.array.elems[0].to = make_jsonpath_item(jpiLast);
+					sbsref->refupperindexpr = lappend(sbsref->refupperindexpr, NULL);
+				}
 			}
 		}
 		else
@@ -381,32 +418,12 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 		ai = lfirst_node(A_Indices, idx);
 
 		if (ai->is_slice)
-		{
-			Node	   *expr = ai->uidx ? ai->uidx : ai->lidx;
+			break;
 
-			ereport(ERROR,
-					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("jsonb subscript does not support slices"),
-					 parser_errposition(pstate, exprLocation(expr))));
-		}
+		Assert(!ai->lidx && ai->uidx);
 
-		if (ai->uidx)
-		{
-			subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
-			subExpr = coerce_jsonpath_subscript_to_int4_or_text(pstate, subExpr);
-		}
-		else
-		{
-			/*
-			 * Slice with omitted upper bound. Should not happen as we already
-			 * errored out on slice earlier, but handle this just in case.
-			 */
-			Assert(ai->is_slice);
-			ereport(ERROR,
-					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("jsonb subscript does not support slices"),
-					 parser_errposition(pstate, exprLocation(ai->uidx))));
-		}
+		subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
+		subExpr = coerce_jsonpath_subscript_to_int4_or_text(pstate, subExpr);
 
 		upperIndexpr = lappend(upperIndexpr, subExpr);
 	}
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.c b/src/interfaces/ecpg/test/expected/sql-sqljson.c
index e6a7ece6dab..935b47a3b9a 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.c
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.c
@@ -505,7 +505,7 @@ if (sqlca.sqlcode < 0) sqlprint();}
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 142 "sqljson.pgc"
 
-	// error
+	printf("Found json=%s\n", json);
 
 	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . * )", ECPGt_EOIT, 
 	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
@@ -525,14 +525,24 @@ if (sqlca.sqlcode < 0) sqlprint();}
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 148 "sqljson.pgc"
 
-	// error
+	printf("Found json=%s\n", json);
 
-  { ECPGdisconnect(__LINE__, "CURRENT");
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ : ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 151 "sqljson.pgc"
 
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 151 "sqljson.pgc"
 
+	printf("Found json=%s\n", json);
+
+  { ECPGdisconnect(__LINE__, "CURRENT");
+#line 154 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 154 "sqljson.pgc"
+
 
   return 0;
 }
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
index 19f8c58af06..f3f899c6d87 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
@@ -339,13 +339,10 @@ SQL error: schema "jsonb" does not exist on line 121
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 142: using PQexec
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_check_PQresult on line 142: bad response - ERROR:  jsonb subscript does not support slices
-LINE 1: ..., {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] )
-                                                                ^
+[NO_PID]: ecpg_process_output on line 142: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 142: RESULT: [12, {"y": 1}] offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: raising sqlstate 42804 (sqlcode -400): jsonb subscript does not support slices on line 142
-[NO_PID]: sqlca: code: -400, state: 42804
-SQL error: jsonb subscript does not support slices on line 142
 [NO_PID]: ecpg_execute on line 145: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . * ); with 0 parameter(s) on connection ecpg1_regression
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 145: using PQexec
@@ -361,12 +358,17 @@ SQL error: row expansion via "*" is not supported here on line 145
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 148: using PQexec
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_check_PQresult on line 148: bad response - ERROR:  jsonb subscript does not support slices
-LINE 1: ...x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )
-                                                                ^
+[NO_PID]: ecpg_process_output on line 148: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 148: RESULT: [{"x": 1}, {"x": [12, {"y": 1}]}] offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 151: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 151: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 151: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 151: RESULT: [{"x": 1}, {"x": [12, {"y": 1}]}] offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: raising sqlstate 42804 (sqlcode -400): jsonb subscript does not support slices on line 148
-[NO_PID]: sqlca: code: -400, state: 42804
-SQL error: jsonb subscript does not support slices on line 148
 [NO_PID]: ecpg_finish: connection ecpg1_regression closed
 [NO_PID]: sqlca: code: 0, state: 00000
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
index 442d36931f1..d01a8457f01 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
@@ -35,3 +35,6 @@ Found json=null
 Found json={"x": 1}
 Found json=[1, [12, {"y": 1}]]
 Found json={"x": 1}
+Found json=[12, {"y": 1}]
+Found json=[{"x": 1}, {"x": [12, {"y": 1}]}]
+Found json=[{"x": 1}, {"x": [12, {"y": 1}]}]
diff --git a/src/interfaces/ecpg/test/sql/sqljson.pgc b/src/interfaces/ecpg/test/sql/sqljson.pgc
index 57a9bff424d..9423d25fd0b 100644
--- a/src/interfaces/ecpg/test/sql/sqljson.pgc
+++ b/src/interfaces/ecpg/test/sql/sqljson.pgc
@@ -140,13 +140,16 @@ EXEC SQL END DECLARE SECTION;
 	printf("Found json=%s\n", json);
 
 	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b[1].x[0:]) INTO :json;
-	// error
+	printf("Found json=%s\n", json);
 
 	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).*) INTO :json;
 	// error
 
 	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb)['b'][0:]) INTO :json;
-	// error
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b[:]) INTO :json;
+	printf("Found json=%s\n", json);
 
   EXEC SQL DISCONNECT;
 
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 37d71b23d5d..2702edc9705 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5247,23 +5247,34 @@ select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b
 (1 row)
 
 select ('{"a": 1}'::jsonb)['a':'b']; -- fails
-ERROR:  jsonb subscript does not support slices
+ERROR:  only non-null integer constants are supported for jsonb simplified accessor subscripting
 LINE 1: select ('{"a": 1}'::jsonb)['a':'b'];
-                                       ^
+                                   ^
+HINT:  use int data type for subscripting with slicing.
 select ('[1, "2", null]'::jsonb)[1:2];
-ERROR:  jsonb subscript does not support slices
-LINE 1: select ('[1, "2", null]'::jsonb)[1:2];
-                                           ^
+    jsonb    
+-------------
+ ["2", null]
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[:2];
-ERROR:  jsonb subscript does not support slices
-LINE 1: select ('[1, "2", null]'::jsonb)[:2];
-                                          ^
+     jsonb      
+----------------
+ [1, "2", null]
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[1:];
-ERROR:  jsonb subscript does not support slices
-LINE 1: select ('[1, "2", null]'::jsonb)[1:];
-                                         ^
+    jsonb    
+-------------
+ ["2", null]
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[:];
-ERROR:  jsonb subscript does not support slices
+     jsonb      
+----------------
+ [1, "2", null]
+(1 row)
+
 create TEMP TABLE test_jsonb_subscript (
        id int,
        test_json jsonb
@@ -6017,8 +6028,42 @@ SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
  
 (1 row)
 
-SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t; -- fails
-ERROR:  jsonb subscript does not support slices
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
+            x             
+--------------------------
+ {"y": "YYY", "z": "ZZZ"}
+(1 row)
+
+SELECT (jb).a[2:3].b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT (jb).a[2:].b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT (jb).a[:2].b FROM test_jsonb_dot_notation;
+  b  
+-----
+ "c"
+(1 row)
+
+SELECT (jb).a[:].b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
+            x             
+--------------------------
+ {"y": "YYY", "z": "ZZZ"}
+(1 row)
+
 SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
 ERROR:  type jsonb is not composite
 -- explains should work
@@ -6054,6 +6099,28 @@ CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_d
 CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
  SELECT (jb).a[3].x.y AS y
    FROM test_jsonb_dot_notation
+CREATE VIEW v2 AS SELECT (jb).a[3:].x.y[:-1] FROM test_jsonb_dot_notation;
+\sv v2
+CREATE OR REPLACE VIEW public.v2 AS
+ SELECT (jb).a[3:].x.y[0:'-1'::integer] AS y
+   FROM test_jsonb_dot_notation
+SELECT * from v2;
+ y 
+---
+ 
+(1 row)
+
+CREATE VIEW v3 AS SELECT (jb).a[:3].x.y[-1:] FROM test_jsonb_dot_notation;
+\sv v3
+CREATE OR REPLACE VIEW public.v3 AS
+ SELECT (jb).a[0:3].x.y['-1'::integer:] AS y
+   FROM test_jsonb_dot_notation
+SELECT * from v3;
+   y   
+-------
+ "yyy"
+(1 row)
+
 -- mixed syntax
 DROP VIEW test_jsonb_dot_notation_v1;
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
@@ -6238,5 +6305,55 @@ SELECT * from test_jsonb_dot_notation_v1;
 (1 row)
 
 DROP VIEW public.test_jsonb_dot_notation_v1;
+-- jsonb array access in plpgsql
+DO $$
+    DECLARE
+        a jsonb := '[1,2,3,4,5,6,7]'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := a[2:];
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+NOTICE:  [1, 2, 3, 4, 5, 6, 7]
+NOTICE:  [3, 4, 5, 6, 7]
+NOTICE:  [5, 6, 7]
+NOTICE:  7
+-- jsonb dot access in plpgsql
+DO $$
+    DECLARE
+        a jsonb := '{"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := COALESCE(a."NU", a[2]); -- fails
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+NOTICE:  {"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}
+ERROR:  missing FROM-clause entry for table "a"
+LINE 1: a := COALESCE(a."NU", a[2])
+                      ^
+QUERY:  a := COALESCE(a."NU", a[2])
+CONTEXT:  PL/pgSQL function inline_code_block line 8 at assignment
+DO $$
+    DECLARE
+        a jsonb := '{"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := COALESCE((a)."NU", a[2]); -- succeeds
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+NOTICE:  {"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}
+NOTICE:  [{"": [[3]]}, [6], [2], "bCi"]
+NOTICE:  [2]
 -- clean up
+DROP VIEW v2;
+DROP VIEW v3;
 DROP TABLE test_jsonb_dot_notation;
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 5fc53e1c9aa..de2c9cda094 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1625,7 +1625,12 @@ SELECT (jb).a[2].b FROM test_jsonb_dot_notation;
 SELECT (jb).a.x.y FROM test_jsonb_dot_notation;
 SELECT (jb).b.x.z FROM test_jsonb_dot_notation;
 SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
-SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t; -- fails
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
+SELECT (jb).a[2:3].b FROM test_jsonb_dot_notation;
+SELECT (jb).a[2:].b FROM test_jsonb_dot_notation;
+SELECT (jb).a[:2].b FROM test_jsonb_dot_notation;
+SELECT (jb).a[:].b FROM test_jsonb_dot_notation;
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
 SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
 
 -- explains should work
@@ -1637,6 +1642,12 @@ SELECT (jb).a[1] FROM test_jsonb_dot_notation;
 -- views should work
 CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
 \sv test_jsonb_dot_notation_v1
+CREATE VIEW v2 AS SELECT (jb).a[3:].x.y[:-1] FROM test_jsonb_dot_notation;
+\sv v2
+SELECT * from v2;
+CREATE VIEW v3 AS SELECT (jb).a[:3].x.y[-1:] FROM test_jsonb_dot_notation;
+\sv v3
+SELECT * from v3;
 
 -- mixed syntax
 DROP VIEW test_jsonb_dot_notation_v1;
@@ -1697,5 +1708,45 @@ SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
 SELECT * from test_jsonb_dot_notation_v1;
 DROP VIEW public.test_jsonb_dot_notation_v1;
 
+-- jsonb array access in plpgsql
+DO $$
+    DECLARE
+        a jsonb := '[1,2,3,4,5,6,7]'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := a[2:];
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+
+-- jsonb dot access in plpgsql
+DO $$
+    DECLARE
+        a jsonb := '{"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := COALESCE(a."NU", a[2]); -- fails
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+
+DO $$
+    DECLARE
+        a jsonb := '{"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := COALESCE((a)."NU", a[2]); -- succeeds
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+
 -- clean up
+DROP VIEW v2;
+DROP VIEW v3;
 DROP TABLE test_jsonb_dot_notation;
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v19-0001-Allow-transformation-of-only-a-sublist-of-subscr.patch (9.1K, ../../CAK98qZ2nezKeQFqPV9GX80mDiLqS2Fh6MRBGz6ox_HkQF86asA@mail.gmail.com/5-v19-0001-Allow-transformation-of-only-a-sublist-of-subscr.patch)
  download | inline diff:
From db918761760421478e2f00ac1f2127eee071f42c Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v19 1/7] Allow transformation of only a sublist of subscripts

This is a preparation step for allowing subscripting containers to
transform only a prefix of an indirection list and modify the list
in-place by removing the processed elements. Currently, all elements
are consumed, and the list is set to NIL after transformation.

In the following commit, subscripting containers will gain the
flexibility to stop transformation when encountering an unsupported
indirection and return the remaining indirections to the caller.

Reviewed-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Jian He <[email protected]>
Reviewed-by: Chao Li <[email protected]>
---
 contrib/hstore/hstore_subs.c      | 10 ++++++----
 src/backend/parser/parse_expr.c   |  9 ++++-----
 src/backend/parser/parse_node.c   |  4 ++--
 src/backend/parser/parse_target.c |  2 +-
 src/backend/utils/adt/arraysubs.c |  6 ++++--
 src/backend/utils/adt/jsonbsubs.c |  6 ++++--
 src/include/nodes/subscripting.h  |  7 ++++++-
 src/include/parser/parse_node.h   |  2 +-
 8 files changed, 28 insertions(+), 18 deletions(-)

diff --git a/contrib/hstore/hstore_subs.c b/contrib/hstore/hstore_subs.c
index 3d03f66fa0d..1b29543ab67 100644
--- a/contrib/hstore/hstore_subs.c
+++ b/contrib/hstore/hstore_subs.c
@@ -40,7 +40,7 @@
  */
 static void
 hstore_subscript_transform(SubscriptingRef *sbsref,
-						   List *indirection,
+						   List **indirection,
 						   ParseState *pstate,
 						   bool isSlice,
 						   bool isAssignment)
@@ -49,15 +49,15 @@ hstore_subscript_transform(SubscriptingRef *sbsref,
 	Node	   *subexpr;
 
 	/* We support only single-subscript, non-slice cases */
-	if (isSlice || list_length(indirection) != 1)
+	if (isSlice || list_length(*indirection) != 1)
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("hstore allows only one subscript"),
 				 parser_errposition(pstate,
-									exprLocation((Node *) indirection))));
+									exprLocation((Node *) *indirection))));
 
 	/* Transform the subscript expression to type text */
-	ai = linitial_node(A_Indices, indirection);
+	ai = linitial_node(A_Indices, *indirection);
 	Assert(ai->uidx != NULL && ai->lidx == NULL && !ai->is_slice);
 
 	subexpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
@@ -81,6 +81,8 @@ hstore_subscript_transform(SubscriptingRef *sbsref,
 	/* Determine the result type of the subscripting operation; always text */
 	sbsref->refrestype = TEXTOID;
 	sbsref->reftypmod = -1;
+
+	*indirection = NIL;
 }
 
 /*
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index e1979a80c19..6e8fd42c612 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -465,14 +465,13 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 			Assert(IsA(n, String));
 
 			/* process subscripts before this field selection */
-			if (subscripts)
+			while (subscripts)
 				result = (Node *) transformContainerSubscripts(pstate,
 															   result,
 															   exprType(result),
 															   exprTypmod(result),
-															   subscripts,
+															   &subscripts,
 															   false);
-			subscripts = NIL;
 
 			newresult = ParseFuncOrColumn(pstate,
 										  list_make1(n),
@@ -487,12 +486,12 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		}
 	}
 	/* process trailing subscripts, if any */
-	if (subscripts)
+	while (subscripts)
 		result = (Node *) transformContainerSubscripts(pstate,
 													   result,
 													   exprType(result),
 													   exprTypmod(result),
-													   subscripts,
+													   &subscripts,
 													   false);
 
 	return result;
diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c
index 203b7a32178..f05baa50a15 100644
--- a/src/backend/parser/parse_node.c
+++ b/src/backend/parser/parse_node.c
@@ -244,7 +244,7 @@ transformContainerSubscripts(ParseState *pstate,
 							 Node *containerBase,
 							 Oid containerType,
 							 int32 containerTypMod,
-							 List *indirection,
+							 List **indirection,
 							 bool isAssignment)
 {
 	SubscriptingRef *sbsref;
@@ -280,7 +280,7 @@ transformContainerSubscripts(ParseState *pstate,
 	 * element.  If any of the items are slice specifiers (lower:upper), then
 	 * the subscript expression means a container slice operation.
 	 */
-	foreach(idx, indirection)
+	foreach(idx, *indirection)
 	{
 		A_Indices  *ai = lfirst_node(A_Indices, idx);
 
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 905c975d83b..a097736229a 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -935,7 +935,7 @@ transformAssignmentSubscripts(ParseState *pstate,
 										  basenode,
 										  containerType,
 										  containerTypMod,
-										  subscripts,
+										  &subscripts,
 										  true);
 
 	typeNeeded = sbsref->refrestype;
diff --git a/src/backend/utils/adt/arraysubs.c b/src/backend/utils/adt/arraysubs.c
index 2940fb8e8d7..234c2c278c1 100644
--- a/src/backend/utils/adt/arraysubs.c
+++ b/src/backend/utils/adt/arraysubs.c
@@ -54,7 +54,7 @@ typedef struct ArraySubWorkspace
  */
 static void
 array_subscript_transform(SubscriptingRef *sbsref,
-						  List *indirection,
+						  List **indirection,
 						  ParseState *pstate,
 						  bool isSlice,
 						  bool isAssignment)
@@ -71,7 +71,7 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	 * indirection items to slices by treating the single subscript as the
 	 * upper bound and supplying an assumed lower bound of 1.
 	 */
-	foreach(idx, indirection)
+	foreach(idx, *indirection)
 	{
 		A_Indices  *ai = lfirst_node(A_Indices, idx);
 		Node	   *subexpr;
@@ -152,6 +152,8 @@ array_subscript_transform(SubscriptingRef *sbsref,
 						list_length(upperIndexpr), MAXDIM)));
 	/* We need not check lowerIndexpr separately */
 
+	*indirection = NIL;
+
 	/*
 	 * Determine the result type of the subscripting operation.  It's the same
 	 * as the array type if we're slicing, else it's the element type.  In
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index e8626d3b4fc..5ead693a3b2 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -41,7 +41,7 @@ typedef struct JsonbSubWorkspace
  */
 static void
 jsonb_subscript_transform(SubscriptingRef *sbsref,
-						  List *indirection,
+						  List **indirection,
 						  ParseState *pstate,
 						  bool isSlice,
 						  bool isAssignment)
@@ -53,7 +53,7 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	 * Transform and convert the subscript expressions. Jsonb subscripting
 	 * does not support slices, look only at the upper index.
 	 */
-	foreach(idx, indirection)
+	foreach(idx, *indirection)
 	{
 		A_Indices  *ai = lfirst_node(A_Indices, idx);
 		Node	   *subExpr;
@@ -159,6 +159,8 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	/* Determine the result type of the subscripting operation; always jsonb */
 	sbsref->refrestype = JSONBOID;
 	sbsref->reftypmod = -1;
+
+	*indirection = NIL;
 }
 
 /*
diff --git a/src/include/nodes/subscripting.h b/src/include/nodes/subscripting.h
index e991f4bf826..8a768fe3037 100644
--- a/src/include/nodes/subscripting.h
+++ b/src/include/nodes/subscripting.h
@@ -71,6 +71,11 @@ typedef struct SubscriptExecSteps SubscriptExecSteps;
  * does not care to support slicing, it can just throw an error if isSlice.)
  * See array_subscript_transform() for sample code.
  *
+ * The transform method receives a pointer to a list of raw indirections.
+ * It may parse a sublist (typically the prefix) of these indirections and
+ * modify the original list in place, allowing the caller to handle any
+ * remaining indirections differently or to raise an error as needed.
+ *
  * The transform method is also responsible for identifying the result type
  * of the subscripting operation.  At call, refcontainertype and reftypmod
  * describe the container type (this will be a base type not a domain), and
@@ -93,7 +98,7 @@ typedef struct SubscriptExecSteps SubscriptExecSteps;
  * assignment must return.
  */
 typedef void (*SubscriptTransform) (SubscriptingRef *sbsref,
-									List *indirection,
+									List **indirection,
 									ParseState *pstate,
 									bool isSlice,
 									bool isAssignment);
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index f7d07c84542..58a4b9df157 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -361,7 +361,7 @@ extern SubscriptingRef *transformContainerSubscripts(ParseState *pstate,
 													 Node *containerBase,
 													 Oid containerType,
 													 int32 containerTypMod,
-													 List *indirection,
+													 List **indirection,
 													 bool isAssignment);
 extern Const *make_const(ParseState *pstate, A_Const *aconst);
 
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v19-0004-Extract-coerce_jsonpath_subscript.patch (5.5K, ../../CAK98qZ2nezKeQFqPV9GX80mDiLqS2Fh6MRBGz6ox_HkQF86asA@mail.gmail.com/6-v19-0004-Extract-coerce_jsonpath_subscript.patch)
  download | inline diff:
From d4dcb1085acb98c8dd7de66d22b68fe7f34d85ac Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v19 4/7] Extract coerce_jsonpath_subscript()

This is a preparation step for a future commit that will reuse the
aforementioned function.

Co-authored-by: Nikita Glukhov <[email protected]>
Co-authored-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Jian He <[email protected]>
Reviewed-by: Chao Li <[email protected]>
---
 src/backend/utils/adt/jsonbsubs.c | 128 +++++++++++++++---------------
 1 file changed, 64 insertions(+), 64 deletions(-)

diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index 2aa410f605b..8852c27a198 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -32,6 +32,69 @@ typedef struct JsonbSubWorkspace
 	Datum	   *index;			/* Subscript values in Datum format */
 } JsonbSubWorkspace;
 
+static Node *
+coerce_jsonpath_subscript_to_int4_or_text(ParseState *pstate, Node *subExpr)
+{
+	Oid			subExprType = exprType(subExpr);
+	Oid			targetType = InvalidOid;
+
+	if (subExprType != UNKNOWNOID)
+	{
+		const Oid	targets[] = {INT4OID, TEXTOID};
+
+		/*
+		 * Jsonb can handle multiple subscript types, but cases when a
+		 * subscript could be coerced to multiple target types must be
+		 * avoided, similar to overloaded functions. It could be possibly
+		 * extend with jsonpath in the future.
+		 */
+		for (int i = 0; i < lengthof(targets); i++)
+		{
+			if (can_coerce_type(1, &subExprType, &targets[i], COERCION_IMPLICIT))
+			{
+				/*
+				 * One type has already succeeded, it means there are two
+				 * coercion targets possible, failure.
+				 */
+				if (OidIsValid(targetType))
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
+							 errhint("jsonb subscript must be coercible to only one type, integer or text."),
+							 parser_errposition(pstate, exprLocation(subExpr))));
+
+				targetType = targets[i];
+			}
+		}
+
+		/*
+		 * No suitable types were found, failure.
+		 */
+		if (!OidIsValid(targetType))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
+					 errhint("jsonb subscript must be coercible to either integer or text."),
+					 parser_errposition(pstate, exprLocation(subExpr))));
+	}
+	else
+		targetType = TEXTOID;
+
+	/*
+	 * We known from can_coerce_type that coercion will succeed, so
+	 * coerce_type could be used. Note the implicit coercion context, which is
+	 * required to handle subscripts of different types, similar to overloaded
+	 * functions.
+	 */
+	subExpr = coerce_type(pstate,
+						  subExpr, subExprType,
+						  targetType, -1,
+						  COERCION_IMPLICIT,
+						  COERCE_IMPLICIT_CAST,
+						  -1);
+
+	return subExpr;
+}
 
 /*
  * Finish parse analysis of a SubscriptingRef expression for a jsonb.
@@ -74,71 +137,8 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 
 		if (ai->uidx)
 		{
-			Oid			subExprType = InvalidOid,
-						targetType = UNKNOWNOID;
-
 			subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
-			subExprType = exprType(subExpr);
-
-			if (subExprType != UNKNOWNOID)
-			{
-				Oid			targets[2] = {INT4OID, TEXTOID};
-
-				/*
-				 * Jsonb can handle multiple subscript types, but cases when a
-				 * subscript could be coerced to multiple target types must be
-				 * avoided, similar to overloaded functions. It could be
-				 * possibly extend with jsonpath in the future.
-				 */
-				for (int i = 0; i < 2; i++)
-				{
-					if (can_coerce_type(1, &subExprType, &targets[i], COERCION_IMPLICIT))
-					{
-						/*
-						 * One type has already succeeded, it means there are
-						 * two coercion targets possible, failure.
-						 */
-						if (targetType != UNKNOWNOID)
-							ereport(ERROR,
-									(errcode(ERRCODE_DATATYPE_MISMATCH),
-									 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
-									 errhint("jsonb subscript must be coercible to only one type, integer or text."),
-									 parser_errposition(pstate, exprLocation(subExpr))));
-
-						targetType = targets[i];
-					}
-				}
-
-				/*
-				 * No suitable types were found, failure.
-				 */
-				if (targetType == UNKNOWNOID)
-					ereport(ERROR,
-							(errcode(ERRCODE_DATATYPE_MISMATCH),
-							 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
-							 errhint("jsonb subscript must be coercible to either integer or text."),
-							 parser_errposition(pstate, exprLocation(subExpr))));
-			}
-			else
-				targetType = TEXTOID;
-
-			/*
-			 * We known from can_coerce_type that coercion will succeed, so
-			 * coerce_type could be used. Note the implicit coercion context,
-			 * which is required to handle subscripts of different types,
-			 * similar to overloaded functions.
-			 */
-			subExpr = coerce_type(pstate,
-								  subExpr, subExprType,
-								  targetType, -1,
-								  COERCION_IMPLICIT,
-								  COERCE_IMPLICIT_CAST,
-								  -1);
-			if (subExpr == NULL)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("jsonb subscript must have text type"),
-						 parser_errposition(pstate, exprLocation(subExpr))));
+			subExpr = coerce_jsonpath_subscript_to_int4_or_text(pstate, subExpr);
 		}
 		else
 		{
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v19-0007-Implement-jsonb-wildcard-member-accessor.patch (31.5K, ../../CAK98qZ2nezKeQFqPV9GX80mDiLqS2Fh6MRBGz6ox_HkQF86asA@mail.gmail.com/7-v19-0007-Implement-jsonb-wildcard-member-accessor.patch)
  download | inline diff:
From d98c0fe5fefd37c86aafcaed46a8ed695aef16ff Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v19 7/7] Implement jsonb wildcard member accessor

This commit adds support for wildcard member access in jsonb, as
specified by the JSON simplified accessor syntax in SQL:2023.

Co-authored-by: Nikita Glukhov <[email protected]>
Co-authored-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Mark Dilger <[email protected]>
Reviewed-by: Jian He <[email protected]>
Reviewed-by: Vik Fearing <[email protected]>
Reviewed-by: Nikita Malakhov <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Jelte Fennema-Nio <[email protected]>
Tested-by: Jelte Fennema-Nio <[email protected]>
Tested-by: Chao Li <[email protected]>
---
 src/backend/nodes/nodeFuncs.c                 |   8 +
 src/backend/parser/gram.y                     |   2 +
 src/backend/parser/parse_expr.c               |  39 +--
 src/backend/parser/parse_target.c             |  67 ++++--
 src/backend/utils/adt/jsonbsubs.c             |  12 +-
 src/backend/utils/adt/ruleutils.c             |   6 +-
 src/include/nodes/primnodes.h                 |  12 +
 src/include/parser/parse_expr.h               |   3 +
 .../ecpg/test/expected/sql-sqljson.c          |  18 +-
 .../ecpg/test/expected/sql-sqljson.stderr     |  23 +-
 .../ecpg/test/expected/sql-sqljson.stdout     |   2 +
 src/interfaces/ecpg/test/sql/sqljson.pgc      |   5 +-
 src/test/regress/expected/jsonb.out           | 222 +++++++++++++++++-
 src/test/regress/sql/jsonb.sql                |  42 +++-
 14 files changed, 406 insertions(+), 55 deletions(-)

diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index d1bd575d9bd..5f3038a1c26 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -291,6 +291,9 @@ exprType(const Node *expr)
 			 */
 			type = TEXTOID;
 			break;
+		case T_Star:
+			type = UNKNOWNOID;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			type = InvalidOid;	/* keep compiler quiet */
@@ -1156,6 +1159,9 @@ exprSetCollation(Node *expr, Oid collation)
 		case T_FieldAccessorExpr:
 			((FieldAccessorExpr *) expr)->faecollid = collation;
 			break;
+		case T_Star:
+			Assert(!OidIsValid(collation));
+			break;
 		case T_SubscriptingRef:
 			((SubscriptingRef *) expr)->refcollid = collation;
 			break;
@@ -2140,6 +2146,7 @@ expression_tree_walker_impl(Node *node,
 		case T_CTESearchClause:
 		case T_MergeSupportFunc:
 		case T_FieldAccessorExpr:
+		case T_Star:
 			/* primitive node types with no expression subnodes */
 			break;
 		case T_WithCheckOption:
@@ -3020,6 +3027,7 @@ expression_tree_mutator_impl(Node *node,
 		case T_CTESearchClause:
 		case T_MergeSupportFunc:
 		case T_FieldAccessorExpr:
+		case T_Star:
 			return copyObject(node);
 		case T_WithCheckOption:
 			{
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9fd48acb1f8..c86ab6fd512 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -18980,6 +18980,7 @@ check_func_name(List *names, core_yyscan_t yyscanner)
 static List *
 check_indirection(List *indirection, core_yyscan_t yyscanner)
 {
+#if 0
 	ListCell   *l;
 
 	foreach(l, indirection)
@@ -18990,6 +18991,7 @@ check_indirection(List *indirection, core_yyscan_t yyscanner)
 				parser_yyerror("improper use of \"*\"");
 		}
 	}
+#endif
 	return indirection;
 }
 
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index f8a0617f823..38ac6503a22 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -73,7 +73,6 @@ static Node *transformColumnRef(ParseState *pstate, ColumnRef *cref);
 static Node *transformWholeRowRef(ParseState *pstate,
 								  ParseNamespaceItem *nsitem,
 								  int sublevels_up, int location);
-static Node *transformIndirection(ParseState *pstate, A_Indirection *ind);
 static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
 static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
 static Node *transformJsonObjectConstructor(ParseState *pstate,
@@ -157,7 +156,7 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 			break;
 
 		case T_A_Indirection:
-			result = transformIndirection(pstate, (A_Indirection *) expr);
+			result = transformIndirection(pstate, (A_Indirection *) expr, NULL);
 			break;
 
 		case T_A_ArrayExpr:
@@ -431,8 +430,9 @@ unknown_attribute(ParseState *pstate, Node *relref, const char *attname,
 	}
 }
 
-static Node *
-transformIndirection(ParseState *pstate, A_Indirection *ind)
+Node *
+transformIndirection(ParseState *pstate, A_Indirection *ind,
+					 bool *trailing_star_expansion)
 {
 	Node	   *last_srf = pstate->p_last_srf;
 	Node	   *result = transformExprRecurse(pstate, ind->arg);
@@ -450,16 +450,8 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 	{
 		Node	   *n = lfirst(i);
 
-		if (IsA(n, A_Indices) || IsA(n, String))
-			subscripts = lappend(subscripts, n);
-		else
-		{
-			Assert(IsA(n, A_Star));
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("row expansion via \"*\" is not supported here"),
-					 parser_errposition(pstate, location)));
-		}
+		Assert (IsA(n, A_Indices) || IsA(n, String) || IsA(n, A_Star));
+		subscripts = lappend(subscripts, n);
 	}
 
 	while (subscripts)
@@ -486,7 +478,21 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 
 			n = linitial(subscripts);
 
-			if (!IsA(n, String))
+			if (IsA(n, A_Star))
+			{
+				/* Success, if trailing star expansion is allowed */
+				if (trailing_star_expansion && list_length(subscripts) == 1)
+				{
+					*trailing_star_expansion = true;
+					return result;
+				}
+
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("row expansion via \"*\" is not supported here"),
+						 parser_errposition(pstate, location)));
+			}
+			else if (!IsA(n, String))
 				ereport(ERROR,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
 						 errmsg("cannot subscript type %s because it does not support subscripting",
@@ -512,6 +518,9 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		result = newresult;
 	}
 
+	if (trailing_star_expansion)
+		*trailing_star_expansion = false;
+
 	return result;
 }
 
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index b89736ff1ea..85c05c7434c 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -47,7 +47,7 @@ static Node *transformAssignmentSubscripts(ParseState *pstate,
 static List *ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
 								 bool make_target_entry);
 static List *ExpandAllTables(ParseState *pstate, int location);
-static List *ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
+static Node *ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
 								   bool make_target_entry, ParseExprKind exprKind);
 static List *ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 							   int sublevels_up, int location,
@@ -133,6 +133,7 @@ transformTargetList(ParseState *pstate, List *targetlist,
 	foreach(o_target, targetlist)
 	{
 		ResTarget  *res = (ResTarget *) lfirst(o_target);
+		Node	   *transformed = NULL;
 
 		/*
 		 * Check for "something.*".  Depending on the complexity of the
@@ -161,13 +162,19 @@ transformTargetList(ParseState *pstate, List *targetlist,
 
 				if (IsA(llast(ind->indirection), A_Star))
 				{
-					/* It is something.*, expand into multiple items */
-					p_target = list_concat(p_target,
-										   ExpandIndirectionStar(pstate,
-																 ind,
-																 true,
-																 exprKind));
-					continue;
+					Node	   *columns = ExpandIndirectionStar(pstate,
+																ind,
+																true,
+																exprKind);
+
+					if (IsA(columns, List))
+					{
+						/* It is something.*, expand into multiple items */
+						p_target = list_concat(p_target, (List *) columns);
+						continue;
+					}
+
+					transformed = (Node *) columns;
 				}
 			}
 		}
@@ -179,7 +186,7 @@ transformTargetList(ParseState *pstate, List *targetlist,
 		p_target = lappend(p_target,
 						   transformTargetEntry(pstate,
 												res->val,
-												NULL,
+												transformed,
 												exprKind,
 												res->name,
 												false));
@@ -250,10 +257,15 @@ transformExpressionList(ParseState *pstate, List *exprlist,
 
 			if (IsA(llast(ind->indirection), A_Star))
 			{
-				/* It is something.*, expand into multiple items */
-				result = list_concat(result,
-									 ExpandIndirectionStar(pstate, ind,
-														   false, exprKind));
+				Node	   *cols = ExpandIndirectionStar(pstate, ind,
+														 false, exprKind);
+
+				if (!cols || IsA(cols, List))
+					/* It is something.*, expand into multiple items */
+					result = list_concat(result, (List *) cols);
+				else
+					result = lappend(result, cols);
+
 				continue;
 			}
 		}
@@ -1344,22 +1356,30 @@ ExpandAllTables(ParseState *pstate, int location)
  * For robustness, we use a separate "make_target_entry" flag to control
  * this rather than relying on exprKind.
  */
-static List *
+static Node *
 ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
 					  bool make_target_entry, ParseExprKind exprKind)
 {
 	Node	   *expr;
+	ParseExprKind sv_expr_kind;
+	bool		trailing_star_expansion = false;
+
+	/* Save and restore identity of expression type we're parsing */
+	Assert(exprKind != EXPR_KIND_NONE);
+	sv_expr_kind = pstate->p_expr_kind;
+	pstate->p_expr_kind = exprKind;
 
 	/* Strip off the '*' to create a reference to the rowtype object */
-	ind = copyObject(ind);
-	ind->indirection = list_truncate(ind->indirection,
-									 list_length(ind->indirection) - 1);
+	expr = transformIndirection(pstate, ind, &trailing_star_expansion);
+
+	pstate->p_expr_kind = sv_expr_kind;
 
-	/* And transform that */
-	expr = transformExpr(pstate, (Node *) ind, exprKind);
+	/* '*' was consumed by generic type subscripting */
+	if (!trailing_star_expansion)
+		return expr;
 
 	/* Expand the rowtype expression into individual fields */
-	return ExpandRowReference(pstate, expr, make_target_entry);
+	return (Node *) ExpandRowReference(pstate, expr, make_target_entry);
 }
 
 /*
@@ -1784,13 +1804,18 @@ FigureColnameInternal(Node *node, char **name)
 				char	   *fname = NULL;
 				ListCell   *l;
 
-				/* find last field name, if any, ignoring "*" and subscripts */
+				/*
+				 * find last field name, if any, ignoring subscripts, and use
+				 * '?column?' when there's a trailing '*'.
+				 */
 				foreach(l, ind->indirection)
 				{
 					Node	   *i = lfirst(l);
 
 					if (IsA(i, String))
 						fname = strVal(i);
+					else if (IsA(i, A_Star))
+						fname = "?column?";
 				}
 				if (fname)
 				{
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index 4762f6bd11c..1dcde658739 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -124,7 +124,7 @@ jsonb_check_jsonpath_needed(List *indirection)
 	{
 		Node	   *accessor = lfirst(lc);
 
-		if (IsA(accessor, String))
+		if (IsA(accessor, String) || IsA(accessor, A_Star))
 			return true;
 		else
 		{
@@ -339,6 +339,16 @@ jsonb_subscript_make_jsonpath(ParseState *pstate, List **indirection, Subscripti
 				}
 			}
 		}
+		else if (IsA(accessor, A_Star))
+		{
+			Star *star_node;
+			jpi = make_jsonpath_item(jpiAnyKey);
+
+			star_node = makeNode(Star);
+			star_node->type = T_Star;
+
+			sbsref->refupperindexpr = lappend(sbsref->refupperindexpr, star_node);
+		}
 		else
 		{
 			/*
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index baa3ae97d57..ace0eff52e2 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -13010,7 +13010,11 @@ printSubscripts(SubscriptingRef *sbsref, deparse_context *context)
 	{
 		Node *upper = (Node *) lfirst(uplist_item);
 
-		if (upper && IsA(upper, FieldAccessorExpr))
+		if (upper && IsA(upper, Star))
+		{
+			appendStringInfoString(buf, ".*");
+		}
+		else if (upper && IsA(upper, FieldAccessorExpr))
 		{
 			FieldAccessorExpr *fae = (FieldAccessorExpr *) upper;
 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 7e89621bd65..7e418830f22 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -2419,4 +2419,16 @@ typedef struct FieldAccessorExpr
 	Oid			faecollid pg_node_attr(query_jumble_ignore);
 }			FieldAccessorExpr;
 
+/*
+ * Star - represents a wildcard member accessor (e.g., ".*") used in JSONB simplified accessor.
+ *
+ * This node serves as a syntactic placeholder in the expression tree and does not get evaluated, hence it
+ * has no associated type or collation.
+ */
+typedef struct Star
+{
+	NodeTag		type;
+}			Star;
+
+
 #endif							/* PRIMNODES_H */
diff --git a/src/include/parser/parse_expr.h b/src/include/parser/parse_expr.h
index efbaff8e710..c9f6a7724c0 100644
--- a/src/include/parser/parse_expr.h
+++ b/src/include/parser/parse_expr.h
@@ -22,4 +22,7 @@ extern Node *transformExpr(ParseState *pstate, Node *expr, ParseExprKind exprKin
 
 extern const char *ParseExprKindName(ParseExprKind exprKind);
 
+extern Node *transformIndirection(ParseState *pstate, A_Indirection *ind,
+								  bool *trailing_star_expansion);
+
 #endif							/* PARSE_EXPR_H */
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.c b/src/interfaces/ecpg/test/expected/sql-sqljson.c
index 935b47a3b9a..585d9b14445 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.c
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.c
@@ -515,9 +515,9 @@ if (sqlca.sqlcode < 0) sqlprint();}
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 145 "sqljson.pgc"
 
-	// error
+	printf("Found json=%s\n", json);
 
-	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )", ECPGt_EOIT, 
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . * . x )", ECPGt_EOIT, 
 	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
 	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 148 "sqljson.pgc"
@@ -527,7 +527,7 @@ if (sqlca.sqlcode < 0) sqlprint();}
 
 	printf("Found json=%s\n", json);
 
-	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ : ] )", ECPGt_EOIT, 
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )", ECPGt_EOIT, 
 	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
 	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 151 "sqljson.pgc"
@@ -537,12 +537,22 @@ if (sqlca.sqlcode < 0) sqlprint();}
 
 	printf("Found json=%s\n", json);
 
-  { ECPGdisconnect(__LINE__, "CURRENT");
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ : ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 154 "sqljson.pgc"
 
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 154 "sqljson.pgc"
 
+	printf("Found json=%s\n", json);
+
+  { ECPGdisconnect(__LINE__, "CURRENT");
+#line 157 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 157 "sqljson.pgc"
+
 
   return 0;
 }
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
index f3f899c6d87..4b9088545d6 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
@@ -347,22 +347,19 @@ SQL error: schema "jsonb" does not exist on line 121
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 145: using PQexec
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_check_PQresult on line 145: bad response - ERROR:  row expansion via "*" is not supported here
-LINE 1: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x...
-                        ^
+[NO_PID]: ecpg_process_output on line 145: correctly got 1 tuples with 1 fields
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: raising sqlstate 0A000 (sqlcode -400): row expansion via "*" is not supported here on line 145
-[NO_PID]: sqlca: code: -400, state: 0A000
-SQL error: row expansion via "*" is not supported here on line 145
-[NO_PID]: ecpg_execute on line 148: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: ecpg_get_data on line 145: RESULT: [{"b": 1, "c": 2}, [{"x": 1}, {"x": [12, {"y": 1}]}]] offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 148: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . * . x ); with 0 parameter(s) on connection ecpg1_regression
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 148: using PQexec
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_process_output on line 148: correctly got 1 tuples with 1 fields
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_get_data on line 148: RESULT: [{"x": 1}, {"x": [12, {"y": 1}]}] offset: -1; array: no
+[NO_PID]: ecpg_get_data on line 148: RESULT: [1, [12, {"y": 1}]] offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_execute on line 151: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: ecpg_execute on line 151: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] ); with 0 parameter(s) on connection ecpg1_regression
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 151: using PQexec
 [NO_PID]: sqlca: code: 0, state: 00000
@@ -370,5 +367,13 @@ SQL error: row expansion via "*" is not supported here on line 145
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_get_data on line 151: RESULT: [{"x": 1}, {"x": [12, {"y": 1}]}] offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 154: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 154: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 154: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 154: RESULT: [{"x": 1}, {"x": [12, {"y": 1}]}] offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_finish: connection ecpg1_regression closed
 [NO_PID]: sqlca: code: 0, state: 00000
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
index d01a8457f01..145dc95d430 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
@@ -36,5 +36,7 @@ Found json={"x": 1}
 Found json=[1, [12, {"y": 1}]]
 Found json={"x": 1}
 Found json=[12, {"y": 1}]
+Found json=[{"b": 1, "c": 2}, [{"x": 1}, {"x": [12, {"y": 1}]}]]
+Found json=[1, [12, {"y": 1}]]
 Found json=[{"x": 1}, {"x": [12, {"y": 1}]}]
 Found json=[{"x": 1}, {"x": [12, {"y": 1}]}]
diff --git a/src/interfaces/ecpg/test/sql/sqljson.pgc b/src/interfaces/ecpg/test/sql/sqljson.pgc
index 9423d25fd0b..2af50b5da4b 100644
--- a/src/interfaces/ecpg/test/sql/sqljson.pgc
+++ b/src/interfaces/ecpg/test/sql/sqljson.pgc
@@ -143,7 +143,10 @@ EXEC SQL END DECLARE SECTION;
 	printf("Found json=%s\n", json);
 
 	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).*) INTO :json;
-	// error
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).*.x) INTO :json;
+	printf("Found json=%s\n", json);
 
 	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb)['b'][0:]) INTO :json;
 	printf("Found json=%s\n", json);
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 2702edc9705..86b7f7677fc 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -6064,8 +6064,175 @@ SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
  {"y": "YYY", "z": "ZZZ"}
 (1 row)
 
-SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
-ERROR:  type jsonb is not composite
+/* wild card member access */
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+                 ?column?                  
+-------------------------------------------
+ ["c", "d", "f", {"y": "yyy", "z": "zzz"}]
+(1 row)
+
+SELECT (jb).* FROM test_jsonb_dot_notation;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (jb).* FROM test_jsonb_dot_notation t;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (jb).* FROM test_jsonb_dot_notation;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (t.jb).* FROM test_jsonb_dot_notation;
+ERROR:  missing FROM-clause entry for table "t"
+LINE 1: SELECT (t.jb).* FROM test_jsonb_dot_notation;
+                ^
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+                 ?column?                  
+-------------------------------------------
+ ["c", "d", "f", {"y": "yyy", "z": "zzz"}]
+(1 row)
+
+SELECT (jb).a.*.b FROM test_jsonb_dot_notation;
+ b 
+---
+ 
+(1 row)
+
+SELECT (jb).a.*.x FROM test_jsonb_dot_notation;
+ x 
+---
+ 
+(1 row)
+
+SELECT (jb).a.*.y FROM test_jsonb_dot_notation;
+   y   
+-------
+ "yyy"
+(1 row)
+
+SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+    ?column?    
+----------------
+ ["yyy", "zzz"]
+(1 row)
+
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT (jb).*.x FROM test_jsonb_dot_notation t;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+ x 
+---
+ 
+(1 row)
+
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+ x 
+---
+ 
+(1 row)
+
+SELECT ((jb).*)[:].x FROM test_jsonb_dot_notation t;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT (jb).*.x.* FROM test_jsonb_dot_notation;
+           ?column?           
+------------------------------
+ ["yyy", "zzz", "YYY", "ZZZ"]
+(1 row)
+
+SELECT (jb).*.x.y FROM test_jsonb_dot_notation;
+       y        
+----------------
+ ["yyy", "YYY"]
+(1 row)
+
+SELECT (jb).*.x.z FROM test_jsonb_dot_notation;
+       z        
+----------------
+ ["zzz", "ZZZ"]
+(1 row)
+
+SELECT (jb).*.*.y FROM test_jsonb_dot_notation;
+       y        
+----------------
+ ["yyy", "YYY"]
+(1 row)
+
+SELECT (jb).*.*.* FROM test_jsonb_dot_notation;
+           ?column?           
+------------------------------
+ ["yyy", "zzz", "YYY", "ZZZ"]
+(1 row)
+
+SELECT (jb).*.*.*.* FROM test_jsonb_dot_notation;
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT (jb).a.b.c.* FROM test_jsonb_dot_notation;
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+    ?column?    
+----------------
+ ["yyy", "zzz"]
+(1 row)
+
+SELECT (jb).a.*[:].* FROM test_jsonb_dot_notation;
+    ?column?    
+----------------
+ ["yyy", "zzz"]
+(1 row)
+
+SELECT (jb).a.*[*].* FROM test_jsonb_dot_notation; -- not supported
+ERROR:  syntax error at or near "*"
+LINE 1: SELECT (jb).a.*[*].* FROM test_jsonb_dot_notation;
+                        ^
+SELECT (jb).a.**.x FROM test_jsonb_dot_notation; -- not supported
+ERROR:  syntax error at or near "**"
+LINE 1: SELECT (jb).a.**.x FROM test_jsonb_dot_notation;
+                      ^
 -- explains should work
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
                   QUERY PLAN                  
@@ -6093,6 +6260,45 @@ SELECT (jb).a[1] FROM test_jsonb_dot_notation;
  2
 (1 row)
 
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.* FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb).a.*
+(2 rows)
+
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+                 ?column?                  
+-------------------------------------------
+ ["c", "d", "f", {"y": "yyy", "z": "zzz"}]
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:].* FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb).a.*[:].*
+(2 rows)
+
+SELECT (jb).a.*[1:].* FROM test_jsonb_dot_notation;
+ ?column? 
+----------
+ 
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb).a.*[:2].*.b
+(2 rows)
+
+SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
+ b 
+---
+ 
+(1 row)
+
 -- views should work
 CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
 \sv test_jsonb_dot_notation_v1
@@ -6121,6 +6327,17 @@ SELECT * from v3;
  "yyy"
 (1 row)
 
+CREATE VIEW v4 AS SELECT (jb).a.*[:].* FROM test_jsonb_dot_notation;
+\sv v4
+CREATE OR REPLACE VIEW public.v4 AS
+ SELECT (jb).a.*[:].* AS "?column?"
+   FROM test_jsonb_dot_notation
+SELECT * from v4;
+    ?column?    
+----------------
+ ["yyy", "zzz"]
+(1 row)
+
 -- mixed syntax
 DROP VIEW test_jsonb_dot_notation_v1;
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
@@ -6356,4 +6573,5 @@ NOTICE:  [2]
 -- clean up
 DROP VIEW v2;
 DROP VIEW v3;
+DROP VIEW v4;
 DROP TABLE test_jsonb_dot_notation;
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index de2c9cda094..07facc280d7 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1631,13 +1631,49 @@ SELECT (jb).a[2:].b FROM test_jsonb_dot_notation;
 SELECT (jb).a[:2].b FROM test_jsonb_dot_notation;
 SELECT (jb).a[:].b FROM test_jsonb_dot_notation;
 SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
-SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
+
+/* wild card member access */
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+SELECT (jb).* FROM test_jsonb_dot_notation;
+SELECT (jb).* FROM test_jsonb_dot_notation t;
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+SELECT (jb).* FROM test_jsonb_dot_notation;
+SELECT (t.jb).* FROM test_jsonb_dot_notation;
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.b FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.x FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.y FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+SELECT (jb).*.x FROM test_jsonb_dot_notation t;
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+SELECT ((jb).*)[:].x FROM test_jsonb_dot_notation t;
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+SELECT (jb).*.x.* FROM test_jsonb_dot_notation;
+SELECT (jb).*.x.y FROM test_jsonb_dot_notation;
+SELECT (jb).*.x.z FROM test_jsonb_dot_notation;
+SELECT (jb).*.*.y FROM test_jsonb_dot_notation;
+SELECT (jb).*.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).*.*.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.b.c.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*[:].* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*[*].* FROM test_jsonb_dot_notation; -- not supported
+SELECT (jb).a.**.x FROM test_jsonb_dot_notation; -- not supported
 
 -- explains should work
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
 SELECT (t.jb).a FROM test_jsonb_dot_notation t;
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
 SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:].* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*[1:].* FROM test_jsonb_dot_notation;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
+SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
 
 -- views should work
 CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
@@ -1648,6 +1684,9 @@ SELECT * from v2;
 CREATE VIEW v3 AS SELECT (jb).a[:3].x.y[-1:] FROM test_jsonb_dot_notation;
 \sv v3
 SELECT * from v3;
+CREATE VIEW v4 AS SELECT (jb).a.*[:].* FROM test_jsonb_dot_notation;
+\sv v4
+SELECT * from v4;
 
 -- mixed syntax
 DROP VIEW test_jsonb_dot_notation_v1;
@@ -1749,4 +1788,5 @@ $$ LANGUAGE plpgsql;
 -- clean up
 DROP VIEW v2;
 DROP VIEW v3;
+DROP VIEW v4;
 DROP TABLE test_jsonb_dot_notation;
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v19-0003-Export-jsonPathFromParseResult.patch (2.8K, ../../CAK98qZ2nezKeQFqPV9GX80mDiLqS2Fh6MRBGz6ox_HkQF86asA@mail.gmail.com/8-v19-0003-Export-jsonPathFromParseResult.patch)
  download | inline diff:
From c028e3649d010ba9d83573bd21a9e9fb5796ffd9 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v19 3/7] Export jsonPathFromParseResult()

This is a preparation step for a future commit that will reuse the
aforementioned function.

Authored-by: Nikita Glukhov <[email protected]>
Reviewed-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Jian He <[email protected]>
Reviewed-by: Chao Li <[email protected]>
---
 src/backend/utils/adt/jsonpath.c | 19 +++++++++++++++----
 src/include/utils/jsonpath.h     |  4 ++++
 2 files changed, 19 insertions(+), 4 deletions(-)

diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index 762f7e8a09d..c83774b2a16 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -166,15 +166,13 @@ jsonpath_send(PG_FUNCTION_ARGS)
  * Converts C-string to a jsonpath value.
  *
  * Uses jsonpath parser to turn string into an AST, then
- * flattenJsonPathParseItem() does second pass turning AST into binary
+ * jsonPathFromParseResult() does second pass turning AST into binary
  * representation of jsonpath.
  */
 static Datum
 jsonPathFromCstring(char *in, int len, struct Node *escontext)
 {
 	JsonPathParseResult *jsonpath = parsejsonpath(in, len, escontext);
-	JsonPath   *res;
-	StringInfoData buf;
 
 	if (SOFT_ERROR_OCCURRED(escontext))
 		return (Datum) 0;
@@ -185,8 +183,21 @@ jsonPathFromCstring(char *in, int len, struct Node *escontext)
 				 errmsg("invalid input syntax for type %s: \"%s\"", "jsonpath",
 						in)));
 
+	return jsonPathFromParseResult(jsonpath, 4 * len, escontext);
+}
+
+/*
+ * Converts jsonpath Abstract Syntax Tree (AST) into jsonpath value in binary.
+ */
+Datum
+jsonPathFromParseResult(const JsonPathParseResult *jsonpath, int estimated_len,
+						struct Node *escontext)
+{
+	JsonPath   *res;
+	StringInfoData buf;
+
 	initStringInfo(&buf);
-	enlargeStringInfo(&buf, 4 * len /* estimation */ );
+	enlargeStringInfo(&buf, estimated_len);
 
 	appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
 
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index 23a76d233e9..0958bc22bb6 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -281,6 +281,10 @@ extern JsonPathParseResult *parsejsonpath(const char *str, int len,
 extern bool jspConvertRegexFlags(uint32 xflags, int *result,
 								 struct Node *escontext);
 
+extern Datum jsonPathFromParseResult(const JsonPathParseResult *jsonpath,
+									 int estimated_len,
+									 struct Node *escontext);
+
 /*
  * Struct for details about external variables passed into jsonpath executor
  */
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v19-0002-Allow-Generic-Type-Subscripting-to-Accept-Dot-No.patch (18.2K, ../../CAK98qZ2nezKeQFqPV9GX80mDiLqS2Fh6MRBGz6ox_HkQF86asA@mail.gmail.com/9-v19-0002-Allow-Generic-Type-Subscripting-to-Accept-Dot-No.patch)
  download | inline diff:
From 3b7aa9b4a09b90e1726b92e0d864c680f01c3991 Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Fri, 5 Sep 2025 14:17:37 -0700
Subject: [PATCH v19 2/7] Allow Generic Type Subscripting to Accept Dot
 Notation (.) as Input
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This change extends generic type subscripting to recognize dot
notation (.) in addition to bracket notation ([]). While this does not
yet provide full support for dot notation, it enables subscripting
containers to process it in the future.

For now, container-specific transform functions only handle
subscripting indices and stop processing when encountering dot
notation. It is up to individual containers to decide how to transform
dot notation in subsequent updates.

This change also updates the SubscriptTransform() API to remove the
"bool isSlice" argument. Each container’s transform function now
determines slice-ness itself, since it may only process a prefix of
the indirection list. For example, array_subscript_transform() stops
at dot notation, so "isSlice" should not depend on slice specifiers
that appear afterward.

Authored-by: Nikita Glukhov <[email protected]>
Authored-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Jian He <[email protected]>
Reviewed-by: Chao Li <[email protected]>
---
 contrib/hstore/hstore_subs.c         | 11 +++--
 src/backend/parser/parse_expr.c      | 68 ++++++++++++++++++----------
 src/backend/parser/parse_node.c      | 57 ++++++++++++++---------
 src/backend/parser/parse_target.c    |  3 +-
 src/backend/utils/adt/arraysubs.c    | 40 ++++++++++++++--
 src/backend/utils/adt/jsonbsubs.c    | 16 +++++--
 src/include/nodes/subscripting.h     |  3 +-
 src/include/parser/parse_node.h      |  3 +-
 src/test/regress/expected/arrays.out | 26 +++++++++--
 src/test/regress/sql/arrays.sql      |  7 ++-
 10 files changed, 163 insertions(+), 71 deletions(-)

diff --git a/contrib/hstore/hstore_subs.c b/contrib/hstore/hstore_subs.c
index 1b29543ab67..f008e5faf03 100644
--- a/contrib/hstore/hstore_subs.c
+++ b/contrib/hstore/hstore_subs.c
@@ -42,14 +42,16 @@ static void
 hstore_subscript_transform(SubscriptingRef *sbsref,
 						   List **indirection,
 						   ParseState *pstate,
-						   bool isSlice,
 						   bool isAssignment)
 {
 	A_Indices  *ai;
 	Node	   *subexpr;
 
+	Assert(*indirection != NIL);
+	ai = linitial_node(A_Indices, *indirection);
+
 	/* We support only single-subscript, non-slice cases */
-	if (isSlice || list_length(*indirection) != 1)
+	if (list_length(*indirection) != 1 || ai->is_slice)
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("hstore allows only one subscript"),
@@ -57,8 +59,7 @@ hstore_subscript_transform(SubscriptingRef *sbsref,
 									exprLocation((Node *) *indirection))));
 
 	/* Transform the subscript expression to type text */
-	ai = linitial_node(A_Indices, *indirection);
-	Assert(ai->uidx != NULL && ai->lidx == NULL && !ai->is_slice);
+	Assert(ai->uidx != NULL && ai->lidx == NULL);
 
 	subexpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
 	/* If it's not text already, try to coerce */
@@ -82,7 +83,7 @@ hstore_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->refrestype = TEXTOID;
 	sbsref->reftypmod = -1;
 
-	*indirection = NIL;
+	*indirection = list_delete_first_n(*indirection, 1);
 }
 
 /*
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 6e8fd42c612..f8a0617f823 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -441,38 +441,59 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 	ListCell   *i;
 
 	/*
-	 * We have to split any field-selection operations apart from
-	 * subscripting.  Adjacent A_Indices nodes have to be treated as a single
+	 * Combine field names and subscripts into a single indirection list, as
+	 * some subscripting containers, such as jsonb, support field access using
+	 * dot notation. Adjacent A_Indices nodes have to be treated as a single
 	 * multidimensional subscript operation.
 	 */
 	foreach(i, ind->indirection)
 	{
 		Node	   *n = lfirst(i);
 
-		if (IsA(n, A_Indices))
+		if (IsA(n, A_Indices) || IsA(n, String))
 			subscripts = lappend(subscripts, n);
-		else if (IsA(n, A_Star))
+		else
 		{
+			Assert(IsA(n, A_Star));
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("row expansion via \"*\" is not supported here"),
 					 parser_errposition(pstate, location)));
 		}
-		else
+	}
+
+	while (subscripts)
+	{
+		/* try processing container subscripts first */
+		Node	   *newresult = (Node *)
+			transformContainerSubscripts(pstate,
+										 result,
+										 exprType(result),
+										 exprTypmod(result),
+										 &subscripts,
+										 false,
+										 true);
+
+		if (!newresult)
 		{
-			Node	   *newresult;
+			/*
+			 * generic subscripting failed; falling back to field selection
+			 * for a composite type.
+			 */
+			Node	   *n;
+
+			Assert(subscripts);
 
-			Assert(IsA(n, String));
+			n = linitial(subscripts);
 
-			/* process subscripts before this field selection */
-			while (subscripts)
-				result = (Node *) transformContainerSubscripts(pstate,
-															   result,
-															   exprType(result),
-															   exprTypmod(result),
-															   &subscripts,
-															   false);
+			if (!IsA(n, String))
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("cannot subscript type %s because it does not support subscripting",
+								format_type_be(exprType(result))),
+						 parser_errposition(pstate, exprLocation(result))));
 
+			/* try to find function for field selection */
 			newresult = ParseFuncOrColumn(pstate,
 										  list_make1(n),
 										  list_make1(result),
@@ -480,19 +501,16 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 										  NULL,
 										  false,
 										  location);
-			if (newresult == NULL)
+
+			if (!newresult)
 				unknown_attribute(pstate, result, strVal(n), location);
-			result = newresult;
+
+			/* consume field select */
+			subscripts = list_delete_first(subscripts);
 		}
+
+		result = newresult;
 	}
-	/* process trailing subscripts, if any */
-	while (subscripts)
-		result = (Node *) transformContainerSubscripts(pstate,
-													   result,
-													   exprType(result),
-													   exprTypmod(result),
-													   &subscripts,
-													   false);
 
 	return result;
 }
diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c
index f05baa50a15..d7b23688b9b 100644
--- a/src/backend/parser/parse_node.c
+++ b/src/backend/parser/parse_node.c
@@ -238,6 +238,8 @@ transformContainerType(Oid *containerType, int32 *containerTypmod)
  * containerTypMod	typmod for the container
  * indirection		Untransformed list of subscripts (must not be NIL)
  * isAssignment		True if this will become a container assignment.
+ * noError			True for return NULL with no error, if the container type
+ * 					is not subscriptable.
  */
 SubscriptingRef *
 transformContainerSubscripts(ParseState *pstate,
@@ -245,13 +247,13 @@ transformContainerSubscripts(ParseState *pstate,
 							 Oid containerType,
 							 int32 containerTypMod,
 							 List **indirection,
-							 bool isAssignment)
+							 bool isAssignment,
+							 bool noError)
 {
 	SubscriptingRef *sbsref;
 	const SubscriptRoutines *sbsroutines;
 	Oid			elementType;
-	bool		isSlice = false;
-	ListCell   *idx;
+	int			indirection_length = list_length(*indirection);
 
 	/*
 	 * Determine the actual container type, smashing any domain.  In the
@@ -267,28 +269,15 @@ transformContainerSubscripts(ParseState *pstate,
 	 */
 	sbsroutines = getSubscriptingRoutines(containerType, &elementType);
 	if (!sbsroutines)
+	{
+		if (noError)
+			return NULL;
+
 		ereport(ERROR,
 				(errcode(ERRCODE_DATATYPE_MISMATCH),
 				 errmsg("cannot subscript type %s because it does not support subscripting",
 						format_type_be(containerType)),
 				 parser_errposition(pstate, exprLocation(containerBase))));
-
-	/*
-	 * Detect whether any of the indirection items are slice specifiers.
-	 *
-	 * A list containing only simple subscripts refers to a single container
-	 * element.  If any of the items are slice specifiers (lower:upper), then
-	 * the subscript expression means a container slice operation.
-	 */
-	foreach(idx, *indirection)
-	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
-
-		if (ai->is_slice)
-		{
-			isSlice = true;
-			break;
-		}
 	}
 
 	/*
@@ -310,7 +299,33 @@ transformContainerSubscripts(ParseState *pstate,
 	 * determine the subscripting result type.
 	 */
 	sbsroutines->transform(sbsref, indirection, pstate,
-						   isSlice, isAssignment);
+						   isAssignment);
+
+	/*
+	 * Error out, if datatype failed to consume any indirection elements.
+	 */
+	if (list_length(*indirection) == indirection_length)
+	{
+		Node	   *ind = linitial(*indirection);
+
+		if (noError)
+			return NULL;
+
+		if (IsA(ind, String))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("type %s does not support dot notation",
+							format_type_be(containerType)),
+					 parser_errposition(pstate, exprLocation(containerBase))));
+		else if (IsA(ind, A_Indices))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("type %s does not support array subscripting",
+							format_type_be(containerType)),
+					 parser_errposition(pstate, exprLocation(containerBase))));
+		else
+			elog(ERROR, "invalid indirection operation: %d", nodeTag(ind));
+	}
 
 	/*
 	 * Verify we got a valid type (this defends, for example, against someone
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index a097736229a..b89736ff1ea 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -936,7 +936,8 @@ transformAssignmentSubscripts(ParseState *pstate,
 										  containerType,
 										  containerTypMod,
 										  &subscripts,
-										  true);
+										  true,
+										  false);
 
 	typeNeeded = sbsref->refrestype;
 	typmodNeeded = sbsref->reftypmod;
diff --git a/src/backend/utils/adt/arraysubs.c b/src/backend/utils/adt/arraysubs.c
index 234c2c278c1..378b6bf16cb 100644
--- a/src/backend/utils/adt/arraysubs.c
+++ b/src/backend/utils/adt/arraysubs.c
@@ -56,12 +56,38 @@ static void
 array_subscript_transform(SubscriptingRef *sbsref,
 						  List **indirection,
 						  ParseState *pstate,
-						  bool isSlice,
 						  bool isAssignment)
 {
 	List	   *upperIndexpr = NIL;
 	List	   *lowerIndexpr = NIL;
 	ListCell   *idx;
+	int			ndim;
+	bool		isSlice = false;
+
+	/*
+	 * Detect whether any of the indirection items are slice specifiers.
+	 *
+	 * A list containing only simple subscripts refers to a single container
+	 * element.  If any of the items are slice specifiers (lower:upper), then
+	 * the subscript expression means a container slice operation.
+	 */
+	foreach(idx, *indirection)
+	{
+		Node	   *ai = lfirst(idx);
+
+		/*
+		 * We should not inspect slice specifiers beyond an indirection node
+		 * type that we don't support.
+		 */
+		if (!IsA(ai, A_Indices))
+			break;
+
+		if (castNode(A_Indices, ai)->is_slice)
+		{
+			isSlice = true;
+			break;
+		}
+	}
 
 	/*
 	 * Transform the subscript expressions, and separate upper and lower
@@ -73,9 +99,14 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	 */
 	foreach(idx, *indirection)
 	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
+		A_Indices  *ai;
 		Node	   *subexpr;
 
+		if (!IsA(lfirst(idx), A_Indices))
+			break;
+
+		ai = lfirst_node(A_Indices, idx);
+
 		if (isSlice)
 		{
 			if (ai->lidx)
@@ -145,14 +176,15 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->reflowerindexpr = lowerIndexpr;
 
 	/* Verify subscript list lengths are within implementation limit */
-	if (list_length(upperIndexpr) > MAXDIM)
+	ndim = list_length(upperIndexpr);
+	if (ndim > MAXDIM)
 		ereport(ERROR,
 				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 				 errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
 						list_length(upperIndexpr), MAXDIM)));
 	/* We need not check lowerIndexpr separately */
 
-	*indirection = NIL;
+	*indirection = list_delete_first_n(*indirection, ndim);
 
 	/*
 	 * Determine the result type of the subscripting operation.  It's the same
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index 5ead693a3b2..2aa410f605b 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -43,7 +43,6 @@ static void
 jsonb_subscript_transform(SubscriptingRef *sbsref,
 						  List **indirection,
 						  ParseState *pstate,
-						  bool isSlice,
 						  bool isAssignment)
 {
 	List	   *upperIndexpr = NIL;
@@ -55,10 +54,15 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	 */
 	foreach(idx, *indirection)
 	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
+		A_Indices  *ai;
 		Node	   *subExpr;
 
-		if (isSlice)
+		if (!IsA(lfirst(idx), A_Indices))
+			break;
+
+		ai = lfirst_node(A_Indices, idx);
+
+		if (ai->is_slice)
 		{
 			Node	   *expr = ai->uidx ? ai->uidx : ai->lidx;
 
@@ -142,7 +146,7 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 			 * Slice with omitted upper bound. Should not happen as we already
 			 * errored out on slice earlier, but handle this just in case.
 			 */
-			Assert(isSlice && ai->is_slice);
+			Assert(ai->is_slice);
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
 					 errmsg("jsonb subscript does not support slices"),
@@ -160,7 +164,9 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->refrestype = JSONBOID;
 	sbsref->reftypmod = -1;
 
-	*indirection = NIL;
+	/* Remove processed elements */
+	if (upperIndexpr)
+		*indirection = list_delete_first_n(*indirection, list_length(upperIndexpr));
 }
 
 /*
diff --git a/src/include/nodes/subscripting.h b/src/include/nodes/subscripting.h
index 8a768fe3037..b1165568f7e 100644
--- a/src/include/nodes/subscripting.h
+++ b/src/include/nodes/subscripting.h
@@ -68,7 +68,7 @@ typedef struct SubscriptExecSteps SubscriptExecSteps;
  * same length as refupperindexpr for a slice operation.  Insert NULLs
  * (that is, an empty parse tree, not a null Const node) for any omitted
  * subscripts in a slice operation.  (Of course, if the transform method
- * does not care to support slicing, it can just throw an error if isSlice.)
+ * does not care to support slicing, it can just throw an error.)
  * See array_subscript_transform() for sample code.
  *
  * The transform method receives a pointer to a list of raw indirections.
@@ -100,7 +100,6 @@ typedef struct SubscriptExecSteps SubscriptExecSteps;
 typedef void (*SubscriptTransform) (SubscriptingRef *sbsref,
 									List **indirection,
 									ParseState *pstate,
-									bool isSlice,
 									bool isAssignment);
 
 /*
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 58a4b9df157..5cc3ce58c30 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -362,7 +362,8 @@ extern SubscriptingRef *transformContainerSubscripts(ParseState *pstate,
 													 Oid containerType,
 													 int32 containerTypMod,
 													 List **indirection,
-													 bool isAssignment);
+													 bool isAssignment,
+													 bool noError);
 extern Const *make_const(ParseState *pstate, A_Const *aconst);
 
 #endif							/* PARSE_NODE_H */
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index b815473f414..78dd9e71bf3 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -1782,17 +1782,17 @@ SELECT max(f1), min(f1), max(f2), min(f2), max(f3), min(f3) FROM arraggtest;
 (1 row)
 
 -- A few simple tests for arrays of composite types
-create type comptype as (f1 int, f2 text);
+create type comptype as (f1 int, f2 text, f3 int[]);
 create table comptable (c1 comptype, c2 comptype[]);
 -- XXX would like to not have to specify row() construct types here ...
 insert into comptable
-  values (row(1,'foo'), array[row(2,'bar')::comptype, row(3,'baz')::comptype]);
+  values (row(1,'foo',array[10,20]), array[row(2,'bar',array[30,40])::comptype, row(3,'baz',array[50,60])::comptype]);
 -- check that implicitly named array type _comptype isn't a problem
 create type _comptype as enum('fooey');
 select * from comptable;
-   c1    |          c2           
----------+-----------------------
- (1,foo) | {"(2,bar)","(3,baz)"}
+        c1         |                      c2                       
+-------------------+-----------------------------------------------
+ (1,foo,"{10,20}") | {"(2,bar,\"{30,40}\")","(3,baz,\"{50,60}\")"}
 (1 row)
 
 select c2[2].f2 from comptable;
@@ -1801,6 +1801,22 @@ select c2[2].f2 from comptable;
  baz
 (1 row)
 
+select c2[2].f3 from comptable;
+   f3    
+---------
+ {50,60}
+(1 row)
+
+select c2[2].f3[1:2] from comptable;
+   f3    
+---------
+ {50,60}
+(1 row)
+
+select c2[1:2].f3[1:2] from comptable;
+ERROR:  column notation .f3 applied to type comptype[], which is not a composite type
+LINE 1: select c2[1:2].f3[1:2] from comptable;
+               ^
 drop type _comptype;
 drop table comptable;
 drop type comptype;
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 47d62c1d38d..450389831a0 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -555,19 +555,22 @@ SELECT max(f1), min(f1), max(f2), min(f2), max(f3), min(f3) FROM arraggtest;
 
 -- A few simple tests for arrays of composite types
 
-create type comptype as (f1 int, f2 text);
+create type comptype as (f1 int, f2 text, f3 int[]);
 
 create table comptable (c1 comptype, c2 comptype[]);
 
 -- XXX would like to not have to specify row() construct types here ...
 insert into comptable
-  values (row(1,'foo'), array[row(2,'bar')::comptype, row(3,'baz')::comptype]);
+  values (row(1,'foo',array[10,20]), array[row(2,'bar',array[30,40])::comptype, row(3,'baz',array[50,60])::comptype]);
 
 -- check that implicitly named array type _comptype isn't a problem
 create type _comptype as enum('fooey');
 
 select * from comptable;
 select c2[2].f2 from comptable;
+select c2[2].f3 from comptable;
+select c2[2].f3[1:2] from comptable;
+select c2[1:2].f3[1:2] from comptable;
 
 drop type _comptype;
 drop table comptable;
-- 
2.39.5 (Apple Git-154)



^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-06-24 13:29                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-07-09 08:01                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-07-10 08:53                                 ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-21 04:53                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-22 08:19                                     ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-22 19:33                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-25 08:09                                         ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-26 03:52                                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-29 07:22                                             ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-02 02:59                                               ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-02 03:32                                                 ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-03 02:20                                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-09-03 06:56                                                     ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-09 23:53                                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-09-10 04:03                                                         ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-10 23:56                                                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-09-15 18:32                                                             ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
@ 2025-09-19 20:40                                                               ` Alexandra Wang <[email protected]>
  2025-09-22 03:32                                                                 ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  0 siblings, 1 reply; 67+ messages in thread

From: Alexandra Wang @ 2025-09-19 20:40 UTC (permalink / raw)
  To: pgsql-hackers; +Cc: Nikita Glukhov <[email protected]>; jian he <[email protected]>; Nikita Malakhov <[email protected]>; Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; David E. Wheeler <[email protected]>; Chao Li <[email protected]>

Hi there,

I've attached v20. It has the following changes:

1. New 0001: It adds test coverage for single-argument functions and
casting for jsonb expressions. This ensures that the relevant behavior
changes become visible in 0005 when field access via dot-notation is
introduced.

Specifically, once member access through dot-notation is enabled for
jsonb, we can no longer write single-argument functions (including
casts) in dot form for jsonb. For example:

Before 0005:

select ('{"a":1}'::jsonb).jsonb_typeof;
  jsonb_typeof
 --------------
 object
(1 row)

select ('[{"name": "alice"}, {"name": "bob"}]'::jsonb).name;
                 name
--------------------------------------
[{"name": "alice"}, {"name": "bob"}]
(1 row)

After 0005:

select ('{"a":1}'::jsonb).jsonb_typeof;
  jsonb_typeof
 --------------

(1 row)

select ('[{"name": "alice"}, {"name": "bob"}]'::jsonb).name;
       name
------------------
 ["alice", "bob"]
 (1 row)

In the meanwhile, these functions still return correct results through
standard syntax:

test=#  select jsonb_typeof(('{"a":1}'::jsonb));
 jsonb_typeof
--------------
 object
(1 row)
test=#  select ('[{"name": "alice"}, {"name": "bob"}]'::jsonb)::name;
                 name
--------------------------------------
 [{"name": "alice"}, {"name": "bob"}]
(1 row)

I don't consider this behavior change a major issue, because the
dot-form for single-argument functions is not standard SQL and seems
to be PostgreSQL-specific. Still, it's worth highlighting here so
users aren't surprised.

2. Refactored 0002: It combines and refactors v19-0001 and v19-0002.
Instead of changing the existing transform() callback in
SubscriptRoutines, it now introduces an additional callback,
transform_partial(). This alternative transform method, used by jsonb,
is more flexible: it accepts a wider range of indirection node types
and can transform only a prefix of the indirection list. This avoids
breaking compatibility for arrays, hstore, and external data types
that supports subscripting.

3. 0003 and 0004 stay unchanged. They are both small and can be squashed
into 0005. I leave them as-is for now for easier review.

4. Added two additional tests in 0005 for assignments using jsonb
dot-notation, showing explicitly that assignment is not yet supported.

5. Removed 0006 (array slicing) and 0007 (wildcard) from the previous
versions, as they need additional work. My immediate goal is to first
reach consensus on the dot-notation implementation.

Best,
Alex


Attachments:

  [application/octet-stream] v20-0005-Implement-read-only-dot-notation-for-jsonb.patch (74.4K, ../../CAK98qZ2EhKC=Z23jNbMX=aGPGi9+n42a8Eiz1rKYmF388UCHUQ@mail.gmail.com/3-v20-0005-Implement-read-only-dot-notation-for-jsonb.patch)
  download | inline diff:
From 089670877f762c140d24af27be27bd7dbccbe883 Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v20 5/5] Implement read-only dot notation for jsonb

This patch introduces JSONB member access using dot notation that
aligns with the JSON simplified accessor specified in SQL:2023.

Examples:

-- Setup
create table t(x int, y jsonb);
insert into t select 1, '{"a": 1, "b": 42}'::jsonb;
insert into t select 1, '{"a": 2, "b": {"c": 42}}'::jsonb;
insert into t select 1, '{"a": 3, "b": {"c": "42"}, "d":[11, 12]}'::jsonb;

-- Existing syntax in PostgreSQL that predates the SQL standard:
select (t.y)->'b' from t;
select (t.y)->'b'->'c' from t;
select (t.y)->'d'->0 from t;

-- JSON simplified accessor specified by the SQL standard:
select (t.y).b from t;
select (t.y).b.c from t;
select (t.y).d[0] from t;

The SQL standard states that simplified access is equivalent to:
JSON_QUERY (VEP, 'lax $.JC' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR)

where:
  VEP = <value expression primary>
  JC = <JSON simplified accessor op chain>

For example, the JSON_QUERY equivalents of the above queries are:

select json_query(y, 'lax $.b' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t;
select json_query(y, 'lax $.b.c' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t;
select json_query(y, 'lax $.d[0]' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t;

Implementation details:

This patch extends the existing container subscripting interface to
support container-specific information, namely a JSONPath expression
for jsonb.

During query transformation, if dot-notation is present, a JSONPath
expression is constructed to represent the access chain.

Then during execution, if a JSONPath expression is present in
JsonbSubWorkspace, executes it via JsonPathQuery().

Note that we cannot simply rewrite the accessors into JSON_QUERY()
during transformation, because the original query structure must be
preserved for EXPLAIN and CREATE VIEW.

Co-authored-by: Nikita Glukhov <[email protected]>
Co-authored-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Mark Dilger <[email protected]>
Reviewed-by: Jian He <[email protected]>
Reviewed-by: Vik Fearing <[email protected]>
Reviewed-by: Nikita Malakhov <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Tested-by: Jelte Fennema-Nio <[email protected]>
---
 doc/src/sgml/json.sgml                        | 301 ++++++++++++
 src/backend/catalog/sql_features.txt          |   4 +-
 src/backend/executor/execExpr.c               |  81 ++--
 src/backend/nodes/nodeFuncs.c                 |  12 +
 src/backend/utils/adt/jsonbsubs.c             | 302 +++++++++++-
 src/backend/utils/adt/ruleutils.c             |  43 +-
 src/include/nodes/primnodes.h                 |  54 ++-
 .../ecpg/test/expected/sql-sqljson.c          | 112 ++++-
 .../ecpg/test/expected/sql-sqljson.stderr     | 100 ++++
 .../ecpg/test/expected/sql-sqljson.stdout     |   7 +
 src/interfaces/ecpg/test/sql/sqljson.pgc      |  33 ++
 src/test/regress/expected/jsonb.out           | 444 +++++++++++++++++-
 src/test/regress/sql/jsonb.sql                | 114 +++++
 src/tools/pgindent/typedefs.list              |   1 +
 14 files changed, 1526 insertions(+), 82 deletions(-)

diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 206eadb8f7b..4e77e9e9512 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -712,6 +712,307 @@ UPDATE table_name SET jsonb_field[1]['a'] = '1';
   </para>
  </sect2>
 
+ <sect2 id="jsonb-simplified-accessor">
+  <title>JSON Simplified Accessor</title>
+  <para>
+   PostgreSQL implements the JSON simplified accessor as specified in SQL:2023.
+   The SQL standard defines the simplified accessor as a chain of operations
+   that can include JSON member accessors (dot notation for object fields)
+   and JSON array accessors (integer subscripts for array elements).
+   This provides a standardized way to access JSON data that complements
+   PostgreSQL's pre-standard subscripting and operator-based access methods.
+  </para>
+
+  <para>
+   The SQL:2023 simplified accessor syntax includes:
+   <itemizedlist>
+    <listitem>
+     <para>
+      <emphasis>JSON member accessor:</emphasis> <literal>jsonb_column.field_name</literal> 
+      for accessing object fields
+     </para>
+    </listitem>
+    <listitem>
+     <para>
+      <emphasis>JSON array accessor:</emphasis> <literal>jsonb_column[index]</literal> 
+      for accessing array elements by integer index
+     </para>
+    </listitem>
+   </itemizedlist>
+   These can be chained together: <literal>jsonb_column.field_name[0].nested_field</literal>.
+   When dot notation is present in the accessor chain, the entire chain follows
+   SQL:2023 semantics with lax mode behavior and conditional array wrapper.
+  </para>
+
+  <para>
+   The JSON simplified accessor is semantically equivalent to using
+   <function>JSON_QUERY</function> with the <literal>lax</literal> mode and
+   <literal>WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR</literal>
+   options. For example, <literal>json_col.field</literal> is equivalent to
+   <literal>JSON_QUERY(json_col, 'lax $.field' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR)</literal>.
+   The conditional array wrapper wraps multiple results in an array
+   but leaves a single result as-is without wrapping.
+  </para>
+
+  <para>
+   Examples of JSON simplified accessor syntax:
+
+<programlisting>
+
+-- Basic field access
+SELECT ('{"color": "red", "rgb": [255, 0, 0]}'::jsonb).color;
+
+-- Nested field access
+SELECT ('{"user": {"profile": {"settings": {"theme": "dark"}}}}'::jsonb).user.profile.settings.theme;
+
+-- JSON member accessor followed by JSON array accessor (both part of simplified accessor)
+SELECT ('{"repertoire": [{"title": "Swan Lake"}, {"title": "The Nutcracker"}]}'::jsonb).repertoire[1].title;
+
+-- In WHERE clauses
+SELECT * FROM users WHERE profile.preferences.theme = '"dark"';
+
+-- Comparison with other access methods (NOT equivalent - different semantics):
+SELECT json_col['address']['city'];           -- Subscripting
+SELECT json_col->'address'->'city';           -- Operator  
+SELECT json_col.address.city;                 -- Simplified accessor (different behavior)
+</programlisting>
+  </para>
+
+  <sect3 id="jsonb-access-method-comparison">
+   <title>Comparison of JSON Access Methods</title>
+   <para>
+    PostgreSQL provides three different approaches for accessing JSON data, each with
+    distinct semantics and behaviors:
+   </para>
+
+   <para>
+    <emphasis>SQL:2023 JSON Simplified Accessor:</emphasis>
+    <itemizedlist>
+     <listitem>
+      <para>
+       Syntax: <literal>json_col.field_name</literal> (member accessor) and 
+       <literal>json_col[index]</literal> (array accessor when used with dot notation)
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Standard SQL:2023 behavior with <literal>lax</literal> mode semantics
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Automatic array wrapping/unwrapping as specified in the SQL standard
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       When accessing a field from an array, operates on each array element and wraps results in an array;
+       when accessing an array index from a non-array, wraps the value as an array first
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Triggered when dot notation is present anywhere in the accessor chain
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Read-only access
+      </para>
+     </listitem>
+    </itemizedlist>
+   </para>
+
+   <para>
+    <emphasis>Pre-standard JSONB Subscripting:</emphasis>
+    <itemizedlist>
+     <listitem>
+      <para>
+       Syntax: <literal>json_col['field_name']</literal> (text-based) and 
+       <literal>json_col[index]</literal> (integer-based when no dot notation present)
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       PostgreSQL's original JSONB subscripting behavior (available since version 14)
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Direct object field and array element access without array wrapping/unwrapping
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Supports both read and write operations
+      </para>
+     </listitem>
+    </itemizedlist>
+   </para>
+
+   <para>
+    <emphasis>Arrow Operators:</emphasis>
+    <itemizedlist>
+     <listitem>
+      <para>
+       Syntax: <literal>json_col->'field_name'</literal> and <literal>json_col->>'field_name'</literal>
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       PostgreSQL's JSON operators that work with both <literal>json</literal> and <literal>jsonb</literal> types
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Direct object field and array element access without array wrapping/unwrapping
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       <literal>-></literal> returns jsonb, <literal>->></literal> returns text
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Read-only access
+      </para>
+     </listitem>
+    </itemizedlist>
+   </para>
+
+   <para>
+    <emphasis>Key Semantic Differences:</emphasis> The most important distinctions are
+    how these methods handle member access from arrays and array access from non-array values.
+   </para>
+
+   <para>
+    <emphasis>Member Access from Arrays:</emphasis>
+<programlisting>
+-- Setup data
+INSERT INTO test_table VALUES 
+  ('{"brightness": 80}'),                      -- Object case
+  ('[{"brightness": 45}, {"brightness": 90}]'); -- Array case
+
+-- Different behaviors:
+SELECT data.brightness FROM test_table;         -- Simplified accessor
+-- Results: 80, [45, 90]  (array elements unwrapped, results wrapped)
+
+SELECT data['brightness'] FROM test_table;      -- Pre-standard subscripting  
+-- Results: 80, NULL    (no array handling)
+
+SELECT data->'brightness' FROM test_table;      -- Arrow operator
+-- Results: 80, NULL    (no array handling)
+</programlisting>
+
+    In the array case, the simplified accessor applies the field access to each array element
+    (unwrapping) and conditionally wraps the results in an array, while subscripting and arrow operators
+    attempt direct field access on the array itself (which returns NULL since
+    arrays don't have named fields).
+   </para>
+
+   <para>
+    <emphasis>Array Access from Objects (Lax Mode Behavior):</emphasis>
+<programlisting>
+-- Setup data
+INSERT INTO test_table VALUES ('{"weather": "sunny", "temperature": "72F"}');
+
+-- Different behaviors when accessing [0] on a non-array value:
+SELECT data[0] FROM test_table;                 -- Simplified accessor (lax mode, if dots present elsewhere)
+-- Result: {"weather": "sunny", "temperature": "72F"}  (object wrapped as array, [0] returns entire object)
+
+SELECT data[0] FROM test_table;                 -- Pre-standard subscripting (strict mode, no dots)
+-- Result: NULL  (no wrapping, direct array access on object fails)
+
+SELECT data->0 FROM test_table;                 -- Arrow operator (strict mode)
+-- Result: NULL  (no wrapping, direct array access on object fails)
+</programlisting>
+
+    In lax mode (simplified accessor), when an array operation is performed on a non-array value,
+    the value is first wrapped in an array, then the operation proceeds. In strict mode 
+    (pre-standard methods), the operation fails and returns NULL.
+   </para>
+
+   <para>
+    <emphasis>When to Use Each Method:</emphasis>
+    <itemizedlist>
+     <listitem>
+      <para>
+       Use <emphasis>SQL:2023 simplified accessor</emphasis> for standard compliance and when you want lax mode and conditional array wrapper
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Use <emphasis>pre-standard subscripting</emphasis> for write operations or when you need direct field access without array processing
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Use <emphasis>arrow operators</emphasis> when you need text output (<literal>->></literal>) or when working with both <literal>json</literal> and <literal>jsonb</literal> types
+      </para>
+     </listitem>
+    </itemizedlist>
+   </para>
+  </sect3>
+
+  <sect3 id="jsonb-accessor-best-practices">
+   <title>Best Practices: Avoid Mixing Access Methods</title>
+   <para>
+    <emphasis>Important:</emphasis> Do not mix SQL:2023 simplified accessor syntax 
+    with pre-standard subscripting syntax in the same accessor chain. These 
+    methods have subtly different semantics and are not interchangeable aliases.
+    Mixing them can lead to confusion and code that is difficult to understand.
+   </para>
+
+   <para>
+    <emphasis>Recommended - Consistent simplified accessor:</emphasis>
+<programlisting>
+-- All parts use simplified accessor (standard behavior)
+SELECT data.location.coordinates.latitude FROM table;  -- Good
+SELECT data.repertoire[0].title FROM table;           -- Good  
+SELECT data.users[1].profile.email FROM table;        -- Good
+</programlisting>
+   </para>
+
+   <para>
+    <emphasis>Recommended - Consistent pre-standard subscripting:</emphasis>
+<programlisting>
+-- All parts use pre-standard subscripting
+SELECT data['location']['coordinates']['latitude'] FROM table; -- Good
+SELECT data[0]['title'] FROM table;                    -- Good (when no dots present)
+SELECT data['users'][1]['profile']['email'] FROM table; -- Good
+</programlisting>
+   </para>
+
+   <para>
+    <emphasis>Not recommended - Mixed syntax:</emphasis>
+<programlisting>
+-- Mixing simplified accessor with pre-standard subscripting
+SELECT data.location['latitude'] FROM table;       -- Avoid
+SELECT data['repertoire'][0].title FROM table;    -- Avoid
+</programlisting>
+    While these mixed forms work as designed, they can be very confusing
+    because the simplified accessor cannot handle text-based subscripts like `['field']`.
+    This forces a fallback to pre-standard semantics for those specific parts,
+    creating a chain that switches between lax mode (for dot notation) and 
+    strict mode (for text subscripts) within the same accessor expression.
+   </para>
+
+   <para>
+    Choose one approach consistently throughout your accessor chain to ensure
+    predictable and maintainable code.
+   </para>
+
+   <para>
+    <emphasis>Current Implementation:</emphasis> The current implementation supports
+    JSON member accessors (dot notation) and JSON array accessors (integer subscripts)
+    as defined in the SQL:2023 simplified accessor specification.
+    Advanced features from the SQL:2023 specification, such as wildcard member
+    accessors and item method accessors, are not yet implemented.
+   </para>
+  </sect3>
+ </sect2>
+
  <sect2 id="datatype-json-transforms">
   <title>Transforms</title>
 
diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_features.txt
index ebe85337c28..457e993305e 100644
--- a/src/backend/catalog/sql_features.txt
+++ b/src/backend/catalog/sql_features.txt
@@ -568,8 +568,8 @@ T838	JSON_TABLE: PLAN DEFAULT clause			NO
 T839	Formatted cast of datetimes to/from character strings			NO	
 T840	Hex integer literals in SQL/JSON path language			YES	
 T851	SQL/JSON: optional keywords for default syntax			YES	
-T860	SQL/JSON simplified accessor: column reference only			NO	
-T861	SQL/JSON simplified accessor: case-sensitive JSON member accessor			NO	
+T860	SQL/JSON simplified accessor: column reference only			YES	
+T861	SQL/JSON simplified accessor: case-sensitive JSON member accessor			YES	
 T862	SQL/JSON simplified accessor: wildcard member accessor			NO	
 T863	SQL/JSON simplified accessor: single-quoted string literal as member accessor			NO	
 T864	SQL/JSON simplified accessor			NO	
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index f1569879b52..385c8d0cefe 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -3320,50 +3320,59 @@ ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref,
 								   state->steps_len - 1);
 	}
 
-	/* Evaluate upper subscripts */
-	i = 0;
-	foreach(lc, sbsref->refupperindexpr)
+	/* Evaluate upper subscripts, unless refjsonbpath is used for execution */
+	if (!sbsref->refjsonbpath)
 	{
-		Expr	   *e = (Expr *) lfirst(lc);
-
-		/* When slicing, individual subscript bounds can be omitted */
-		if (!e)
-		{
-			sbsrefstate->upperprovided[i] = false;
-			sbsrefstate->upperindexnull[i] = true;
-		}
-		else
+		i = 0;
+		foreach(lc, sbsref->refupperindexpr)
 		{
-			sbsrefstate->upperprovided[i] = true;
-			/* Each subscript is evaluated into appropriate array entry */
-			ExecInitExprRec(e, state,
-							&sbsrefstate->upperindex[i],
-							&sbsrefstate->upperindexnull[i]);
+			Expr *e = (Expr *) lfirst(lc);
+
+			/* When slicing, individual subscript bounds can be omitted */
+			if (!e)
+			{
+				sbsrefstate->upperprovided[i] = false;
+				sbsrefstate->upperindexnull[i] = true;
+			}
+			else
+			{
+				sbsrefstate->upperprovided[i] = true;
+				/* Each subscript is evaluated into appropriate array entry */
+				ExecInitExprRec(e, state,
+								&sbsrefstate->upperindex[i],
+								&sbsrefstate->upperindexnull[i]);
+			}
+			i++;
 		}
-		i++;
 	}
 
-	/* Evaluate lower subscripts similarly */
-	i = 0;
-	foreach(lc, sbsref->reflowerindexpr)
+	/*
+	 * Evaluate lower subscripts similarly, unless refjsonbpath is used for
+	 * execution
+	 */
+	if (!sbsref->refjsonbpath)
 	{
-		Expr	   *e = (Expr *) lfirst(lc);
-
-		/* When slicing, individual subscript bounds can be omitted */
-		if (!e)
-		{
-			sbsrefstate->lowerprovided[i] = false;
-			sbsrefstate->lowerindexnull[i] = true;
-		}
-		else
+		i = 0;
+		foreach(lc, sbsref->reflowerindexpr)
 		{
-			sbsrefstate->lowerprovided[i] = true;
-			/* Each subscript is evaluated into appropriate array entry */
-			ExecInitExprRec(e, state,
-							&sbsrefstate->lowerindex[i],
-							&sbsrefstate->lowerindexnull[i]);
+			Expr	   *e = (Expr *) lfirst(lc);
+
+			/* When slicing, individual subscript bounds can be omitted */
+			if (!e)
+			{
+				sbsrefstate->lowerprovided[i] = false;
+				sbsrefstate->lowerindexnull[i] = true;
+			}
+			else
+			{
+				sbsrefstate->lowerprovided[i] = true;
+				/* Each subscript is evaluated into appropriate array entry */
+				ExecInitExprRec(e, state,
+								&sbsrefstate->lowerindex[i],
+								&sbsrefstate->lowerindexnull[i]);
+			}
+			i++;
 		}
-		i++;
 	}
 
 	/* SBSREF_SUBSCRIPTS checks and converts all the subscripts at once */
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 7bc823507f1..d1bd575d9bd 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -284,6 +284,13 @@ exprType(const Node *expr)
 		case T_PlaceHolderVar:
 			type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_FieldAccessorExpr:
+			/*
+			 * FieldAccessorExpr is not evaluable. Treat it as TEXT for collation,
+			 * deparsing, and similar purposes, since it represents a JSON field name.
+			 */
+			type = TEXTOID;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			type = InvalidOid;	/* keep compiler quiet */
@@ -1146,6 +1153,9 @@ exprSetCollation(Node *expr, Oid collation)
 		case T_MergeSupportFunc:
 			((MergeSupportFunc *) expr)->msfcollid = collation;
 			break;
+		case T_FieldAccessorExpr:
+			((FieldAccessorExpr *) expr)->faecollid = collation;
+			break;
 		case T_SubscriptingRef:
 			((SubscriptingRef *) expr)->refcollid = collation;
 			break;
@@ -2129,6 +2139,7 @@ expression_tree_walker_impl(Node *node,
 		case T_SortGroupClause:
 		case T_CTESearchClause:
 		case T_MergeSupportFunc:
+		case T_FieldAccessorExpr:
 			/* primitive node types with no expression subnodes */
 			break;
 		case T_WithCheckOption:
@@ -3008,6 +3019,7 @@ expression_tree_mutator_impl(Node *node,
 		case T_SortGroupClause:
 		case T_CTESearchClause:
 		case T_MergeSupportFunc:
+		case T_FieldAccessorExpr:
 			return copyObject(node);
 		case T_WithCheckOption:
 			{
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index 516146d1146..bdfe6a310de 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -15,21 +15,30 @@
 #include "postgres.h"
 
 #include "executor/execExpr.h"
+#include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "nodes/subscripting.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_expr.h"
 #include "utils/builtins.h"
 #include "utils/jsonb.h"
+#include "utils/jsonpath.h"
 
 
-/* SubscriptingRefState.workspace for jsonb subscripting execution */
+/*
+ * SubscriptingRefState.workspace for generic jsonb subscripting execution.
+ *
+ * Stores state for both jsonb simple subscripting and dot notation access.
+ * Dot notation additionally uses `jsonpath` for JsonPath evaluation.
+ */
 typedef struct JsonbSubWorkspace
 {
 	bool		expectArray;	/* jsonb root is expected to be an array */
 	Oid		   *indexOid;		/* OID of coerced subscript expression, could
 								 * be only integer or text */
 	Datum	   *index;			/* Subscript values in Datum format */
+	JsonPath   *jsonpath;		/* JsonPath for dot notation execution via
+								 * JsonPathQuery() */
 } JsonbSubWorkspace;
 
 static Node *
@@ -96,6 +105,233 @@ coerce_jsonpath_subscript_to_int4_or_text(ParseState *pstate, Node *subExpr)
 	return subExpr;
 }
 
+/*
+ * During transformation, determine whether to build a JsonPath
+ * for JsonPathQuery() execution.
+ *
+ * JsonPath is needed if the indirection list includes:
+ * - String-based access (dot notation)
+ * - Slice-based subscripting (when isSlice is true)
+ *
+ * Otherwise, simple jsonb subscripting is enough.
+ */
+static bool
+jsonb_check_jsonpath_needed(List *indirection)
+{
+	ListCell   *lc;
+
+	foreach(lc, indirection)
+	{
+		Node	   *accessor = lfirst(lc);
+
+		if (IsA(accessor, String))
+			return true;
+		else
+			Assert(IsA(accessor, A_Indices));
+	}
+
+	return false;
+}
+
+/*
+ * Helper functions for constructing JsonPath expressions.
+ *
+ * The make_jsonpath_item_* functions create various types of JsonPathParseItem
+ * nodes, which are used to build JsonPath expressions for jsonb simplified
+ * accessor.
+ */
+
+static JsonPathParseItem *
+make_jsonpath_item(JsonPathItemType type)
+{
+	JsonPathParseItem *v = palloc(sizeof(*v));
+
+	v->type = type;
+	v->next = NULL;
+
+	return v;
+}
+
+/*
+ * Convert a constant integer expression into a JsonPathParseItem.
+ *
+ * The input expression must be a non-null constant of type INT4. Returns NULL otherwise.
+ * This function constructs a jpiNumeric item for use in JsonPath and appends a matching
+ * Const(INT4) node to the given expression list for use in EXPLAIN, views, etc.
+ *
+ * Parameters:
+ * - pstate: parse state context
+ * - expr: input expression node
+ * - exprs: list of expression nodes (updated in place)
+ */
+static JsonPathParseItem *
+make_jsonpath_item_expr(ParseState *pstate, Node *expr, List **exprs)
+{
+	Const	   *cnst;
+
+	expr = transformExpr(pstate, expr, pstate->p_expr_kind);
+
+	if (IsA(expr, Const))
+	{
+		cnst = (Const *) expr;
+		if (cnst->consttype == INT4OID && !(cnst->constisnull))
+		{
+			JsonPathParseItem *jpi = make_jsonpath_item(jpiNumeric);
+
+			jpi->value.numeric =
+				DatumGetNumeric(DirectFunctionCall1(int4_numeric, Int32GetDatum(cnst->constvalue)));
+
+			*exprs = lappend(*exprs, makeConst(INT4OID, -1, InvalidOid, 4,
+											   Int32GetDatum(cnst->constvalue), false, true));
+
+			return jpi;
+		}
+	}
+
+	return NULL;
+}
+
+/*
+ * Constructs a JsonPath expression from a list of indirections.
+ * This function is used when jsonb subscripting involves dot notation,
+ * requiring JsonPath-based evaluation.
+ *
+ * The function modifies the indirection list in place, removing processed
+ * elements as it converts them into JsonPath components, as follows:
+ * - String keys (dot notation) -> jpiKey items.
+ * - Array indices -> jpiIndexArray items.
+ *
+ * In addition to building the JsonPath expression, this function populates
+ * the following fields of the given SubscriptingRef:
+ * - refjsonbpath: the generated JsonPath
+ * - refupperindexpr: upper index expressions (object keys or array indexes)
+ * - reflowerindexpr: lower index expressions, remains NIL as slices are not supported.
+ *
+ * Parameters:
+ * - pstate: Parse state context.
+ * - indirection: List of subscripting expressions (modified in-place).
+ * - sbsref: SubscriptingRef node to update
+ */
+static int
+jsonb_subscript_make_jsonpath(ParseState *pstate, List *indirection, SubscriptingRef *sbsref)
+{
+	JsonPathParseResult jpres;
+	JsonPathParseItem *path = make_jsonpath_item(jpiRoot);
+	ListCell   *lc;
+	Datum		jsp;
+	int			pathlen = 0;
+
+	sbsref->refupperindexpr = NIL;
+	sbsref->reflowerindexpr = NIL;
+	sbsref->refjsonbpath = NULL;
+
+	jpres.expr = path;
+	jpres.lax = true;
+
+	foreach(lc, indirection)
+	{
+		Node	   *accessor = lfirst(lc);
+		JsonPathParseItem *jpi;
+
+		if (IsA(accessor, String))
+		{
+			char	   *field = strVal(accessor);
+			FieldAccessorExpr *accessor_expr;
+
+			jpi = make_jsonpath_item(jpiKey);
+			jpi->value.string.val = field;
+			jpi->value.string.len = strlen(field);
+
+			accessor_expr = makeNode(FieldAccessorExpr);
+			accessor_expr->type = T_FieldAccessorExpr;
+			accessor_expr->fieldname = field;
+
+			sbsref->refupperindexpr = lappend(sbsref->refupperindexpr, accessor_expr);
+		}
+		else if (IsA(accessor, A_Indices))
+		{
+			A_Indices  *ai = castNode(A_Indices, accessor);
+
+			if (!ai->is_slice)
+			{
+				JsonPathParseItem *jpi_from = NULL;
+
+				Assert(ai->uidx && !ai->lidx);
+				jpi_from = make_jsonpath_item_expr(pstate, ai->uidx, &sbsref->refupperindexpr);
+				if (jpi_from == NULL)
+				{
+					/*
+					 * Break out of the loop if the subscript is not a
+					 * non-null integer constant, so that we can fall back to
+					 * jsonb subscripting logic.
+					 *
+					 * This is needed to handle cases with mixed usage of SQL
+					 * standard json simplified accessor syntax and PostgreSQL
+					 * jsonb subscripting syntax, e.g:
+					 *
+					 * select (jb).a['b'].c from jsonb_table;
+					 *
+					 * where dot-notation (.a and .c) is the SQL standard json
+					 * simplified accessor syntax, and the ['b'] subscript is
+					 * the PostgreSQL jsonb subscripting syntax, because 'b'
+					 * is not a non-null constant integer and cannot be used
+					 * for json array access.
+					 *
+					 * In this case, we cannot create a JsonPath item, so we
+					 * break out of the loop and let
+					 * jsonb_subscript_transform() handle this indirection as
+					 * a PostgreSQL jsonb subscript.
+					 */
+					break;
+				}
+
+				jpi = make_jsonpath_item(jpiIndexArray);
+				jpi->value.array.nelems = 1;
+				jpi->value.array.elems = palloc(sizeof(jpi->value.array.elems[0]));
+
+				jpi->value.array.elems[0].from = jpi_from;
+				jpi->value.array.elems[0].to = NULL;
+			}
+			else
+			{
+				Node	   *expr = ai->uidx ? ai->uidx : ai->lidx;
+
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("jsonb subscript does not support slices"),
+						 parser_errposition(pstate, exprLocation(expr))));
+			}
+		}
+		else
+		{
+			/*
+			 * Unexpected node type in indirection list. This should not
+			 * happen with current grammar, but we handle it defensively by
+			 * breaking out of the loop rather than crashing. In case of
+			 * future grammar changes that might introduce new node types,
+			 * this allows us to create a jsonpath from as many indirection
+			 * elements as we can and let transformIndirection() fallback to
+			 * alternative logic to handle the remaining indirection elements.
+			 */
+			Assert(false);		/* not reachable */
+			break;
+		}
+
+		/* append path item */
+		path->next = jpi;
+		path = jpi;
+		pathlen++;
+	}
+
+	if (pathlen != 0)
+	{
+		jsp = jsonPathFromParseResult(&jpres, 0, NULL);
+		sbsref->refjsonbpath = (Node *) makeConst(JSONPATHOID, -1, InvalidOid, -1, jsp, false, false);
+	}
+
+	return pathlen;
+}
+
 /*
  * Finish parse analysis of a SubscriptingRef expression for a jsonb.
  *
@@ -111,9 +347,29 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	List	   *upperIndexpr = NIL;
 	ListCell   *idx;
 
+	/* Determine the result type of the subscripting operation; always jsonb */
+	sbsref->refrestype = JSONBOID;
+	sbsref->reftypmod = -1;
+
+	if (jsonb_check_jsonpath_needed(indirection))
+	{
+		int pathlen;
+
+		pathlen = jsonb_subscript_make_jsonpath(pstate, indirection, sbsref);
+		if (sbsref->refjsonbpath)
+			return pathlen;
+	}
+
 	/*
-	 * Transform and convert the subscript expressions. Jsonb subscripting
-	 * does not support slices, look only at the upper index.
+	 * We reach here only in two cases: (a) the JSON simplified accessor is
+	 * not needed at all (for example, a plain array subscript like [1] or
+	 * object key access like ['a']), or (b) jsonb_subscript_make_jsonpath()
+	 * was called but could not complete the JsonPath construction (for
+	 * example, when mixing dot notation with non-integer subscripts like
+	 * (jb)['a'].b where 'a' is not a constant integer).
+	 *
+	 * In both cases we fall back to pre-standard jsonb subscripting, coercing
+	 * each subscript to array index or object key as needed.
 	 */
 	foreach(idx, indirection)
 	{
@@ -160,10 +416,6 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->refupperindexpr = upperIndexpr;
 	sbsref->reflowerindexpr = NIL;
 
-	/* Determine the result type of the subscripting operation; always jsonb */
-	sbsref->refrestype = JSONBOID;
-	sbsref->reftypmod = -1;
-
 	return list_length(upperIndexpr);
 }
 
@@ -216,7 +468,7 @@ jsonb_subscript_check_subscripts(ExprState *state,
 			 * For jsonb fetch and assign functions we need to provide path in
 			 * text format. Convert if it's not already text.
 			 */
-			if (workspace->indexOid[i] == INT4OID)
+			if (!workspace->jsonpath && workspace->indexOid[i] == INT4OID)
 			{
 				Datum		datum = sbsrefstate->upperindex[i];
 				char	   *cs = DatumGetCString(DirectFunctionCall1(int4out, datum));
@@ -244,17 +496,32 @@ jsonb_subscript_fetch(ExprState *state,
 {
 	SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
 	JsonbSubWorkspace *workspace = (JsonbSubWorkspace *) sbsrefstate->workspace;
-	Jsonb	   *jsonbSource;
 
 	/* Should not get here if source jsonb (or any subscript) is null */
 	Assert(!(*op->resnull));
 
-	jsonbSource = DatumGetJsonbP(*op->resvalue);
-	*op->resvalue = jsonb_get_element(jsonbSource,
-									  workspace->index,
-									  sbsrefstate->numupper,
-									  op->resnull,
-									  false);
+	if (workspace->jsonpath)
+	{
+		bool		empty = false;
+		bool		error = false;
+
+		*op->resvalue = JsonPathQuery(*op->resvalue, workspace->jsonpath,
+									  JSW_CONDITIONAL,
+									  &empty, &error, NULL,
+									  NULL);
+
+		*op->resnull = empty || error;
+	}
+	else
+	{
+		Jsonb	   *jsonbSource = DatumGetJsonbP(*op->resvalue);
+
+		*op->resvalue = jsonb_get_element(jsonbSource,
+										  workspace->index,
+										  sbsrefstate->numupper,
+										  op->resnull,
+										  false);
+	}
 }
 
 /*
@@ -362,7 +629,7 @@ jsonb_exec_setup(const SubscriptingRef *sbsref,
 {
 	JsonbSubWorkspace *workspace;
 	ListCell   *lc;
-	int			nupper = sbsref->refupperindexpr->length;
+	int			nupper = list_length(sbsref->refupperindexpr);
 	char	   *ptr;
 
 	/* Allocate type-specific workspace with space for per-subscript data */
@@ -371,6 +638,9 @@ jsonb_exec_setup(const SubscriptingRef *sbsref,
 	workspace->expectArray = false;
 	ptr = ((char *) workspace) + MAXALIGN(sizeof(JsonbSubWorkspace));
 
+	if (sbsref->refjsonbpath)
+		workspace->jsonpath = DatumGetJsonPathP(castNode(Const, sbsref->refjsonbpath)->constvalue);
+
 	/*
 	 * This coding assumes sizeof(Datum) >= sizeof(Oid), else we might
 	 * misalign the indexOid pointer
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 0408a95941d..c899734c2e3 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9329,10 +9329,13 @@ get_rule_expr(Node *node, deparse_context *context,
 				 * Parenthesize the argument unless it's a simple Var or a
 				 * FieldSelect.  (In particular, if it's another
 				 * SubscriptingRef, we *must* parenthesize to avoid
-				 * confusion.)
+				 * confusion.) Always add parenthesis if JSON simplified
+				 * accessor is used, for now.
 				 */
-				need_parens = !IsA(sbsref->refexpr, Var) &&
-					!IsA(sbsref->refexpr, FieldSelect);
+				need_parens = (!IsA(sbsref->refexpr, Var) &&
+					!IsA(sbsref->refexpr, FieldSelect)) ||
+						sbsref->refjsonbpath;
+
 				if (need_parens)
 					appendStringInfoChar(buf, '(');
 				get_rule_expr((Node *) sbsref->refexpr, context, showimplicit);
@@ -13005,17 +13008,35 @@ printSubscripts(SubscriptingRef *sbsref, deparse_context *context)
 	lowlist_item = list_head(sbsref->reflowerindexpr);	/* could be NULL */
 	foreach(uplist_item, sbsref->refupperindexpr)
 	{
-		appendStringInfoChar(buf, '[');
-		if (lowlist_item)
+		Node *upper = (Node *) lfirst(uplist_item);
+
+		if (upper && IsA(upper, FieldAccessorExpr))
 		{
+			FieldAccessorExpr *fae = (FieldAccessorExpr *) upper;
+
+			/* Use dot-notation for field access */
+			appendStringInfoChar(buf, '.');
+			appendStringInfoString(buf, quote_identifier(fae->fieldname));
+
+			/* Skip matching low index — field access doesn't use slices */
+			if (lowlist_item)
+				lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
+		}
+		else
+		{
+			/* Use JSONB array subscripting */
+			appendStringInfoChar(buf, '[');
+			if (lowlist_item)
+			{
+				/* If subexpression is NULL, get_rule_expr prints nothing */
+				get_rule_expr((Node *) lfirst(lowlist_item), context, false);
+				appendStringInfoChar(buf, ':');
+				lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
+			}
 			/* If subexpression is NULL, get_rule_expr prints nothing */
-			get_rule_expr((Node *) lfirst(lowlist_item), context, false);
-			appendStringInfoChar(buf, ':');
-			lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
+			get_rule_expr(upper, context, false);
+			appendStringInfoChar(buf, ']');
 		}
-		/* If subexpression is NULL, get_rule_expr prints nothing */
-		get_rule_expr((Node *) lfirst(uplist_item), context, false);
-		appendStringInfoChar(buf, ']');
 	}
 }
 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 6dfca3cb35b..7e89621bd65 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -708,18 +708,30 @@ typedef struct SubscriptingRef
 	int32		reftypmod pg_node_attr(query_jumble_ignore);
 	/* collation of result, or InvalidOid if none */
 	Oid			refcollid pg_node_attr(query_jumble_ignore);
-	/* expressions that evaluate to upper container indexes */
+
+	/*
+	 * expressions that evaluate to upper container indexes or expressions
+	 * that are collected but not evaluated when refjsonbpath is set.
+	 */
 	List	   *refupperindexpr;
 
 	/*
-	 * expressions that evaluate to lower container indexes, or NIL for single
-	 * container element.
+	 * expressions that evaluate to lower container indexes, or NIL for a
+	 * single container element, or expressions that are collected but not
+	 * evaluated when refjsonbpath is set.
 	 */
 	List	   *reflowerindexpr;
 	/* the expression that evaluates to a container value */
 	Expr	   *refexpr;
 	/* expression for the source value, or NULL if fetch */
 	Expr	   *refassgnexpr;
+
+	/*
+	 * container-specific extra information, currently used only by jsonb.
+	 * stores a JsonPath expression when jsonb dot notation is used. NULL for
+	 * simple subscripting.
+	 */
+	Node	   *refjsonbpath;
 } SubscriptingRef;
 
 /*
@@ -2371,4 +2383,40 @@ typedef struct OnConflictExpr
 	List	   *exclRelTlist;	/* tlist of the EXCLUDED pseudo relation */
 } OnConflictExpr;
 
+/*
+ * FieldAccessorExpr - represents a single object member access using dot-notation
+ *		in JSON simplified accessor syntax (e.g., jsonb_col.a).
+ *
+ * These nodes appear as list elements in SubscriptingRef.refupperindexpr to
+ * indicate JSON object key access. They are not evaluable expressions by
+ * themselves but serve as placeholders to preserve source-level syntax for
+ * rule rewriting and deparsing (e.g., in EXPLAIN and view definitions).
+ * Execution is handled by the enclosing SubscriptingRef.
+ *
+ * If dot-notation is used in a SubscriptingRef, the JSON path is represented
+ * as a flat list of FieldAccessorExpr nodes (for object field access), Const
+ * nodes (for array indexes), and NULLs (for omitted slice bounds), rather than
+ * through nested expression trees.
+ *
+ * Note: The flat representation avoids nested FieldAccessorExpr chains,
+ * simplifying evaluation and enabling standard-compliant behavior such as
+ * conditional array wrapping. This avoids the need for position-aware
+ * wrapping/unwrapping logic during execution.
+ *
+ * For example, in the expression:
+ *		('{"a": [{"b": 1}]}'::jsonb).a[0].b
+ * the SubscriptingRef will contain:
+ *		- refexpr: the base expression (the jsonb value)
+ *		- refupperindexpr: [FieldAccessorExpr("a"), Const(0),
+ *			FieldAccessorExpr("b")]
+ *		- reflowerindexpr: [NULL, NULL, NULL] (slice lower bounds not used here)
+ */
+typedef struct FieldAccessorExpr
+{
+	NodeTag		type;
+	char	   *fieldname;		/* name of the JSONB object field accessed via
+								 * dot notation */
+	Oid			faecollid pg_node_attr(query_jumble_ignore);
+}			FieldAccessorExpr;
+
 #endif							/* PRIMNODES_H */
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.c b/src/interfaces/ecpg/test/expected/sql-sqljson.c
index 39221f9ea5d..e6a7ece6dab 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.c
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.c
@@ -417,12 +417,122 @@ if (sqlca.sqlcode < 0) sqlprint();}
 	  for (int i = 0; i < sizeof(is_json); i++)
 		  printf("Found is_json[%d]: %s\n", i, is_json[i] ? "true" : "false");
 
-  { ECPGdisconnect(__LINE__, "CURRENT");
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . \"a\" )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 118 "sqljson.pgc"
 
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 118 "sqljson.pgc"
 
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb . \"a\" )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 121 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 121 "sqljson.pgc"
+
+	// error
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . a )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 124 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 124 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . a . b )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 127 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 127 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( coalesce ( json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . c ) , 'null' ) )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 130 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 130 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ 0 ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 133 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 133 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b . x )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 136 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 136 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) [ 'b' ] [ 0 ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 139 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 139 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 142 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 142 "sqljson.pgc"
+
+	// error
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . * )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 145 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 145 "sqljson.pgc"
+
+	// error
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 148 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 148 "sqljson.pgc"
+
+	// error
+
+  { ECPGdisconnect(__LINE__, "CURRENT");
+#line 151 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 151 "sqljson.pgc"
+
 
   return 0;
 }
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
index e55a95dd711..19f8c58af06 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
@@ -268,5 +268,105 @@ SQL error: cannot use type jsonb in RETURNING clause of JSON_SERIALIZE() on line
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_get_data on line 102: RESULT: f offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 118: query: select json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . "a" ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 118: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 118: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 118: RESULT: {"b": 1, "c": 2} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 121: query: select json ( '{"a": {"b": 1, "c": 2}}' :: jsonb . "a" ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 121: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 121: bad response - ERROR:  schema "jsonb" does not exist
+LINE 1: select json ( '{"a": {"b": 1, "c": 2}}' :: jsonb . "a" )
+                                                   ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 3F000 (sqlcode -400): schema "jsonb" does not exist on line 121
+[NO_PID]: sqlca: code: -400, state: 3F000
+SQL error: schema "jsonb" does not exist on line 121
+[NO_PID]: ecpg_execute on line 124: query: select json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . a ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 124: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 124: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 124: RESULT: {"b": 1, "c": 2} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 127: query: select json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . a . b ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 127: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 127: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 127: RESULT: 1 offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 130: query: select json ( coalesce ( json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . c ) , 'null' ) ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 130: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 130: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 130: RESULT: null offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 133: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 0 ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 133: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 133: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 133: RESULT: {"x": 1} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 136: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b . x ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 136: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 136: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 136: RESULT: [1, [12, {"y": 1}]] offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 139: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 139: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 139: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 139: RESULT: {"x": 1} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 142: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 142: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 142: bad response - ERROR:  jsonb subscript does not support slices
+LINE 1: ..., {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] )
+                                                                ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 42804 (sqlcode -400): jsonb subscript does not support slices on line 142
+[NO_PID]: sqlca: code: -400, state: 42804
+SQL error: jsonb subscript does not support slices on line 142
+[NO_PID]: ecpg_execute on line 145: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . * ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 145: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 145: bad response - ERROR:  row expansion via "*" is not supported here
+LINE 1: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x...
+                        ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 0A000 (sqlcode -400): row expansion via "*" is not supported here on line 145
+[NO_PID]: sqlca: code: -400, state: 0A000
+SQL error: row expansion via "*" is not supported here on line 145
+[NO_PID]: ecpg_execute on line 148: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 148: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 148: bad response - ERROR:  jsonb subscript does not support slices
+LINE 1: ...x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )
+                                                                ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 42804 (sqlcode -400): jsonb subscript does not support slices on line 148
+[NO_PID]: sqlca: code: -400, state: 42804
+SQL error: jsonb subscript does not support slices on line 148
 [NO_PID]: ecpg_finish: connection ecpg1_regression closed
 [NO_PID]: sqlca: code: 0, state: 00000
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
index 83f8df13e5a..442d36931f1 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
@@ -28,3 +28,10 @@ Found is_json[4]: false
 Found is_json[5]: false
 Found is_json[6]: true
 Found is_json[7]: false
+Found json={"b": 1, "c": 2}
+Found json={"b": 1, "c": 2}
+Found json=1
+Found json=null
+Found json={"x": 1}
+Found json=[1, [12, {"y": 1}]]
+Found json={"x": 1}
diff --git a/src/interfaces/ecpg/test/sql/sqljson.pgc b/src/interfaces/ecpg/test/sql/sqljson.pgc
index ddcbcc3b3cb..57a9bff424d 100644
--- a/src/interfaces/ecpg/test/sql/sqljson.pgc
+++ b/src/interfaces/ecpg/test/sql/sqljson.pgc
@@ -115,6 +115,39 @@ EXEC SQL END DECLARE SECTION;
 	  for (int i = 0; i < sizeof(is_json); i++)
 		  printf("Found is_json[%d]: %s\n", i, is_json[i] ? "true" : "false");
 
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}}'::jsonb)."a") INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON('{"a": {"b": 1, "c": 2}}'::jsonb."a") INTO :json;
+	// error
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}}'::jsonb).a) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}}'::jsonb).a.b) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(COALESCE(JSON(('{"a": {"b": 1, "c": 2}}'::jsonb).c), 'null')) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b[0]) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b.x) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb)['b'][0]) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b[1].x[0:]) INTO :json;
+	// error
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).*) INTO :json;
+	// error
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb)['b'][0:]) INTO :json;
+	// error
+
   EXEC SQL DISCONNECT;
 
   return 0;
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index fb47c8a893a..685f229e682 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -4989,6 +4989,12 @@ select ('123'::jsonb)['a'];
  
 (1 row)
 
+select ('123'::jsonb).a;
+ a 
+---
+ 
+(1 row)
+
 select ('123'::jsonb)[0];
  jsonb 
 -------
@@ -5001,12 +5007,24 @@ select ('123'::jsonb)[NULL];
  
 (1 row)
 
+select ('123'::jsonb).NULL;
+ null 
+------
+ 
+(1 row)
+
 select ('{"a": 1}'::jsonb)['a'];
  jsonb 
 -------
  1
 (1 row)
 
+select ('{"a": 1}'::jsonb).a;
+ a 
+---
+ 1
+(1 row)
+
 select ('{"a": 1}'::jsonb)[0];
  jsonb 
 -------
@@ -5019,6 +5037,12 @@ select ('{"a": 1}'::jsonb)['not_exist'];
  
 (1 row)
 
+select ('{"a": 1}'::jsonb)."not_exist";
+ not_exist 
+-----------
+ 
+(1 row)
+
 select ('{"a": 1}'::jsonb)[NULL];
  jsonb 
 -------
@@ -5031,6 +5055,12 @@ select ('[1, "2", null]'::jsonb)['a'];
  
 (1 row)
 
+select ('[1, "2", null]'::jsonb).a;
+ a 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[0];
  jsonb 
 -------
@@ -5043,6 +5073,12 @@ select ('[1, "2", null]'::jsonb)['1'];
  "2"
 (1 row)
 
+select ('[1, "2", null]'::jsonb)."1";
+ 1 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[1.0];
 ERROR:  subscript type numeric is not supported
 LINE 1: select ('[1, "2", null]'::jsonb)[1.0];
@@ -5072,6 +5108,12 @@ select ('[1, "2", null]'::jsonb)[1]['a'];
  
 (1 row)
 
+select ('[1, "2", null]'::jsonb)[1].a;
+ a 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[1][0];
  jsonb 
 -------
@@ -5084,54 +5126,126 @@ select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['b'];
  "c"
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).b;
+  b  
+-----
+ "c"
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'];
    jsonb   
 -----------
  [1, 2, 3]
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d;
+     d     
+-----------
+ [1, 2, 3]
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'][1];
  jsonb 
 -------
  2
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d[1];
+ d 
+---
+ 2
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d']['a'];
  jsonb 
 -------
  
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['a'];
+ d 
+---
+ 
+(1 row)
+
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d.a;
+ a 
+---
+ 
+(1 row)
+
+select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d['a'][0];
+ d 
+---
+ 1
+(1 row)
+
+select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d.a[0];
+ a 
+---
+ 1
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1'];
      jsonb     
 ---------------
  {"a2": "aaa"}
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1;
+      a1       
+---------------
+ {"a2": "aaa"}
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2'];
  jsonb 
 -------
  "aaa"
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2;
+  a2   
+-------
+ "aaa"
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']['a3'];
  jsonb 
 -------
  
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2.a3;
+ a3 
+----
+ 
+(1 row)
+
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'];
          jsonb         
 -----------------------
  ["aaa", "bbb", "ccc"]
 (1 row)
 
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1;
+          b1           
+-----------------------
+ ["aaa", "bbb", "ccc"]
+(1 row)
+
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'][2];
  jsonb 
 -------
  "ccc"
 (1 row)
 
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1[2];
+  b1   
+-------
+ "ccc"
+(1 row)
+
 -- slices are not supported
 select ('{"a": 1}'::jsonb)['a':'b'];
 ERROR:  jsonb subscript does not support slices
@@ -5841,7 +5955,7 @@ select jsonb_typeof('{"a":1}'::jsonb);
 select ('{"a":1}'::jsonb).jsonb_typeof;
  jsonb_typeof 
 --------------
- object
+ 
 (1 row)
 
 select jsonb_array_length('["a", "b", "c"]'::jsonb);
@@ -5853,7 +5967,7 @@ select jsonb_array_length('["a", "b", "c"]'::jsonb);
 select ('["a", "b", "c"]'::jsonb).jsonb_array_length;
  jsonb_array_length 
 --------------------
-                  3
+ 
 (1 row)
 
 select jsonb_object_keys('{"a":1, "b":2}'::jsonb);
@@ -5866,9 +5980,8 @@ select jsonb_object_keys('{"a":1, "b":2}'::jsonb);
 select ('{"a":1, "b":2}'::jsonb).jsonb_object_keys;
  jsonb_object_keys 
 -------------------
- a
- b
-(2 rows)
+ 
+(1 row)
 
 -- cast jsonb to other types as (jsonb)::type and (jsonb).type
 select ('123.45'::jsonb)::numeric;
@@ -5880,7 +5993,7 @@ select ('123.45'::jsonb)::numeric;
 select ('123.45'::jsonb).numeric;
  numeric 
 ---------
-  123.45
+ 
 (1 row)
 
 select ('[{"name": "alice"}, {"name": "bob"}]'::jsonb)::name;
@@ -5890,9 +6003,9 @@ select ('[{"name": "alice"}, {"name": "bob"}]'::jsonb)::name;
 (1 row)
 
 select ('[{"name": "alice"}, {"name": "bob"}]'::jsonb).name;
-                 name                 
---------------------------------------
- [{"name": "alice"}, {"name": "bob"}]
+       name       
+------------------
+ ["alice", "bob"]
 (1 row)
 
 select ('true'::jsonb)::bool;
@@ -5904,7 +6017,7 @@ select ('true'::jsonb)::bool;
 select ('true'::jsonb).bool;
  bool 
 ------
- t
+ 
 (1 row)
 
 select ('{"text": "hello"}'::jsonb)::text;
@@ -5914,8 +6027,313 @@ select ('{"text": "hello"}'::jsonb)::text;
 (1 row)
 
 select ('{"text": "hello"}'::jsonb).text;
-       text        
--------------------
- {"text": "hello"}
+  text   
+---------
+ "hello"
+(1 row)
+
+-- dot notation
+CREATE TABLE test_jsonb_dot_notation AS
+SELECT '{"a": [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], "b": [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]}'::jsonb jb;
+SELECT (jb).a FROM test_jsonb_dot_notation;
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+SELECT (jb)."a" FROM test_jsonb_dot_notation; -- double quote should work
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+SELECT (jb).'a' FROM test_jsonb_dot_notation; -- single quote should not work
+ERROR:  syntax error at or near "'a'"
+LINE 1: SELECT (jb).'a' FROM test_jsonb_dot_notation;
+                    ^
+select (jb)[0].a from test_jsonb_dot_notation; -- returns same result as (jb).a
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+select (jb)[1].a from test_jsonb_dot_notation; -- returns NULL
+ a 
+---
+ 
+(1 row)
+
+SELECT (jb).b FROM test_jsonb_dot_notation;
+                         b                         
+---------------------------------------------------
+ [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]
+(1 row)
+
+SELECT (jb).c FROM test_jsonb_dot_notation;
+ c 
+---
+ 
+(1 row)
+
+SELECT (jb).a.b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT (jb).a[2].b FROM test_jsonb_dot_notation;
+  b  
+-----
+ "c"
+(1 row)
+
+SELECT (jb).a.x.y FROM test_jsonb_dot_notation;
+   y   
+-------
+ "yyy"
+(1 row)
+
+SELECT (jb).b.x.z FROM test_jsonb_dot_notation;
+   z   
+-------
+ "ZZZ"
+(1 row)
+
+SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
+ c 
+---
+ 
+(1 row)
+
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t; -- fails
+ERROR:  jsonb subscript does not support slices
+SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
+ERROR:  type jsonb is not composite
+-- assignment is not supported
+UPDATE test_jsonb_dot_notation SET jb.a = '1';
+ERROR:  cannot assign to field "a" of column "jb" because its type jsonb is not a composite type
+LINE 1: UPDATE test_jsonb_dot_notation SET jb.a = '1';
+                                           ^
+UPDATE test_jsonb_dot_notation SET (jb).a = '1';
+ERROR:  syntax error at or near "."
+LINE 1: UPDATE test_jsonb_dot_notation SET (jb).a = '1';
+                                               ^
+-- explains should work
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+                  QUERY PLAN                  
+----------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation t
+   Output: (jb).a
+(2 rows)
+
+SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb).a[1]
+(2 rows)
+
+SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+ a 
+---
+ 2
+(1 row)
+
+-- views should work
+CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT (jb).a[3].x.y AS y
+   FROM test_jsonb_dot_notation
+-- mixed syntax
+DROP VIEW test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb['a'::text]).b
+(2 rows)
+
+SELECT (jb)['a'].b FROM test_jsonb_dot_notation; -- returns an array due to lax mode
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT (jb['a'::text]).b AS b
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: ((jb).a)['b'::text]
+(2 rows)
+
+SELECT (jb).a['b'] FROM test_jsonb_dot_notation; -- returns NULL because ['b'] looks for strict match in an object
+ a 
+---
+ 
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT ((jb).a)['b'::text] AS a
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+ a 
+---
+ 
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: ((jb).b.x)['z'::text]
+(2 rows)
+
+SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation; -- warnings
+   x   
+-------
+ "ZZZ"
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT ((jb).b.x)['z'::text] AS x
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+   x   
+-------
+ "ZZZ"
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: ((jb['b'::text]).x)['z'::text]
+(2 rows)
+
+SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;  -- warnings
+   x   
+-------
+ "ZZZ"
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT ((jb['b'::text]).x)['z'::text] AS x
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+   x   
+-------
+ "ZZZ"
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (((jb).b)['x'::text]).z
+(2 rows)
+
+SELECT (jb).b['x'].z FROM test_jsonb_dot_notation; -- returns NULL
+ z 
+---
+ 
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT (((jb).b)['x'::text]).z AS z
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+ z 
+---
+ 
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: ((jb).b)['x'::text]['z'::text]
+(2 rows)
+
+SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation; -- returns NULL
+ b 
+---
+ 
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT ((jb).b)['x'::text]['z'::text] AS b
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+ b 
+---
+ 
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb['b'::text]['x'::text]).z
+(2 rows)
+
+SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation; -- returns NULL
+ z 
+---
+ 
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT (jb['b'::text]['x'::text]).z AS z
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+ z 
+---
+ 
 (1 row)
 
+DROP VIEW public.test_jsonb_dot_notation_v1;
+-- clean up
+DROP TABLE test_jsonb_dot_notation;
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 970ed1cffca..cb0c398d65b 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1304,30 +1304,49 @@ select jsonb_insert('{"a": {"b": "value"}}', '{a, b}', '"new_value"', true);
 
 -- jsonb subscript
 select ('123'::jsonb)['a'];
+select ('123'::jsonb).a;
 select ('123'::jsonb)[0];
 select ('123'::jsonb)[NULL];
+select ('123'::jsonb).NULL;
 select ('{"a": 1}'::jsonb)['a'];
+select ('{"a": 1}'::jsonb).a;
 select ('{"a": 1}'::jsonb)[0];
 select ('{"a": 1}'::jsonb)['not_exist'];
+select ('{"a": 1}'::jsonb)."not_exist";
 select ('{"a": 1}'::jsonb)[NULL];
 select ('[1, "2", null]'::jsonb)['a'];
+select ('[1, "2", null]'::jsonb).a;
 select ('[1, "2", null]'::jsonb)[0];
 select ('[1, "2", null]'::jsonb)['1'];
+select ('[1, "2", null]'::jsonb)."1";
 select ('[1, "2", null]'::jsonb)[1.0];
 select ('[1, "2", null]'::jsonb)[2];
 select ('[1, "2", null]'::jsonb)[3];
 select ('[1, "2", null]'::jsonb)[-2];
 select ('[1, "2", null]'::jsonb)[1]['a'];
+select ('[1, "2", null]'::jsonb)[1].a;
 select ('[1, "2", null]'::jsonb)[1][0];
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['b'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).b;
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d;
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'][1];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d[1];
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d']['a'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['a'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d.a;
+select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d['a'][0];
+select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d.a[0];
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1;
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2;
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']['a3'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2.a3;
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'];
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1;
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'][2];
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1[2];
 
 -- slices are not supported
 select ('{"a": 1}'::jsonb)['a':'b'];
@@ -1608,3 +1627,98 @@ select ('true'::jsonb)::bool;
 select ('true'::jsonb).bool;
 select ('{"text": "hello"}'::jsonb)::text;
 select ('{"text": "hello"}'::jsonb).text;
+
+-- dot notation
+CREATE TABLE test_jsonb_dot_notation AS
+SELECT '{"a": [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], "b": [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]}'::jsonb jb;
+
+SELECT (jb).a FROM test_jsonb_dot_notation;
+SELECT (jb)."a" FROM test_jsonb_dot_notation; -- double quote should work
+SELECT (jb).'a' FROM test_jsonb_dot_notation; -- single quote should not work
+select (jb)[0].a from test_jsonb_dot_notation; -- returns same result as (jb).a
+select (jb)[1].a from test_jsonb_dot_notation; -- returns NULL
+SELECT (jb).b FROM test_jsonb_dot_notation;
+SELECT (jb).c FROM test_jsonb_dot_notation;
+SELECT (jb).a.b FROM test_jsonb_dot_notation;
+SELECT (jb).a[2].b FROM test_jsonb_dot_notation;
+SELECT (jb).a.x.y FROM test_jsonb_dot_notation;
+SELECT (jb).b.x.z FROM test_jsonb_dot_notation;
+SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t; -- fails
+SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
+
+-- assignment is not supported
+UPDATE test_jsonb_dot_notation SET jb.a = '1';
+UPDATE test_jsonb_dot_notation SET (jb).a = '1';
+
+-- explains should work
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+
+-- views should work
+CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+
+-- mixed syntax
+DROP VIEW test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
+SELECT (jb)['a'].b FROM test_jsonb_dot_notation; -- returns an array due to lax mode
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+SELECT (jb).a['b'] FROM test_jsonb_dot_notation; -- returns NULL because ['b'] looks for strict match in an object
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation; -- warnings
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;  -- warnings
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+SELECT (jb).b['x'].z FROM test_jsonb_dot_notation; -- returns NULL
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation; -- returns NULL
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
+SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation; -- returns NULL
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+-- clean up
+DROP TABLE test_jsonb_dot_notation;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index e90af5b2ad3..23e38c163c5 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -805,6 +805,7 @@ FdwRoutine
 FetchDirection
 FetchDirectionKeywords
 FetchStmt
+FieldAccessorExpr
 FieldSelect
 FieldStore
 File
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v20-0004-Extract-coerce_jsonpath_subscript.patch (5.5K, ../../CAK98qZ2EhKC=Z23jNbMX=aGPGi9+n42a8Eiz1rKYmF388UCHUQ@mail.gmail.com/4-v20-0004-Extract-coerce_jsonpath_subscript.patch)
  download | inline diff:
From 8128920e7fe6972fba41856e118ad1a8059b8435 Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v20 4/5] Extract coerce_jsonpath_subscript()

This is a preparation step for a future commit that will reuse the
aforementioned function.

Co-authored-by: Nikita Glukhov <[email protected]>
Co-authored-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Jian He <[email protected]>
Reviewed-by: Chao Li <[email protected]>
---
 src/backend/utils/adt/jsonbsubs.c | 128 +++++++++++++++---------------
 1 file changed, 64 insertions(+), 64 deletions(-)

diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index dfe7da55e8e..516146d1146 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -32,6 +32,69 @@ typedef struct JsonbSubWorkspace
 	Datum	   *index;			/* Subscript values in Datum format */
 } JsonbSubWorkspace;
 
+static Node *
+coerce_jsonpath_subscript_to_int4_or_text(ParseState *pstate, Node *subExpr)
+{
+	Oid			subExprType = exprType(subExpr);
+	Oid			targetType = InvalidOid;
+
+	if (subExprType != UNKNOWNOID)
+	{
+		const Oid	targets[] = {INT4OID, TEXTOID};
+
+		/*
+		 * Jsonb can handle multiple subscript types, but cases when a
+		 * subscript could be coerced to multiple target types must be
+		 * avoided, similar to overloaded functions. It could be possibly
+		 * extend with jsonpath in the future.
+		 */
+		for (int i = 0; i < lengthof(targets); i++)
+		{
+			if (can_coerce_type(1, &subExprType, &targets[i], COERCION_IMPLICIT))
+			{
+				/*
+				 * One type has already succeeded, it means there are two
+				 * coercion targets possible, failure.
+				 */
+				if (OidIsValid(targetType))
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
+							 errhint("jsonb subscript must be coercible to only one type, integer or text."),
+							 parser_errposition(pstate, exprLocation(subExpr))));
+
+				targetType = targets[i];
+			}
+		}
+
+		/*
+		 * No suitable types were found, failure.
+		 */
+		if (!OidIsValid(targetType))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
+					 errhint("jsonb subscript must be coercible to either integer or text."),
+					 parser_errposition(pstate, exprLocation(subExpr))));
+	}
+	else
+		targetType = TEXTOID;
+
+	/*
+	 * We known from can_coerce_type that coercion will succeed, so
+	 * coerce_type could be used. Note the implicit coercion context, which is
+	 * required to handle subscripts of different types, similar to overloaded
+	 * functions.
+	 */
+	subExpr = coerce_type(pstate,
+						  subExpr, subExprType,
+						  targetType, -1,
+						  COERCION_IMPLICIT,
+						  COERCE_IMPLICIT_CAST,
+						  -1);
+
+	return subExpr;
+}
 
 /*
  * Finish parse analysis of a SubscriptingRef expression for a jsonb.
@@ -74,71 +137,8 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 
 		if (ai->uidx)
 		{
-			Oid			subExprType = InvalidOid,
-						targetType = UNKNOWNOID;
-
 			subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
-			subExprType = exprType(subExpr);
-
-			if (subExprType != UNKNOWNOID)
-			{
-				Oid			targets[2] = {INT4OID, TEXTOID};
-
-				/*
-				 * Jsonb can handle multiple subscript types, but cases when a
-				 * subscript could be coerced to multiple target types must be
-				 * avoided, similar to overloaded functions. It could be
-				 * possibly extend with jsonpath in the future.
-				 */
-				for (int i = 0; i < 2; i++)
-				{
-					if (can_coerce_type(1, &subExprType, &targets[i], COERCION_IMPLICIT))
-					{
-						/*
-						 * One type has already succeeded, it means there are
-						 * two coercion targets possible, failure.
-						 */
-						if (targetType != UNKNOWNOID)
-							ereport(ERROR,
-									(errcode(ERRCODE_DATATYPE_MISMATCH),
-									 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
-									 errhint("jsonb subscript must be coercible to only one type, integer or text."),
-									 parser_errposition(pstate, exprLocation(subExpr))));
-
-						targetType = targets[i];
-					}
-				}
-
-				/*
-				 * No suitable types were found, failure.
-				 */
-				if (targetType == UNKNOWNOID)
-					ereport(ERROR,
-							(errcode(ERRCODE_DATATYPE_MISMATCH),
-							 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
-							 errhint("jsonb subscript must be coercible to either integer or text."),
-							 parser_errposition(pstate, exprLocation(subExpr))));
-			}
-			else
-				targetType = TEXTOID;
-
-			/*
-			 * We known from can_coerce_type that coercion will succeed, so
-			 * coerce_type could be used. Note the implicit coercion context,
-			 * which is required to handle subscripts of different types,
-			 * similar to overloaded functions.
-			 */
-			subExpr = coerce_type(pstate,
-								  subExpr, subExprType,
-								  targetType, -1,
-								  COERCION_IMPLICIT,
-								  COERCE_IMPLICIT_CAST,
-								  -1);
-			if (subExpr == NULL)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("jsonb subscript must have text type"),
-						 parser_errposition(pstate, exprLocation(subExpr))));
+			subExpr = coerce_jsonpath_subscript_to_int4_or_text(pstate, subExpr);
 		}
 		else
 		{
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v20-0001-Add-test-coverage-for-indirection-transformation.patch (6.9K, ../../CAK98qZ2EhKC=Z23jNbMX=aGPGi9+n42a8Eiz1rKYmF388UCHUQ@mail.gmail.com/5-v20-0001-Add-test-coverage-for-indirection-transformation.patch)
  download | inline diff:
From e89e5148504e8a3587f39c27c02895d71f7546ba Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Thu, 18 Sep 2025 15:54:24 -0700
Subject: [PATCH v20 1/5] Add test coverage for indirection transformation

These tests cover nested arrays of composite data types,
single-argument functions, and casting using dot-notation, providing a
baseline for future enhancements to jsonb dot-notation support.
---
 src/test/regress/expected/arrays.out | 26 ++++++--
 src/test/regress/expected/jsonb.out  | 88 ++++++++++++++++++++++++++++
 src/test/regress/sql/arrays.sql      |  7 ++-
 src/test/regress/sql/jsonb.sql       | 18 ++++++
 4 files changed, 132 insertions(+), 7 deletions(-)

diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 69ea2cf5ad8..e1ab6dc278a 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -1782,17 +1782,17 @@ SELECT max(f1), min(f1), max(f2), min(f2), max(f3), min(f3) FROM arraggtest;
 (1 row)
 
 -- A few simple tests for arrays of composite types
-create type comptype as (f1 int, f2 text);
+create type comptype as (f1 int, f2 text, f3 int[]);
 create table comptable (c1 comptype, c2 comptype[]);
 -- XXX would like to not have to specify row() construct types here ...
 insert into comptable
-  values (row(1,'foo'), array[row(2,'bar')::comptype, row(3,'baz')::comptype]);
+  values (row(1,'foo',array[10,20]), array[row(2,'bar',array[30,40])::comptype, row(3,'baz',array[50,60])::comptype]);
 -- check that implicitly named array type _comptype isn't a problem
 create type _comptype as enum('fooey');
 select * from comptable;
-   c1    |          c2           
----------+-----------------------
- (1,foo) | {"(2,bar)","(3,baz)"}
+        c1         |                      c2                       
+-------------------+-----------------------------------------------
+ (1,foo,"{10,20}") | {"(2,bar,\"{30,40}\")","(3,baz,\"{50,60}\")"}
 (1 row)
 
 select c2[2].f2 from comptable;
@@ -1801,6 +1801,22 @@ select c2[2].f2 from comptable;
  baz
 (1 row)
 
+select c2[2].f3 from comptable;
+   f3    
+---------
+ {50,60}
+(1 row)
+
+select c2[2].f3[1:2] from comptable;
+   f3    
+---------
+ {50,60}
+(1 row)
+
+select c2[1:2].f3[1:2] from comptable;
+ERROR:  column notation .f3 applied to type comptype[], which is not a composite type
+LINE 1: select c2[1:2].f3[1:2] from comptable;
+               ^
 drop type _comptype;
 drop table comptable;
 drop type comptype;
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5a1eb18aba2..fb47c8a893a 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5831,3 +5831,91 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
  12345
 (1 row)
 
+-- single argument jsonb functions as jsonb_function(jsonb) and jsonb.jsonb_function
+select jsonb_typeof('{"a":1}'::jsonb);
+ jsonb_typeof 
+--------------
+ object
+(1 row)
+
+select ('{"a":1}'::jsonb).jsonb_typeof;
+ jsonb_typeof 
+--------------
+ object
+(1 row)
+
+select jsonb_array_length('["a", "b", "c"]'::jsonb);
+ jsonb_array_length 
+--------------------
+                  3
+(1 row)
+
+select ('["a", "b", "c"]'::jsonb).jsonb_array_length;
+ jsonb_array_length 
+--------------------
+                  3
+(1 row)
+
+select jsonb_object_keys('{"a":1, "b":2}'::jsonb);
+ jsonb_object_keys 
+-------------------
+ a
+ b
+(2 rows)
+
+select ('{"a":1, "b":2}'::jsonb).jsonb_object_keys;
+ jsonb_object_keys 
+-------------------
+ a
+ b
+(2 rows)
+
+-- cast jsonb to other types as (jsonb)::type and (jsonb).type
+select ('123.45'::jsonb)::numeric;
+ numeric 
+---------
+  123.45
+(1 row)
+
+select ('123.45'::jsonb).numeric;
+ numeric 
+---------
+  123.45
+(1 row)
+
+select ('[{"name": "alice"}, {"name": "bob"}]'::jsonb)::name;
+                 name                 
+--------------------------------------
+ [{"name": "alice"}, {"name": "bob"}]
+(1 row)
+
+select ('[{"name": "alice"}, {"name": "bob"}]'::jsonb).name;
+                 name                 
+--------------------------------------
+ [{"name": "alice"}, {"name": "bob"}]
+(1 row)
+
+select ('true'::jsonb)::bool;
+ bool 
+------
+ t
+(1 row)
+
+select ('true'::jsonb).bool;
+ bool 
+------
+ t
+(1 row)
+
+select ('{"text": "hello"}'::jsonb)::text;
+       text        
+-------------------
+ {"text": "hello"}
+(1 row)
+
+select ('{"text": "hello"}'::jsonb).text;
+       text        
+-------------------
+ {"text": "hello"}
+(1 row)
+
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 47d62c1d38d..450389831a0 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -555,19 +555,22 @@ SELECT max(f1), min(f1), max(f2), min(f2), max(f3), min(f3) FROM arraggtest;
 
 -- A few simple tests for arrays of composite types
 
-create type comptype as (f1 int, f2 text);
+create type comptype as (f1 int, f2 text, f3 int[]);
 
 create table comptable (c1 comptype, c2 comptype[]);
 
 -- XXX would like to not have to specify row() construct types here ...
 insert into comptable
-  values (row(1,'foo'), array[row(2,'bar')::comptype, row(3,'baz')::comptype]);
+  values (row(1,'foo',array[10,20]), array[row(2,'bar',array[30,40])::comptype, row(3,'baz',array[50,60])::comptype]);
 
 -- check that implicitly named array type _comptype isn't a problem
 create type _comptype as enum('fooey');
 
 select * from comptable;
 select c2[2].f2 from comptable;
+select c2[2].f3 from comptable;
+select c2[2].f3[1:2] from comptable;
+select c2[1:2].f3[1:2] from comptable;
 
 drop type _comptype;
 drop table comptable;
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 57c11acddfe..970ed1cffca 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1590,3 +1590,21 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::float8;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int2;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int4;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
+
+-- single argument jsonb functions as jsonb_function(jsonb) and jsonb.jsonb_function
+select jsonb_typeof('{"a":1}'::jsonb);
+select ('{"a":1}'::jsonb).jsonb_typeof;
+select jsonb_array_length('["a", "b", "c"]'::jsonb);
+select ('["a", "b", "c"]'::jsonb).jsonb_array_length;
+select jsonb_object_keys('{"a":1, "b":2}'::jsonb);
+select ('{"a":1, "b":2}'::jsonb).jsonb_object_keys;
+
+-- cast jsonb to other types as (jsonb)::type and (jsonb).type
+select ('123.45'::jsonb)::numeric;
+select ('123.45'::jsonb).numeric;
+select ('[{"name": "alice"}, {"name": "bob"}]'::jsonb)::name;
+select ('[{"name": "alice"}, {"name": "bob"}]'::jsonb).name;
+select ('true'::jsonb)::bool;
+select ('true'::jsonb).bool;
+select ('{"text": "hello"}'::jsonb)::text;
+select ('{"text": "hello"}'::jsonb).text;
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v20-0002-Add-an-alternative-transform-function-in-Subscri.patch (15.6K, ../../CAK98qZ2EhKC=Z23jNbMX=aGPGi9+n42a8Eiz1rKYmF388UCHUQ@mail.gmail.com/6-v20-0002-Add-an-alternative-transform-function-in-Subscri.patch)
  download | inline diff:
From 68e97c36a2e84dd42e39a4ce79a90058890dc1f5 Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Thu, 18 Sep 2025 16:34:47 -0700
Subject: [PATCH v20 2/5] Add an alternative transform function in
 SubscriptRoutines

Add a transform_partial() function pointer to enable processing a
prefix of indirection lists. Data types that support subscripting can
opt to use the transform() function that transforms the full input
indirection list (e.g., arrays, hstore), or can opt to use the
transform_partial() function to be more flexible on indirection node
types, and make best effort in transforming only a prefix of the
indirection list, letting the caller handle the remaining
indirections.

This allows transform functions to accept dot notation indirection as
input, preparing for future JSONB dot notation support.
---
 contrib/hstore/hstore_subs.c      |  1 +
 src/backend/parser/parse_expr.c   | 76 +++++++++++++++----------
 src/backend/parser/parse_node.c   | 92 +++++++++++++++++++++++--------
 src/backend/parser/parse_target.c |  6 +-
 src/backend/utils/adt/arraysubs.c |  2 +
 src/backend/utils/adt/jsonbsubs.c | 19 +++++--
 src/include/nodes/subscripting.h  | 24 +++++++-
 src/include/parser/parse_node.h   |  3 +-
 8 files changed, 163 insertions(+), 60 deletions(-)

diff --git a/contrib/hstore/hstore_subs.c b/contrib/hstore/hstore_subs.c
index 3d03f66fa0d..d678dc56f86 100644
--- a/contrib/hstore/hstore_subs.c
+++ b/contrib/hstore/hstore_subs.c
@@ -287,6 +287,7 @@ hstore_subscript_handler(PG_FUNCTION_ARGS)
 {
 	static const SubscriptRoutines sbsroutines = {
 		.transform = hstore_subscript_transform,
+		.transform_partial = NULL,
 		.exec_setup = hstore_exec_setup,
 		.fetch_strict = true,	/* fetch returns NULL for NULL inputs */
 		.fetch_leakproof = true,	/* fetch returns NULL for bad subscript */
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index e1979a80c19..95ce330e506 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -436,44 +436,65 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 {
 	Node	   *last_srf = pstate->p_last_srf;
 	Node	   *result = transformExprRecurse(pstate, ind->arg);
-	List	   *subscripts = NIL;
+	List	   *indirections = NIL;
 	int			location = exprLocation(result);
 	ListCell   *i;
 
 	/*
-	 * We have to split any field-selection operations apart from
-	 * subscripting.  Adjacent A_Indices nodes have to be treated as a single
+	 * Combine field names and subscripts into a single indirection list, as
+	 * some subscripting containers, such as jsonb, support field access using
+	 * dot notation. Adjacent A_Indices nodes have to be treated as a single
 	 * multidimensional subscript operation.
 	 */
 	foreach(i, ind->indirection)
 	{
 		Node	   *n = lfirst(i);
 
-		if (IsA(n, A_Indices))
-			subscripts = lappend(subscripts, n);
-		else if (IsA(n, A_Star))
+		if (IsA(n, A_Indices) || IsA(n, String))
+			indirections = lappend(indirections, n);
+		else
 		{
+			Assert(IsA(n, A_Star));
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("row expansion via \"*\" is not supported here"),
 					 parser_errposition(pstate, location)));
 		}
-		else
+	}
+
+	while (indirections)
+	{
+		/* try processing container subscripts first */
+		int			transformed_count = 0;
+		Node	   *newresult = (Node *)
+			transformContainerSubscripts(pstate,
+										 result,
+										 exprType(result),
+										 exprTypmod(result),
+										 indirections,
+										 false,
+										 &transformed_count);
+
+		if (!newresult)
 		{
-			Node	   *newresult;
+			/*
+			 * generic subscripting failed; falling back to field selection
+			 * for a composite type, or a single-argument function.
+			 */
+			Node	   *n;
+
+			Assert(indirections);
 
-			Assert(IsA(n, String));
+			n = linitial(indirections);
 
-			/* process subscripts before this field selection */
-			if (subscripts)
-				result = (Node *) transformContainerSubscripts(pstate,
-															   result,
-															   exprType(result),
-															   exprTypmod(result),
-															   subscripts,
-															   false);
-			subscripts = NIL;
+			if (!IsA(n, String))
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("cannot subscript type %s because it does not support subscripting",
+								format_type_be(exprType(result))),
+						 parser_errposition(pstate, exprLocation(result))));
 
+			/* try to parse function or field selection */
 			newresult = ParseFuncOrColumn(pstate,
 										  list_make1(n),
 										  list_make1(result),
@@ -481,19 +502,18 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 										  NULL,
 										  false,
 										  location);
-			if (newresult == NULL)
+
+			if (!newresult)
 				unknown_attribute(pstate, result, strVal(n), location);
-			result = newresult;
+			else
+				transformed_count = 1;
 		}
+
+		/* remove the processed indirections */
+		indirections = list_delete_first_n(indirections, transformed_count);
+
+		result = newresult;
 	}
-	/* process trailing subscripts, if any */
-	if (subscripts)
-		result = (Node *) transformContainerSubscripts(pstate,
-													   result,
-													   exprType(result),
-													   exprTypmod(result),
-													   subscripts,
-													   false);
 
 	return result;
 }
diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c
index 203b7a32178..71ff31a9638 100644
--- a/src/backend/parser/parse_node.c
+++ b/src/backend/parser/parse_node.c
@@ -238,20 +238,22 @@ transformContainerType(Oid *containerType, int32 *containerTypmod)
  * containerTypMod	typmod for the container
  * indirection		Untransformed list of subscripts (must not be NIL)
  * isAssignment		True if this will become a container assignment.
- */
+ * nSubscripts		Output parameter for number of transformed subscripts.
+*/
 SubscriptingRef *
 transformContainerSubscripts(ParseState *pstate,
 							 Node *containerBase,
 							 Oid containerType,
 							 int32 containerTypMod,
 							 List *indirection,
-							 bool isAssignment)
+							 bool isAssignment,
+							 int *nSubscripts)
 {
 	SubscriptingRef *sbsref;
 	const SubscriptRoutines *sbsroutines;
 	Oid			elementType;
-	bool		isSlice = false;
-	ListCell   *idx;
+
+	*nSubscripts = 0;
 
 	/*
 	 * Determine the actual container type, smashing any domain.  In the
@@ -267,28 +269,15 @@ transformContainerSubscripts(ParseState *pstate,
 	 */
 	sbsroutines = getSubscriptingRoutines(containerType, &elementType);
 	if (!sbsroutines)
+	{
+		if (!isAssignment)
+			return NULL;
+
 		ereport(ERROR,
 				(errcode(ERRCODE_DATATYPE_MISMATCH),
 				 errmsg("cannot subscript type %s because it does not support subscripting",
 						format_type_be(containerType)),
 				 parser_errposition(pstate, exprLocation(containerBase))));
-
-	/*
-	 * Detect whether any of the indirection items are slice specifiers.
-	 *
-	 * A list containing only simple subscripts refers to a single container
-	 * element.  If any of the items are slice specifiers (lower:upper), then
-	 * the subscript expression means a container slice operation.
-	 */
-	foreach(idx, indirection)
-	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
-
-		if (ai->is_slice)
-		{
-			isSlice = true;
-			break;
-		}
 	}
 
 	/*
@@ -309,8 +298,65 @@ transformContainerSubscripts(ParseState *pstate,
 	 * Call the container-type-specific logic to transform the subscripts and
 	 * determine the subscripting result type.
 	 */
-	sbsroutines->transform(sbsref, indirection, pstate,
-						   isSlice, isAssignment);
+	if (sbsroutines->transform_partial != NULL)
+	{
+		/*
+		 * If the container type provides a partial transform function, use it
+		 * here. This function can accept any node types in the indirection
+		 * list as input, and is responsible for identifying and transforming
+		 * as many leading elements as it can handle, which may be only a
+		 * prefix of the indirection list. For example, it might process
+		 * A_Indices nodes, String nodes (for jsonb dot-notation), or other
+		 * node types, depending on the container's requirements. It returns
+		 * the number of elements it transformed.
+		 */
+		*nSubscripts = sbsroutines->transform_partial(sbsref, indirection, pstate, isAssignment);
+	}
+	else
+	{
+		/*
+		 * If there is no partial transform function, use the full transform
+		 * function, which only accepts bracket subscripts (A_Indices nodes).
+		 * We pre-collect the leading A_Indices nodes from the indirection
+		 * list, then call the transform function to process this prefix of
+		 * subscripts.
+		 */
+		List	   *subscriptlist = NIL;
+		ListCell   *lc;
+		bool		isSlice = false;
+
+		/* Collect leading A_Indices subscripts */
+		foreach(lc, indirection)
+		{
+			Node	   *n = lfirst(lc);
+
+			if (IsA(n, A_Indices))
+			{
+				A_Indices  *ai = (A_Indices *) n;
+
+				subscriptlist = lappend(subscriptlist, n);
+				if (ai->is_slice)
+					isSlice = true;
+			}
+			else
+				break;
+		}
+
+		if (subscriptlist)
+			sbsroutines->transform(sbsref, subscriptlist, pstate, isSlice, isAssignment);
+
+		*nSubscripts = list_length(subscriptlist);
+	}
+
+	if (*nSubscripts == 0)
+	{
+		/* Fallback to field selection in caller */
+		if (!isAssignment)
+			return NULL;
+
+		/* This should not happen with well-behaved transform functions */
+		elog(ERROR, "subscripting transform function failed to consume any indirection elements");
+	}
 
 	/*
 	 * Verify we got a valid type (this defends, for example, against someone
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 905c975d83b..30cf77338ae 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -922,6 +922,7 @@ transformAssignmentSubscripts(ParseState *pstate,
 	Oid			typeNeeded;
 	int32		typmodNeeded;
 	Oid			collationNeeded;
+	int 		nSubscripts = 0;
 
 	Assert(subscripts != NIL);
 
@@ -936,7 +937,10 @@ transformAssignmentSubscripts(ParseState *pstate,
 										  containerType,
 										  containerTypMod,
 										  subscripts,
-										  true);
+										  true,
+										  &nSubscripts);
+
+	Assert(nSubscripts == list_length(subscripts));
 
 	typeNeeded = sbsref->refrestype;
 	typmodNeeded = sbsref->reftypmod;
diff --git a/src/backend/utils/adt/arraysubs.c b/src/backend/utils/adt/arraysubs.c
index 2940fb8e8d7..0049907b942 100644
--- a/src/backend/utils/adt/arraysubs.c
+++ b/src/backend/utils/adt/arraysubs.c
@@ -541,6 +541,7 @@ array_subscript_handler(PG_FUNCTION_ARGS)
 {
 	static const SubscriptRoutines sbsroutines = {
 		.transform = array_subscript_transform,
+		.transform_partial = NULL,
 		.exec_setup = array_exec_setup,
 		.fetch_strict = true,	/* fetch returns NULL for NULL inputs */
 		.fetch_leakproof = true,	/* fetch returns NULL for bad subscript */
@@ -568,6 +569,7 @@ raw_array_subscript_handler(PG_FUNCTION_ARGS)
 {
 	static const SubscriptRoutines sbsroutines = {
 		.transform = array_subscript_transform,
+		.transform_partial = NULL,
 		.exec_setup = array_exec_setup,
 		.fetch_strict = true,	/* fetch returns NULL for NULL inputs */
 		.fetch_leakproof = true,	/* fetch returns NULL for bad subscript */
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index e8626d3b4fc..dfe7da55e8e 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -39,11 +39,10 @@ typedef struct JsonbSubWorkspace
  * Transform the subscript expressions, coerce them to text,
  * and determine the result type of the SubscriptingRef node.
  */
-static void
+static int
 jsonb_subscript_transform(SubscriptingRef *sbsref,
 						  List *indirection,
 						  ParseState *pstate,
-						  bool isSlice,
 						  bool isAssignment)
 {
 	List	   *upperIndexpr = NIL;
@@ -55,10 +54,15 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	 */
 	foreach(idx, indirection)
 	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
+		A_Indices  *ai;
 		Node	   *subExpr;
 
-		if (isSlice)
+		if (!IsA(lfirst(idx), A_Indices))
+			break;
+
+		ai = lfirst_node(A_Indices, idx);
+
+		if (ai->is_slice)
 		{
 			Node	   *expr = ai->uidx ? ai->uidx : ai->lidx;
 
@@ -142,7 +146,7 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 			 * Slice with omitted upper bound. Should not happen as we already
 			 * errored out on slice earlier, but handle this just in case.
 			 */
-			Assert(isSlice && ai->is_slice);
+			Assert(ai->is_slice);
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
 					 errmsg("jsonb subscript does not support slices"),
@@ -159,6 +163,8 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	/* Determine the result type of the subscripting operation; always jsonb */
 	sbsref->refrestype = JSONBOID;
 	sbsref->reftypmod = -1;
+
+	return list_length(upperIndexpr);
 }
 
 /*
@@ -402,7 +408,8 @@ Datum
 jsonb_subscript_handler(PG_FUNCTION_ARGS)
 {
 	static const SubscriptRoutines sbsroutines = {
-		.transform = jsonb_subscript_transform,
+		.transform = NULL,		/* jsonb uses partial transform instead */
+		.transform_partial = jsonb_subscript_transform,
 		.exec_setup = jsonb_exec_setup,
 		.fetch_strict = true,	/* fetch returns NULL for NULL inputs */
 		.fetch_leakproof = true,	/* fetch returns NULL for bad subscript */
diff --git a/src/include/nodes/subscripting.h b/src/include/nodes/subscripting.h
index e991f4bf826..3b819dc8d65 100644
--- a/src/include/nodes/subscripting.h
+++ b/src/include/nodes/subscripting.h
@@ -98,6 +98,25 @@ typedef void (*SubscriptTransform) (SubscriptingRef *sbsref,
 									bool isSlice,
 									bool isAssignment);
 
+/*
+ * SubscriptTransformPartial is an alternative to SubscriptTransform for
+ * container types that can accept non-A_Indices indirection as input
+ * (e.g., JSONB accepts dot-notation (String node) for field access).
+
+ * It may transform a prefix of the indirection list and leave the rest
+ * unprocessed. It returns the number of indirections it transformed.
+ * The caller will then remove that many items from the head of the
+ * list, and handle the
+ * remaining indirections differently or to raise an error as needed.
+ *
+ * If transform_partial is NULL, the complete transform function is used,
+ * which accepts only A_Indices (bracket) nodes.
+ */
+typedef int (*SubscriptTransformPartial) (SubscriptingRef *sbsref,
+										  List *indirection,
+										  ParseState *pstate,
+										  bool isAssignment);
+
 /*
  * The exec_setup method is called during executor-startup compilation of a
  * SubscriptingRef node in an expression.  It must fill *methods with pointers
@@ -157,7 +176,10 @@ typedef void (*SubscriptExecSetup) (const SubscriptingRef *sbsref,
 /* Struct returned by the SQL-visible subscript handler function */
 typedef struct SubscriptRoutines
 {
-	SubscriptTransform transform;	/* parse analysis function */
+	SubscriptTransform transform;	/* parse analysis function, or NULL */
+	SubscriptTransformPartial transform_partial;	/* alternative parse
+													 * analysis function, or
+													 * NULL */
 	SubscriptExecSetup exec_setup;	/* expression compilation function */
 	bool		fetch_strict;	/* is fetch SubscriptRef strict? */
 	bool		fetch_leakproof;	/* is fetch SubscriptRef leakproof? */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index f7d07c84542..80f486acff0 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -362,7 +362,8 @@ extern SubscriptingRef *transformContainerSubscripts(ParseState *pstate,
 													 Oid containerType,
 													 int32 containerTypMod,
 													 List *indirection,
-													 bool isAssignment);
+													 bool isAssignment,
+													 int *consumed_count);
 extern Const *make_const(ParseState *pstate, A_Const *aconst);
 
 #endif							/* PARSE_NODE_H */
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v20-0003-Export-jsonPathFromParseResult.patch (2.8K, ../../CAK98qZ2EhKC=Z23jNbMX=aGPGi9+n42a8Eiz1rKYmF388UCHUQ@mail.gmail.com/7-v20-0003-Export-jsonPathFromParseResult.patch)
  download | inline diff:
From c3eb1f90829a97693e703a0d11ba989e632f4b6a Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v20 3/5] Export jsonPathFromParseResult()

This is a preparation step for a future commit that will reuse the
aforementioned function.

Authored-by: Nikita Glukhov <[email protected]>
Reviewed-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Jian He <[email protected]>
Reviewed-by: Chao Li <[email protected]>
---
 src/backend/utils/adt/jsonpath.c | 19 +++++++++++++++----
 src/include/utils/jsonpath.h     |  4 ++++
 2 files changed, 19 insertions(+), 4 deletions(-)

diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index 762f7e8a09d..c83774b2a16 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -166,15 +166,13 @@ jsonpath_send(PG_FUNCTION_ARGS)
  * Converts C-string to a jsonpath value.
  *
  * Uses jsonpath parser to turn string into an AST, then
- * flattenJsonPathParseItem() does second pass turning AST into binary
+ * jsonPathFromParseResult() does second pass turning AST into binary
  * representation of jsonpath.
  */
 static Datum
 jsonPathFromCstring(char *in, int len, struct Node *escontext)
 {
 	JsonPathParseResult *jsonpath = parsejsonpath(in, len, escontext);
-	JsonPath   *res;
-	StringInfoData buf;
 
 	if (SOFT_ERROR_OCCURRED(escontext))
 		return (Datum) 0;
@@ -185,8 +183,21 @@ jsonPathFromCstring(char *in, int len, struct Node *escontext)
 				 errmsg("invalid input syntax for type %s: \"%s\"", "jsonpath",
 						in)));
 
+	return jsonPathFromParseResult(jsonpath, 4 * len, escontext);
+}
+
+/*
+ * Converts jsonpath Abstract Syntax Tree (AST) into jsonpath value in binary.
+ */
+Datum
+jsonPathFromParseResult(const JsonPathParseResult *jsonpath, int estimated_len,
+						struct Node *escontext)
+{
+	JsonPath   *res;
+	StringInfoData buf;
+
 	initStringInfo(&buf);
-	enlargeStringInfo(&buf, 4 * len /* estimation */ );
+	enlargeStringInfo(&buf, estimated_len);
 
 	appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
 
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index 23a76d233e9..0958bc22bb6 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -281,6 +281,10 @@ extern JsonPathParseResult *parsejsonpath(const char *str, int len,
 extern bool jspConvertRegexFlags(uint32 xflags, int *result,
 								 struct Node *escontext);
 
+extern Datum jsonPathFromParseResult(const JsonPathParseResult *jsonpath,
+									 int estimated_len,
+									 struct Node *escontext);
+
 /*
  * Struct for details about external variables passed into jsonpath executor
  */
-- 
2.39.5 (Apple Git-154)



^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-06-24 13:29                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-07-09 08:01                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-07-10 08:53                                 ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-21 04:53                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-22 08:19                                     ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-22 19:33                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-25 08:09                                         ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-26 03:52                                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-29 07:22                                             ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-02 02:59                                               ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-02 03:32                                                 ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-03 02:20                                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-09-03 06:56                                                     ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-09 23:53                                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-09-10 04:03                                                         ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-10 23:56                                                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-09-15 18:32                                                             ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-09-19 20:40                                                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
@ 2025-09-22 03:32                                                                 ` Chao Li <[email protected]>
  2025-09-22 17:31                                                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  0 siblings, 1 reply; 67+ messages in thread

From: Chao Li @ 2025-09-22 03:32 UTC (permalink / raw)
  To: Alexandra Wang <[email protected]>; +Cc: pgsql-hackers; Nikita Glukhov <[email protected]>; jian he <[email protected]>; Nikita Malakhov <[email protected]>; Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; David E. Wheeler <[email protected]>

Looks like patch files need rebase.

> On Sep 20, 2025, at 04:40, Alexandra Wang <[email protected]> wrote:
> 
> Hi there,
> 
> I've attached v20. It has the following changes:
> 
> 1. New 0001: It adds test coverage for single-argument functions and
> casting for jsonb expressions. This ensures that the relevant behavior
> changes become visible in 0005 when field access via dot-notation is
> introduced.
> 
> Specifically, once member access through dot-notation is enabled for
> jsonb, we can no longer write single-argument functions (including
> casts) in dot form for jsonb. For example:
> 
> Before 0005: 
> 
> select ('{"a":1}'::jsonb).jsonb_typeof;
>   jsonb_typeof
>  --------------
>  object
> (1 row)
> 
> select ('[{"name": "alice"}, {"name": "bob"}]'::jsonb).name;
>                  name
> --------------------------------------
> [{"name": "alice"}, {"name": "bob"}]
> (1 row)
> 
> After 0005:
> 
> select ('{"a":1}'::jsonb).jsonb_typeof;
>   jsonb_typeof
>  --------------
> 
> (1 row)
> 
> select ('[{"name": "alice"}, {"name": "bob"}]'::jsonb).name;
>        name
> ------------------
>  ["alice", "bob"]
>  (1 row)
> 
> In the meanwhile, these functions still return correct results through
> standard syntax:
> 
> test=#  select jsonb_typeof(('{"a":1}'::jsonb));
>  jsonb_typeof
> --------------
>  object
> (1 row)
> test=#  select ('[{"name": "alice"}, {"name": "bob"}]'::jsonb)::name;
>                  name
> --------------------------------------
>  [{"name": "alice"}, {"name": "bob"}]
> (1 row)
> 
> I don't consider this behavior change a major issue, because the
> dot-form for single-argument functions is not standard SQL and seems
> to be PostgreSQL-specific. Still, it's worth highlighting here so
> users aren't surprised.
> 
> 2. Refactored 0002: It combines and refactors v19-0001 and v19-0002.
> Instead of changing the existing transform() callback in
> SubscriptRoutines, it now introduces an additional callback,
> transform_partial(). This alternative transform method, used by jsonb,
> is more flexible: it accepts a wider range of indirection node types
> and can transform only a prefix of the indirection list. This avoids
> breaking compatibility for arrays, hstore, and external data types
> that supports subscripting.
> 
> 3. 0003 and 0004 stay unchanged. They are both small and can be squashed
> into 0005. I leave them as-is for now for easier review.
> 
> 4. Added two additional tests in 0005 for assignments using jsonb
> dot-notation, showing explicitly that assignment is not yet supported.
> 
> 5. Removed 0006 (array slicing) and 0007 (wildcard) from the previous
> versions, as they need additional work. My immediate goal is to first
> reach consensus on the dot-notation implementation.
> 
> Best,
> Alex
> 
> <v20-0005-Implement-read-only-dot-notation-for-jsonb.patch><v20-0004-Extract-coerce_jsonpath_subscript.patch><v20-0001-Add-test-coverage-for-indirection-transformation.patch><v20-0002-Add-an-alternative-transform-function-in-Subscri.patch><v20-0003-Export-jsonPathFromParseResult.patch>

--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/






^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-06-24 13:29                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-07-09 08:01                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-07-10 08:53                                 ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-21 04:53                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-22 08:19                                     ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-22 19:33                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-25 08:09                                         ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-26 03:52                                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-29 07:22                                             ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-02 02:59                                               ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-02 03:32                                                 ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-03 02:20                                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-09-03 06:56                                                     ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-09 23:53                                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-09-10 04:03                                                         ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-10 23:56                                                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-09-15 18:32                                                             ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-09-19 20:40                                                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-09-22 03:32                                                                 ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
@ 2025-09-22 17:31                                                                   ` Alexandra Wang <[email protected]>
  2025-09-23 05:48                                                                     ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  0 siblings, 1 reply; 67+ messages in thread

From: Alexandra Wang @ 2025-09-22 17:31 UTC (permalink / raw)
  To: Chao Li <[email protected]>; +Cc: pgsql-hackers; Nikita Glukhov <[email protected]>; jian he <[email protected]>; Nikita Malakhov <[email protected]>; Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; David E. Wheeler <[email protected]>

On Sun, Sep 21, 2025 at 8:32 PM Chao Li <[email protected]> wrote:

> Looks like patch files need rebase.
>

There were trailing whitespaces in the documentation I added, I’ve fixed
them now.


Attachments:

  [application/octet-stream] v21-0004-Extract-coerce_jsonpath_subscript.patch (5.5K, ../../CAK98qZ3fATHtD8n471AjniZ2KHFE59MfJzjo+P0SR3_oUT8Jbw@mail.gmail.com/3-v21-0004-Extract-coerce_jsonpath_subscript.patch)
  download | inline diff:
From 9d92c2c8d93549bdafa16f90ecd58ab581269e3c Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v21 4/5] Extract coerce_jsonpath_subscript()

This is a preparation step for a future commit that will reuse the
aforementioned function.

Co-authored-by: Nikita Glukhov <[email protected]>
Co-authored-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Jian He <[email protected]>
Reviewed-by: Chao Li <[email protected]>
---
 src/backend/utils/adt/jsonbsubs.c | 128 +++++++++++++++---------------
 1 file changed, 64 insertions(+), 64 deletions(-)

diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index dfe7da55e8e..516146d1146 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -32,6 +32,69 @@ typedef struct JsonbSubWorkspace
 	Datum	   *index;			/* Subscript values in Datum format */
 } JsonbSubWorkspace;
 
+static Node *
+coerce_jsonpath_subscript_to_int4_or_text(ParseState *pstate, Node *subExpr)
+{
+	Oid			subExprType = exprType(subExpr);
+	Oid			targetType = InvalidOid;
+
+	if (subExprType != UNKNOWNOID)
+	{
+		const Oid	targets[] = {INT4OID, TEXTOID};
+
+		/*
+		 * Jsonb can handle multiple subscript types, but cases when a
+		 * subscript could be coerced to multiple target types must be
+		 * avoided, similar to overloaded functions. It could be possibly
+		 * extend with jsonpath in the future.
+		 */
+		for (int i = 0; i < lengthof(targets); i++)
+		{
+			if (can_coerce_type(1, &subExprType, &targets[i], COERCION_IMPLICIT))
+			{
+				/*
+				 * One type has already succeeded, it means there are two
+				 * coercion targets possible, failure.
+				 */
+				if (OidIsValid(targetType))
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
+							 errhint("jsonb subscript must be coercible to only one type, integer or text."),
+							 parser_errposition(pstate, exprLocation(subExpr))));
+
+				targetType = targets[i];
+			}
+		}
+
+		/*
+		 * No suitable types were found, failure.
+		 */
+		if (!OidIsValid(targetType))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
+					 errhint("jsonb subscript must be coercible to either integer or text."),
+					 parser_errposition(pstate, exprLocation(subExpr))));
+	}
+	else
+		targetType = TEXTOID;
+
+	/*
+	 * We known from can_coerce_type that coercion will succeed, so
+	 * coerce_type could be used. Note the implicit coercion context, which is
+	 * required to handle subscripts of different types, similar to overloaded
+	 * functions.
+	 */
+	subExpr = coerce_type(pstate,
+						  subExpr, subExprType,
+						  targetType, -1,
+						  COERCION_IMPLICIT,
+						  COERCE_IMPLICIT_CAST,
+						  -1);
+
+	return subExpr;
+}
 
 /*
  * Finish parse analysis of a SubscriptingRef expression for a jsonb.
@@ -74,71 +137,8 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 
 		if (ai->uidx)
 		{
-			Oid			subExprType = InvalidOid,
-						targetType = UNKNOWNOID;
-
 			subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
-			subExprType = exprType(subExpr);
-
-			if (subExprType != UNKNOWNOID)
-			{
-				Oid			targets[2] = {INT4OID, TEXTOID};
-
-				/*
-				 * Jsonb can handle multiple subscript types, but cases when a
-				 * subscript could be coerced to multiple target types must be
-				 * avoided, similar to overloaded functions. It could be
-				 * possibly extend with jsonpath in the future.
-				 */
-				for (int i = 0; i < 2; i++)
-				{
-					if (can_coerce_type(1, &subExprType, &targets[i], COERCION_IMPLICIT))
-					{
-						/*
-						 * One type has already succeeded, it means there are
-						 * two coercion targets possible, failure.
-						 */
-						if (targetType != UNKNOWNOID)
-							ereport(ERROR,
-									(errcode(ERRCODE_DATATYPE_MISMATCH),
-									 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
-									 errhint("jsonb subscript must be coercible to only one type, integer or text."),
-									 parser_errposition(pstate, exprLocation(subExpr))));
-
-						targetType = targets[i];
-					}
-				}
-
-				/*
-				 * No suitable types were found, failure.
-				 */
-				if (targetType == UNKNOWNOID)
-					ereport(ERROR,
-							(errcode(ERRCODE_DATATYPE_MISMATCH),
-							 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
-							 errhint("jsonb subscript must be coercible to either integer or text."),
-							 parser_errposition(pstate, exprLocation(subExpr))));
-			}
-			else
-				targetType = TEXTOID;
-
-			/*
-			 * We known from can_coerce_type that coercion will succeed, so
-			 * coerce_type could be used. Note the implicit coercion context,
-			 * which is required to handle subscripts of different types,
-			 * similar to overloaded functions.
-			 */
-			subExpr = coerce_type(pstate,
-								  subExpr, subExprType,
-								  targetType, -1,
-								  COERCION_IMPLICIT,
-								  COERCE_IMPLICIT_CAST,
-								  -1);
-			if (subExpr == NULL)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("jsonb subscript must have text type"),
-						 parser_errposition(pstate, exprLocation(subExpr))));
+			subExpr = coerce_jsonpath_subscript_to_int4_or_text(pstate, subExpr);
 		}
 		else
 		{
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v21-0005-Implement-read-only-dot-notation-for-jsonb.patch (74.4K, ../../CAK98qZ3fATHtD8n471AjniZ2KHFE59MfJzjo+P0SR3_oUT8Jbw@mail.gmail.com/4-v21-0005-Implement-read-only-dot-notation-for-jsonb.patch)
  download | inline diff:
From 4005dcf853188876cc22ab453d52113e41ea1b29 Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v21 5/5] Implement read-only dot notation for jsonb

This patch introduces JSONB member access using dot notation that
aligns with the JSON simplified accessor specified in SQL:2023.

Examples:

-- Setup
create table t(x int, y jsonb);
insert into t select 1, '{"a": 1, "b": 42}'::jsonb;
insert into t select 1, '{"a": 2, "b": {"c": 42}}'::jsonb;
insert into t select 1, '{"a": 3, "b": {"c": "42"}, "d":[11, 12]}'::jsonb;

-- Existing syntax in PostgreSQL that predates the SQL standard:
select (t.y)->'b' from t;
select (t.y)->'b'->'c' from t;
select (t.y)->'d'->0 from t;

-- JSON simplified accessor specified by the SQL standard:
select (t.y).b from t;
select (t.y).b.c from t;
select (t.y).d[0] from t;

The SQL standard states that simplified access is equivalent to:
JSON_QUERY (VEP, 'lax $.JC' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR)

where:
  VEP = <value expression primary>
  JC = <JSON simplified accessor op chain>

For example, the JSON_QUERY equivalents of the above queries are:

select json_query(y, 'lax $.b' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t;
select json_query(y, 'lax $.b.c' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t;
select json_query(y, 'lax $.d[0]' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t;

Implementation details:

This patch extends the existing container subscripting interface to
support container-specific information, namely a JSONPath expression
for jsonb.

During query transformation, if dot-notation is present, a JSONPath
expression is constructed to represent the access chain.

Then during execution, if a JSONPath expression is present in
JsonbSubWorkspace, executes it via JsonPathQuery().

Note that we cannot simply rewrite the accessors into JSON_QUERY()
during transformation, because the original query structure must be
preserved for EXPLAIN and CREATE VIEW.

Co-authored-by: Nikita Glukhov <[email protected]>
Co-authored-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Mark Dilger <[email protected]>
Reviewed-by: Jian He <[email protected]>
Reviewed-by: Vik Fearing <[email protected]>
Reviewed-by: Nikita Malakhov <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Tested-by: Jelte Fennema-Nio <[email protected]>
---
 doc/src/sgml/json.sgml                        | 301 ++++++++++++
 src/backend/catalog/sql_features.txt          |   4 +-
 src/backend/executor/execExpr.c               |  81 ++--
 src/backend/nodes/nodeFuncs.c                 |  12 +
 src/backend/utils/adt/jsonbsubs.c             | 302 +++++++++++-
 src/backend/utils/adt/ruleutils.c             |  43 +-
 src/include/nodes/primnodes.h                 |  54 ++-
 .../ecpg/test/expected/sql-sqljson.c          | 112 ++++-
 .../ecpg/test/expected/sql-sqljson.stderr     | 100 ++++
 .../ecpg/test/expected/sql-sqljson.stdout     |   7 +
 src/interfaces/ecpg/test/sql/sqljson.pgc      |  33 ++
 src/test/regress/expected/jsonb.out           | 444 +++++++++++++++++-
 src/test/regress/sql/jsonb.sql                | 114 +++++
 src/tools/pgindent/typedefs.list              |   1 +
 14 files changed, 1526 insertions(+), 82 deletions(-)

diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 206eadb8f7b..4405570d66e 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -712,6 +712,307 @@ UPDATE table_name SET jsonb_field[1]['a'] = '1';
   </para>
  </sect2>
 
+ <sect2 id="jsonb-simplified-accessor">
+  <title>JSON Simplified Accessor</title>
+  <para>
+   PostgreSQL implements the JSON simplified accessor as specified in SQL:2023.
+   The SQL standard defines the simplified accessor as a chain of operations
+   that can include JSON member accessors (dot notation for object fields)
+   and JSON array accessors (integer subscripts for array elements).
+   This provides a standardized way to access JSON data that complements
+   PostgreSQL's pre-standard subscripting and operator-based access methods.
+  </para>
+
+  <para>
+   The SQL:2023 simplified accessor syntax includes:
+   <itemizedlist>
+    <listitem>
+     <para>
+      <emphasis>JSON member accessor:</emphasis> <literal>jsonb_column.field_name</literal>
+      for accessing object fields
+     </para>
+    </listitem>
+    <listitem>
+     <para>
+      <emphasis>JSON array accessor:</emphasis> <literal>jsonb_column[index]</literal>
+      for accessing array elements by integer index
+     </para>
+    </listitem>
+   </itemizedlist>
+   These can be chained together: <literal>jsonb_column.field_name[0].nested_field</literal>.
+   When dot notation is present in the accessor chain, the entire chain follows
+   SQL:2023 semantics with lax mode behavior and conditional array wrapper.
+  </para>
+
+  <para>
+   The JSON simplified accessor is semantically equivalent to using
+   <function>JSON_QUERY</function> with the <literal>lax</literal> mode and
+   <literal>WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR</literal>
+   options. For example, <literal>json_col.field</literal> is equivalent to
+   <literal>JSON_QUERY(json_col, 'lax $.field' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR)</literal>.
+   The conditional array wrapper wraps multiple results in an array
+   but leaves a single result as-is without wrapping.
+  </para>
+
+  <para>
+   Examples of JSON simplified accessor syntax:
+
+<programlisting>
+
+-- Basic field access
+SELECT ('{"color": "red", "rgb": [255, 0, 0]}'::jsonb).color;
+
+-- Nested field access
+SELECT ('{"user": {"profile": {"settings": {"theme": "dark"}}}}'::jsonb).user.profile.settings.theme;
+
+-- JSON member accessor followed by JSON array accessor (both part of simplified accessor)
+SELECT ('{"repertoire": [{"title": "Swan Lake"}, {"title": "The Nutcracker"}]}'::jsonb).repertoire[1].title;
+
+-- In WHERE clauses
+SELECT * FROM users WHERE profile.preferences.theme = '"dark"';
+
+-- Comparison with other access methods (NOT equivalent - different semantics):
+SELECT json_col['address']['city'];           -- Subscripting
+SELECT json_col->'address'->'city';           -- Operator
+SELECT json_col.address.city;                 -- Simplified accessor (different behavior)
+</programlisting>
+  </para>
+
+  <sect3 id="jsonb-access-method-comparison">
+   <title>Comparison of JSON Access Methods</title>
+   <para>
+    PostgreSQL provides three different approaches for accessing JSON data, each with
+    distinct semantics and behaviors:
+   </para>
+
+   <para>
+    <emphasis>SQL:2023 JSON Simplified Accessor:</emphasis>
+    <itemizedlist>
+     <listitem>
+      <para>
+       Syntax: <literal>json_col.field_name</literal> (member accessor) and
+       <literal>json_col[index]</literal> (array accessor when used with dot notation)
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Standard SQL:2023 behavior with <literal>lax</literal> mode semantics
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Automatic array wrapping/unwrapping as specified in the SQL standard
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       When accessing a field from an array, operates on each array element and wraps results in an array;
+       when accessing an array index from a non-array, wraps the value as an array first
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Triggered when dot notation is present anywhere in the accessor chain
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Read-only access
+      </para>
+     </listitem>
+    </itemizedlist>
+   </para>
+
+   <para>
+    <emphasis>Pre-standard JSONB Subscripting:</emphasis>
+    <itemizedlist>
+     <listitem>
+      <para>
+       Syntax: <literal>json_col['field_name']</literal> (text-based) and
+       <literal>json_col[index]</literal> (integer-based when no dot notation present)
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       PostgreSQL's original JSONB subscripting behavior (available since version 14)
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Direct object field and array element access without array wrapping/unwrapping
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Supports both read and write operations
+      </para>
+     </listitem>
+    </itemizedlist>
+   </para>
+
+   <para>
+    <emphasis>Arrow Operators:</emphasis>
+    <itemizedlist>
+     <listitem>
+      <para>
+       Syntax: <literal>json_col->'field_name'</literal> and <literal>json_col->>'field_name'</literal>
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       PostgreSQL's JSON operators that work with both <literal>json</literal> and <literal>jsonb</literal> types
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Direct object field and array element access without array wrapping/unwrapping
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       <literal>-></literal> returns jsonb, <literal>->></literal> returns text
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Read-only access
+      </para>
+     </listitem>
+    </itemizedlist>
+   </para>
+
+   <para>
+    <emphasis>Key Semantic Differences:</emphasis> The most important distinctions are
+    how these methods handle member access from arrays and array access from non-array values.
+   </para>
+
+   <para>
+    <emphasis>Member Access from Arrays:</emphasis>
+<programlisting>
+-- Setup data
+INSERT INTO test_table VALUES
+  ('{"brightness": 80}'),                      -- Object case
+  ('[{"brightness": 45}, {"brightness": 90}]'); -- Array case
+
+-- Different behaviors:
+SELECT data.brightness FROM test_table;         -- Simplified accessor
+-- Results: 80, [45, 90]  (array elements unwrapped, results wrapped)
+
+SELECT data['brightness'] FROM test_table;      -- Pre-standard subscripting
+-- Results: 80, NULL    (no array handling)
+
+SELECT data->'brightness' FROM test_table;      -- Arrow operator
+-- Results: 80, NULL    (no array handling)
+</programlisting>
+
+    In the array case, the simplified accessor applies the field access to each array element
+    (unwrapping) and conditionally wraps the results in an array, while subscripting and arrow operators
+    attempt direct field access on the array itself (which returns NULL since
+    arrays don't have named fields).
+   </para>
+
+   <para>
+    <emphasis>Array Access from Objects (Lax Mode Behavior):</emphasis>
+<programlisting>
+-- Setup data
+INSERT INTO test_table VALUES ('{"weather": "sunny", "temperature": "72F"}');
+
+-- Different behaviors when accessing [0] on a non-array value:
+SELECT data[0] FROM test_table;                 -- Simplified accessor (lax mode, if dots present elsewhere)
+-- Result: {"weather": "sunny", "temperature": "72F"}  (object wrapped as array, [0] returns entire object)
+
+SELECT data[0] FROM test_table;                 -- Pre-standard subscripting (strict mode, no dots)
+-- Result: NULL  (no wrapping, direct array access on object fails)
+
+SELECT data->0 FROM test_table;                 -- Arrow operator (strict mode)
+-- Result: NULL  (no wrapping, direct array access on object fails)
+</programlisting>
+
+    In lax mode (simplified accessor), when an array operation is performed on a non-array value,
+    the value is first wrapped in an array, then the operation proceeds. In strict mode
+    (pre-standard methods), the operation fails and returns NULL.
+   </para>
+
+   <para>
+    <emphasis>When to Use Each Method:</emphasis>
+    <itemizedlist>
+     <listitem>
+      <para>
+       Use <emphasis>SQL:2023 simplified accessor</emphasis> for standard compliance and when you want lax mode and conditional array wrapper
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Use <emphasis>pre-standard subscripting</emphasis> for write operations or when you need direct field access without array processing
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Use <emphasis>arrow operators</emphasis> when you need text output (<literal>->></literal>) or when working with both <literal>json</literal> and <literal>jsonb</literal> types
+      </para>
+     </listitem>
+    </itemizedlist>
+   </para>
+  </sect3>
+
+  <sect3 id="jsonb-accessor-best-practices">
+   <title>Best Practices: Avoid Mixing Access Methods</title>
+   <para>
+    <emphasis>Important:</emphasis> Do not mix SQL:2023 simplified accessor syntax
+    with pre-standard subscripting syntax in the same accessor chain. These
+    methods have subtly different semantics and are not interchangeable aliases.
+    Mixing them can lead to confusion and code that is difficult to understand.
+   </para>
+
+   <para>
+    <emphasis>Recommended - Consistent simplified accessor:</emphasis>
+<programlisting>
+-- All parts use simplified accessor (standard behavior)
+SELECT data.location.coordinates.latitude FROM table;  -- Good
+SELECT data.repertoire[0].title FROM table;           -- Good
+SELECT data.users[1].profile.email FROM table;        -- Good
+</programlisting>
+   </para>
+
+   <para>
+    <emphasis>Recommended - Consistent pre-standard subscripting:</emphasis>
+<programlisting>
+-- All parts use pre-standard subscripting
+SELECT data['location']['coordinates']['latitude'] FROM table; -- Good
+SELECT data[0]['title'] FROM table;                    -- Good (when no dots present)
+SELECT data['users'][1]['profile']['email'] FROM table; -- Good
+</programlisting>
+   </para>
+
+   <para>
+    <emphasis>Not recommended - Mixed syntax:</emphasis>
+<programlisting>
+-- Mixing simplified accessor with pre-standard subscripting
+SELECT data.location['latitude'] FROM table;       -- Avoid
+SELECT data['repertoire'][0].title FROM table;    -- Avoid
+</programlisting>
+    While these mixed forms work as designed, they can be very confusing
+    because the simplified accessor cannot handle text-based subscripts like `['field']`.
+    This forces a fallback to pre-standard semantics for those specific parts,
+    creating a chain that switches between lax mode (for dot notation) and
+    strict mode (for text subscripts) within the same accessor expression.
+   </para>
+
+   <para>
+    Choose one approach consistently throughout your accessor chain to ensure
+    predictable and maintainable code.
+   </para>
+
+   <para>
+    <emphasis>Current Implementation:</emphasis> The current implementation supports
+    JSON member accessors (dot notation) and JSON array accessors (integer subscripts)
+    as defined in the SQL:2023 simplified accessor specification.
+    Advanced features from the SQL:2023 specification, such as wildcard member
+    accessors and item method accessors, are not yet implemented.
+   </para>
+  </sect3>
+ </sect2>
+
  <sect2 id="datatype-json-transforms">
   <title>Transforms</title>
 
diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_features.txt
index ebe85337c28..457e993305e 100644
--- a/src/backend/catalog/sql_features.txt
+++ b/src/backend/catalog/sql_features.txt
@@ -568,8 +568,8 @@ T838	JSON_TABLE: PLAN DEFAULT clause			NO
 T839	Formatted cast of datetimes to/from character strings			NO	
 T840	Hex integer literals in SQL/JSON path language			YES	
 T851	SQL/JSON: optional keywords for default syntax			YES	
-T860	SQL/JSON simplified accessor: column reference only			NO	
-T861	SQL/JSON simplified accessor: case-sensitive JSON member accessor			NO	
+T860	SQL/JSON simplified accessor: column reference only			YES	
+T861	SQL/JSON simplified accessor: case-sensitive JSON member accessor			YES	
 T862	SQL/JSON simplified accessor: wildcard member accessor			NO	
 T863	SQL/JSON simplified accessor: single-quoted string literal as member accessor			NO	
 T864	SQL/JSON simplified accessor			NO	
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index f1569879b52..385c8d0cefe 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -3320,50 +3320,59 @@ ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref,
 								   state->steps_len - 1);
 	}
 
-	/* Evaluate upper subscripts */
-	i = 0;
-	foreach(lc, sbsref->refupperindexpr)
+	/* Evaluate upper subscripts, unless refjsonbpath is used for execution */
+	if (!sbsref->refjsonbpath)
 	{
-		Expr	   *e = (Expr *) lfirst(lc);
-
-		/* When slicing, individual subscript bounds can be omitted */
-		if (!e)
-		{
-			sbsrefstate->upperprovided[i] = false;
-			sbsrefstate->upperindexnull[i] = true;
-		}
-		else
+		i = 0;
+		foreach(lc, sbsref->refupperindexpr)
 		{
-			sbsrefstate->upperprovided[i] = true;
-			/* Each subscript is evaluated into appropriate array entry */
-			ExecInitExprRec(e, state,
-							&sbsrefstate->upperindex[i],
-							&sbsrefstate->upperindexnull[i]);
+			Expr *e = (Expr *) lfirst(lc);
+
+			/* When slicing, individual subscript bounds can be omitted */
+			if (!e)
+			{
+				sbsrefstate->upperprovided[i] = false;
+				sbsrefstate->upperindexnull[i] = true;
+			}
+			else
+			{
+				sbsrefstate->upperprovided[i] = true;
+				/* Each subscript is evaluated into appropriate array entry */
+				ExecInitExprRec(e, state,
+								&sbsrefstate->upperindex[i],
+								&sbsrefstate->upperindexnull[i]);
+			}
+			i++;
 		}
-		i++;
 	}
 
-	/* Evaluate lower subscripts similarly */
-	i = 0;
-	foreach(lc, sbsref->reflowerindexpr)
+	/*
+	 * Evaluate lower subscripts similarly, unless refjsonbpath is used for
+	 * execution
+	 */
+	if (!sbsref->refjsonbpath)
 	{
-		Expr	   *e = (Expr *) lfirst(lc);
-
-		/* When slicing, individual subscript bounds can be omitted */
-		if (!e)
-		{
-			sbsrefstate->lowerprovided[i] = false;
-			sbsrefstate->lowerindexnull[i] = true;
-		}
-		else
+		i = 0;
+		foreach(lc, sbsref->reflowerindexpr)
 		{
-			sbsrefstate->lowerprovided[i] = true;
-			/* Each subscript is evaluated into appropriate array entry */
-			ExecInitExprRec(e, state,
-							&sbsrefstate->lowerindex[i],
-							&sbsrefstate->lowerindexnull[i]);
+			Expr	   *e = (Expr *) lfirst(lc);
+
+			/* When slicing, individual subscript bounds can be omitted */
+			if (!e)
+			{
+				sbsrefstate->lowerprovided[i] = false;
+				sbsrefstate->lowerindexnull[i] = true;
+			}
+			else
+			{
+				sbsrefstate->lowerprovided[i] = true;
+				/* Each subscript is evaluated into appropriate array entry */
+				ExecInitExprRec(e, state,
+								&sbsrefstate->lowerindex[i],
+								&sbsrefstate->lowerindexnull[i]);
+			}
+			i++;
 		}
-		i++;
 	}
 
 	/* SBSREF_SUBSCRIPTS checks and converts all the subscripts at once */
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 7bc823507f1..d1bd575d9bd 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -284,6 +284,13 @@ exprType(const Node *expr)
 		case T_PlaceHolderVar:
 			type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_FieldAccessorExpr:
+			/*
+			 * FieldAccessorExpr is not evaluable. Treat it as TEXT for collation,
+			 * deparsing, and similar purposes, since it represents a JSON field name.
+			 */
+			type = TEXTOID;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			type = InvalidOid;	/* keep compiler quiet */
@@ -1146,6 +1153,9 @@ exprSetCollation(Node *expr, Oid collation)
 		case T_MergeSupportFunc:
 			((MergeSupportFunc *) expr)->msfcollid = collation;
 			break;
+		case T_FieldAccessorExpr:
+			((FieldAccessorExpr *) expr)->faecollid = collation;
+			break;
 		case T_SubscriptingRef:
 			((SubscriptingRef *) expr)->refcollid = collation;
 			break;
@@ -2129,6 +2139,7 @@ expression_tree_walker_impl(Node *node,
 		case T_SortGroupClause:
 		case T_CTESearchClause:
 		case T_MergeSupportFunc:
+		case T_FieldAccessorExpr:
 			/* primitive node types with no expression subnodes */
 			break;
 		case T_WithCheckOption:
@@ -3008,6 +3019,7 @@ expression_tree_mutator_impl(Node *node,
 		case T_SortGroupClause:
 		case T_CTESearchClause:
 		case T_MergeSupportFunc:
+		case T_FieldAccessorExpr:
 			return copyObject(node);
 		case T_WithCheckOption:
 			{
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index 516146d1146..bdfe6a310de 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -15,21 +15,30 @@
 #include "postgres.h"
 
 #include "executor/execExpr.h"
+#include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "nodes/subscripting.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_expr.h"
 #include "utils/builtins.h"
 #include "utils/jsonb.h"
+#include "utils/jsonpath.h"
 
 
-/* SubscriptingRefState.workspace for jsonb subscripting execution */
+/*
+ * SubscriptingRefState.workspace for generic jsonb subscripting execution.
+ *
+ * Stores state for both jsonb simple subscripting and dot notation access.
+ * Dot notation additionally uses `jsonpath` for JsonPath evaluation.
+ */
 typedef struct JsonbSubWorkspace
 {
 	bool		expectArray;	/* jsonb root is expected to be an array */
 	Oid		   *indexOid;		/* OID of coerced subscript expression, could
 								 * be only integer or text */
 	Datum	   *index;			/* Subscript values in Datum format */
+	JsonPath   *jsonpath;		/* JsonPath for dot notation execution via
+								 * JsonPathQuery() */
 } JsonbSubWorkspace;
 
 static Node *
@@ -96,6 +105,233 @@ coerce_jsonpath_subscript_to_int4_or_text(ParseState *pstate, Node *subExpr)
 	return subExpr;
 }
 
+/*
+ * During transformation, determine whether to build a JsonPath
+ * for JsonPathQuery() execution.
+ *
+ * JsonPath is needed if the indirection list includes:
+ * - String-based access (dot notation)
+ * - Slice-based subscripting (when isSlice is true)
+ *
+ * Otherwise, simple jsonb subscripting is enough.
+ */
+static bool
+jsonb_check_jsonpath_needed(List *indirection)
+{
+	ListCell   *lc;
+
+	foreach(lc, indirection)
+	{
+		Node	   *accessor = lfirst(lc);
+
+		if (IsA(accessor, String))
+			return true;
+		else
+			Assert(IsA(accessor, A_Indices));
+	}
+
+	return false;
+}
+
+/*
+ * Helper functions for constructing JsonPath expressions.
+ *
+ * The make_jsonpath_item_* functions create various types of JsonPathParseItem
+ * nodes, which are used to build JsonPath expressions for jsonb simplified
+ * accessor.
+ */
+
+static JsonPathParseItem *
+make_jsonpath_item(JsonPathItemType type)
+{
+	JsonPathParseItem *v = palloc(sizeof(*v));
+
+	v->type = type;
+	v->next = NULL;
+
+	return v;
+}
+
+/*
+ * Convert a constant integer expression into a JsonPathParseItem.
+ *
+ * The input expression must be a non-null constant of type INT4. Returns NULL otherwise.
+ * This function constructs a jpiNumeric item for use in JsonPath and appends a matching
+ * Const(INT4) node to the given expression list for use in EXPLAIN, views, etc.
+ *
+ * Parameters:
+ * - pstate: parse state context
+ * - expr: input expression node
+ * - exprs: list of expression nodes (updated in place)
+ */
+static JsonPathParseItem *
+make_jsonpath_item_expr(ParseState *pstate, Node *expr, List **exprs)
+{
+	Const	   *cnst;
+
+	expr = transformExpr(pstate, expr, pstate->p_expr_kind);
+
+	if (IsA(expr, Const))
+	{
+		cnst = (Const *) expr;
+		if (cnst->consttype == INT4OID && !(cnst->constisnull))
+		{
+			JsonPathParseItem *jpi = make_jsonpath_item(jpiNumeric);
+
+			jpi->value.numeric =
+				DatumGetNumeric(DirectFunctionCall1(int4_numeric, Int32GetDatum(cnst->constvalue)));
+
+			*exprs = lappend(*exprs, makeConst(INT4OID, -1, InvalidOid, 4,
+											   Int32GetDatum(cnst->constvalue), false, true));
+
+			return jpi;
+		}
+	}
+
+	return NULL;
+}
+
+/*
+ * Constructs a JsonPath expression from a list of indirections.
+ * This function is used when jsonb subscripting involves dot notation,
+ * requiring JsonPath-based evaluation.
+ *
+ * The function modifies the indirection list in place, removing processed
+ * elements as it converts them into JsonPath components, as follows:
+ * - String keys (dot notation) -> jpiKey items.
+ * - Array indices -> jpiIndexArray items.
+ *
+ * In addition to building the JsonPath expression, this function populates
+ * the following fields of the given SubscriptingRef:
+ * - refjsonbpath: the generated JsonPath
+ * - refupperindexpr: upper index expressions (object keys or array indexes)
+ * - reflowerindexpr: lower index expressions, remains NIL as slices are not supported.
+ *
+ * Parameters:
+ * - pstate: Parse state context.
+ * - indirection: List of subscripting expressions (modified in-place).
+ * - sbsref: SubscriptingRef node to update
+ */
+static int
+jsonb_subscript_make_jsonpath(ParseState *pstate, List *indirection, SubscriptingRef *sbsref)
+{
+	JsonPathParseResult jpres;
+	JsonPathParseItem *path = make_jsonpath_item(jpiRoot);
+	ListCell   *lc;
+	Datum		jsp;
+	int			pathlen = 0;
+
+	sbsref->refupperindexpr = NIL;
+	sbsref->reflowerindexpr = NIL;
+	sbsref->refjsonbpath = NULL;
+
+	jpres.expr = path;
+	jpres.lax = true;
+
+	foreach(lc, indirection)
+	{
+		Node	   *accessor = lfirst(lc);
+		JsonPathParseItem *jpi;
+
+		if (IsA(accessor, String))
+		{
+			char	   *field = strVal(accessor);
+			FieldAccessorExpr *accessor_expr;
+
+			jpi = make_jsonpath_item(jpiKey);
+			jpi->value.string.val = field;
+			jpi->value.string.len = strlen(field);
+
+			accessor_expr = makeNode(FieldAccessorExpr);
+			accessor_expr->type = T_FieldAccessorExpr;
+			accessor_expr->fieldname = field;
+
+			sbsref->refupperindexpr = lappend(sbsref->refupperindexpr, accessor_expr);
+		}
+		else if (IsA(accessor, A_Indices))
+		{
+			A_Indices  *ai = castNode(A_Indices, accessor);
+
+			if (!ai->is_slice)
+			{
+				JsonPathParseItem *jpi_from = NULL;
+
+				Assert(ai->uidx && !ai->lidx);
+				jpi_from = make_jsonpath_item_expr(pstate, ai->uidx, &sbsref->refupperindexpr);
+				if (jpi_from == NULL)
+				{
+					/*
+					 * Break out of the loop if the subscript is not a
+					 * non-null integer constant, so that we can fall back to
+					 * jsonb subscripting logic.
+					 *
+					 * This is needed to handle cases with mixed usage of SQL
+					 * standard json simplified accessor syntax and PostgreSQL
+					 * jsonb subscripting syntax, e.g:
+					 *
+					 * select (jb).a['b'].c from jsonb_table;
+					 *
+					 * where dot-notation (.a and .c) is the SQL standard json
+					 * simplified accessor syntax, and the ['b'] subscript is
+					 * the PostgreSQL jsonb subscripting syntax, because 'b'
+					 * is not a non-null constant integer and cannot be used
+					 * for json array access.
+					 *
+					 * In this case, we cannot create a JsonPath item, so we
+					 * break out of the loop and let
+					 * jsonb_subscript_transform() handle this indirection as
+					 * a PostgreSQL jsonb subscript.
+					 */
+					break;
+				}
+
+				jpi = make_jsonpath_item(jpiIndexArray);
+				jpi->value.array.nelems = 1;
+				jpi->value.array.elems = palloc(sizeof(jpi->value.array.elems[0]));
+
+				jpi->value.array.elems[0].from = jpi_from;
+				jpi->value.array.elems[0].to = NULL;
+			}
+			else
+			{
+				Node	   *expr = ai->uidx ? ai->uidx : ai->lidx;
+
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("jsonb subscript does not support slices"),
+						 parser_errposition(pstate, exprLocation(expr))));
+			}
+		}
+		else
+		{
+			/*
+			 * Unexpected node type in indirection list. This should not
+			 * happen with current grammar, but we handle it defensively by
+			 * breaking out of the loop rather than crashing. In case of
+			 * future grammar changes that might introduce new node types,
+			 * this allows us to create a jsonpath from as many indirection
+			 * elements as we can and let transformIndirection() fallback to
+			 * alternative logic to handle the remaining indirection elements.
+			 */
+			Assert(false);		/* not reachable */
+			break;
+		}
+
+		/* append path item */
+		path->next = jpi;
+		path = jpi;
+		pathlen++;
+	}
+
+	if (pathlen != 0)
+	{
+		jsp = jsonPathFromParseResult(&jpres, 0, NULL);
+		sbsref->refjsonbpath = (Node *) makeConst(JSONPATHOID, -1, InvalidOid, -1, jsp, false, false);
+	}
+
+	return pathlen;
+}
+
 /*
  * Finish parse analysis of a SubscriptingRef expression for a jsonb.
  *
@@ -111,9 +347,29 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	List	   *upperIndexpr = NIL;
 	ListCell   *idx;
 
+	/* Determine the result type of the subscripting operation; always jsonb */
+	sbsref->refrestype = JSONBOID;
+	sbsref->reftypmod = -1;
+
+	if (jsonb_check_jsonpath_needed(indirection))
+	{
+		int pathlen;
+
+		pathlen = jsonb_subscript_make_jsonpath(pstate, indirection, sbsref);
+		if (sbsref->refjsonbpath)
+			return pathlen;
+	}
+
 	/*
-	 * Transform and convert the subscript expressions. Jsonb subscripting
-	 * does not support slices, look only at the upper index.
+	 * We reach here only in two cases: (a) the JSON simplified accessor is
+	 * not needed at all (for example, a plain array subscript like [1] or
+	 * object key access like ['a']), or (b) jsonb_subscript_make_jsonpath()
+	 * was called but could not complete the JsonPath construction (for
+	 * example, when mixing dot notation with non-integer subscripts like
+	 * (jb)['a'].b where 'a' is not a constant integer).
+	 *
+	 * In both cases we fall back to pre-standard jsonb subscripting, coercing
+	 * each subscript to array index or object key as needed.
 	 */
 	foreach(idx, indirection)
 	{
@@ -160,10 +416,6 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->refupperindexpr = upperIndexpr;
 	sbsref->reflowerindexpr = NIL;
 
-	/* Determine the result type of the subscripting operation; always jsonb */
-	sbsref->refrestype = JSONBOID;
-	sbsref->reftypmod = -1;
-
 	return list_length(upperIndexpr);
 }
 
@@ -216,7 +468,7 @@ jsonb_subscript_check_subscripts(ExprState *state,
 			 * For jsonb fetch and assign functions we need to provide path in
 			 * text format. Convert if it's not already text.
 			 */
-			if (workspace->indexOid[i] == INT4OID)
+			if (!workspace->jsonpath && workspace->indexOid[i] == INT4OID)
 			{
 				Datum		datum = sbsrefstate->upperindex[i];
 				char	   *cs = DatumGetCString(DirectFunctionCall1(int4out, datum));
@@ -244,17 +496,32 @@ jsonb_subscript_fetch(ExprState *state,
 {
 	SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
 	JsonbSubWorkspace *workspace = (JsonbSubWorkspace *) sbsrefstate->workspace;
-	Jsonb	   *jsonbSource;
 
 	/* Should not get here if source jsonb (or any subscript) is null */
 	Assert(!(*op->resnull));
 
-	jsonbSource = DatumGetJsonbP(*op->resvalue);
-	*op->resvalue = jsonb_get_element(jsonbSource,
-									  workspace->index,
-									  sbsrefstate->numupper,
-									  op->resnull,
-									  false);
+	if (workspace->jsonpath)
+	{
+		bool		empty = false;
+		bool		error = false;
+
+		*op->resvalue = JsonPathQuery(*op->resvalue, workspace->jsonpath,
+									  JSW_CONDITIONAL,
+									  &empty, &error, NULL,
+									  NULL);
+
+		*op->resnull = empty || error;
+	}
+	else
+	{
+		Jsonb	   *jsonbSource = DatumGetJsonbP(*op->resvalue);
+
+		*op->resvalue = jsonb_get_element(jsonbSource,
+										  workspace->index,
+										  sbsrefstate->numupper,
+										  op->resnull,
+										  false);
+	}
 }
 
 /*
@@ -362,7 +629,7 @@ jsonb_exec_setup(const SubscriptingRef *sbsref,
 {
 	JsonbSubWorkspace *workspace;
 	ListCell   *lc;
-	int			nupper = sbsref->refupperindexpr->length;
+	int			nupper = list_length(sbsref->refupperindexpr);
 	char	   *ptr;
 
 	/* Allocate type-specific workspace with space for per-subscript data */
@@ -371,6 +638,9 @@ jsonb_exec_setup(const SubscriptingRef *sbsref,
 	workspace->expectArray = false;
 	ptr = ((char *) workspace) + MAXALIGN(sizeof(JsonbSubWorkspace));
 
+	if (sbsref->refjsonbpath)
+		workspace->jsonpath = DatumGetJsonPathP(castNode(Const, sbsref->refjsonbpath)->constvalue);
+
 	/*
 	 * This coding assumes sizeof(Datum) >= sizeof(Oid), else we might
 	 * misalign the indexOid pointer
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 0408a95941d..c899734c2e3 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9329,10 +9329,13 @@ get_rule_expr(Node *node, deparse_context *context,
 				 * Parenthesize the argument unless it's a simple Var or a
 				 * FieldSelect.  (In particular, if it's another
 				 * SubscriptingRef, we *must* parenthesize to avoid
-				 * confusion.)
+				 * confusion.) Always add parenthesis if JSON simplified
+				 * accessor is used, for now.
 				 */
-				need_parens = !IsA(sbsref->refexpr, Var) &&
-					!IsA(sbsref->refexpr, FieldSelect);
+				need_parens = (!IsA(sbsref->refexpr, Var) &&
+					!IsA(sbsref->refexpr, FieldSelect)) ||
+						sbsref->refjsonbpath;
+
 				if (need_parens)
 					appendStringInfoChar(buf, '(');
 				get_rule_expr((Node *) sbsref->refexpr, context, showimplicit);
@@ -13005,17 +13008,35 @@ printSubscripts(SubscriptingRef *sbsref, deparse_context *context)
 	lowlist_item = list_head(sbsref->reflowerindexpr);	/* could be NULL */
 	foreach(uplist_item, sbsref->refupperindexpr)
 	{
-		appendStringInfoChar(buf, '[');
-		if (lowlist_item)
+		Node *upper = (Node *) lfirst(uplist_item);
+
+		if (upper && IsA(upper, FieldAccessorExpr))
 		{
+			FieldAccessorExpr *fae = (FieldAccessorExpr *) upper;
+
+			/* Use dot-notation for field access */
+			appendStringInfoChar(buf, '.');
+			appendStringInfoString(buf, quote_identifier(fae->fieldname));
+
+			/* Skip matching low index — field access doesn't use slices */
+			if (lowlist_item)
+				lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
+		}
+		else
+		{
+			/* Use JSONB array subscripting */
+			appendStringInfoChar(buf, '[');
+			if (lowlist_item)
+			{
+				/* If subexpression is NULL, get_rule_expr prints nothing */
+				get_rule_expr((Node *) lfirst(lowlist_item), context, false);
+				appendStringInfoChar(buf, ':');
+				lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
+			}
 			/* If subexpression is NULL, get_rule_expr prints nothing */
-			get_rule_expr((Node *) lfirst(lowlist_item), context, false);
-			appendStringInfoChar(buf, ':');
-			lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
+			get_rule_expr(upper, context, false);
+			appendStringInfoChar(buf, ']');
 		}
-		/* If subexpression is NULL, get_rule_expr prints nothing */
-		get_rule_expr((Node *) lfirst(uplist_item), context, false);
-		appendStringInfoChar(buf, ']');
 	}
 }
 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 6dfca3cb35b..7e89621bd65 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -708,18 +708,30 @@ typedef struct SubscriptingRef
 	int32		reftypmod pg_node_attr(query_jumble_ignore);
 	/* collation of result, or InvalidOid if none */
 	Oid			refcollid pg_node_attr(query_jumble_ignore);
-	/* expressions that evaluate to upper container indexes */
+
+	/*
+	 * expressions that evaluate to upper container indexes or expressions
+	 * that are collected but not evaluated when refjsonbpath is set.
+	 */
 	List	   *refupperindexpr;
 
 	/*
-	 * expressions that evaluate to lower container indexes, or NIL for single
-	 * container element.
+	 * expressions that evaluate to lower container indexes, or NIL for a
+	 * single container element, or expressions that are collected but not
+	 * evaluated when refjsonbpath is set.
 	 */
 	List	   *reflowerindexpr;
 	/* the expression that evaluates to a container value */
 	Expr	   *refexpr;
 	/* expression for the source value, or NULL if fetch */
 	Expr	   *refassgnexpr;
+
+	/*
+	 * container-specific extra information, currently used only by jsonb.
+	 * stores a JsonPath expression when jsonb dot notation is used. NULL for
+	 * simple subscripting.
+	 */
+	Node	   *refjsonbpath;
 } SubscriptingRef;
 
 /*
@@ -2371,4 +2383,40 @@ typedef struct OnConflictExpr
 	List	   *exclRelTlist;	/* tlist of the EXCLUDED pseudo relation */
 } OnConflictExpr;
 
+/*
+ * FieldAccessorExpr - represents a single object member access using dot-notation
+ *		in JSON simplified accessor syntax (e.g., jsonb_col.a).
+ *
+ * These nodes appear as list elements in SubscriptingRef.refupperindexpr to
+ * indicate JSON object key access. They are not evaluable expressions by
+ * themselves but serve as placeholders to preserve source-level syntax for
+ * rule rewriting and deparsing (e.g., in EXPLAIN and view definitions).
+ * Execution is handled by the enclosing SubscriptingRef.
+ *
+ * If dot-notation is used in a SubscriptingRef, the JSON path is represented
+ * as a flat list of FieldAccessorExpr nodes (for object field access), Const
+ * nodes (for array indexes), and NULLs (for omitted slice bounds), rather than
+ * through nested expression trees.
+ *
+ * Note: The flat representation avoids nested FieldAccessorExpr chains,
+ * simplifying evaluation and enabling standard-compliant behavior such as
+ * conditional array wrapping. This avoids the need for position-aware
+ * wrapping/unwrapping logic during execution.
+ *
+ * For example, in the expression:
+ *		('{"a": [{"b": 1}]}'::jsonb).a[0].b
+ * the SubscriptingRef will contain:
+ *		- refexpr: the base expression (the jsonb value)
+ *		- refupperindexpr: [FieldAccessorExpr("a"), Const(0),
+ *			FieldAccessorExpr("b")]
+ *		- reflowerindexpr: [NULL, NULL, NULL] (slice lower bounds not used here)
+ */
+typedef struct FieldAccessorExpr
+{
+	NodeTag		type;
+	char	   *fieldname;		/* name of the JSONB object field accessed via
+								 * dot notation */
+	Oid			faecollid pg_node_attr(query_jumble_ignore);
+}			FieldAccessorExpr;
+
 #endif							/* PRIMNODES_H */
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.c b/src/interfaces/ecpg/test/expected/sql-sqljson.c
index 39221f9ea5d..e6a7ece6dab 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.c
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.c
@@ -417,12 +417,122 @@ if (sqlca.sqlcode < 0) sqlprint();}
 	  for (int i = 0; i < sizeof(is_json); i++)
 		  printf("Found is_json[%d]: %s\n", i, is_json[i] ? "true" : "false");
 
-  { ECPGdisconnect(__LINE__, "CURRENT");
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . \"a\" )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 118 "sqljson.pgc"
 
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 118 "sqljson.pgc"
 
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb . \"a\" )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 121 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 121 "sqljson.pgc"
+
+	// error
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . a )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 124 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 124 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . a . b )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 127 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 127 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( coalesce ( json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . c ) , 'null' ) )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 130 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 130 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ 0 ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 133 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 133 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b . x )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 136 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 136 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) [ 'b' ] [ 0 ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 139 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 139 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 142 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 142 "sqljson.pgc"
+
+	// error
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . * )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 145 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 145 "sqljson.pgc"
+
+	// error
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 148 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 148 "sqljson.pgc"
+
+	// error
+
+  { ECPGdisconnect(__LINE__, "CURRENT");
+#line 151 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 151 "sqljson.pgc"
+
 
   return 0;
 }
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
index e55a95dd711..19f8c58af06 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
@@ -268,5 +268,105 @@ SQL error: cannot use type jsonb in RETURNING clause of JSON_SERIALIZE() on line
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_get_data on line 102: RESULT: f offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 118: query: select json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . "a" ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 118: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 118: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 118: RESULT: {"b": 1, "c": 2} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 121: query: select json ( '{"a": {"b": 1, "c": 2}}' :: jsonb . "a" ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 121: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 121: bad response - ERROR:  schema "jsonb" does not exist
+LINE 1: select json ( '{"a": {"b": 1, "c": 2}}' :: jsonb . "a" )
+                                                   ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 3F000 (sqlcode -400): schema "jsonb" does not exist on line 121
+[NO_PID]: sqlca: code: -400, state: 3F000
+SQL error: schema "jsonb" does not exist on line 121
+[NO_PID]: ecpg_execute on line 124: query: select json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . a ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 124: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 124: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 124: RESULT: {"b": 1, "c": 2} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 127: query: select json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . a . b ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 127: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 127: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 127: RESULT: 1 offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 130: query: select json ( coalesce ( json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . c ) , 'null' ) ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 130: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 130: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 130: RESULT: null offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 133: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 0 ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 133: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 133: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 133: RESULT: {"x": 1} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 136: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b . x ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 136: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 136: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 136: RESULT: [1, [12, {"y": 1}]] offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 139: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 139: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 139: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 139: RESULT: {"x": 1} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 142: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 142: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 142: bad response - ERROR:  jsonb subscript does not support slices
+LINE 1: ..., {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] )
+                                                                ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 42804 (sqlcode -400): jsonb subscript does not support slices on line 142
+[NO_PID]: sqlca: code: -400, state: 42804
+SQL error: jsonb subscript does not support slices on line 142
+[NO_PID]: ecpg_execute on line 145: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . * ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 145: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 145: bad response - ERROR:  row expansion via "*" is not supported here
+LINE 1: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x...
+                        ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 0A000 (sqlcode -400): row expansion via "*" is not supported here on line 145
+[NO_PID]: sqlca: code: -400, state: 0A000
+SQL error: row expansion via "*" is not supported here on line 145
+[NO_PID]: ecpg_execute on line 148: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 148: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 148: bad response - ERROR:  jsonb subscript does not support slices
+LINE 1: ...x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )
+                                                                ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 42804 (sqlcode -400): jsonb subscript does not support slices on line 148
+[NO_PID]: sqlca: code: -400, state: 42804
+SQL error: jsonb subscript does not support slices on line 148
 [NO_PID]: ecpg_finish: connection ecpg1_regression closed
 [NO_PID]: sqlca: code: 0, state: 00000
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
index 83f8df13e5a..442d36931f1 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
@@ -28,3 +28,10 @@ Found is_json[4]: false
 Found is_json[5]: false
 Found is_json[6]: true
 Found is_json[7]: false
+Found json={"b": 1, "c": 2}
+Found json={"b": 1, "c": 2}
+Found json=1
+Found json=null
+Found json={"x": 1}
+Found json=[1, [12, {"y": 1}]]
+Found json={"x": 1}
diff --git a/src/interfaces/ecpg/test/sql/sqljson.pgc b/src/interfaces/ecpg/test/sql/sqljson.pgc
index ddcbcc3b3cb..57a9bff424d 100644
--- a/src/interfaces/ecpg/test/sql/sqljson.pgc
+++ b/src/interfaces/ecpg/test/sql/sqljson.pgc
@@ -115,6 +115,39 @@ EXEC SQL END DECLARE SECTION;
 	  for (int i = 0; i < sizeof(is_json); i++)
 		  printf("Found is_json[%d]: %s\n", i, is_json[i] ? "true" : "false");
 
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}}'::jsonb)."a") INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON('{"a": {"b": 1, "c": 2}}'::jsonb."a") INTO :json;
+	// error
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}}'::jsonb).a) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}}'::jsonb).a.b) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(COALESCE(JSON(('{"a": {"b": 1, "c": 2}}'::jsonb).c), 'null')) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b[0]) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b.x) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb)['b'][0]) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b[1].x[0:]) INTO :json;
+	// error
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).*) INTO :json;
+	// error
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb)['b'][0:]) INTO :json;
+	// error
+
   EXEC SQL DISCONNECT;
 
   return 0;
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index fb47c8a893a..685f229e682 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -4989,6 +4989,12 @@ select ('123'::jsonb)['a'];
  
 (1 row)
 
+select ('123'::jsonb).a;
+ a 
+---
+ 
+(1 row)
+
 select ('123'::jsonb)[0];
  jsonb 
 -------
@@ -5001,12 +5007,24 @@ select ('123'::jsonb)[NULL];
  
 (1 row)
 
+select ('123'::jsonb).NULL;
+ null 
+------
+ 
+(1 row)
+
 select ('{"a": 1}'::jsonb)['a'];
  jsonb 
 -------
  1
 (1 row)
 
+select ('{"a": 1}'::jsonb).a;
+ a 
+---
+ 1
+(1 row)
+
 select ('{"a": 1}'::jsonb)[0];
  jsonb 
 -------
@@ -5019,6 +5037,12 @@ select ('{"a": 1}'::jsonb)['not_exist'];
  
 (1 row)
 
+select ('{"a": 1}'::jsonb)."not_exist";
+ not_exist 
+-----------
+ 
+(1 row)
+
 select ('{"a": 1}'::jsonb)[NULL];
  jsonb 
 -------
@@ -5031,6 +5055,12 @@ select ('[1, "2", null]'::jsonb)['a'];
  
 (1 row)
 
+select ('[1, "2", null]'::jsonb).a;
+ a 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[0];
  jsonb 
 -------
@@ -5043,6 +5073,12 @@ select ('[1, "2", null]'::jsonb)['1'];
  "2"
 (1 row)
 
+select ('[1, "2", null]'::jsonb)."1";
+ 1 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[1.0];
 ERROR:  subscript type numeric is not supported
 LINE 1: select ('[1, "2", null]'::jsonb)[1.0];
@@ -5072,6 +5108,12 @@ select ('[1, "2", null]'::jsonb)[1]['a'];
  
 (1 row)
 
+select ('[1, "2", null]'::jsonb)[1].a;
+ a 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[1][0];
  jsonb 
 -------
@@ -5084,54 +5126,126 @@ select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['b'];
  "c"
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).b;
+  b  
+-----
+ "c"
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'];
    jsonb   
 -----------
  [1, 2, 3]
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d;
+     d     
+-----------
+ [1, 2, 3]
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'][1];
  jsonb 
 -------
  2
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d[1];
+ d 
+---
+ 2
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d']['a'];
  jsonb 
 -------
  
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['a'];
+ d 
+---
+ 
+(1 row)
+
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d.a;
+ a 
+---
+ 
+(1 row)
+
+select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d['a'][0];
+ d 
+---
+ 1
+(1 row)
+
+select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d.a[0];
+ a 
+---
+ 1
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1'];
      jsonb     
 ---------------
  {"a2": "aaa"}
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1;
+      a1       
+---------------
+ {"a2": "aaa"}
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2'];
  jsonb 
 -------
  "aaa"
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2;
+  a2   
+-------
+ "aaa"
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']['a3'];
  jsonb 
 -------
  
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2.a3;
+ a3 
+----
+ 
+(1 row)
+
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'];
          jsonb         
 -----------------------
  ["aaa", "bbb", "ccc"]
 (1 row)
 
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1;
+          b1           
+-----------------------
+ ["aaa", "bbb", "ccc"]
+(1 row)
+
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'][2];
  jsonb 
 -------
  "ccc"
 (1 row)
 
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1[2];
+  b1   
+-------
+ "ccc"
+(1 row)
+
 -- slices are not supported
 select ('{"a": 1}'::jsonb)['a':'b'];
 ERROR:  jsonb subscript does not support slices
@@ -5841,7 +5955,7 @@ select jsonb_typeof('{"a":1}'::jsonb);
 select ('{"a":1}'::jsonb).jsonb_typeof;
  jsonb_typeof 
 --------------
- object
+ 
 (1 row)
 
 select jsonb_array_length('["a", "b", "c"]'::jsonb);
@@ -5853,7 +5967,7 @@ select jsonb_array_length('["a", "b", "c"]'::jsonb);
 select ('["a", "b", "c"]'::jsonb).jsonb_array_length;
  jsonb_array_length 
 --------------------
-                  3
+ 
 (1 row)
 
 select jsonb_object_keys('{"a":1, "b":2}'::jsonb);
@@ -5866,9 +5980,8 @@ select jsonb_object_keys('{"a":1, "b":2}'::jsonb);
 select ('{"a":1, "b":2}'::jsonb).jsonb_object_keys;
  jsonb_object_keys 
 -------------------
- a
- b
-(2 rows)
+ 
+(1 row)
 
 -- cast jsonb to other types as (jsonb)::type and (jsonb).type
 select ('123.45'::jsonb)::numeric;
@@ -5880,7 +5993,7 @@ select ('123.45'::jsonb)::numeric;
 select ('123.45'::jsonb).numeric;
  numeric 
 ---------
-  123.45
+ 
 (1 row)
 
 select ('[{"name": "alice"}, {"name": "bob"}]'::jsonb)::name;
@@ -5890,9 +6003,9 @@ select ('[{"name": "alice"}, {"name": "bob"}]'::jsonb)::name;
 (1 row)
 
 select ('[{"name": "alice"}, {"name": "bob"}]'::jsonb).name;
-                 name                 
---------------------------------------
- [{"name": "alice"}, {"name": "bob"}]
+       name       
+------------------
+ ["alice", "bob"]
 (1 row)
 
 select ('true'::jsonb)::bool;
@@ -5904,7 +6017,7 @@ select ('true'::jsonb)::bool;
 select ('true'::jsonb).bool;
  bool 
 ------
- t
+ 
 (1 row)
 
 select ('{"text": "hello"}'::jsonb)::text;
@@ -5914,8 +6027,313 @@ select ('{"text": "hello"}'::jsonb)::text;
 (1 row)
 
 select ('{"text": "hello"}'::jsonb).text;
-       text        
--------------------
- {"text": "hello"}
+  text   
+---------
+ "hello"
+(1 row)
+
+-- dot notation
+CREATE TABLE test_jsonb_dot_notation AS
+SELECT '{"a": [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], "b": [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]}'::jsonb jb;
+SELECT (jb).a FROM test_jsonb_dot_notation;
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+SELECT (jb)."a" FROM test_jsonb_dot_notation; -- double quote should work
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+SELECT (jb).'a' FROM test_jsonb_dot_notation; -- single quote should not work
+ERROR:  syntax error at or near "'a'"
+LINE 1: SELECT (jb).'a' FROM test_jsonb_dot_notation;
+                    ^
+select (jb)[0].a from test_jsonb_dot_notation; -- returns same result as (jb).a
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+select (jb)[1].a from test_jsonb_dot_notation; -- returns NULL
+ a 
+---
+ 
+(1 row)
+
+SELECT (jb).b FROM test_jsonb_dot_notation;
+                         b                         
+---------------------------------------------------
+ [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]
+(1 row)
+
+SELECT (jb).c FROM test_jsonb_dot_notation;
+ c 
+---
+ 
+(1 row)
+
+SELECT (jb).a.b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT (jb).a[2].b FROM test_jsonb_dot_notation;
+  b  
+-----
+ "c"
+(1 row)
+
+SELECT (jb).a.x.y FROM test_jsonb_dot_notation;
+   y   
+-------
+ "yyy"
+(1 row)
+
+SELECT (jb).b.x.z FROM test_jsonb_dot_notation;
+   z   
+-------
+ "ZZZ"
+(1 row)
+
+SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
+ c 
+---
+ 
+(1 row)
+
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t; -- fails
+ERROR:  jsonb subscript does not support slices
+SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
+ERROR:  type jsonb is not composite
+-- assignment is not supported
+UPDATE test_jsonb_dot_notation SET jb.a = '1';
+ERROR:  cannot assign to field "a" of column "jb" because its type jsonb is not a composite type
+LINE 1: UPDATE test_jsonb_dot_notation SET jb.a = '1';
+                                           ^
+UPDATE test_jsonb_dot_notation SET (jb).a = '1';
+ERROR:  syntax error at or near "."
+LINE 1: UPDATE test_jsonb_dot_notation SET (jb).a = '1';
+                                               ^
+-- explains should work
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+                  QUERY PLAN                  
+----------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation t
+   Output: (jb).a
+(2 rows)
+
+SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb).a[1]
+(2 rows)
+
+SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+ a 
+---
+ 2
+(1 row)
+
+-- views should work
+CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT (jb).a[3].x.y AS y
+   FROM test_jsonb_dot_notation
+-- mixed syntax
+DROP VIEW test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb['a'::text]).b
+(2 rows)
+
+SELECT (jb)['a'].b FROM test_jsonb_dot_notation; -- returns an array due to lax mode
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT (jb['a'::text]).b AS b
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: ((jb).a)['b'::text]
+(2 rows)
+
+SELECT (jb).a['b'] FROM test_jsonb_dot_notation; -- returns NULL because ['b'] looks for strict match in an object
+ a 
+---
+ 
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT ((jb).a)['b'::text] AS a
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+ a 
+---
+ 
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: ((jb).b.x)['z'::text]
+(2 rows)
+
+SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation; -- warnings
+   x   
+-------
+ "ZZZ"
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT ((jb).b.x)['z'::text] AS x
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+   x   
+-------
+ "ZZZ"
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: ((jb['b'::text]).x)['z'::text]
+(2 rows)
+
+SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;  -- warnings
+   x   
+-------
+ "ZZZ"
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT ((jb['b'::text]).x)['z'::text] AS x
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+   x   
+-------
+ "ZZZ"
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (((jb).b)['x'::text]).z
+(2 rows)
+
+SELECT (jb).b['x'].z FROM test_jsonb_dot_notation; -- returns NULL
+ z 
+---
+ 
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT (((jb).b)['x'::text]).z AS z
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+ z 
+---
+ 
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: ((jb).b)['x'::text]['z'::text]
+(2 rows)
+
+SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation; -- returns NULL
+ b 
+---
+ 
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT ((jb).b)['x'::text]['z'::text] AS b
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+ b 
+---
+ 
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb['b'::text]['x'::text]).z
+(2 rows)
+
+SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation; -- returns NULL
+ z 
+---
+ 
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT (jb['b'::text]['x'::text]).z AS z
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+ z 
+---
+ 
 (1 row)
 
+DROP VIEW public.test_jsonb_dot_notation_v1;
+-- clean up
+DROP TABLE test_jsonb_dot_notation;
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 970ed1cffca..cb0c398d65b 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1304,30 +1304,49 @@ select jsonb_insert('{"a": {"b": "value"}}', '{a, b}', '"new_value"', true);
 
 -- jsonb subscript
 select ('123'::jsonb)['a'];
+select ('123'::jsonb).a;
 select ('123'::jsonb)[0];
 select ('123'::jsonb)[NULL];
+select ('123'::jsonb).NULL;
 select ('{"a": 1}'::jsonb)['a'];
+select ('{"a": 1}'::jsonb).a;
 select ('{"a": 1}'::jsonb)[0];
 select ('{"a": 1}'::jsonb)['not_exist'];
+select ('{"a": 1}'::jsonb)."not_exist";
 select ('{"a": 1}'::jsonb)[NULL];
 select ('[1, "2", null]'::jsonb)['a'];
+select ('[1, "2", null]'::jsonb).a;
 select ('[1, "2", null]'::jsonb)[0];
 select ('[1, "2", null]'::jsonb)['1'];
+select ('[1, "2", null]'::jsonb)."1";
 select ('[1, "2", null]'::jsonb)[1.0];
 select ('[1, "2", null]'::jsonb)[2];
 select ('[1, "2", null]'::jsonb)[3];
 select ('[1, "2", null]'::jsonb)[-2];
 select ('[1, "2", null]'::jsonb)[1]['a'];
+select ('[1, "2", null]'::jsonb)[1].a;
 select ('[1, "2", null]'::jsonb)[1][0];
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['b'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).b;
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d;
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'][1];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d[1];
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d']['a'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['a'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d.a;
+select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d['a'][0];
+select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d.a[0];
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1;
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2;
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']['a3'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2.a3;
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'];
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1;
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'][2];
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1[2];
 
 -- slices are not supported
 select ('{"a": 1}'::jsonb)['a':'b'];
@@ -1608,3 +1627,98 @@ select ('true'::jsonb)::bool;
 select ('true'::jsonb).bool;
 select ('{"text": "hello"}'::jsonb)::text;
 select ('{"text": "hello"}'::jsonb).text;
+
+-- dot notation
+CREATE TABLE test_jsonb_dot_notation AS
+SELECT '{"a": [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], "b": [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]}'::jsonb jb;
+
+SELECT (jb).a FROM test_jsonb_dot_notation;
+SELECT (jb)."a" FROM test_jsonb_dot_notation; -- double quote should work
+SELECT (jb).'a' FROM test_jsonb_dot_notation; -- single quote should not work
+select (jb)[0].a from test_jsonb_dot_notation; -- returns same result as (jb).a
+select (jb)[1].a from test_jsonb_dot_notation; -- returns NULL
+SELECT (jb).b FROM test_jsonb_dot_notation;
+SELECT (jb).c FROM test_jsonb_dot_notation;
+SELECT (jb).a.b FROM test_jsonb_dot_notation;
+SELECT (jb).a[2].b FROM test_jsonb_dot_notation;
+SELECT (jb).a.x.y FROM test_jsonb_dot_notation;
+SELECT (jb).b.x.z FROM test_jsonb_dot_notation;
+SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t; -- fails
+SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
+
+-- assignment is not supported
+UPDATE test_jsonb_dot_notation SET jb.a = '1';
+UPDATE test_jsonb_dot_notation SET (jb).a = '1';
+
+-- explains should work
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+
+-- views should work
+CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+
+-- mixed syntax
+DROP VIEW test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
+SELECT (jb)['a'].b FROM test_jsonb_dot_notation; -- returns an array due to lax mode
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+SELECT (jb).a['b'] FROM test_jsonb_dot_notation; -- returns NULL because ['b'] looks for strict match in an object
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation; -- warnings
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;  -- warnings
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+SELECT (jb).b['x'].z FROM test_jsonb_dot_notation; -- returns NULL
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation; -- returns NULL
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
+SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation; -- returns NULL
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+-- clean up
+DROP TABLE test_jsonb_dot_notation;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index e90af5b2ad3..23e38c163c5 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -805,6 +805,7 @@ FdwRoutine
 FetchDirection
 FetchDirectionKeywords
 FetchStmt
+FieldAccessorExpr
 FieldSelect
 FieldStore
 File
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v21-0001-Add-test-coverage-for-indirection-transformation.patch (6.9K, ../../CAK98qZ3fATHtD8n471AjniZ2KHFE59MfJzjo+P0SR3_oUT8Jbw@mail.gmail.com/5-v21-0001-Add-test-coverage-for-indirection-transformation.patch)
  download | inline diff:
From b5276d94b532a66159c4b1d6c28ce4556ca89507 Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Thu, 18 Sep 2025 15:54:24 -0700
Subject: [PATCH v21 1/5] Add test coverage for indirection transformation

These tests cover nested arrays of composite data types,
single-argument functions, and casting using dot-notation, providing a
baseline for future enhancements to jsonb dot-notation support.
---
 src/test/regress/expected/arrays.out | 26 ++++++--
 src/test/regress/expected/jsonb.out  | 88 ++++++++++++++++++++++++++++
 src/test/regress/sql/arrays.sql      |  7 ++-
 src/test/regress/sql/jsonb.sql       | 18 ++++++
 4 files changed, 132 insertions(+), 7 deletions(-)

diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 69ea2cf5ad8..e1ab6dc278a 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -1782,17 +1782,17 @@ SELECT max(f1), min(f1), max(f2), min(f2), max(f3), min(f3) FROM arraggtest;
 (1 row)
 
 -- A few simple tests for arrays of composite types
-create type comptype as (f1 int, f2 text);
+create type comptype as (f1 int, f2 text, f3 int[]);
 create table comptable (c1 comptype, c2 comptype[]);
 -- XXX would like to not have to specify row() construct types here ...
 insert into comptable
-  values (row(1,'foo'), array[row(2,'bar')::comptype, row(3,'baz')::comptype]);
+  values (row(1,'foo',array[10,20]), array[row(2,'bar',array[30,40])::comptype, row(3,'baz',array[50,60])::comptype]);
 -- check that implicitly named array type _comptype isn't a problem
 create type _comptype as enum('fooey');
 select * from comptable;
-   c1    |          c2           
----------+-----------------------
- (1,foo) | {"(2,bar)","(3,baz)"}
+        c1         |                      c2                       
+-------------------+-----------------------------------------------
+ (1,foo,"{10,20}") | {"(2,bar,\"{30,40}\")","(3,baz,\"{50,60}\")"}
 (1 row)
 
 select c2[2].f2 from comptable;
@@ -1801,6 +1801,22 @@ select c2[2].f2 from comptable;
  baz
 (1 row)
 
+select c2[2].f3 from comptable;
+   f3    
+---------
+ {50,60}
+(1 row)
+
+select c2[2].f3[1:2] from comptable;
+   f3    
+---------
+ {50,60}
+(1 row)
+
+select c2[1:2].f3[1:2] from comptable;
+ERROR:  column notation .f3 applied to type comptype[], which is not a composite type
+LINE 1: select c2[1:2].f3[1:2] from comptable;
+               ^
 drop type _comptype;
 drop table comptable;
 drop type comptype;
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5a1eb18aba2..fb47c8a893a 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5831,3 +5831,91 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
  12345
 (1 row)
 
+-- single argument jsonb functions as jsonb_function(jsonb) and jsonb.jsonb_function
+select jsonb_typeof('{"a":1}'::jsonb);
+ jsonb_typeof 
+--------------
+ object
+(1 row)
+
+select ('{"a":1}'::jsonb).jsonb_typeof;
+ jsonb_typeof 
+--------------
+ object
+(1 row)
+
+select jsonb_array_length('["a", "b", "c"]'::jsonb);
+ jsonb_array_length 
+--------------------
+                  3
+(1 row)
+
+select ('["a", "b", "c"]'::jsonb).jsonb_array_length;
+ jsonb_array_length 
+--------------------
+                  3
+(1 row)
+
+select jsonb_object_keys('{"a":1, "b":2}'::jsonb);
+ jsonb_object_keys 
+-------------------
+ a
+ b
+(2 rows)
+
+select ('{"a":1, "b":2}'::jsonb).jsonb_object_keys;
+ jsonb_object_keys 
+-------------------
+ a
+ b
+(2 rows)
+
+-- cast jsonb to other types as (jsonb)::type and (jsonb).type
+select ('123.45'::jsonb)::numeric;
+ numeric 
+---------
+  123.45
+(1 row)
+
+select ('123.45'::jsonb).numeric;
+ numeric 
+---------
+  123.45
+(1 row)
+
+select ('[{"name": "alice"}, {"name": "bob"}]'::jsonb)::name;
+                 name                 
+--------------------------------------
+ [{"name": "alice"}, {"name": "bob"}]
+(1 row)
+
+select ('[{"name": "alice"}, {"name": "bob"}]'::jsonb).name;
+                 name                 
+--------------------------------------
+ [{"name": "alice"}, {"name": "bob"}]
+(1 row)
+
+select ('true'::jsonb)::bool;
+ bool 
+------
+ t
+(1 row)
+
+select ('true'::jsonb).bool;
+ bool 
+------
+ t
+(1 row)
+
+select ('{"text": "hello"}'::jsonb)::text;
+       text        
+-------------------
+ {"text": "hello"}
+(1 row)
+
+select ('{"text": "hello"}'::jsonb).text;
+       text        
+-------------------
+ {"text": "hello"}
+(1 row)
+
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 47d62c1d38d..450389831a0 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -555,19 +555,22 @@ SELECT max(f1), min(f1), max(f2), min(f2), max(f3), min(f3) FROM arraggtest;
 
 -- A few simple tests for arrays of composite types
 
-create type comptype as (f1 int, f2 text);
+create type comptype as (f1 int, f2 text, f3 int[]);
 
 create table comptable (c1 comptype, c2 comptype[]);
 
 -- XXX would like to not have to specify row() construct types here ...
 insert into comptable
-  values (row(1,'foo'), array[row(2,'bar')::comptype, row(3,'baz')::comptype]);
+  values (row(1,'foo',array[10,20]), array[row(2,'bar',array[30,40])::comptype, row(3,'baz',array[50,60])::comptype]);
 
 -- check that implicitly named array type _comptype isn't a problem
 create type _comptype as enum('fooey');
 
 select * from comptable;
 select c2[2].f2 from comptable;
+select c2[2].f3 from comptable;
+select c2[2].f3[1:2] from comptable;
+select c2[1:2].f3[1:2] from comptable;
 
 drop type _comptype;
 drop table comptable;
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 57c11acddfe..970ed1cffca 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1590,3 +1590,21 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::float8;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int2;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int4;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
+
+-- single argument jsonb functions as jsonb_function(jsonb) and jsonb.jsonb_function
+select jsonb_typeof('{"a":1}'::jsonb);
+select ('{"a":1}'::jsonb).jsonb_typeof;
+select jsonb_array_length('["a", "b", "c"]'::jsonb);
+select ('["a", "b", "c"]'::jsonb).jsonb_array_length;
+select jsonb_object_keys('{"a":1, "b":2}'::jsonb);
+select ('{"a":1, "b":2}'::jsonb).jsonb_object_keys;
+
+-- cast jsonb to other types as (jsonb)::type and (jsonb).type
+select ('123.45'::jsonb)::numeric;
+select ('123.45'::jsonb).numeric;
+select ('[{"name": "alice"}, {"name": "bob"}]'::jsonb)::name;
+select ('[{"name": "alice"}, {"name": "bob"}]'::jsonb).name;
+select ('true'::jsonb)::bool;
+select ('true'::jsonb).bool;
+select ('{"text": "hello"}'::jsonb)::text;
+select ('{"text": "hello"}'::jsonb).text;
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v21-0002-Add-an-alternative-transform-function-in-Subscri.patch (15.6K, ../../CAK98qZ3fATHtD8n471AjniZ2KHFE59MfJzjo+P0SR3_oUT8Jbw@mail.gmail.com/6-v21-0002-Add-an-alternative-transform-function-in-Subscri.patch)
  download | inline diff:
From f4da17238f6450114eb17b8cd24a7edb09d49360 Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Thu, 18 Sep 2025 16:34:47 -0700
Subject: [PATCH v21 2/5] Add an alternative transform function in
 SubscriptRoutines

Add a transform_partial() function pointer to enable processing a
prefix of indirection lists. Data types that support subscripting can
opt to use the transform() function that transforms the full input
indirection list (e.g., arrays, hstore), or can opt to use the
transform_partial() function to be more flexible on indirection node
types, and make best effort in transforming only a prefix of the
indirection list, letting the caller handle the remaining
indirections.

This allows transform functions to accept dot notation indirection as
input, preparing for future JSONB dot notation support.
---
 contrib/hstore/hstore_subs.c      |  1 +
 src/backend/parser/parse_expr.c   | 76 +++++++++++++++----------
 src/backend/parser/parse_node.c   | 92 +++++++++++++++++++++++--------
 src/backend/parser/parse_target.c |  6 +-
 src/backend/utils/adt/arraysubs.c |  2 +
 src/backend/utils/adt/jsonbsubs.c | 19 +++++--
 src/include/nodes/subscripting.h  | 24 +++++++-
 src/include/parser/parse_node.h   |  3 +-
 8 files changed, 163 insertions(+), 60 deletions(-)

diff --git a/contrib/hstore/hstore_subs.c b/contrib/hstore/hstore_subs.c
index 3d03f66fa0d..d678dc56f86 100644
--- a/contrib/hstore/hstore_subs.c
+++ b/contrib/hstore/hstore_subs.c
@@ -287,6 +287,7 @@ hstore_subscript_handler(PG_FUNCTION_ARGS)
 {
 	static const SubscriptRoutines sbsroutines = {
 		.transform = hstore_subscript_transform,
+		.transform_partial = NULL,
 		.exec_setup = hstore_exec_setup,
 		.fetch_strict = true,	/* fetch returns NULL for NULL inputs */
 		.fetch_leakproof = true,	/* fetch returns NULL for bad subscript */
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index e1979a80c19..95ce330e506 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -436,44 +436,65 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 {
 	Node	   *last_srf = pstate->p_last_srf;
 	Node	   *result = transformExprRecurse(pstate, ind->arg);
-	List	   *subscripts = NIL;
+	List	   *indirections = NIL;
 	int			location = exprLocation(result);
 	ListCell   *i;
 
 	/*
-	 * We have to split any field-selection operations apart from
-	 * subscripting.  Adjacent A_Indices nodes have to be treated as a single
+	 * Combine field names and subscripts into a single indirection list, as
+	 * some subscripting containers, such as jsonb, support field access using
+	 * dot notation. Adjacent A_Indices nodes have to be treated as a single
 	 * multidimensional subscript operation.
 	 */
 	foreach(i, ind->indirection)
 	{
 		Node	   *n = lfirst(i);
 
-		if (IsA(n, A_Indices))
-			subscripts = lappend(subscripts, n);
-		else if (IsA(n, A_Star))
+		if (IsA(n, A_Indices) || IsA(n, String))
+			indirections = lappend(indirections, n);
+		else
 		{
+			Assert(IsA(n, A_Star));
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("row expansion via \"*\" is not supported here"),
 					 parser_errposition(pstate, location)));
 		}
-		else
+	}
+
+	while (indirections)
+	{
+		/* try processing container subscripts first */
+		int			transformed_count = 0;
+		Node	   *newresult = (Node *)
+			transformContainerSubscripts(pstate,
+										 result,
+										 exprType(result),
+										 exprTypmod(result),
+										 indirections,
+										 false,
+										 &transformed_count);
+
+		if (!newresult)
 		{
-			Node	   *newresult;
+			/*
+			 * generic subscripting failed; falling back to field selection
+			 * for a composite type, or a single-argument function.
+			 */
+			Node	   *n;
+
+			Assert(indirections);
 
-			Assert(IsA(n, String));
+			n = linitial(indirections);
 
-			/* process subscripts before this field selection */
-			if (subscripts)
-				result = (Node *) transformContainerSubscripts(pstate,
-															   result,
-															   exprType(result),
-															   exprTypmod(result),
-															   subscripts,
-															   false);
-			subscripts = NIL;
+			if (!IsA(n, String))
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("cannot subscript type %s because it does not support subscripting",
+								format_type_be(exprType(result))),
+						 parser_errposition(pstate, exprLocation(result))));
 
+			/* try to parse function or field selection */
 			newresult = ParseFuncOrColumn(pstate,
 										  list_make1(n),
 										  list_make1(result),
@@ -481,19 +502,18 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 										  NULL,
 										  false,
 										  location);
-			if (newresult == NULL)
+
+			if (!newresult)
 				unknown_attribute(pstate, result, strVal(n), location);
-			result = newresult;
+			else
+				transformed_count = 1;
 		}
+
+		/* remove the processed indirections */
+		indirections = list_delete_first_n(indirections, transformed_count);
+
+		result = newresult;
 	}
-	/* process trailing subscripts, if any */
-	if (subscripts)
-		result = (Node *) transformContainerSubscripts(pstate,
-													   result,
-													   exprType(result),
-													   exprTypmod(result),
-													   subscripts,
-													   false);
 
 	return result;
 }
diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c
index 203b7a32178..71ff31a9638 100644
--- a/src/backend/parser/parse_node.c
+++ b/src/backend/parser/parse_node.c
@@ -238,20 +238,22 @@ transformContainerType(Oid *containerType, int32 *containerTypmod)
  * containerTypMod	typmod for the container
  * indirection		Untransformed list of subscripts (must not be NIL)
  * isAssignment		True if this will become a container assignment.
- */
+ * nSubscripts		Output parameter for number of transformed subscripts.
+*/
 SubscriptingRef *
 transformContainerSubscripts(ParseState *pstate,
 							 Node *containerBase,
 							 Oid containerType,
 							 int32 containerTypMod,
 							 List *indirection,
-							 bool isAssignment)
+							 bool isAssignment,
+							 int *nSubscripts)
 {
 	SubscriptingRef *sbsref;
 	const SubscriptRoutines *sbsroutines;
 	Oid			elementType;
-	bool		isSlice = false;
-	ListCell   *idx;
+
+	*nSubscripts = 0;
 
 	/*
 	 * Determine the actual container type, smashing any domain.  In the
@@ -267,28 +269,15 @@ transformContainerSubscripts(ParseState *pstate,
 	 */
 	sbsroutines = getSubscriptingRoutines(containerType, &elementType);
 	if (!sbsroutines)
+	{
+		if (!isAssignment)
+			return NULL;
+
 		ereport(ERROR,
 				(errcode(ERRCODE_DATATYPE_MISMATCH),
 				 errmsg("cannot subscript type %s because it does not support subscripting",
 						format_type_be(containerType)),
 				 parser_errposition(pstate, exprLocation(containerBase))));
-
-	/*
-	 * Detect whether any of the indirection items are slice specifiers.
-	 *
-	 * A list containing only simple subscripts refers to a single container
-	 * element.  If any of the items are slice specifiers (lower:upper), then
-	 * the subscript expression means a container slice operation.
-	 */
-	foreach(idx, indirection)
-	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
-
-		if (ai->is_slice)
-		{
-			isSlice = true;
-			break;
-		}
 	}
 
 	/*
@@ -309,8 +298,65 @@ transformContainerSubscripts(ParseState *pstate,
 	 * Call the container-type-specific logic to transform the subscripts and
 	 * determine the subscripting result type.
 	 */
-	sbsroutines->transform(sbsref, indirection, pstate,
-						   isSlice, isAssignment);
+	if (sbsroutines->transform_partial != NULL)
+	{
+		/*
+		 * If the container type provides a partial transform function, use it
+		 * here. This function can accept any node types in the indirection
+		 * list as input, and is responsible for identifying and transforming
+		 * as many leading elements as it can handle, which may be only a
+		 * prefix of the indirection list. For example, it might process
+		 * A_Indices nodes, String nodes (for jsonb dot-notation), or other
+		 * node types, depending on the container's requirements. It returns
+		 * the number of elements it transformed.
+		 */
+		*nSubscripts = sbsroutines->transform_partial(sbsref, indirection, pstate, isAssignment);
+	}
+	else
+	{
+		/*
+		 * If there is no partial transform function, use the full transform
+		 * function, which only accepts bracket subscripts (A_Indices nodes).
+		 * We pre-collect the leading A_Indices nodes from the indirection
+		 * list, then call the transform function to process this prefix of
+		 * subscripts.
+		 */
+		List	   *subscriptlist = NIL;
+		ListCell   *lc;
+		bool		isSlice = false;
+
+		/* Collect leading A_Indices subscripts */
+		foreach(lc, indirection)
+		{
+			Node	   *n = lfirst(lc);
+
+			if (IsA(n, A_Indices))
+			{
+				A_Indices  *ai = (A_Indices *) n;
+
+				subscriptlist = lappend(subscriptlist, n);
+				if (ai->is_slice)
+					isSlice = true;
+			}
+			else
+				break;
+		}
+
+		if (subscriptlist)
+			sbsroutines->transform(sbsref, subscriptlist, pstate, isSlice, isAssignment);
+
+		*nSubscripts = list_length(subscriptlist);
+	}
+
+	if (*nSubscripts == 0)
+	{
+		/* Fallback to field selection in caller */
+		if (!isAssignment)
+			return NULL;
+
+		/* This should not happen with well-behaved transform functions */
+		elog(ERROR, "subscripting transform function failed to consume any indirection elements");
+	}
 
 	/*
 	 * Verify we got a valid type (this defends, for example, against someone
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 905c975d83b..30cf77338ae 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -922,6 +922,7 @@ transformAssignmentSubscripts(ParseState *pstate,
 	Oid			typeNeeded;
 	int32		typmodNeeded;
 	Oid			collationNeeded;
+	int 		nSubscripts = 0;
 
 	Assert(subscripts != NIL);
 
@@ -936,7 +937,10 @@ transformAssignmentSubscripts(ParseState *pstate,
 										  containerType,
 										  containerTypMod,
 										  subscripts,
-										  true);
+										  true,
+										  &nSubscripts);
+
+	Assert(nSubscripts == list_length(subscripts));
 
 	typeNeeded = sbsref->refrestype;
 	typmodNeeded = sbsref->reftypmod;
diff --git a/src/backend/utils/adt/arraysubs.c b/src/backend/utils/adt/arraysubs.c
index 2940fb8e8d7..0049907b942 100644
--- a/src/backend/utils/adt/arraysubs.c
+++ b/src/backend/utils/adt/arraysubs.c
@@ -541,6 +541,7 @@ array_subscript_handler(PG_FUNCTION_ARGS)
 {
 	static const SubscriptRoutines sbsroutines = {
 		.transform = array_subscript_transform,
+		.transform_partial = NULL,
 		.exec_setup = array_exec_setup,
 		.fetch_strict = true,	/* fetch returns NULL for NULL inputs */
 		.fetch_leakproof = true,	/* fetch returns NULL for bad subscript */
@@ -568,6 +569,7 @@ raw_array_subscript_handler(PG_FUNCTION_ARGS)
 {
 	static const SubscriptRoutines sbsroutines = {
 		.transform = array_subscript_transform,
+		.transform_partial = NULL,
 		.exec_setup = array_exec_setup,
 		.fetch_strict = true,	/* fetch returns NULL for NULL inputs */
 		.fetch_leakproof = true,	/* fetch returns NULL for bad subscript */
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index e8626d3b4fc..dfe7da55e8e 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -39,11 +39,10 @@ typedef struct JsonbSubWorkspace
  * Transform the subscript expressions, coerce them to text,
  * and determine the result type of the SubscriptingRef node.
  */
-static void
+static int
 jsonb_subscript_transform(SubscriptingRef *sbsref,
 						  List *indirection,
 						  ParseState *pstate,
-						  bool isSlice,
 						  bool isAssignment)
 {
 	List	   *upperIndexpr = NIL;
@@ -55,10 +54,15 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	 */
 	foreach(idx, indirection)
 	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
+		A_Indices  *ai;
 		Node	   *subExpr;
 
-		if (isSlice)
+		if (!IsA(lfirst(idx), A_Indices))
+			break;
+
+		ai = lfirst_node(A_Indices, idx);
+
+		if (ai->is_slice)
 		{
 			Node	   *expr = ai->uidx ? ai->uidx : ai->lidx;
 
@@ -142,7 +146,7 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 			 * Slice with omitted upper bound. Should not happen as we already
 			 * errored out on slice earlier, but handle this just in case.
 			 */
-			Assert(isSlice && ai->is_slice);
+			Assert(ai->is_slice);
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
 					 errmsg("jsonb subscript does not support slices"),
@@ -159,6 +163,8 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	/* Determine the result type of the subscripting operation; always jsonb */
 	sbsref->refrestype = JSONBOID;
 	sbsref->reftypmod = -1;
+
+	return list_length(upperIndexpr);
 }
 
 /*
@@ -402,7 +408,8 @@ Datum
 jsonb_subscript_handler(PG_FUNCTION_ARGS)
 {
 	static const SubscriptRoutines sbsroutines = {
-		.transform = jsonb_subscript_transform,
+		.transform = NULL,		/* jsonb uses partial transform instead */
+		.transform_partial = jsonb_subscript_transform,
 		.exec_setup = jsonb_exec_setup,
 		.fetch_strict = true,	/* fetch returns NULL for NULL inputs */
 		.fetch_leakproof = true,	/* fetch returns NULL for bad subscript */
diff --git a/src/include/nodes/subscripting.h b/src/include/nodes/subscripting.h
index e991f4bf826..3b819dc8d65 100644
--- a/src/include/nodes/subscripting.h
+++ b/src/include/nodes/subscripting.h
@@ -98,6 +98,25 @@ typedef void (*SubscriptTransform) (SubscriptingRef *sbsref,
 									bool isSlice,
 									bool isAssignment);
 
+/*
+ * SubscriptTransformPartial is an alternative to SubscriptTransform for
+ * container types that can accept non-A_Indices indirection as input
+ * (e.g., JSONB accepts dot-notation (String node) for field access).
+
+ * It may transform a prefix of the indirection list and leave the rest
+ * unprocessed. It returns the number of indirections it transformed.
+ * The caller will then remove that many items from the head of the
+ * list, and handle the
+ * remaining indirections differently or to raise an error as needed.
+ *
+ * If transform_partial is NULL, the complete transform function is used,
+ * which accepts only A_Indices (bracket) nodes.
+ */
+typedef int (*SubscriptTransformPartial) (SubscriptingRef *sbsref,
+										  List *indirection,
+										  ParseState *pstate,
+										  bool isAssignment);
+
 /*
  * The exec_setup method is called during executor-startup compilation of a
  * SubscriptingRef node in an expression.  It must fill *methods with pointers
@@ -157,7 +176,10 @@ typedef void (*SubscriptExecSetup) (const SubscriptingRef *sbsref,
 /* Struct returned by the SQL-visible subscript handler function */
 typedef struct SubscriptRoutines
 {
-	SubscriptTransform transform;	/* parse analysis function */
+	SubscriptTransform transform;	/* parse analysis function, or NULL */
+	SubscriptTransformPartial transform_partial;	/* alternative parse
+													 * analysis function, or
+													 * NULL */
 	SubscriptExecSetup exec_setup;	/* expression compilation function */
 	bool		fetch_strict;	/* is fetch SubscriptRef strict? */
 	bool		fetch_leakproof;	/* is fetch SubscriptRef leakproof? */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index f7d07c84542..80f486acff0 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -362,7 +362,8 @@ extern SubscriptingRef *transformContainerSubscripts(ParseState *pstate,
 													 Oid containerType,
 													 int32 containerTypMod,
 													 List *indirection,
-													 bool isAssignment);
+													 bool isAssignment,
+													 int *consumed_count);
 extern Const *make_const(ParseState *pstate, A_Const *aconst);
 
 #endif							/* PARSE_NODE_H */
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v21-0003-Export-jsonPathFromParseResult.patch (2.8K, ../../CAK98qZ3fATHtD8n471AjniZ2KHFE59MfJzjo+P0SR3_oUT8Jbw@mail.gmail.com/7-v21-0003-Export-jsonPathFromParseResult.patch)
  download | inline diff:
From d8212221b7d86baa0b5a46c3859353c8f7e9d3ec Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v21 3/5] Export jsonPathFromParseResult()

This is a preparation step for a future commit that will reuse the
aforementioned function.

Authored-by: Nikita Glukhov <[email protected]>
Reviewed-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Jian He <[email protected]>
Reviewed-by: Chao Li <[email protected]>
---
 src/backend/utils/adt/jsonpath.c | 19 +++++++++++++++----
 src/include/utils/jsonpath.h     |  4 ++++
 2 files changed, 19 insertions(+), 4 deletions(-)

diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index 762f7e8a09d..c83774b2a16 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -166,15 +166,13 @@ jsonpath_send(PG_FUNCTION_ARGS)
  * Converts C-string to a jsonpath value.
  *
  * Uses jsonpath parser to turn string into an AST, then
- * flattenJsonPathParseItem() does second pass turning AST into binary
+ * jsonPathFromParseResult() does second pass turning AST into binary
  * representation of jsonpath.
  */
 static Datum
 jsonPathFromCstring(char *in, int len, struct Node *escontext)
 {
 	JsonPathParseResult *jsonpath = parsejsonpath(in, len, escontext);
-	JsonPath   *res;
-	StringInfoData buf;
 
 	if (SOFT_ERROR_OCCURRED(escontext))
 		return (Datum) 0;
@@ -185,8 +183,21 @@ jsonPathFromCstring(char *in, int len, struct Node *escontext)
 				 errmsg("invalid input syntax for type %s: \"%s\"", "jsonpath",
 						in)));
 
+	return jsonPathFromParseResult(jsonpath, 4 * len, escontext);
+}
+
+/*
+ * Converts jsonpath Abstract Syntax Tree (AST) into jsonpath value in binary.
+ */
+Datum
+jsonPathFromParseResult(const JsonPathParseResult *jsonpath, int estimated_len,
+						struct Node *escontext)
+{
+	JsonPath   *res;
+	StringInfoData buf;
+
 	initStringInfo(&buf);
-	enlargeStringInfo(&buf, 4 * len /* estimation */ );
+	enlargeStringInfo(&buf, estimated_len);
 
 	appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
 
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index 23a76d233e9..0958bc22bb6 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -281,6 +281,10 @@ extern JsonPathParseResult *parsejsonpath(const char *str, int len,
 extern bool jspConvertRegexFlags(uint32 xflags, int *result,
 								 struct Node *escontext);
 
+extern Datum jsonPathFromParseResult(const JsonPathParseResult *jsonpath,
+									 int estimated_len,
+									 struct Node *escontext);
+
 /*
  * Struct for details about external variables passed into jsonpath executor
  */
-- 
2.39.5 (Apple Git-154)



^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-06-24 13:29                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-07-09 08:01                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-07-10 08:53                                 ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-21 04:53                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-22 08:19                                     ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-22 19:33                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-25 08:09                                         ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-26 03:52                                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-29 07:22                                             ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-02 02:59                                               ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-02 03:32                                                 ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-03 02:20                                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-09-03 06:56                                                     ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-09 23:53                                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-09-10 04:03                                                         ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-10 23:56                                                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-09-15 18:32                                                             ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-09-19 20:40                                                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-09-22 03:32                                                                 ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-22 17:31                                                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
@ 2025-09-23 05:48                                                                     ` Chao Li <[email protected]>
  2025-09-24 01:05                                                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  0 siblings, 1 reply; 67+ messages in thread

From: Chao Li @ 2025-09-23 05:48 UTC (permalink / raw)
  To: Alexandra Wang <[email protected]>; +Cc: pgsql-hackers; Nikita Glukhov <[email protected]>; jian he <[email protected]>; Nikita Malakhov <[email protected]>; Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; David E. Wheeler <[email protected]>

The new approach of introducing “transform_partial” looks like a better solution, which leads to less code change to hstore_subs and arraysubs. However, when I tested the v21, I encountered errors when combine composite type, array and jsonb together.

Prepare test data:
```
drop table if exists people;
drop type if exists person;
CREATE TYPE person AS (
    name text,
    size int[],
    meta jsonb[]
);

CREATE TABLE people (
    p person
);

INSERT INTO people VALUES (ROW('Alice', array[10, 20], array['{"a": 30}'::jsonb, '{"a": 40}'::jsonb]));
```

Then run the test:
```
# old jsonb accessor works to extract a jsonb field from an array item of a composite field
evantest=# select (p).meta[1]->'a' from people;
 ?column?
----------
 30
(1 row)

# dot notation also works
evantest=# select (p).meta[1].a from people;
 a
----
 30
(1 row)

# but index accessor doesn’t work
evantest=# select (p).meta[1]['a'] from people;
ERROR:  invalid input syntax for type integer: "a"
LINE 1: select (p).meta[1]['a'] from people;
                           ^
```

Other than that, I got a few new comments:

> On Sep 23, 2025, at 01:31, Alexandra Wang <[email protected]> wrote:
> 
> 
> There were trailing whitespaces in the documentation I added, I’ve fixed them now.
> 
> <v21-0004-Extract-coerce_jsonpath_subscript.patch><v21-0005-Implement-read-only-dot-notation-for-jsonb.patch><v21-0001-Add-test-coverage-for-indirection-transformation.patch><v21-0002-Add-an-alternative-transform-function-in-Subscri.patch><v21-0003-Export-jsonPathFromParseResult.patch>


1 - 0001 - overall looks good

2 - 0002
```
+		/* Collect leading A_Indices subscripts */
+		foreach(lc, indirection)
+		{
+			Node	   *n = lfirst(lc);
+
+			if (IsA(n, A_Indices))
+			{
+				A_Indices  *ai = (A_Indices *) n;
+
+				subscriptlist = lappend(subscriptlist, n);
+				if (ai->is_slice)
+					isSlice = true;
+			}
+			else
+				break;
```

We can break after “isSlice=true”.

3 - 0002
```
+ * list, and handle the
+ * remaining indirections differently or to raise an error as needed.
```

Not well formatted,  “remaining” can go to the previous line. 

4 - 0002
```
+	if (sbsroutines->transform_partial != NULL)
+	{
```

Do we want to assert that one of transform and transform_partial should not be NULL before “if"?

5 - 0002
```
+		/*
+		 * If there is no partial transform function, use the full transform
+		 * function, which only accepts bracket subscripts (A_Indices nodes).
+		 * We pre-collect the leading A_Indices nodes from the indirection
```

“If there is no partial transform function” sounds redundant, I think we can just go with “Full transform function only accepts …”.

6 - 0002
```
+		/* This should not happen with well-behaved transform functions */
+		elog(ERROR, "subscripting transform function failed to consume any indirection elements”);
```

I don’t see an existing error message uses “indirection” and “transform”. This error message looks more like a log message rather than a message to show to end users.

7 - 0002
```
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -39,11 +39,10 @@ typedef struct JsonbSubWorkspace
  * Transform the subscript expressions, coerce them to text,
  * and determine the result type of the SubscriptingRef node.
  */
-static void
+static int
 jsonb_subscript_transform(SubscriptingRef *sbsref,
 						  List *indirection,
 						  ParseState *pstate,
-						  bool isSlice,
 						  bool isAssignment)
```

As return type is changed, function comment should be updated accordingly.

8 - 0005 - jsonb.sql

As we discussed earlier, now 

select ('{"a": 1}'::jsonb)[0]['a']; 

and 

select ('{"a": 1}'::jsonb)[0].a;


Will return different results. Maybe that part needs more discussion, but we at least don’t want random behavior. So I would suggest add the two cases into the test script, so that other reviewers may easily notice that, thus gets more inputs from more people.

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/






^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-06-24 13:29                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-07-09 08:01                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-07-10 08:53                                 ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-21 04:53                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-22 08:19                                     ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-22 19:33                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-25 08:09                                         ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-26 03:52                                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-29 07:22                                             ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-02 02:59                                               ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-02 03:32                                                 ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-03 02:20                                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-09-03 06:56                                                     ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-09 23:53                                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-09-10 04:03                                                         ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-10 23:56                                                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-09-15 18:32                                                             ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-09-19 20:40                                                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-09-22 03:32                                                                 ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-22 17:31                                                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-09-23 05:48                                                                     ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
@ 2025-09-24 01:05                                                                       ` Alexandra Wang <[email protected]>
  2025-09-24 06:37                                                                         ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-12-10 15:11                                                                         ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  0 siblings, 2 replies; 67+ messages in thread

From: Alexandra Wang @ 2025-09-24 01:05 UTC (permalink / raw)
  To: Chao Li <[email protected]>; +Cc: pgsql-hackers; Nikita Glukhov <[email protected]>; jian he <[email protected]>; Nikita Malakhov <[email protected]>; Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; David E. Wheeler <[email protected]>

Hi Chao,

Thanks for reviewing. I'm glad you like the new approach of
introducing "transform_partial". I've attached v22, which addresses
some of your feedback, and I ran pgindent again.

See detailed replies below.

On Mon, Sep 22, 2025 at 10:48 PM Chao Li <[email protected]> wrote:

> The new approach of introducing “transform_partial” looks like a better
> solution, which leads to less code change to hstore_subs and arraysubs.
> However, when I tested the v21, I encountered errors when combine composite
> type, array and jsonb together.
>
> Prepare test data:
> ```
> drop table if exists people;
> drop type if exists person;
> CREATE TYPE person AS (
>     name text,
>     size int[],
>     meta jsonb[]
> );
>
> CREATE TABLE people (
>     p person
> );
>
> INSERT INTO people VALUES (ROW('Alice', array[10, 20], array['{"a":
> 30}'::jsonb, '{"a": 40}'::jsonb]));
> ```
>
> Then run the test:
> ```
> # old jsonb accessor works to extract a jsonb field from an array item of
> a composite field
> evantest=# select (p).meta[1]->'a' from people;
>  ?column?
> ----------
>  30
> (1 row)
>
> # dot notation also works
> evantest=# select (p).meta[1].a from people;
>  a
> ----
>  30
> (1 row)
>
> # but index accessor doesn’t work
> evantest=# select (p).meta[1]['a'] from people;
> ERROR:  invalid input syntax for type integer: "a"
> LINE 1: select (p).meta[1]['a'] from people;
>                            ^
>

This is the expected behavior for array subscripting, and my patch
doesn't change that. I don't think this is a problem. With or without
my patch, you can avoid the ERROR by adding parentheses:

test=# select ((p).meta[1])['a'] from people; meta ------ 30 (1 row)

On Mon, Sep 22, 2025 at 10:48 PM Chao Li <[email protected]> wrote:

> 2 - 0002
> ```
> + /* Collect leading A_Indices subscripts */
> + foreach(lc, indirection)
> + {
> + Node   *n = lfirst(lc);
> +
> + if (IsA(n, A_Indices))
> + {
> + A_Indices  *ai = (A_Indices *) n;
> +
> + subscriptlist = lappend(subscriptlist, n);
> + if (ai->is_slice)
> + isSlice = true;
> + }
> + else
> + break;
> ```
>
> We can break after “isSlice=true”.
>

Why? We still want to get the whole prefix list of A_Indices.

On Mon, Sep 22, 2025 at 10:48 PM Chao Li <[email protected]> wrote:

> 6 - 0002
> ```
> + /* This should not happen with well-behaved transform functions */
> + elog(ERROR, "subscripting transform function failed to consume any
> indirection elements”);
> ```
>
> I don’t see an existing error message uses “indirection” and “transform”.
> This error message looks more like a log message rather than a message to
> show to end users.
>

This is a defensive elog message that should not happen. So it is a
log message for developers. That said, I'm open to suggestions for
better wording.

The rest of your feedback I've made changes accordingly as you suggested.

Best,
Alex


Attachments:

  [application/octet-stream] v22-0005-Implement-read-only-dot-notation-for-jsonb.patch (74.6K, ../../CAK98qZ0R1ufQ-uQq8DxOPnmfacrzxBKy1phbUt8TA6+EDj9+PA@mail.gmail.com/3-v22-0005-Implement-read-only-dot-notation-for-jsonb.patch)
  download | inline diff:
From be9683f89f8fb484112f738b77fbe72bfe625a64 Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v22 5/5] Implement read-only dot notation for jsonb

This patch introduces JSONB member access using dot notation that
aligns with the JSON simplified accessor specified in SQL:2023.

Examples:

-- Setup
create table t(x int, y jsonb);
insert into t select 1, '{"a": 1, "b": 42}'::jsonb;
insert into t select 1, '{"a": 2, "b": {"c": 42}}'::jsonb;
insert into t select 1, '{"a": 3, "b": {"c": "42"}, "d":[11, 12]}'::jsonb;

-- Existing syntax in PostgreSQL that predates the SQL standard:
select (t.y)->'b' from t;
select (t.y)->'b'->'c' from t;
select (t.y)->'d'->0 from t;

-- JSON simplified accessor specified by the SQL standard:
select (t.y).b from t;
select (t.y).b.c from t;
select (t.y).d[0] from t;

The SQL standard states that simplified access is equivalent to:
JSON_QUERY (VEP, 'lax $.JC' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR)

where:
  VEP = <value expression primary>
  JC = <JSON simplified accessor op chain>

For example, the JSON_QUERY equivalents of the above queries are:

select json_query(y, 'lax $.b' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t;
select json_query(y, 'lax $.b.c' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t;
select json_query(y, 'lax $.d[0]' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t;

Implementation details:

This patch extends the existing container subscripting interface to
support container-specific information, namely a JSONPath expression
for jsonb.

During query transformation, if dot-notation is present, a JSONPath
expression is constructed to represent the access chain.

Then during execution, if a JSONPath expression is present in
JsonbSubWorkspace, executes it via JsonPathQuery().

Note that we cannot simply rewrite the accessors into JSON_QUERY()
during transformation, because the original query structure must be
preserved for EXPLAIN and CREATE VIEW.

Co-authored-by: Nikita Glukhov <[email protected]>
Co-authored-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Mark Dilger <[email protected]>
Reviewed-by: Jian He <[email protected]>
Reviewed-by: Vik Fearing <[email protected]>
Reviewed-by: Nikita Malakhov <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Tested-by: Jelte Fennema-Nio <[email protected]>
---
 doc/src/sgml/json.sgml                        | 301 ++++++++++++
 src/backend/catalog/sql_features.txt          |   4 +-
 src/backend/executor/execExpr.c               |  81 ++--
 src/backend/nodes/nodeFuncs.c                 |  14 +
 src/backend/utils/adt/jsonbsubs.c             | 302 +++++++++++-
 src/backend/utils/adt/ruleutils.c             |  43 +-
 src/include/nodes/primnodes.h                 |  54 ++-
 .../ecpg/test/expected/sql-sqljson.c          | 112 ++++-
 .../ecpg/test/expected/sql-sqljson.stderr     | 100 ++++
 .../ecpg/test/expected/sql-sqljson.stdout     |   7 +
 src/interfaces/ecpg/test/sql/sqljson.pgc      |  33 ++
 src/test/regress/expected/jsonb.out           | 450 +++++++++++++++++-
 src/test/regress/sql/jsonb.sql                | 115 +++++
 src/tools/pgindent/typedefs.list              |   1 +
 14 files changed, 1535 insertions(+), 82 deletions(-)

diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 206eadb8f7b..4405570d66e 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -712,6 +712,307 @@ UPDATE table_name SET jsonb_field[1]['a'] = '1';
   </para>
  </sect2>
 
+ <sect2 id="jsonb-simplified-accessor">
+  <title>JSON Simplified Accessor</title>
+  <para>
+   PostgreSQL implements the JSON simplified accessor as specified in SQL:2023.
+   The SQL standard defines the simplified accessor as a chain of operations
+   that can include JSON member accessors (dot notation for object fields)
+   and JSON array accessors (integer subscripts for array elements).
+   This provides a standardized way to access JSON data that complements
+   PostgreSQL's pre-standard subscripting and operator-based access methods.
+  </para>
+
+  <para>
+   The SQL:2023 simplified accessor syntax includes:
+   <itemizedlist>
+    <listitem>
+     <para>
+      <emphasis>JSON member accessor:</emphasis> <literal>jsonb_column.field_name</literal>
+      for accessing object fields
+     </para>
+    </listitem>
+    <listitem>
+     <para>
+      <emphasis>JSON array accessor:</emphasis> <literal>jsonb_column[index]</literal>
+      for accessing array elements by integer index
+     </para>
+    </listitem>
+   </itemizedlist>
+   These can be chained together: <literal>jsonb_column.field_name[0].nested_field</literal>.
+   When dot notation is present in the accessor chain, the entire chain follows
+   SQL:2023 semantics with lax mode behavior and conditional array wrapper.
+  </para>
+
+  <para>
+   The JSON simplified accessor is semantically equivalent to using
+   <function>JSON_QUERY</function> with the <literal>lax</literal> mode and
+   <literal>WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR</literal>
+   options. For example, <literal>json_col.field</literal> is equivalent to
+   <literal>JSON_QUERY(json_col, 'lax $.field' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR)</literal>.
+   The conditional array wrapper wraps multiple results in an array
+   but leaves a single result as-is without wrapping.
+  </para>
+
+  <para>
+   Examples of JSON simplified accessor syntax:
+
+<programlisting>
+
+-- Basic field access
+SELECT ('{"color": "red", "rgb": [255, 0, 0]}'::jsonb).color;
+
+-- Nested field access
+SELECT ('{"user": {"profile": {"settings": {"theme": "dark"}}}}'::jsonb).user.profile.settings.theme;
+
+-- JSON member accessor followed by JSON array accessor (both part of simplified accessor)
+SELECT ('{"repertoire": [{"title": "Swan Lake"}, {"title": "The Nutcracker"}]}'::jsonb).repertoire[1].title;
+
+-- In WHERE clauses
+SELECT * FROM users WHERE profile.preferences.theme = '"dark"';
+
+-- Comparison with other access methods (NOT equivalent - different semantics):
+SELECT json_col['address']['city'];           -- Subscripting
+SELECT json_col->'address'->'city';           -- Operator
+SELECT json_col.address.city;                 -- Simplified accessor (different behavior)
+</programlisting>
+  </para>
+
+  <sect3 id="jsonb-access-method-comparison">
+   <title>Comparison of JSON Access Methods</title>
+   <para>
+    PostgreSQL provides three different approaches for accessing JSON data, each with
+    distinct semantics and behaviors:
+   </para>
+
+   <para>
+    <emphasis>SQL:2023 JSON Simplified Accessor:</emphasis>
+    <itemizedlist>
+     <listitem>
+      <para>
+       Syntax: <literal>json_col.field_name</literal> (member accessor) and
+       <literal>json_col[index]</literal> (array accessor when used with dot notation)
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Standard SQL:2023 behavior with <literal>lax</literal> mode semantics
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Automatic array wrapping/unwrapping as specified in the SQL standard
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       When accessing a field from an array, operates on each array element and wraps results in an array;
+       when accessing an array index from a non-array, wraps the value as an array first
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Triggered when dot notation is present anywhere in the accessor chain
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Read-only access
+      </para>
+     </listitem>
+    </itemizedlist>
+   </para>
+
+   <para>
+    <emphasis>Pre-standard JSONB Subscripting:</emphasis>
+    <itemizedlist>
+     <listitem>
+      <para>
+       Syntax: <literal>json_col['field_name']</literal> (text-based) and
+       <literal>json_col[index]</literal> (integer-based when no dot notation present)
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       PostgreSQL's original JSONB subscripting behavior (available since version 14)
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Direct object field and array element access without array wrapping/unwrapping
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Supports both read and write operations
+      </para>
+     </listitem>
+    </itemizedlist>
+   </para>
+
+   <para>
+    <emphasis>Arrow Operators:</emphasis>
+    <itemizedlist>
+     <listitem>
+      <para>
+       Syntax: <literal>json_col->'field_name'</literal> and <literal>json_col->>'field_name'</literal>
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       PostgreSQL's JSON operators that work with both <literal>json</literal> and <literal>jsonb</literal> types
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Direct object field and array element access without array wrapping/unwrapping
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       <literal>-></literal> returns jsonb, <literal>->></literal> returns text
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Read-only access
+      </para>
+     </listitem>
+    </itemizedlist>
+   </para>
+
+   <para>
+    <emphasis>Key Semantic Differences:</emphasis> The most important distinctions are
+    how these methods handle member access from arrays and array access from non-array values.
+   </para>
+
+   <para>
+    <emphasis>Member Access from Arrays:</emphasis>
+<programlisting>
+-- Setup data
+INSERT INTO test_table VALUES
+  ('{"brightness": 80}'),                      -- Object case
+  ('[{"brightness": 45}, {"brightness": 90}]'); -- Array case
+
+-- Different behaviors:
+SELECT data.brightness FROM test_table;         -- Simplified accessor
+-- Results: 80, [45, 90]  (array elements unwrapped, results wrapped)
+
+SELECT data['brightness'] FROM test_table;      -- Pre-standard subscripting
+-- Results: 80, NULL    (no array handling)
+
+SELECT data->'brightness' FROM test_table;      -- Arrow operator
+-- Results: 80, NULL    (no array handling)
+</programlisting>
+
+    In the array case, the simplified accessor applies the field access to each array element
+    (unwrapping) and conditionally wraps the results in an array, while subscripting and arrow operators
+    attempt direct field access on the array itself (which returns NULL since
+    arrays don't have named fields).
+   </para>
+
+   <para>
+    <emphasis>Array Access from Objects (Lax Mode Behavior):</emphasis>
+<programlisting>
+-- Setup data
+INSERT INTO test_table VALUES ('{"weather": "sunny", "temperature": "72F"}');
+
+-- Different behaviors when accessing [0] on a non-array value:
+SELECT data[0] FROM test_table;                 -- Simplified accessor (lax mode, if dots present elsewhere)
+-- Result: {"weather": "sunny", "temperature": "72F"}  (object wrapped as array, [0] returns entire object)
+
+SELECT data[0] FROM test_table;                 -- Pre-standard subscripting (strict mode, no dots)
+-- Result: NULL  (no wrapping, direct array access on object fails)
+
+SELECT data->0 FROM test_table;                 -- Arrow operator (strict mode)
+-- Result: NULL  (no wrapping, direct array access on object fails)
+</programlisting>
+
+    In lax mode (simplified accessor), when an array operation is performed on a non-array value,
+    the value is first wrapped in an array, then the operation proceeds. In strict mode
+    (pre-standard methods), the operation fails and returns NULL.
+   </para>
+
+   <para>
+    <emphasis>When to Use Each Method:</emphasis>
+    <itemizedlist>
+     <listitem>
+      <para>
+       Use <emphasis>SQL:2023 simplified accessor</emphasis> for standard compliance and when you want lax mode and conditional array wrapper
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Use <emphasis>pre-standard subscripting</emphasis> for write operations or when you need direct field access without array processing
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Use <emphasis>arrow operators</emphasis> when you need text output (<literal>->></literal>) or when working with both <literal>json</literal> and <literal>jsonb</literal> types
+      </para>
+     </listitem>
+    </itemizedlist>
+   </para>
+  </sect3>
+
+  <sect3 id="jsonb-accessor-best-practices">
+   <title>Best Practices: Avoid Mixing Access Methods</title>
+   <para>
+    <emphasis>Important:</emphasis> Do not mix SQL:2023 simplified accessor syntax
+    with pre-standard subscripting syntax in the same accessor chain. These
+    methods have subtly different semantics and are not interchangeable aliases.
+    Mixing them can lead to confusion and code that is difficult to understand.
+   </para>
+
+   <para>
+    <emphasis>Recommended - Consistent simplified accessor:</emphasis>
+<programlisting>
+-- All parts use simplified accessor (standard behavior)
+SELECT data.location.coordinates.latitude FROM table;  -- Good
+SELECT data.repertoire[0].title FROM table;           -- Good
+SELECT data.users[1].profile.email FROM table;        -- Good
+</programlisting>
+   </para>
+
+   <para>
+    <emphasis>Recommended - Consistent pre-standard subscripting:</emphasis>
+<programlisting>
+-- All parts use pre-standard subscripting
+SELECT data['location']['coordinates']['latitude'] FROM table; -- Good
+SELECT data[0]['title'] FROM table;                    -- Good (when no dots present)
+SELECT data['users'][1]['profile']['email'] FROM table; -- Good
+</programlisting>
+   </para>
+
+   <para>
+    <emphasis>Not recommended - Mixed syntax:</emphasis>
+<programlisting>
+-- Mixing simplified accessor with pre-standard subscripting
+SELECT data.location['latitude'] FROM table;       -- Avoid
+SELECT data['repertoire'][0].title FROM table;    -- Avoid
+</programlisting>
+    While these mixed forms work as designed, they can be very confusing
+    because the simplified accessor cannot handle text-based subscripts like `['field']`.
+    This forces a fallback to pre-standard semantics for those specific parts,
+    creating a chain that switches between lax mode (for dot notation) and
+    strict mode (for text subscripts) within the same accessor expression.
+   </para>
+
+   <para>
+    Choose one approach consistently throughout your accessor chain to ensure
+    predictable and maintainable code.
+   </para>
+
+   <para>
+    <emphasis>Current Implementation:</emphasis> The current implementation supports
+    JSON member accessors (dot notation) and JSON array accessors (integer subscripts)
+    as defined in the SQL:2023 simplified accessor specification.
+    Advanced features from the SQL:2023 specification, such as wildcard member
+    accessors and item method accessors, are not yet implemented.
+   </para>
+  </sect3>
+ </sect2>
+
  <sect2 id="datatype-json-transforms">
   <title>Transforms</title>
 
diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_features.txt
index ebe85337c28..457e993305e 100644
--- a/src/backend/catalog/sql_features.txt
+++ b/src/backend/catalog/sql_features.txt
@@ -568,8 +568,8 @@ T838	JSON_TABLE: PLAN DEFAULT clause			NO
 T839	Formatted cast of datetimes to/from character strings			NO	
 T840	Hex integer literals in SQL/JSON path language			YES	
 T851	SQL/JSON: optional keywords for default syntax			YES	
-T860	SQL/JSON simplified accessor: column reference only			NO	
-T861	SQL/JSON simplified accessor: case-sensitive JSON member accessor			NO	
+T860	SQL/JSON simplified accessor: column reference only			YES	
+T861	SQL/JSON simplified accessor: case-sensitive JSON member accessor			YES	
 T862	SQL/JSON simplified accessor: wildcard member accessor			NO	
 T863	SQL/JSON simplified accessor: single-quoted string literal as member accessor			NO	
 T864	SQL/JSON simplified accessor			NO	
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index f1569879b52..385c8d0cefe 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -3320,50 +3320,59 @@ ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref,
 								   state->steps_len - 1);
 	}
 
-	/* Evaluate upper subscripts */
-	i = 0;
-	foreach(lc, sbsref->refupperindexpr)
+	/* Evaluate upper subscripts, unless refjsonbpath is used for execution */
+	if (!sbsref->refjsonbpath)
 	{
-		Expr	   *e = (Expr *) lfirst(lc);
-
-		/* When slicing, individual subscript bounds can be omitted */
-		if (!e)
-		{
-			sbsrefstate->upperprovided[i] = false;
-			sbsrefstate->upperindexnull[i] = true;
-		}
-		else
+		i = 0;
+		foreach(lc, sbsref->refupperindexpr)
 		{
-			sbsrefstate->upperprovided[i] = true;
-			/* Each subscript is evaluated into appropriate array entry */
-			ExecInitExprRec(e, state,
-							&sbsrefstate->upperindex[i],
-							&sbsrefstate->upperindexnull[i]);
+			Expr *e = (Expr *) lfirst(lc);
+
+			/* When slicing, individual subscript bounds can be omitted */
+			if (!e)
+			{
+				sbsrefstate->upperprovided[i] = false;
+				sbsrefstate->upperindexnull[i] = true;
+			}
+			else
+			{
+				sbsrefstate->upperprovided[i] = true;
+				/* Each subscript is evaluated into appropriate array entry */
+				ExecInitExprRec(e, state,
+								&sbsrefstate->upperindex[i],
+								&sbsrefstate->upperindexnull[i]);
+			}
+			i++;
 		}
-		i++;
 	}
 
-	/* Evaluate lower subscripts similarly */
-	i = 0;
-	foreach(lc, sbsref->reflowerindexpr)
+	/*
+	 * Evaluate lower subscripts similarly, unless refjsonbpath is used for
+	 * execution
+	 */
+	if (!sbsref->refjsonbpath)
 	{
-		Expr	   *e = (Expr *) lfirst(lc);
-
-		/* When slicing, individual subscript bounds can be omitted */
-		if (!e)
-		{
-			sbsrefstate->lowerprovided[i] = false;
-			sbsrefstate->lowerindexnull[i] = true;
-		}
-		else
+		i = 0;
+		foreach(lc, sbsref->reflowerindexpr)
 		{
-			sbsrefstate->lowerprovided[i] = true;
-			/* Each subscript is evaluated into appropriate array entry */
-			ExecInitExprRec(e, state,
-							&sbsrefstate->lowerindex[i],
-							&sbsrefstate->lowerindexnull[i]);
+			Expr	   *e = (Expr *) lfirst(lc);
+
+			/* When slicing, individual subscript bounds can be omitted */
+			if (!e)
+			{
+				sbsrefstate->lowerprovided[i] = false;
+				sbsrefstate->lowerindexnull[i] = true;
+			}
+			else
+			{
+				sbsrefstate->lowerprovided[i] = true;
+				/* Each subscript is evaluated into appropriate array entry */
+				ExecInitExprRec(e, state,
+								&sbsrefstate->lowerindex[i],
+								&sbsrefstate->lowerindexnull[i]);
+			}
+			i++;
 		}
-		i++;
 	}
 
 	/* SBSREF_SUBSCRIPTS checks and converts all the subscripts at once */
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 7bc823507f1..cab5d8d9f32 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -284,6 +284,15 @@ exprType(const Node *expr)
 		case T_PlaceHolderVar:
 			type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_FieldAccessorExpr:
+
+			/*
+			 * FieldAccessorExpr is not evaluable. Treat it as TEXT for
+			 * collation, deparsing, and similar purposes, since it represents
+			 * a JSON field name.
+			 */
+			type = TEXTOID;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			type = InvalidOid;	/* keep compiler quiet */
@@ -1146,6 +1155,9 @@ exprSetCollation(Node *expr, Oid collation)
 		case T_MergeSupportFunc:
 			((MergeSupportFunc *) expr)->msfcollid = collation;
 			break;
+		case T_FieldAccessorExpr:
+			((FieldAccessorExpr *) expr)->faecollid = collation;
+			break;
 		case T_SubscriptingRef:
 			((SubscriptingRef *) expr)->refcollid = collation;
 			break;
@@ -2129,6 +2141,7 @@ expression_tree_walker_impl(Node *node,
 		case T_SortGroupClause:
 		case T_CTESearchClause:
 		case T_MergeSupportFunc:
+		case T_FieldAccessorExpr:
 			/* primitive node types with no expression subnodes */
 			break;
 		case T_WithCheckOption:
@@ -3008,6 +3021,7 @@ expression_tree_mutator_impl(Node *node,
 		case T_SortGroupClause:
 		case T_CTESearchClause:
 		case T_MergeSupportFunc:
+		case T_FieldAccessorExpr:
 			return copyObject(node);
 		case T_WithCheckOption:
 			{
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index 5757334f4eb..308e66e8d4f 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -15,21 +15,30 @@
 #include "postgres.h"
 
 #include "executor/execExpr.h"
+#include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "nodes/subscripting.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_expr.h"
 #include "utils/builtins.h"
 #include "utils/jsonb.h"
+#include "utils/jsonpath.h"
 
 
-/* SubscriptingRefState.workspace for jsonb subscripting execution */
+/*
+ * SubscriptingRefState.workspace for generic jsonb subscripting execution.
+ *
+ * Stores state for both jsonb simple subscripting and dot notation access.
+ * Dot notation additionally uses `jsonpath` for JsonPath evaluation.
+ */
 typedef struct JsonbSubWorkspace
 {
 	bool		expectArray;	/* jsonb root is expected to be an array */
 	Oid		   *indexOid;		/* OID of coerced subscript expression, could
 								 * be only integer or text */
 	Datum	   *index;			/* Subscript values in Datum format */
+	JsonPath   *jsonpath;		/* JsonPath for dot notation execution via
+								 * JsonPathQuery() */
 } JsonbSubWorkspace;
 
 static Node *
@@ -96,6 +105,233 @@ coerce_jsonpath_subscript_to_int4_or_text(ParseState *pstate, Node *subExpr)
 	return subExpr;
 }
 
+/*
+ * During transformation, determine whether to build a JsonPath
+ * for JsonPathQuery() execution.
+ *
+ * JsonPath is needed if the indirection list includes:
+ * - String-based access (dot notation)
+ * - Slice-based subscripting (when isSlice is true)
+ *
+ * Otherwise, simple jsonb subscripting is enough.
+ */
+static bool
+jsonb_check_jsonpath_needed(List *indirection)
+{
+	ListCell   *lc;
+
+	foreach(lc, indirection)
+	{
+		Node	   *accessor = lfirst(lc);
+
+		if (IsA(accessor, String))
+			return true;
+		else
+			Assert(IsA(accessor, A_Indices));
+	}
+
+	return false;
+}
+
+/*
+ * Helper functions for constructing JsonPath expressions.
+ *
+ * The make_jsonpath_item_* functions create various types of JsonPathParseItem
+ * nodes, which are used to build JsonPath expressions for jsonb simplified
+ * accessor.
+ */
+
+static JsonPathParseItem *
+make_jsonpath_item(JsonPathItemType type)
+{
+	JsonPathParseItem *v = palloc(sizeof(*v));
+
+	v->type = type;
+	v->next = NULL;
+
+	return v;
+}
+
+/*
+ * Convert a constant integer expression into a JsonPathParseItem.
+ *
+ * The input expression must be a non-null constant of type INT4. Returns NULL otherwise.
+ * This function constructs a jpiNumeric item for use in JsonPath and appends a matching
+ * Const(INT4) node to the given expression list for use in EXPLAIN, views, etc.
+ *
+ * Parameters:
+ * - pstate: parse state context
+ * - expr: input expression node
+ * - exprs: list of expression nodes (updated in place)
+ */
+static JsonPathParseItem *
+make_jsonpath_item_expr(ParseState *pstate, Node *expr, List **exprs)
+{
+	Const	   *cnst;
+
+	expr = transformExpr(pstate, expr, pstate->p_expr_kind);
+
+	if (IsA(expr, Const))
+	{
+		cnst = (Const *) expr;
+		if (cnst->consttype == INT4OID && !(cnst->constisnull))
+		{
+			JsonPathParseItem *jpi = make_jsonpath_item(jpiNumeric);
+
+			jpi->value.numeric =
+				DatumGetNumeric(DirectFunctionCall1(int4_numeric, Int32GetDatum(cnst->constvalue)));
+
+			*exprs = lappend(*exprs, makeConst(INT4OID, -1, InvalidOid, 4,
+											   Int32GetDatum(cnst->constvalue), false, true));
+
+			return jpi;
+		}
+	}
+
+	return NULL;
+}
+
+/*
+ * Constructs a JsonPath expression from a list of indirections.
+ * This function is used when jsonb subscripting involves dot notation,
+ * requiring JsonPath-based evaluation.
+ *
+ * The function modifies the indirection list in place, removing processed
+ * elements as it converts them into JsonPath components, as follows:
+ * - String keys (dot notation) -> jpiKey items.
+ * - Array indices -> jpiIndexArray items.
+ *
+ * In addition to building the JsonPath expression, this function populates
+ * the following fields of the given SubscriptingRef:
+ * - refjsonbpath: the generated JsonPath
+ * - refupperindexpr: upper index expressions (object keys or array indexes)
+ * - reflowerindexpr: lower index expressions, remains NIL as slices are not supported.
+ *
+ * Parameters:
+ * - pstate: Parse state context.
+ * - indirection: List of subscripting expressions (modified in-place).
+ * - sbsref: SubscriptingRef node to update
+ */
+static int
+jsonb_subscript_make_jsonpath(ParseState *pstate, List *indirection, SubscriptingRef *sbsref)
+{
+	JsonPathParseResult jpres;
+	JsonPathParseItem *path = make_jsonpath_item(jpiRoot);
+	ListCell   *lc;
+	Datum		jsp;
+	int			pathlen = 0;
+
+	sbsref->refupperindexpr = NIL;
+	sbsref->reflowerindexpr = NIL;
+	sbsref->refjsonbpath = NULL;
+
+	jpres.expr = path;
+	jpres.lax = true;
+
+	foreach(lc, indirection)
+	{
+		Node	   *accessor = lfirst(lc);
+		JsonPathParseItem *jpi;
+
+		if (IsA(accessor, String))
+		{
+			char	   *field = strVal(accessor);
+			FieldAccessorExpr *accessor_expr;
+
+			jpi = make_jsonpath_item(jpiKey);
+			jpi->value.string.val = field;
+			jpi->value.string.len = strlen(field);
+
+			accessor_expr = makeNode(FieldAccessorExpr);
+			accessor_expr->type = T_FieldAccessorExpr;
+			accessor_expr->fieldname = field;
+
+			sbsref->refupperindexpr = lappend(sbsref->refupperindexpr, accessor_expr);
+		}
+		else if (IsA(accessor, A_Indices))
+		{
+			A_Indices  *ai = castNode(A_Indices, accessor);
+
+			if (!ai->is_slice)
+			{
+				JsonPathParseItem *jpi_from = NULL;
+
+				Assert(ai->uidx && !ai->lidx);
+				jpi_from = make_jsonpath_item_expr(pstate, ai->uidx, &sbsref->refupperindexpr);
+				if (jpi_from == NULL)
+				{
+					/*
+					 * Break out of the loop if the subscript is not a
+					 * non-null integer constant, so that we can fall back to
+					 * jsonb subscripting logic.
+					 *
+					 * This is needed to handle cases with mixed usage of SQL
+					 * standard json simplified accessor syntax and PostgreSQL
+					 * jsonb subscripting syntax, e.g:
+					 *
+					 * select (jb).a['b'].c from jsonb_table;
+					 *
+					 * where dot-notation (.a and .c) is the SQL standard json
+					 * simplified accessor syntax, and the ['b'] subscript is
+					 * the PostgreSQL jsonb subscripting syntax, because 'b'
+					 * is not a non-null constant integer and cannot be used
+					 * for json array access.
+					 *
+					 * In this case, we cannot create a JsonPath item, so we
+					 * break out of the loop and let
+					 * jsonb_subscript_transform() handle this indirection as
+					 * a PostgreSQL jsonb subscript.
+					 */
+					break;
+				}
+
+				jpi = make_jsonpath_item(jpiIndexArray);
+				jpi->value.array.nelems = 1;
+				jpi->value.array.elems = palloc(sizeof(jpi->value.array.elems[0]));
+
+				jpi->value.array.elems[0].from = jpi_from;
+				jpi->value.array.elems[0].to = NULL;
+			}
+			else
+			{
+				Node	   *expr = ai->uidx ? ai->uidx : ai->lidx;
+
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("jsonb subscript does not support slices"),
+						 parser_errposition(pstate, exprLocation(expr))));
+			}
+		}
+		else
+		{
+			/*
+			 * Unexpected node type in indirection list. This should not
+			 * happen with current grammar, but we handle it defensively by
+			 * breaking out of the loop rather than crashing. In case of
+			 * future grammar changes that might introduce new node types,
+			 * this allows us to create a jsonpath from as many indirection
+			 * elements as we can and let transformIndirection() fallback to
+			 * alternative logic to handle the remaining indirection elements.
+			 */
+			Assert(false);		/* not reachable */
+			break;
+		}
+
+		/* append path item */
+		path->next = jpi;
+		path = jpi;
+		pathlen++;
+	}
+
+	if (pathlen != 0)
+	{
+		jsp = jsonPathFromParseResult(&jpres, 0, NULL);
+		sbsref->refjsonbpath = (Node *) makeConst(JSONPATHOID, -1, InvalidOid, -1, jsp, false, false);
+	}
+
+	return pathlen;
+}
+
 /*
  * Finish parse analysis of a SubscriptingRef expression for a jsonb.
  *
@@ -114,9 +350,29 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	List	   *upperIndexpr = NIL;
 	ListCell   *idx;
 
+	/* Determine the result type of the subscripting operation; always jsonb */
+	sbsref->refrestype = JSONBOID;
+	sbsref->reftypmod = -1;
+
+	if (jsonb_check_jsonpath_needed(indirection))
+	{
+		int			pathlen;
+
+		pathlen = jsonb_subscript_make_jsonpath(pstate, indirection, sbsref);
+		if (sbsref->refjsonbpath)
+			return pathlen;
+	}
+
 	/*
-	 * Transform and convert the subscript expressions. Jsonb subscripting
-	 * does not support slices, look only at the upper index.
+	 * We reach here only in two cases: (a) the JSON simplified accessor is
+	 * not needed at all (for example, a plain array subscript like [1] or
+	 * object key access like ['a']), or (b) jsonb_subscript_make_jsonpath()
+	 * was called but could not complete the JsonPath construction (for
+	 * example, when mixing dot notation with non-integer subscripts like
+	 * (jb)['a'].b where 'a' is not a constant integer).
+	 *
+	 * In both cases we fall back to pre-standard jsonb subscripting, coercing
+	 * each subscript to array index or object key as needed.
 	 */
 	foreach(idx, indirection)
 	{
@@ -163,10 +419,6 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->refupperindexpr = upperIndexpr;
 	sbsref->reflowerindexpr = NIL;
 
-	/* Determine the result type of the subscripting operation; always jsonb */
-	sbsref->refrestype = JSONBOID;
-	sbsref->reftypmod = -1;
-
 	return list_length(upperIndexpr);
 }
 
@@ -219,7 +471,7 @@ jsonb_subscript_check_subscripts(ExprState *state,
 			 * For jsonb fetch and assign functions we need to provide path in
 			 * text format. Convert if it's not already text.
 			 */
-			if (workspace->indexOid[i] == INT4OID)
+			if (!workspace->jsonpath && workspace->indexOid[i] == INT4OID)
 			{
 				Datum		datum = sbsrefstate->upperindex[i];
 				char	   *cs = DatumGetCString(DirectFunctionCall1(int4out, datum));
@@ -247,17 +499,32 @@ jsonb_subscript_fetch(ExprState *state,
 {
 	SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
 	JsonbSubWorkspace *workspace = (JsonbSubWorkspace *) sbsrefstate->workspace;
-	Jsonb	   *jsonbSource;
 
 	/* Should not get here if source jsonb (or any subscript) is null */
 	Assert(!(*op->resnull));
 
-	jsonbSource = DatumGetJsonbP(*op->resvalue);
-	*op->resvalue = jsonb_get_element(jsonbSource,
-									  workspace->index,
-									  sbsrefstate->numupper,
-									  op->resnull,
-									  false);
+	if (workspace->jsonpath)
+	{
+		bool		empty = false;
+		bool		error = false;
+
+		*op->resvalue = JsonPathQuery(*op->resvalue, workspace->jsonpath,
+									  JSW_CONDITIONAL,
+									  &empty, &error, NULL,
+									  NULL);
+
+		*op->resnull = empty || error;
+	}
+	else
+	{
+		Jsonb	   *jsonbSource = DatumGetJsonbP(*op->resvalue);
+
+		*op->resvalue = jsonb_get_element(jsonbSource,
+										  workspace->index,
+										  sbsrefstate->numupper,
+										  op->resnull,
+										  false);
+	}
 }
 
 /*
@@ -365,7 +632,7 @@ jsonb_exec_setup(const SubscriptingRef *sbsref,
 {
 	JsonbSubWorkspace *workspace;
 	ListCell   *lc;
-	int			nupper = sbsref->refupperindexpr->length;
+	int			nupper = list_length(sbsref->refupperindexpr);
 	char	   *ptr;
 
 	/* Allocate type-specific workspace with space for per-subscript data */
@@ -374,6 +641,9 @@ jsonb_exec_setup(const SubscriptingRef *sbsref,
 	workspace->expectArray = false;
 	ptr = ((char *) workspace) + MAXALIGN(sizeof(JsonbSubWorkspace));
 
+	if (sbsref->refjsonbpath)
+		workspace->jsonpath = DatumGetJsonPathP(castNode(Const, sbsref->refjsonbpath)->constvalue);
+
 	/*
 	 * This coding assumes sizeof(Datum) >= sizeof(Oid), else we might
 	 * misalign the indexOid pointer
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 0408a95941d..761918f899e 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9329,10 +9329,13 @@ get_rule_expr(Node *node, deparse_context *context,
 				 * Parenthesize the argument unless it's a simple Var or a
 				 * FieldSelect.  (In particular, if it's another
 				 * SubscriptingRef, we *must* parenthesize to avoid
-				 * confusion.)
+				 * confusion.) Always add parenthesis if JSON simplified
+				 * accessor is used, for now.
 				 */
-				need_parens = !IsA(sbsref->refexpr, Var) &&
-					!IsA(sbsref->refexpr, FieldSelect);
+				need_parens = (!IsA(sbsref->refexpr, Var) &&
+							   !IsA(sbsref->refexpr, FieldSelect)) ||
+					sbsref->refjsonbpath;
+
 				if (need_parens)
 					appendStringInfoChar(buf, '(');
 				get_rule_expr((Node *) sbsref->refexpr, context, showimplicit);
@@ -13005,17 +13008,35 @@ printSubscripts(SubscriptingRef *sbsref, deparse_context *context)
 	lowlist_item = list_head(sbsref->reflowerindexpr);	/* could be NULL */
 	foreach(uplist_item, sbsref->refupperindexpr)
 	{
-		appendStringInfoChar(buf, '[');
-		if (lowlist_item)
+		Node	   *upper = (Node *) lfirst(uplist_item);
+
+		if (upper && IsA(upper, FieldAccessorExpr))
 		{
+			FieldAccessorExpr *fae = (FieldAccessorExpr *) upper;
+
+			/* Use dot-notation for field access */
+			appendStringInfoChar(buf, '.');
+			appendStringInfoString(buf, quote_identifier(fae->fieldname));
+
+			/* Skip matching low index — field access doesn't use slices */
+			if (lowlist_item)
+				lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
+		}
+		else
+		{
+			/* Use JSONB array subscripting */
+			appendStringInfoChar(buf, '[');
+			if (lowlist_item)
+			{
+				/* If subexpression is NULL, get_rule_expr prints nothing */
+				get_rule_expr((Node *) lfirst(lowlist_item), context, false);
+				appendStringInfoChar(buf, ':');
+				lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
+			}
 			/* If subexpression is NULL, get_rule_expr prints nothing */
-			get_rule_expr((Node *) lfirst(lowlist_item), context, false);
-			appendStringInfoChar(buf, ':');
-			lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
+			get_rule_expr(upper, context, false);
+			appendStringInfoChar(buf, ']');
 		}
-		/* If subexpression is NULL, get_rule_expr prints nothing */
-		get_rule_expr((Node *) lfirst(uplist_item), context, false);
-		appendStringInfoChar(buf, ']');
 	}
 }
 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 6dfca3cb35b..4f519f5031f 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -708,18 +708,30 @@ typedef struct SubscriptingRef
 	int32		reftypmod pg_node_attr(query_jumble_ignore);
 	/* collation of result, or InvalidOid if none */
 	Oid			refcollid pg_node_attr(query_jumble_ignore);
-	/* expressions that evaluate to upper container indexes */
+
+	/*
+	 * expressions that evaluate to upper container indexes or expressions
+	 * that are collected but not evaluated when refjsonbpath is set.
+	 */
 	List	   *refupperindexpr;
 
 	/*
-	 * expressions that evaluate to lower container indexes, or NIL for single
-	 * container element.
+	 * expressions that evaluate to lower container indexes, or NIL for a
+	 * single container element, or expressions that are collected but not
+	 * evaluated when refjsonbpath is set.
 	 */
 	List	   *reflowerindexpr;
 	/* the expression that evaluates to a container value */
 	Expr	   *refexpr;
 	/* expression for the source value, or NULL if fetch */
 	Expr	   *refassgnexpr;
+
+	/*
+	 * container-specific extra information, currently used only by jsonb.
+	 * stores a JsonPath expression when jsonb dot notation is used. NULL for
+	 * simple subscripting.
+	 */
+	Node	   *refjsonbpath;
 } SubscriptingRef;
 
 /*
@@ -2371,4 +2383,40 @@ typedef struct OnConflictExpr
 	List	   *exclRelTlist;	/* tlist of the EXCLUDED pseudo relation */
 } OnConflictExpr;
 
+/*
+ * FieldAccessorExpr - represents a single object member access using dot-notation
+ *		in JSON simplified accessor syntax (e.g., jsonb_col.a).
+ *
+ * These nodes appear as list elements in SubscriptingRef.refupperindexpr to
+ * indicate JSON object key access. They are not evaluable expressions by
+ * themselves but serve as placeholders to preserve source-level syntax for
+ * rule rewriting and deparsing (e.g., in EXPLAIN and view definitions).
+ * Execution is handled by the enclosing SubscriptingRef.
+ *
+ * If dot-notation is used in a SubscriptingRef, the JSON path is represented
+ * as a flat list of FieldAccessorExpr nodes (for object field access), Const
+ * nodes (for array indexes), and NULLs (for omitted slice bounds), rather than
+ * through nested expression trees.
+ *
+ * Note: The flat representation avoids nested FieldAccessorExpr chains,
+ * simplifying evaluation and enabling standard-compliant behavior such as
+ * conditional array wrapping. This avoids the need for position-aware
+ * wrapping/unwrapping logic during execution.
+ *
+ * For example, in the expression:
+ *		('{"a": [{"b": 1}]}'::jsonb).a[0].b
+ * the SubscriptingRef will contain:
+ *		- refexpr: the base expression (the jsonb value)
+ *		- refupperindexpr: [FieldAccessorExpr("a"), Const(0),
+ *			FieldAccessorExpr("b")]
+ *		- reflowerindexpr: [NULL, NULL, NULL] (slice lower bounds not used here)
+ */
+typedef struct FieldAccessorExpr
+{
+	NodeTag		type;
+	char	   *fieldname;		/* name of the JSONB object field accessed via
+								 * dot notation */
+	Oid			faecollid pg_node_attr(query_jumble_ignore);
+} FieldAccessorExpr;
+
 #endif							/* PRIMNODES_H */
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.c b/src/interfaces/ecpg/test/expected/sql-sqljson.c
index 39221f9ea5d..e6a7ece6dab 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.c
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.c
@@ -417,12 +417,122 @@ if (sqlca.sqlcode < 0) sqlprint();}
 	  for (int i = 0; i < sizeof(is_json); i++)
 		  printf("Found is_json[%d]: %s\n", i, is_json[i] ? "true" : "false");
 
-  { ECPGdisconnect(__LINE__, "CURRENT");
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . \"a\" )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 118 "sqljson.pgc"
 
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 118 "sqljson.pgc"
 
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb . \"a\" )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 121 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 121 "sqljson.pgc"
+
+	// error
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . a )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 124 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 124 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . a . b )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 127 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 127 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( coalesce ( json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . c ) , 'null' ) )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 130 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 130 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ 0 ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 133 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 133 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b . x )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 136 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 136 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) [ 'b' ] [ 0 ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 139 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 139 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 142 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 142 "sqljson.pgc"
+
+	// error
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . * )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 145 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 145 "sqljson.pgc"
+
+	// error
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 148 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 148 "sqljson.pgc"
+
+	// error
+
+  { ECPGdisconnect(__LINE__, "CURRENT");
+#line 151 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 151 "sqljson.pgc"
+
 
   return 0;
 }
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
index e55a95dd711..19f8c58af06 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
@@ -268,5 +268,105 @@ SQL error: cannot use type jsonb in RETURNING clause of JSON_SERIALIZE() on line
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_get_data on line 102: RESULT: f offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 118: query: select json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . "a" ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 118: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 118: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 118: RESULT: {"b": 1, "c": 2} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 121: query: select json ( '{"a": {"b": 1, "c": 2}}' :: jsonb . "a" ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 121: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 121: bad response - ERROR:  schema "jsonb" does not exist
+LINE 1: select json ( '{"a": {"b": 1, "c": 2}}' :: jsonb . "a" )
+                                                   ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 3F000 (sqlcode -400): schema "jsonb" does not exist on line 121
+[NO_PID]: sqlca: code: -400, state: 3F000
+SQL error: schema "jsonb" does not exist on line 121
+[NO_PID]: ecpg_execute on line 124: query: select json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . a ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 124: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 124: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 124: RESULT: {"b": 1, "c": 2} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 127: query: select json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . a . b ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 127: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 127: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 127: RESULT: 1 offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 130: query: select json ( coalesce ( json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . c ) , 'null' ) ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 130: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 130: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 130: RESULT: null offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 133: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 0 ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 133: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 133: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 133: RESULT: {"x": 1} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 136: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b . x ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 136: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 136: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 136: RESULT: [1, [12, {"y": 1}]] offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 139: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 139: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 139: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 139: RESULT: {"x": 1} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 142: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 142: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 142: bad response - ERROR:  jsonb subscript does not support slices
+LINE 1: ..., {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] )
+                                                                ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 42804 (sqlcode -400): jsonb subscript does not support slices on line 142
+[NO_PID]: sqlca: code: -400, state: 42804
+SQL error: jsonb subscript does not support slices on line 142
+[NO_PID]: ecpg_execute on line 145: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . * ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 145: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 145: bad response - ERROR:  row expansion via "*" is not supported here
+LINE 1: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x...
+                        ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 0A000 (sqlcode -400): row expansion via "*" is not supported here on line 145
+[NO_PID]: sqlca: code: -400, state: 0A000
+SQL error: row expansion via "*" is not supported here on line 145
+[NO_PID]: ecpg_execute on line 148: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 148: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 148: bad response - ERROR:  jsonb subscript does not support slices
+LINE 1: ...x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )
+                                                                ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 42804 (sqlcode -400): jsonb subscript does not support slices on line 148
+[NO_PID]: sqlca: code: -400, state: 42804
+SQL error: jsonb subscript does not support slices on line 148
 [NO_PID]: ecpg_finish: connection ecpg1_regression closed
 [NO_PID]: sqlca: code: 0, state: 00000
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
index 83f8df13e5a..442d36931f1 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
@@ -28,3 +28,10 @@ Found is_json[4]: false
 Found is_json[5]: false
 Found is_json[6]: true
 Found is_json[7]: false
+Found json={"b": 1, "c": 2}
+Found json={"b": 1, "c": 2}
+Found json=1
+Found json=null
+Found json={"x": 1}
+Found json=[1, [12, {"y": 1}]]
+Found json={"x": 1}
diff --git a/src/interfaces/ecpg/test/sql/sqljson.pgc b/src/interfaces/ecpg/test/sql/sqljson.pgc
index ddcbcc3b3cb..57a9bff424d 100644
--- a/src/interfaces/ecpg/test/sql/sqljson.pgc
+++ b/src/interfaces/ecpg/test/sql/sqljson.pgc
@@ -115,6 +115,39 @@ EXEC SQL END DECLARE SECTION;
 	  for (int i = 0; i < sizeof(is_json); i++)
 		  printf("Found is_json[%d]: %s\n", i, is_json[i] ? "true" : "false");
 
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}}'::jsonb)."a") INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON('{"a": {"b": 1, "c": 2}}'::jsonb."a") INTO :json;
+	// error
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}}'::jsonb).a) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}}'::jsonb).a.b) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(COALESCE(JSON(('{"a": {"b": 1, "c": 2}}'::jsonb).c), 'null')) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b[0]) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b.x) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb)['b'][0]) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b[1].x[0:]) INTO :json;
+	// error
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).*) INTO :json;
+	// error
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb)['b'][0:]) INTO :json;
+	// error
+
   EXEC SQL DISCONNECT;
 
   return 0;
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index fb47c8a893a..0947f836de8 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -4989,6 +4989,12 @@ select ('123'::jsonb)['a'];
  
 (1 row)
 
+select ('123'::jsonb).a;
+ a 
+---
+ 
+(1 row)
+
 select ('123'::jsonb)[0];
  jsonb 
 -------
@@ -5001,12 +5007,24 @@ select ('123'::jsonb)[NULL];
  
 (1 row)
 
+select ('123'::jsonb).NULL;
+ null 
+------
+ 
+(1 row)
+
 select ('{"a": 1}'::jsonb)['a'];
  jsonb 
 -------
  1
 (1 row)
 
+select ('{"a": 1}'::jsonb).a;
+ a 
+---
+ 1
+(1 row)
+
 select ('{"a": 1}'::jsonb)[0];
  jsonb 
 -------
@@ -5019,6 +5037,12 @@ select ('{"a": 1}'::jsonb)['not_exist'];
  
 (1 row)
 
+select ('{"a": 1}'::jsonb)."not_exist";
+ not_exist 
+-----------
+ 
+(1 row)
+
 select ('{"a": 1}'::jsonb)[NULL];
  jsonb 
 -------
@@ -5031,6 +5055,12 @@ select ('[1, "2", null]'::jsonb)['a'];
  
 (1 row)
 
+select ('[1, "2", null]'::jsonb).a;
+ a 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[0];
  jsonb 
 -------
@@ -5043,6 +5073,12 @@ select ('[1, "2", null]'::jsonb)['1'];
  "2"
 (1 row)
 
+select ('[1, "2", null]'::jsonb)."1";
+ 1 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[1.0];
 ERROR:  subscript type numeric is not supported
 LINE 1: select ('[1, "2", null]'::jsonb)[1.0];
@@ -5072,6 +5108,12 @@ select ('[1, "2", null]'::jsonb)[1]['a'];
  
 (1 row)
 
+select ('[1, "2", null]'::jsonb)[1].a;
+ a 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[1][0];
  jsonb 
 -------
@@ -5084,54 +5126,126 @@ select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['b'];
  "c"
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).b;
+  b  
+-----
+ "c"
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'];
    jsonb   
 -----------
  [1, 2, 3]
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d;
+     d     
+-----------
+ [1, 2, 3]
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'][1];
  jsonb 
 -------
  2
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d[1];
+ d 
+---
+ 2
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d']['a'];
  jsonb 
 -------
  
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['a'];
+ d 
+---
+ 
+(1 row)
+
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d.a;
+ a 
+---
+ 
+(1 row)
+
+select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d['a'][0];
+ d 
+---
+ 1
+(1 row)
+
+select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d.a[0];
+ a 
+---
+ 1
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1'];
      jsonb     
 ---------------
  {"a2": "aaa"}
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1;
+      a1       
+---------------
+ {"a2": "aaa"}
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2'];
  jsonb 
 -------
  "aaa"
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2;
+  a2   
+-------
+ "aaa"
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']['a3'];
  jsonb 
 -------
  
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2.a3;
+ a3 
+----
+ 
+(1 row)
+
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'];
          jsonb         
 -----------------------
  ["aaa", "bbb", "ccc"]
 (1 row)
 
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1;
+          b1           
+-----------------------
+ ["aaa", "bbb", "ccc"]
+(1 row)
+
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'][2];
  jsonb 
 -------
  "ccc"
 (1 row)
 
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1[2];
+  b1   
+-------
+ "ccc"
+(1 row)
+
 -- slices are not supported
 select ('{"a": 1}'::jsonb)['a':'b'];
 ERROR:  jsonb subscript does not support slices
@@ -5841,7 +5955,7 @@ select jsonb_typeof('{"a":1}'::jsonb);
 select ('{"a":1}'::jsonb).jsonb_typeof;
  jsonb_typeof 
 --------------
- object
+ 
 (1 row)
 
 select jsonb_array_length('["a", "b", "c"]'::jsonb);
@@ -5853,7 +5967,7 @@ select jsonb_array_length('["a", "b", "c"]'::jsonb);
 select ('["a", "b", "c"]'::jsonb).jsonb_array_length;
  jsonb_array_length 
 --------------------
-                  3
+ 
 (1 row)
 
 select jsonb_object_keys('{"a":1, "b":2}'::jsonb);
@@ -5866,9 +5980,8 @@ select jsonb_object_keys('{"a":1, "b":2}'::jsonb);
 select ('{"a":1, "b":2}'::jsonb).jsonb_object_keys;
  jsonb_object_keys 
 -------------------
- a
- b
-(2 rows)
+ 
+(1 row)
 
 -- cast jsonb to other types as (jsonb)::type and (jsonb).type
 select ('123.45'::jsonb)::numeric;
@@ -5880,7 +5993,7 @@ select ('123.45'::jsonb)::numeric;
 select ('123.45'::jsonb).numeric;
  numeric 
 ---------
-  123.45
+ 
 (1 row)
 
 select ('[{"name": "alice"}, {"name": "bob"}]'::jsonb)::name;
@@ -5890,9 +6003,9 @@ select ('[{"name": "alice"}, {"name": "bob"}]'::jsonb)::name;
 (1 row)
 
 select ('[{"name": "alice"}, {"name": "bob"}]'::jsonb).name;
-                 name                 
---------------------------------------
- [{"name": "alice"}, {"name": "bob"}]
+       name       
+------------------
+ ["alice", "bob"]
 (1 row)
 
 select ('true'::jsonb)::bool;
@@ -5904,7 +6017,7 @@ select ('true'::jsonb)::bool;
 select ('true'::jsonb).bool;
  bool 
 ------
- t
+ 
 (1 row)
 
 select ('{"text": "hello"}'::jsonb)::text;
@@ -5914,8 +6027,319 @@ select ('{"text": "hello"}'::jsonb)::text;
 (1 row)
 
 select ('{"text": "hello"}'::jsonb).text;
-       text        
--------------------
- {"text": "hello"}
+  text   
+---------
+ "hello"
+(1 row)
+
+-- dot notation
+CREATE TABLE test_jsonb_dot_notation AS
+SELECT '{"a": [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], "b": [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]}'::jsonb jb;
+SELECT (jb).a FROM test_jsonb_dot_notation;
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+SELECT (jb)."a" FROM test_jsonb_dot_notation; -- double quote should work
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+SELECT (jb).'a' FROM test_jsonb_dot_notation; -- single quote should not work
+ERROR:  syntax error at or near "'a'"
+LINE 1: SELECT (jb).'a' FROM test_jsonb_dot_notation;
+                    ^
+select (jb)[0].a from test_jsonb_dot_notation; -- returns same result as (jb).a
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+select (jb)[0]['a'] from test_jsonb_dot_notation; -- returns NULL
+ jb 
+----
+ 
+(1 row)
+
+select (jb)[1].a from test_jsonb_dot_notation; -- returns NULL
+ a 
+---
+ 
+(1 row)
+
+SELECT (jb).b FROM test_jsonb_dot_notation;
+                         b                         
+---------------------------------------------------
+ [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]
+(1 row)
+
+SELECT (jb).c FROM test_jsonb_dot_notation;
+ c 
+---
+ 
+(1 row)
+
+SELECT (jb).a.b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT (jb).a[2].b FROM test_jsonb_dot_notation;
+  b  
+-----
+ "c"
+(1 row)
+
+SELECT (jb).a.x.y FROM test_jsonb_dot_notation;
+   y   
+-------
+ "yyy"
+(1 row)
+
+SELECT (jb).b.x.z FROM test_jsonb_dot_notation;
+   z   
+-------
+ "ZZZ"
+(1 row)
+
+SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
+ c 
+---
+ 
+(1 row)
+
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t; -- fails
+ERROR:  jsonb subscript does not support slices
+SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
+ERROR:  type jsonb is not composite
+-- assignment is not supported
+UPDATE test_jsonb_dot_notation SET jb.a = '1';
+ERROR:  cannot assign to field "a" of column "jb" because its type jsonb is not a composite type
+LINE 1: UPDATE test_jsonb_dot_notation SET jb.a = '1';
+                                           ^
+UPDATE test_jsonb_dot_notation SET (jb).a = '1';
+ERROR:  syntax error at or near "."
+LINE 1: UPDATE test_jsonb_dot_notation SET (jb).a = '1';
+                                               ^
+-- explains should work
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+                  QUERY PLAN                  
+----------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation t
+   Output: (jb).a
+(2 rows)
+
+SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb).a[1]
+(2 rows)
+
+SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+ a 
+---
+ 2
+(1 row)
+
+-- views should work
+CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT (jb).a[3].x.y AS y
+   FROM test_jsonb_dot_notation
+-- mixed syntax
+DROP VIEW test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb['a'::text]).b
+(2 rows)
+
+SELECT (jb)['a'].b FROM test_jsonb_dot_notation; -- returns an array due to lax mode
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT (jb['a'::text]).b AS b
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: ((jb).a)['b'::text]
+(2 rows)
+
+SELECT (jb).a['b'] FROM test_jsonb_dot_notation; -- returns NULL because ['b'] looks for strict match in an object
+ a 
+---
+ 
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT ((jb).a)['b'::text] AS a
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+ a 
+---
+ 
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: ((jb).b.x)['z'::text]
+(2 rows)
+
+SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation; -- warnings
+   x   
+-------
+ "ZZZ"
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT ((jb).b.x)['z'::text] AS x
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+   x   
+-------
+ "ZZZ"
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: ((jb['b'::text]).x)['z'::text]
+(2 rows)
+
+SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;  -- warnings
+   x   
+-------
+ "ZZZ"
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT ((jb['b'::text]).x)['z'::text] AS x
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+   x   
+-------
+ "ZZZ"
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (((jb).b)['x'::text]).z
+(2 rows)
+
+SELECT (jb).b['x'].z FROM test_jsonb_dot_notation; -- returns NULL
+ z 
+---
+ 
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT (((jb).b)['x'::text]).z AS z
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+ z 
+---
+ 
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: ((jb).b)['x'::text]['z'::text]
+(2 rows)
+
+SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation; -- returns NULL
+ b 
+---
+ 
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT ((jb).b)['x'::text]['z'::text] AS b
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+ b 
+---
+ 
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb['b'::text]['x'::text]).z
+(2 rows)
+
+SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation; -- returns NULL
+ z 
+---
+ 
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT (jb['b'::text]['x'::text]).z AS z
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+ z 
+---
+ 
 (1 row)
 
+DROP VIEW public.test_jsonb_dot_notation_v1;
+-- clean up
+DROP TABLE test_jsonb_dot_notation;
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 970ed1cffca..bd89678c718 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1304,30 +1304,49 @@ select jsonb_insert('{"a": {"b": "value"}}', '{a, b}', '"new_value"', true);
 
 -- jsonb subscript
 select ('123'::jsonb)['a'];
+select ('123'::jsonb).a;
 select ('123'::jsonb)[0];
 select ('123'::jsonb)[NULL];
+select ('123'::jsonb).NULL;
 select ('{"a": 1}'::jsonb)['a'];
+select ('{"a": 1}'::jsonb).a;
 select ('{"a": 1}'::jsonb)[0];
 select ('{"a": 1}'::jsonb)['not_exist'];
+select ('{"a": 1}'::jsonb)."not_exist";
 select ('{"a": 1}'::jsonb)[NULL];
 select ('[1, "2", null]'::jsonb)['a'];
+select ('[1, "2", null]'::jsonb).a;
 select ('[1, "2", null]'::jsonb)[0];
 select ('[1, "2", null]'::jsonb)['1'];
+select ('[1, "2", null]'::jsonb)."1";
 select ('[1, "2", null]'::jsonb)[1.0];
 select ('[1, "2", null]'::jsonb)[2];
 select ('[1, "2", null]'::jsonb)[3];
 select ('[1, "2", null]'::jsonb)[-2];
 select ('[1, "2", null]'::jsonb)[1]['a'];
+select ('[1, "2", null]'::jsonb)[1].a;
 select ('[1, "2", null]'::jsonb)[1][0];
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['b'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).b;
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d;
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'][1];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d[1];
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d']['a'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['a'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d.a;
+select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d['a'][0];
+select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d.a[0];
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1;
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2;
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']['a3'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2.a3;
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'];
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1;
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'][2];
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1[2];
 
 -- slices are not supported
 select ('{"a": 1}'::jsonb)['a':'b'];
@@ -1608,3 +1627,99 @@ select ('true'::jsonb)::bool;
 select ('true'::jsonb).bool;
 select ('{"text": "hello"}'::jsonb)::text;
 select ('{"text": "hello"}'::jsonb).text;
+
+-- dot notation
+CREATE TABLE test_jsonb_dot_notation AS
+SELECT '{"a": [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], "b": [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]}'::jsonb jb;
+
+SELECT (jb).a FROM test_jsonb_dot_notation;
+SELECT (jb)."a" FROM test_jsonb_dot_notation; -- double quote should work
+SELECT (jb).'a' FROM test_jsonb_dot_notation; -- single quote should not work
+select (jb)[0].a from test_jsonb_dot_notation; -- returns same result as (jb).a
+select (jb)[0]['a'] from test_jsonb_dot_notation; -- returns NULL
+select (jb)[1].a from test_jsonb_dot_notation; -- returns NULL
+SELECT (jb).b FROM test_jsonb_dot_notation;
+SELECT (jb).c FROM test_jsonb_dot_notation;
+SELECT (jb).a.b FROM test_jsonb_dot_notation;
+SELECT (jb).a[2].b FROM test_jsonb_dot_notation;
+SELECT (jb).a.x.y FROM test_jsonb_dot_notation;
+SELECT (jb).b.x.z FROM test_jsonb_dot_notation;
+SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t; -- fails
+SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
+
+-- assignment is not supported
+UPDATE test_jsonb_dot_notation SET jb.a = '1';
+UPDATE test_jsonb_dot_notation SET (jb).a = '1';
+
+-- explains should work
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+
+-- views should work
+CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+
+-- mixed syntax
+DROP VIEW test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
+SELECT (jb)['a'].b FROM test_jsonb_dot_notation; -- returns an array due to lax mode
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+SELECT (jb).a['b'] FROM test_jsonb_dot_notation; -- returns NULL because ['b'] looks for strict match in an object
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation; -- warnings
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;  -- warnings
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+SELECT (jb).b['x'].z FROM test_jsonb_dot_notation; -- returns NULL
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation; -- returns NULL
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
+SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation; -- returns NULL
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+-- clean up
+DROP TABLE test_jsonb_dot_notation;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3c80d49b67e..965cac21d84 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -805,6 +805,7 @@ FdwRoutine
 FetchDirection
 FetchDirectionKeywords
 FetchStmt
+FieldAccessorExpr
 FieldSelect
 FieldStore
 File
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v22-0004-Extract-coerce_jsonpath_subscript.patch (5.5K, ../../CAK98qZ0R1ufQ-uQq8DxOPnmfacrzxBKy1phbUt8TA6+EDj9+PA@mail.gmail.com/4-v22-0004-Extract-coerce_jsonpath_subscript.patch)
  download | inline diff:
From 1467744b23775624f27031a6bc8b71f33bd1c271 Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v22 4/5] Extract coerce_jsonpath_subscript()

This is a preparation step for a future commit that will reuse the
aforementioned function.

Co-authored-by: Nikita Glukhov <[email protected]>
Co-authored-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Jian He <[email protected]>
Reviewed-by: Chao Li <[email protected]>
---
 src/backend/utils/adt/jsonbsubs.c | 128 +++++++++++++++---------------
 1 file changed, 64 insertions(+), 64 deletions(-)

diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index b4824d46a5b..5757334f4eb 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -32,6 +32,69 @@ typedef struct JsonbSubWorkspace
 	Datum	   *index;			/* Subscript values in Datum format */
 } JsonbSubWorkspace;
 
+static Node *
+coerce_jsonpath_subscript_to_int4_or_text(ParseState *pstate, Node *subExpr)
+{
+	Oid			subExprType = exprType(subExpr);
+	Oid			targetType = InvalidOid;
+
+	if (subExprType != UNKNOWNOID)
+	{
+		const Oid	targets[] = {INT4OID, TEXTOID};
+
+		/*
+		 * Jsonb can handle multiple subscript types, but cases when a
+		 * subscript could be coerced to multiple target types must be
+		 * avoided, similar to overloaded functions. It could be possibly
+		 * extend with jsonpath in the future.
+		 */
+		for (int i = 0; i < lengthof(targets); i++)
+		{
+			if (can_coerce_type(1, &subExprType, &targets[i], COERCION_IMPLICIT))
+			{
+				/*
+				 * One type has already succeeded, it means there are two
+				 * coercion targets possible, failure.
+				 */
+				if (OidIsValid(targetType))
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
+							 errhint("jsonb subscript must be coercible to only one type, integer or text."),
+							 parser_errposition(pstate, exprLocation(subExpr))));
+
+				targetType = targets[i];
+			}
+		}
+
+		/*
+		 * No suitable types were found, failure.
+		 */
+		if (!OidIsValid(targetType))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
+					 errhint("jsonb subscript must be coercible to either integer or text."),
+					 parser_errposition(pstate, exprLocation(subExpr))));
+	}
+	else
+		targetType = TEXTOID;
+
+	/*
+	 * We known from can_coerce_type that coercion will succeed, so
+	 * coerce_type could be used. Note the implicit coercion context, which is
+	 * required to handle subscripts of different types, similar to overloaded
+	 * functions.
+	 */
+	subExpr = coerce_type(pstate,
+						  subExpr, subExprType,
+						  targetType, -1,
+						  COERCION_IMPLICIT,
+						  COERCE_IMPLICIT_CAST,
+						  -1);
+
+	return subExpr;
+}
 
 /*
  * Finish parse analysis of a SubscriptingRef expression for a jsonb.
@@ -77,71 +140,8 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 
 		if (ai->uidx)
 		{
-			Oid			subExprType = InvalidOid,
-						targetType = UNKNOWNOID;
-
 			subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
-			subExprType = exprType(subExpr);
-
-			if (subExprType != UNKNOWNOID)
-			{
-				Oid			targets[2] = {INT4OID, TEXTOID};
-
-				/*
-				 * Jsonb can handle multiple subscript types, but cases when a
-				 * subscript could be coerced to multiple target types must be
-				 * avoided, similar to overloaded functions. It could be
-				 * possibly extend with jsonpath in the future.
-				 */
-				for (int i = 0; i < 2; i++)
-				{
-					if (can_coerce_type(1, &subExprType, &targets[i], COERCION_IMPLICIT))
-					{
-						/*
-						 * One type has already succeeded, it means there are
-						 * two coercion targets possible, failure.
-						 */
-						if (targetType != UNKNOWNOID)
-							ereport(ERROR,
-									(errcode(ERRCODE_DATATYPE_MISMATCH),
-									 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
-									 errhint("jsonb subscript must be coercible to only one type, integer or text."),
-									 parser_errposition(pstate, exprLocation(subExpr))));
-
-						targetType = targets[i];
-					}
-				}
-
-				/*
-				 * No suitable types were found, failure.
-				 */
-				if (targetType == UNKNOWNOID)
-					ereport(ERROR,
-							(errcode(ERRCODE_DATATYPE_MISMATCH),
-							 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
-							 errhint("jsonb subscript must be coercible to either integer or text."),
-							 parser_errposition(pstate, exprLocation(subExpr))));
-			}
-			else
-				targetType = TEXTOID;
-
-			/*
-			 * We known from can_coerce_type that coercion will succeed, so
-			 * coerce_type could be used. Note the implicit coercion context,
-			 * which is required to handle subscripts of different types,
-			 * similar to overloaded functions.
-			 */
-			subExpr = coerce_type(pstate,
-								  subExpr, subExprType,
-								  targetType, -1,
-								  COERCION_IMPLICIT,
-								  COERCE_IMPLICIT_CAST,
-								  -1);
-			if (subExpr == NULL)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("jsonb subscript must have text type"),
-						 parser_errposition(pstate, exprLocation(subExpr))));
+			subExpr = coerce_jsonpath_subscript_to_int4_or_text(pstate, subExpr);
 		}
 		else
 		{
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v22-0001-Add-test-coverage-for-indirection-transformation.patch (6.9K, ../../CAK98qZ0R1ufQ-uQq8DxOPnmfacrzxBKy1phbUt8TA6+EDj9+PA@mail.gmail.com/5-v22-0001-Add-test-coverage-for-indirection-transformation.patch)
  download | inline diff:
From bed0db957b9d544d158b1bf7f7cc2c480902137c Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Thu, 18 Sep 2025 15:54:24 -0700
Subject: [PATCH v22 1/5] Add test coverage for indirection transformation

These tests cover nested arrays of composite data types,
single-argument functions, and casting using dot-notation, providing a
baseline for future enhancements to jsonb dot-notation support.
---
 src/test/regress/expected/arrays.out | 26 ++++++--
 src/test/regress/expected/jsonb.out  | 88 ++++++++++++++++++++++++++++
 src/test/regress/sql/arrays.sql      |  7 ++-
 src/test/regress/sql/jsonb.sql       | 18 ++++++
 4 files changed, 132 insertions(+), 7 deletions(-)

diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 69ea2cf5ad8..e1ab6dc278a 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -1782,17 +1782,17 @@ SELECT max(f1), min(f1), max(f2), min(f2), max(f3), min(f3) FROM arraggtest;
 (1 row)
 
 -- A few simple tests for arrays of composite types
-create type comptype as (f1 int, f2 text);
+create type comptype as (f1 int, f2 text, f3 int[]);
 create table comptable (c1 comptype, c2 comptype[]);
 -- XXX would like to not have to specify row() construct types here ...
 insert into comptable
-  values (row(1,'foo'), array[row(2,'bar')::comptype, row(3,'baz')::comptype]);
+  values (row(1,'foo',array[10,20]), array[row(2,'bar',array[30,40])::comptype, row(3,'baz',array[50,60])::comptype]);
 -- check that implicitly named array type _comptype isn't a problem
 create type _comptype as enum('fooey');
 select * from comptable;
-   c1    |          c2           
----------+-----------------------
- (1,foo) | {"(2,bar)","(3,baz)"}
+        c1         |                      c2                       
+-------------------+-----------------------------------------------
+ (1,foo,"{10,20}") | {"(2,bar,\"{30,40}\")","(3,baz,\"{50,60}\")"}
 (1 row)
 
 select c2[2].f2 from comptable;
@@ -1801,6 +1801,22 @@ select c2[2].f2 from comptable;
  baz
 (1 row)
 
+select c2[2].f3 from comptable;
+   f3    
+---------
+ {50,60}
+(1 row)
+
+select c2[2].f3[1:2] from comptable;
+   f3    
+---------
+ {50,60}
+(1 row)
+
+select c2[1:2].f3[1:2] from comptable;
+ERROR:  column notation .f3 applied to type comptype[], which is not a composite type
+LINE 1: select c2[1:2].f3[1:2] from comptable;
+               ^
 drop type _comptype;
 drop table comptable;
 drop type comptype;
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5a1eb18aba2..fb47c8a893a 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5831,3 +5831,91 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
  12345
 (1 row)
 
+-- single argument jsonb functions as jsonb_function(jsonb) and jsonb.jsonb_function
+select jsonb_typeof('{"a":1}'::jsonb);
+ jsonb_typeof 
+--------------
+ object
+(1 row)
+
+select ('{"a":1}'::jsonb).jsonb_typeof;
+ jsonb_typeof 
+--------------
+ object
+(1 row)
+
+select jsonb_array_length('["a", "b", "c"]'::jsonb);
+ jsonb_array_length 
+--------------------
+                  3
+(1 row)
+
+select ('["a", "b", "c"]'::jsonb).jsonb_array_length;
+ jsonb_array_length 
+--------------------
+                  3
+(1 row)
+
+select jsonb_object_keys('{"a":1, "b":2}'::jsonb);
+ jsonb_object_keys 
+-------------------
+ a
+ b
+(2 rows)
+
+select ('{"a":1, "b":2}'::jsonb).jsonb_object_keys;
+ jsonb_object_keys 
+-------------------
+ a
+ b
+(2 rows)
+
+-- cast jsonb to other types as (jsonb)::type and (jsonb).type
+select ('123.45'::jsonb)::numeric;
+ numeric 
+---------
+  123.45
+(1 row)
+
+select ('123.45'::jsonb).numeric;
+ numeric 
+---------
+  123.45
+(1 row)
+
+select ('[{"name": "alice"}, {"name": "bob"}]'::jsonb)::name;
+                 name                 
+--------------------------------------
+ [{"name": "alice"}, {"name": "bob"}]
+(1 row)
+
+select ('[{"name": "alice"}, {"name": "bob"}]'::jsonb).name;
+                 name                 
+--------------------------------------
+ [{"name": "alice"}, {"name": "bob"}]
+(1 row)
+
+select ('true'::jsonb)::bool;
+ bool 
+------
+ t
+(1 row)
+
+select ('true'::jsonb).bool;
+ bool 
+------
+ t
+(1 row)
+
+select ('{"text": "hello"}'::jsonb)::text;
+       text        
+-------------------
+ {"text": "hello"}
+(1 row)
+
+select ('{"text": "hello"}'::jsonb).text;
+       text        
+-------------------
+ {"text": "hello"}
+(1 row)
+
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 47d62c1d38d..450389831a0 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -555,19 +555,22 @@ SELECT max(f1), min(f1), max(f2), min(f2), max(f3), min(f3) FROM arraggtest;
 
 -- A few simple tests for arrays of composite types
 
-create type comptype as (f1 int, f2 text);
+create type comptype as (f1 int, f2 text, f3 int[]);
 
 create table comptable (c1 comptype, c2 comptype[]);
 
 -- XXX would like to not have to specify row() construct types here ...
 insert into comptable
-  values (row(1,'foo'), array[row(2,'bar')::comptype, row(3,'baz')::comptype]);
+  values (row(1,'foo',array[10,20]), array[row(2,'bar',array[30,40])::comptype, row(3,'baz',array[50,60])::comptype]);
 
 -- check that implicitly named array type _comptype isn't a problem
 create type _comptype as enum('fooey');
 
 select * from comptable;
 select c2[2].f2 from comptable;
+select c2[2].f3 from comptable;
+select c2[2].f3[1:2] from comptable;
+select c2[1:2].f3[1:2] from comptable;
 
 drop type _comptype;
 drop table comptable;
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 57c11acddfe..970ed1cffca 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1590,3 +1590,21 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::float8;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int2;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int4;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
+
+-- single argument jsonb functions as jsonb_function(jsonb) and jsonb.jsonb_function
+select jsonb_typeof('{"a":1}'::jsonb);
+select ('{"a":1}'::jsonb).jsonb_typeof;
+select jsonb_array_length('["a", "b", "c"]'::jsonb);
+select ('["a", "b", "c"]'::jsonb).jsonb_array_length;
+select jsonb_object_keys('{"a":1, "b":2}'::jsonb);
+select ('{"a":1, "b":2}'::jsonb).jsonb_object_keys;
+
+-- cast jsonb to other types as (jsonb)::type and (jsonb).type
+select ('123.45'::jsonb)::numeric;
+select ('123.45'::jsonb).numeric;
+select ('[{"name": "alice"}, {"name": "bob"}]'::jsonb)::name;
+select ('[{"name": "alice"}, {"name": "bob"}]'::jsonb).name;
+select ('true'::jsonb)::bool;
+select ('true'::jsonb).bool;
+select ('{"text": "hello"}'::jsonb)::text;
+select ('{"text": "hello"}'::jsonb).text;
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v22-0002-Add-an-alternative-transform-function-in-Subscri.patch (15.9K, ../../CAK98qZ0R1ufQ-uQq8DxOPnmfacrzxBKy1phbUt8TA6+EDj9+PA@mail.gmail.com/6-v22-0002-Add-an-alternative-transform-function-in-Subscri.patch)
  download | inline diff:
From 6d46b6d10cb077bfc2ab261fbd74304d8b19d33e Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Thu, 18 Sep 2025 16:34:47 -0700
Subject: [PATCH v22 2/5] Add an alternative transform function in
 SubscriptRoutines

Add a transform_partial() function pointer to enable processing a
prefix of indirection lists. Data types that support subscripting can
opt to use the transform() function that transforms the full input
indirection list (e.g., arrays, hstore), or can opt to use the
transform_partial() function to be more flexible on indirection node
types, and make best effort in transforming only a prefix of the
indirection list, letting the caller handle the remaining
indirections.

This allows transform functions to accept dot notation indirection as
input, preparing for future JSONB dot notation support.
---
 contrib/hstore/hstore_subs.c      |  1 +
 src/backend/parser/parse_expr.c   | 76 +++++++++++++++----------
 src/backend/parser/parse_node.c   | 92 +++++++++++++++++++++++--------
 src/backend/parser/parse_target.c |  6 +-
 src/backend/utils/adt/arraysubs.c |  2 +
 src/backend/utils/adt/jsonbsubs.c | 26 ++++++---
 src/include/nodes/subscripting.h  | 24 +++++++-
 src/include/parser/parse_node.h   |  3 +-
 8 files changed, 168 insertions(+), 62 deletions(-)

diff --git a/contrib/hstore/hstore_subs.c b/contrib/hstore/hstore_subs.c
index 3d03f66fa0d..d678dc56f86 100644
--- a/contrib/hstore/hstore_subs.c
+++ b/contrib/hstore/hstore_subs.c
@@ -287,6 +287,7 @@ hstore_subscript_handler(PG_FUNCTION_ARGS)
 {
 	static const SubscriptRoutines sbsroutines = {
 		.transform = hstore_subscript_transform,
+		.transform_partial = NULL,
 		.exec_setup = hstore_exec_setup,
 		.fetch_strict = true,	/* fetch returns NULL for NULL inputs */
 		.fetch_leakproof = true,	/* fetch returns NULL for bad subscript */
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index e1979a80c19..95ce330e506 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -436,44 +436,65 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 {
 	Node	   *last_srf = pstate->p_last_srf;
 	Node	   *result = transformExprRecurse(pstate, ind->arg);
-	List	   *subscripts = NIL;
+	List	   *indirections = NIL;
 	int			location = exprLocation(result);
 	ListCell   *i;
 
 	/*
-	 * We have to split any field-selection operations apart from
-	 * subscripting.  Adjacent A_Indices nodes have to be treated as a single
+	 * Combine field names and subscripts into a single indirection list, as
+	 * some subscripting containers, such as jsonb, support field access using
+	 * dot notation. Adjacent A_Indices nodes have to be treated as a single
 	 * multidimensional subscript operation.
 	 */
 	foreach(i, ind->indirection)
 	{
 		Node	   *n = lfirst(i);
 
-		if (IsA(n, A_Indices))
-			subscripts = lappend(subscripts, n);
-		else if (IsA(n, A_Star))
+		if (IsA(n, A_Indices) || IsA(n, String))
+			indirections = lappend(indirections, n);
+		else
 		{
+			Assert(IsA(n, A_Star));
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("row expansion via \"*\" is not supported here"),
 					 parser_errposition(pstate, location)));
 		}
-		else
+	}
+
+	while (indirections)
+	{
+		/* try processing container subscripts first */
+		int			transformed_count = 0;
+		Node	   *newresult = (Node *)
+			transformContainerSubscripts(pstate,
+										 result,
+										 exprType(result),
+										 exprTypmod(result),
+										 indirections,
+										 false,
+										 &transformed_count);
+
+		if (!newresult)
 		{
-			Node	   *newresult;
+			/*
+			 * generic subscripting failed; falling back to field selection
+			 * for a composite type, or a single-argument function.
+			 */
+			Node	   *n;
+
+			Assert(indirections);
 
-			Assert(IsA(n, String));
+			n = linitial(indirections);
 
-			/* process subscripts before this field selection */
-			if (subscripts)
-				result = (Node *) transformContainerSubscripts(pstate,
-															   result,
-															   exprType(result),
-															   exprTypmod(result),
-															   subscripts,
-															   false);
-			subscripts = NIL;
+			if (!IsA(n, String))
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("cannot subscript type %s because it does not support subscripting",
+								format_type_be(exprType(result))),
+						 parser_errposition(pstate, exprLocation(result))));
 
+			/* try to parse function or field selection */
 			newresult = ParseFuncOrColumn(pstate,
 										  list_make1(n),
 										  list_make1(result),
@@ -481,19 +502,18 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 										  NULL,
 										  false,
 										  location);
-			if (newresult == NULL)
+
+			if (!newresult)
 				unknown_attribute(pstate, result, strVal(n), location);
-			result = newresult;
+			else
+				transformed_count = 1;
 		}
+
+		/* remove the processed indirections */
+		indirections = list_delete_first_n(indirections, transformed_count);
+
+		result = newresult;
 	}
-	/* process trailing subscripts, if any */
-	if (subscripts)
-		result = (Node *) transformContainerSubscripts(pstate,
-													   result,
-													   exprType(result),
-													   exprTypmod(result),
-													   subscripts,
-													   false);
 
 	return result;
 }
diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c
index 203b7a32178..803089ab6fc 100644
--- a/src/backend/parser/parse_node.c
+++ b/src/backend/parser/parse_node.c
@@ -238,20 +238,22 @@ transformContainerType(Oid *containerType, int32 *containerTypmod)
  * containerTypMod	typmod for the container
  * indirection		Untransformed list of subscripts (must not be NIL)
  * isAssignment		True if this will become a container assignment.
- */
+ * nSubscripts		Output parameter for number of transformed subscripts.
+*/
 SubscriptingRef *
 transformContainerSubscripts(ParseState *pstate,
 							 Node *containerBase,
 							 Oid containerType,
 							 int32 containerTypMod,
 							 List *indirection,
-							 bool isAssignment)
+							 bool isAssignment,
+							 int *nSubscripts)
 {
 	SubscriptingRef *sbsref;
 	const SubscriptRoutines *sbsroutines;
 	Oid			elementType;
-	bool		isSlice = false;
-	ListCell   *idx;
+
+	*nSubscripts = 0;
 
 	/*
 	 * Determine the actual container type, smashing any domain.  In the
@@ -267,28 +269,15 @@ transformContainerSubscripts(ParseState *pstate,
 	 */
 	sbsroutines = getSubscriptingRoutines(containerType, &elementType);
 	if (!sbsroutines)
+	{
+		if (!isAssignment)
+			return NULL;
+
 		ereport(ERROR,
 				(errcode(ERRCODE_DATATYPE_MISMATCH),
 				 errmsg("cannot subscript type %s because it does not support subscripting",
 						format_type_be(containerType)),
 				 parser_errposition(pstate, exprLocation(containerBase))));
-
-	/*
-	 * Detect whether any of the indirection items are slice specifiers.
-	 *
-	 * A list containing only simple subscripts refers to a single container
-	 * element.  If any of the items are slice specifiers (lower:upper), then
-	 * the subscript expression means a container slice operation.
-	 */
-	foreach(idx, indirection)
-	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
-
-		if (ai->is_slice)
-		{
-			isSlice = true;
-			break;
-		}
 	}
 
 	/*
@@ -309,8 +298,65 @@ transformContainerSubscripts(ParseState *pstate,
 	 * Call the container-type-specific logic to transform the subscripts and
 	 * determine the subscripting result type.
 	 */
-	sbsroutines->transform(sbsref, indirection, pstate,
-						   isSlice, isAssignment);
+	Assert((sbsroutines->transform_partial == NULL) ^ (sbsroutines->transform == NULL));
+	if (sbsroutines->transform_partial != NULL)
+	{
+		/*
+		 * If the container type provides a partial transform function, use it
+		 * here. This function can accept any node types in the indirection
+		 * list as input, and is responsible for identifying and transforming
+		 * as many leading elements as it can handle, which may be only a
+		 * prefix of the indirection list. For example, it might process
+		 * A_Indices nodes, String nodes (for jsonb dot-notation), or other
+		 * node types, depending on the container's requirements. It returns
+		 * the number of elements it transformed.
+		 */
+		*nSubscripts = sbsroutines->transform_partial(sbsref, indirection, pstate, isAssignment);
+	}
+	else
+	{
+		/*
+		 * Full transform only accepts bracket subscripts (A_Indices nodes).
+		 * We pre-collect the leading A_Indices nodes from the indirection
+		 * list, then call the transform function to process this prefix of
+		 * subscripts.
+		 */
+		List	   *subscriptlist = NIL;
+		ListCell   *lc;
+		bool		isSlice = false;
+
+		/* Collect leading A_Indices subscripts */
+		foreach(lc, indirection)
+		{
+			Node	   *n = lfirst(lc);
+
+			if (IsA(n, A_Indices))
+			{
+				A_Indices  *ai = (A_Indices *) n;
+
+				subscriptlist = lappend(subscriptlist, n);
+				if (ai->is_slice)
+					isSlice = true;
+			}
+			else
+				break;
+		}
+
+		if (subscriptlist)
+			sbsroutines->transform(sbsref, subscriptlist, pstate, isSlice, isAssignment);
+
+		*nSubscripts = list_length(subscriptlist);
+	}
+
+	if (*nSubscripts == 0)
+	{
+		/* Fallback to field selection in caller */
+		if (!isAssignment)
+			return NULL;
+
+		/* This should not happen with well-behaved transform functions */
+		elog(ERROR, "subscripting transform function failed to consume any indirection elements");
+	}
 
 	/*
 	 * Verify we got a valid type (this defends, for example, against someone
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 905c975d83b..a14f8fb85bf 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -922,6 +922,7 @@ transformAssignmentSubscripts(ParseState *pstate,
 	Oid			typeNeeded;
 	int32		typmodNeeded;
 	Oid			collationNeeded;
+	int			nSubscripts = 0;
 
 	Assert(subscripts != NIL);
 
@@ -936,7 +937,10 @@ transformAssignmentSubscripts(ParseState *pstate,
 										  containerType,
 										  containerTypMod,
 										  subscripts,
-										  true);
+										  true,
+										  &nSubscripts);
+
+	Assert(nSubscripts == list_length(subscripts));
 
 	typeNeeded = sbsref->refrestype;
 	typmodNeeded = sbsref->reftypmod;
diff --git a/src/backend/utils/adt/arraysubs.c b/src/backend/utils/adt/arraysubs.c
index 2940fb8e8d7..0049907b942 100644
--- a/src/backend/utils/adt/arraysubs.c
+++ b/src/backend/utils/adt/arraysubs.c
@@ -541,6 +541,7 @@ array_subscript_handler(PG_FUNCTION_ARGS)
 {
 	static const SubscriptRoutines sbsroutines = {
 		.transform = array_subscript_transform,
+		.transform_partial = NULL,
 		.exec_setup = array_exec_setup,
 		.fetch_strict = true,	/* fetch returns NULL for NULL inputs */
 		.fetch_leakproof = true,	/* fetch returns NULL for bad subscript */
@@ -568,6 +569,7 @@ raw_array_subscript_handler(PG_FUNCTION_ARGS)
 {
 	static const SubscriptRoutines sbsroutines = {
 		.transform = array_subscript_transform,
+		.transform_partial = NULL,
 		.exec_setup = array_exec_setup,
 		.fetch_strict = true,	/* fetch returns NULL for NULL inputs */
 		.fetch_leakproof = true,	/* fetch returns NULL for bad subscript */
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index e8626d3b4fc..b4824d46a5b 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -36,14 +36,16 @@ typedef struct JsonbSubWorkspace
 /*
  * Finish parse analysis of a SubscriptingRef expression for a jsonb.
  *
- * Transform the subscript expressions, coerce them to text,
- * and determine the result type of the SubscriptingRef node.
+ * This is a partial transform function that transforms the subscript
+ * expressions, coerces them to text, and determines the result type
+ * of the SubscriptingRef node.
+ *
+ * Returns the number of indirection elements processed.
  */
-static void
+static int
 jsonb_subscript_transform(SubscriptingRef *sbsref,
 						  List *indirection,
 						  ParseState *pstate,
-						  bool isSlice,
 						  bool isAssignment)
 {
 	List	   *upperIndexpr = NIL;
@@ -55,10 +57,15 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	 */
 	foreach(idx, indirection)
 	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
+		A_Indices  *ai;
 		Node	   *subExpr;
 
-		if (isSlice)
+		if (!IsA(lfirst(idx), A_Indices))
+			break;
+
+		ai = lfirst_node(A_Indices, idx);
+
+		if (ai->is_slice)
 		{
 			Node	   *expr = ai->uidx ? ai->uidx : ai->lidx;
 
@@ -142,7 +149,7 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 			 * Slice with omitted upper bound. Should not happen as we already
 			 * errored out on slice earlier, but handle this just in case.
 			 */
-			Assert(isSlice && ai->is_slice);
+			Assert(ai->is_slice);
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
 					 errmsg("jsonb subscript does not support slices"),
@@ -159,6 +166,8 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	/* Determine the result type of the subscripting operation; always jsonb */
 	sbsref->refrestype = JSONBOID;
 	sbsref->reftypmod = -1;
+
+	return list_length(upperIndexpr);
 }
 
 /*
@@ -402,7 +411,8 @@ Datum
 jsonb_subscript_handler(PG_FUNCTION_ARGS)
 {
 	static const SubscriptRoutines sbsroutines = {
-		.transform = jsonb_subscript_transform,
+		.transform = NULL,		/* jsonb uses partial transform instead */
+		.transform_partial = jsonb_subscript_transform,
 		.exec_setup = jsonb_exec_setup,
 		.fetch_strict = true,	/* fetch returns NULL for NULL inputs */
 		.fetch_leakproof = true,	/* fetch returns NULL for bad subscript */
diff --git a/src/include/nodes/subscripting.h b/src/include/nodes/subscripting.h
index e991f4bf826..008076b8b57 100644
--- a/src/include/nodes/subscripting.h
+++ b/src/include/nodes/subscripting.h
@@ -98,6 +98,25 @@ typedef void (*SubscriptTransform) (SubscriptingRef *sbsref,
 									bool isSlice,
 									bool isAssignment);
 
+/*
+ * SubscriptTransformPartial is an alternative to SubscriptTransform for
+ * container types that can accept non-A_Indices indirection as input
+ * (e.g., JSONB accepts dot-notation (String node) for field access).
+
+ * It may transform a prefix of the indirection list and leave the rest
+ * unprocessed. It returns the number of indirections it transformed.
+ * The caller will then remove that many items from the head of the
+ * list, and handle the remaining indirections differently or to raise
+ * an error as needed.
+ *
+ * If transform_partial is NULL, the complete transform function is used,
+ * which accepts only A_Indices (bracket) nodes.
+ */
+typedef int (*SubscriptTransformPartial) (SubscriptingRef *sbsref,
+										  List *indirection,
+										  ParseState *pstate,
+										  bool isAssignment);
+
 /*
  * The exec_setup method is called during executor-startup compilation of a
  * SubscriptingRef node in an expression.  It must fill *methods with pointers
@@ -157,7 +176,10 @@ typedef void (*SubscriptExecSetup) (const SubscriptingRef *sbsref,
 /* Struct returned by the SQL-visible subscript handler function */
 typedef struct SubscriptRoutines
 {
-	SubscriptTransform transform;	/* parse analysis function */
+	SubscriptTransform transform;	/* parse analysis function, or NULL */
+	SubscriptTransformPartial transform_partial;	/* alternative parse
+													 * analysis function, or
+													 * NULL */
 	SubscriptExecSetup exec_setup;	/* expression compilation function */
 	bool		fetch_strict;	/* is fetch SubscriptRef strict? */
 	bool		fetch_leakproof;	/* is fetch SubscriptRef leakproof? */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index f7d07c84542..80f486acff0 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -362,7 +362,8 @@ extern SubscriptingRef *transformContainerSubscripts(ParseState *pstate,
 													 Oid containerType,
 													 int32 containerTypMod,
 													 List *indirection,
-													 bool isAssignment);
+													 bool isAssignment,
+													 int *consumed_count);
 extern Const *make_const(ParseState *pstate, A_Const *aconst);
 
 #endif							/* PARSE_NODE_H */
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v22-0003-Export-jsonPathFromParseResult.patch (2.8K, ../../CAK98qZ0R1ufQ-uQq8DxOPnmfacrzxBKy1phbUt8TA6+EDj9+PA@mail.gmail.com/7-v22-0003-Export-jsonPathFromParseResult.patch)
  download | inline diff:
From 6d48ceb2063932b679e681ed84d3cce9b9215dce Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v22 3/5] Export jsonPathFromParseResult()

This is a preparation step for a future commit that will reuse the
aforementioned function.

Authored-by: Nikita Glukhov <[email protected]>
Reviewed-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Jian He <[email protected]>
Reviewed-by: Chao Li <[email protected]>
---
 src/backend/utils/adt/jsonpath.c | 19 +++++++++++++++----
 src/include/utils/jsonpath.h     |  4 ++++
 2 files changed, 19 insertions(+), 4 deletions(-)

diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index 762f7e8a09d..c83774b2a16 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -166,15 +166,13 @@ jsonpath_send(PG_FUNCTION_ARGS)
  * Converts C-string to a jsonpath value.
  *
  * Uses jsonpath parser to turn string into an AST, then
- * flattenJsonPathParseItem() does second pass turning AST into binary
+ * jsonPathFromParseResult() does second pass turning AST into binary
  * representation of jsonpath.
  */
 static Datum
 jsonPathFromCstring(char *in, int len, struct Node *escontext)
 {
 	JsonPathParseResult *jsonpath = parsejsonpath(in, len, escontext);
-	JsonPath   *res;
-	StringInfoData buf;
 
 	if (SOFT_ERROR_OCCURRED(escontext))
 		return (Datum) 0;
@@ -185,8 +183,21 @@ jsonPathFromCstring(char *in, int len, struct Node *escontext)
 				 errmsg("invalid input syntax for type %s: \"%s\"", "jsonpath",
 						in)));
 
+	return jsonPathFromParseResult(jsonpath, 4 * len, escontext);
+}
+
+/*
+ * Converts jsonpath Abstract Syntax Tree (AST) into jsonpath value in binary.
+ */
+Datum
+jsonPathFromParseResult(const JsonPathParseResult *jsonpath, int estimated_len,
+						struct Node *escontext)
+{
+	JsonPath   *res;
+	StringInfoData buf;
+
 	initStringInfo(&buf);
-	enlargeStringInfo(&buf, 4 * len /* estimation */ );
+	enlargeStringInfo(&buf, estimated_len);
 
 	appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
 
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index 23a76d233e9..0958bc22bb6 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -281,6 +281,10 @@ extern JsonPathParseResult *parsejsonpath(const char *str, int len,
 extern bool jspConvertRegexFlags(uint32 xflags, int *result,
 								 struct Node *escontext);
 
+extern Datum jsonPathFromParseResult(const JsonPathParseResult *jsonpath,
+									 int estimated_len,
+									 struct Node *escontext);
+
 /*
  * Struct for details about external variables passed into jsonpath executor
  */
-- 
2.39.5 (Apple Git-154)



^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-06-24 13:29                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-07-09 08:01                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-07-10 08:53                                 ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-21 04:53                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-22 08:19                                     ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-22 19:33                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-25 08:09                                         ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-26 03:52                                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-29 07:22                                             ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-02 02:59                                               ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-02 03:32                                                 ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-03 02:20                                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-09-03 06:56                                                     ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-09 23:53                                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-09-10 04:03                                                         ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-10 23:56                                                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-09-15 18:32                                                             ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-09-19 20:40                                                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-09-22 03:32                                                                 ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-22 17:31                                                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-09-23 05:48                                                                     ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-24 01:05                                                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
@ 2025-09-24 06:37                                                                         ` Chao Li <[email protected]>
  1 sibling, 0 replies; 67+ messages in thread

From: Chao Li @ 2025-09-24 06:37 UTC (permalink / raw)
  To: Alexandra Wang <[email protected]>; +Cc: pgsql-hackers; Nikita Glukhov <[email protected]>; jian he <[email protected]>; Nikita Malakhov <[email protected]>; Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; David E. Wheeler <[email protected]>



> On Sep 24, 2025, at 09:05, Alexandra Wang <[email protected]> wrote:
> 
> This is the expected behavior for array subscripting, and my patch
> doesn't change that. I don't think this is a problem. With or without
> my patch, you can avoid the ERROR by adding parentheses:
> 
> test=# select ((p).meta[1])['a'] from people; meta ------ 30 (1 row)
> 

Okay, make sense.


>> 2 - 0002
>> ```
>> +		/* Collect leading A_Indices subscripts */
>> +		foreach(lc, indirection)
>> +		{
>> +			Node	   *n = lfirst(lc);
>> +
>> +			if (IsA(n, A_Indices))
>> +			{
>> +				A_Indices  *ai = (A_Indices *) n;
>> +
>> +				subscriptlist = lappend(subscriptlist, n);
>> +				if (ai->is_slice)
>> +					isSlice = true;
>> +			}
>> +			else
>> +				break;
>> ```
>> 
>> We can break after “isSlice=true”.
> 
> Why? We still want to get the whole prefix list of A_Indices. 

Ah, I just realized the main goal of the foreach loop is to build a new “subscrptlist”. In that case, it should not break.

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/






^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-06-24 13:29                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-07-09 08:01                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-07-10 08:53                                 ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-21 04:53                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-22 08:19                                     ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-22 19:33                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-25 08:09                                         ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-26 03:52                                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-29 07:22                                             ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-02 02:59                                               ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-02 03:32                                                 ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-03 02:20                                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-09-03 06:56                                                     ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-09 23:53                                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-09-10 04:03                                                         ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-10 23:56                                                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-09-15 18:32                                                             ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-09-19 20:40                                                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-09-22 03:32                                                                 ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-22 17:31                                                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-09-23 05:48                                                                     ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-24 01:05                                                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
@ 2025-12-10 15:11                                                                         ` jian he <[email protected]>
  1 sibling, 0 replies; 67+ messages in thread

From: jian he @ 2025-12-10 15:11 UTC (permalink / raw)
  To: Alexandra Wang <[email protected]>; +Cc: Chao Li <[email protected]>; pgsql-hackers; Nikita Glukhov <[email protected]>; Nikita Malakhov <[email protected]>; Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; David E. Wheeler <[email protected]>

On Wed, Sep 24, 2025 at 9:06 AM Alexandra Wang
<[email protected]> wrote:
>
> The rest of your feedback I've made changes accordingly as you suggested.
>
> Best,
> Alex
>

hi.

+  <para>
+   PostgreSQL implements the JSON simplified accessor as specified in SQL:2023.
not sure we need to decorated SQL:2023 as <acronym>SQL:2023</acronym>,
but PostgreSQL should be decorated as <productname>PostgreSQL</productname>.

I believe
SELECT * FROM users WHERE profile.preferences.theme = '"dark"';
should be
SELECT * FROM users WHERE (profile).preferences.theme = '"dark"';

+INSERT INTO test_table VALUES
+  ('{"brightness": 80}'),                      -- Object case
+  ('[{"brightness": 45}, {"brightness": 90}]'); -- Array case
comments no need, i think.

+  <sect3 id="jsonb-access-method-comparison">
+   <title>Comparison of JSON Access Methods</title>
+   <para>
I am worried that the wording "Access Methods" would be confused with "Table
Access Methods".

+-- Comparison with other access methods (NOT equivalent - different semantics):
+SELECT json_col['address']['city'];           -- Subscripting
+SELECT json_col->'address'->'city';           -- Operator
+SELECT json_col.address.city;                 -- Simplified accessor
(different behavior)
+</programlisting>
"access methods" would be confusing as mentioned above.
also these SQL query with SELECT is wrong? since no FROM clause.
again, I think the last one should be
SELECT (json_col).address.city;
we generally expect </programlisting> content can be passed to psql.


+-- Different behaviors:
+SELECT data.brightness FROM test_table;         -- Simplified accessor
+-- Results: 80, [45, 90]  (array elements unwrapped, results wrapped)

Again, here I believe, it should be
SELECT (data).brightness FROM test_table;
also the Results should be two rows, so this needs to change.

+<programlisting>
+-- Setup data
+INSERT INTO test_table VALUES ('{"weather": "sunny", "temperature": "72F"}');
+
+-- Different behaviors when accessing [0] on a non-array value:
+SELECT data[0] FROM test_table;                 -- Simplified
accessor (lax mode, if dots present elsewhere)
+-- Result: {"weather": "sunny", "temperature": "72F"}  (object
wrapped as array, [0] returns entire object)
+
there is no GUC about json lex mode or not, we can only specify it via jsonpath.
but in the HEAD
select (jsonb '{"weather": "sunny", "temperature": "72F"}')[0];
return NULL.
so I am confused with the above comment.


+<programlisting>
+-- All parts use simplified accessor (standard behavior)
+SELECT data.location.coordinates.latitude FROM table;  -- Good
+SELECT data.repertoire[0].title FROM table;           -- Good
+SELECT data.users[1].profile.email FROM table;        -- Good
+</programlisting>
+   </para>
TABLE is a reserved word, ``SELECT FROM table;`` will result in syntax error.
That means most of the examples in
<sect3 id="jsonb-accessor-best-practices"> needs more polishing.


+  <sect3 id="jsonb-accessor-best-practices">
+   <title>Best Practices: Avoid Mixing Access Methods</title>
+   <para>
+    <emphasis>Important:</emphasis> Do not mix SQL:2023 simplified
accessor syntax
+    with pre-standard subscripting syntax in the same accessor chain. These
+    methods have subtly different semantics and are not
interchangeable aliases.
+    Mixing them can lead to confusion and code that is difficult to understand.
+   </para>
If we want users not to confuse SQL:2023 simplified accessor with pre-standard
subscripting syntax, we can wrap this important information in a <note> tag.

some changes are reflected on the attached file, but some I don't know
how to change,
so I didn't do it.
some sgml lines are way too line, I have split them into separate lines.


--
jian
https://www.enterprisedb.com


Attachments:

  [application/octet-stream] v22-doc_change.nocfbot (7.3K, ../../CACJufxGqZ6NvyY5CL2zK07ebDFyADZrAi2G_P3V=9Aau5ifS0g@mail.gmail.com/2-v22-doc_change.nocfbot)
  download

^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-06-24 13:29                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-07-09 08:01                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-07-10 08:53                                 ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-21 04:53                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-22 08:19                                     ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-22 19:33                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-25 08:09                                         ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-26 03:52                                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-29 07:22                                             ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-02 02:59                                               ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
@ 2025-09-03 02:16                                                 ` Alexandra Wang <[email protected]>
  2025-09-03 03:56                                                   ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-03 07:11                                                   ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  1 sibling, 2 replies; 67+ messages in thread

From: Alexandra Wang @ 2025-09-03 02:16 UTC (permalink / raw)
  To: Chao Li <[email protected]>; +Cc: jian he <[email protected]>; Nikita Malakhov <[email protected]>; Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>

Hi Chao,

Thanks for reviewing! I've attached v15, which addresses your
feedback.

I didn't make all the changes you suggested, please see my individual
comments below and in the next email.

On Mon, Sep 1, 2025 at 7:59 PM Chao Li <[email protected]> wrote:

> --- a/src/backend/parser/parse_node.c
> +++ b/src/backend/parser/parse_node.c
> @@ -244,7 +244,7 @@ transformContainerSubscripts(ParseState *pstate,
>                                                          Node
> *containerBase,
>                                                          Oid containerType,
>                                                          int32
> containerTypMod,
> -                                                        List *indirection,
> +                                                        List
> **indirection,
>                                                          bool isAssignment)
>  {
>         SubscriptingRef *sbsref;
> @@ -280,7 +280,7 @@ transformContainerSubscripts(ParseState *pstate,
>          * element.  If any of the items are slice specifiers
> (lower:upper), then
>          * the subscript expression means a container slice operation.
>          */
> -       foreach(idx, indirection)
> +       foreach(idx, *indirection)
>         {
>                 A_Indices  *ai = lfirst_node(A_Indices, idx);
>
> Should this foreach be pull up to out of transformContainerSubscripts()?
> Originally transformContainerSubscripts() runs only once, but now it’s
> placed in a while loop, and every time this foreach needs to go through the
> entire indirection list, which are duplicated computation.
>
>
> After revisiting the code, I think this loop of checking is_slice should
> be removed here.
>
> It seems only arrsubs cares about this is_slice flag.
>
> hstore_subs can only take a single indirection, so it can do a simple
> check like:
>
> ```
> ai = initial_node(A_Indices, *indirection);
> If (list_length(*indirection) != 1 || ai->is_slice)
> {
>     ereport(…)
> }
> ```
>
> jsonbsubs doesn’t need to check is_slice flag as well, I will explain that
> in my next email tougher with my new comments.
>
> So that, “bool isSlace” can be removed from SubscriptTransform, and let
> every individual subs check is_slice.
>

I don't like the idea of removing the "bool isSlice" argument from the
*SubscriptTransform() function pointer. This patch series should make
only minimal changes to the subscripting API. While it's true that
each implementation can easily determine the boolean value of isSlice
itself, I don't see a clear benefit to changing the interface. As you
noted, arrsubs cares about isSlice; and users can also define custom
data types that support subscripting, so the interface is not limited
to built-in types.

As the comment for *SubscriptTransform() explains:

> (Of course, if the transform method
> * does not care to support slicing, it can just throw an error if isSlice.)


It's possible that in the end the "isSlice" argument isn't needed at
all, but I don’t think this patch set is the right place for that
refactor.

Best,
Alex


Attachments:

  [application/octet-stream] v15-0007-Implement-jsonb-wildcard-member-accessor.patch (31.4K, ../../CAK98qZ1XFee_x1uQFcwJkZCSVcPBy-A1ib3N5Ls2uDdZviKVOw@mail.gmail.com/3-v15-0007-Implement-jsonb-wildcard-member-accessor.patch)
  download | inline diff:
From b4f3704a30a0c41915750c1cecf2ba5640f00bf8 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v15 7/7] Implement jsonb wildcard member accessor

This commit adds support for wildcard member access in jsonb, as
specified by the JSON simplified accessor syntax in SQL:2023.

Co-authored-by: Nikita Glukhov <[email protected]>
Co-authored-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Mark Dilger <[email protected]>
Reviewed-by: Jian He <[email protected]>
Reviewed-by: Vik Fearing <[email protected]>
Reviewed-by: Nikita Malakhov <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Jelte Fennema-Nio <[email protected]>
Tested-by: Jelte Fennema-Nio <[email protected]>
---
 src/backend/nodes/nodeFuncs.c                 |   8 +
 src/backend/parser/gram.y                     |   2 +
 src/backend/parser/parse_expr.c               |  34 ++-
 src/backend/parser/parse_target.c             |  67 ++++--
 src/backend/utils/adt/jsonbsubs.c             |  12 +-
 src/backend/utils/adt/ruleutils.c             |   6 +-
 src/include/nodes/primnodes.h                 |  12 +
 src/include/parser/parse_expr.h               |   3 +
 .../ecpg/test/expected/sql-sqljson.c          |  18 +-
 .../ecpg/test/expected/sql-sqljson.stderr     |  23 +-
 .../ecpg/test/expected/sql-sqljson.stdout     |   2 +
 src/interfaces/ecpg/test/sql/sqljson.pgc      |   5 +-
 src/test/regress/expected/jsonb.out           | 222 +++++++++++++++++-
 src/test/regress/sql/jsonb.sql                |  42 +++-
 14 files changed, 405 insertions(+), 51 deletions(-)

diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index d1bd575d9bd..5f3038a1c26 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -291,6 +291,9 @@ exprType(const Node *expr)
 			 */
 			type = TEXTOID;
 			break;
+		case T_Star:
+			type = UNKNOWNOID;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			type = InvalidOid;	/* keep compiler quiet */
@@ -1156,6 +1159,9 @@ exprSetCollation(Node *expr, Oid collation)
 		case T_FieldAccessorExpr:
 			((FieldAccessorExpr *) expr)->faecollid = collation;
 			break;
+		case T_Star:
+			Assert(!OidIsValid(collation));
+			break;
 		case T_SubscriptingRef:
 			((SubscriptingRef *) expr)->refcollid = collation;
 			break;
@@ -2140,6 +2146,7 @@ expression_tree_walker_impl(Node *node,
 		case T_CTESearchClause:
 		case T_MergeSupportFunc:
 		case T_FieldAccessorExpr:
+		case T_Star:
 			/* primitive node types with no expression subnodes */
 			break;
 		case T_WithCheckOption:
@@ -3020,6 +3027,7 @@ expression_tree_mutator_impl(Node *node,
 		case T_CTESearchClause:
 		case T_MergeSupportFunc:
 		case T_FieldAccessorExpr:
+		case T_Star:
 			return copyObject(node);
 		case T_WithCheckOption:
 			{
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index db43034b9db..703c7416b0d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -18979,6 +18979,7 @@ check_func_name(List *names, core_yyscan_t yyscanner)
 static List *
 check_indirection(List *indirection, core_yyscan_t yyscanner)
 {
+#if 0
 	ListCell   *l;
 
 	foreach(l, indirection)
@@ -18989,6 +18990,7 @@ check_indirection(List *indirection, core_yyscan_t yyscanner)
 				parser_yyerror("improper use of \"*\"");
 		}
 	}
+#endif
 	return indirection;
 }
 
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index ff104c95311..ad721c0ec9c 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -73,7 +73,6 @@ static Node *transformColumnRef(ParseState *pstate, ColumnRef *cref);
 static Node *transformWholeRowRef(ParseState *pstate,
 								  ParseNamespaceItem *nsitem,
 								  int sublevels_up, int location);
-static Node *transformIndirection(ParseState *pstate, A_Indirection *ind);
 static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
 static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
 static Node *transformJsonObjectConstructor(ParseState *pstate,
@@ -157,7 +156,7 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 			break;
 
 		case T_A_Indirection:
-			result = transformIndirection(pstate, (A_Indirection *) expr);
+			result = transformIndirection(pstate, (A_Indirection *) expr, NULL);
 			break;
 
 		case T_A_ArrayExpr:
@@ -431,8 +430,9 @@ unknown_attribute(ParseState *pstate, Node *relref, const char *attname,
 	}
 }
 
-static Node *
-transformIndirection(ParseState *pstate, A_Indirection *ind)
+Node *
+transformIndirection(ParseState *pstate, A_Indirection *ind,
+					 bool *trailing_star_expansion)
 {
 	Node	   *last_srf = pstate->p_last_srf;
 	Node	   *result = transformExprRecurse(pstate, ind->arg);
@@ -453,12 +453,7 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		if (IsA(n, A_Indices))
 			subscripts = lappend(subscripts, n);
 		else if (IsA(n, A_Star))
-		{
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("row expansion via \"*\" is not supported here"),
-					 parser_errposition(pstate, location)));
-		}
+			subscripts = lappend(subscripts, n);
 		else
 		{
 			Assert(IsA(n, String));
@@ -490,7 +485,21 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 
 			n = linitial(subscripts);
 
-			if (!IsA(n, String))
+			if (IsA(n, A_Star))
+			{
+				/* Success, if trailing star expansion is allowed */
+				if (trailing_star_expansion && list_length(subscripts) == 1)
+				{
+					*trailing_star_expansion = true;
+					return result;
+				}
+
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("row expansion via \"*\" is not supported here"),
+						 parser_errposition(pstate, location)));
+			}
+			else if (!IsA(n, String))
 				ereport(ERROR,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
 						 errmsg("cannot subscript type %s because it does not support subscripting",
@@ -516,6 +525,9 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		result = newresult;
 	}
 
+	if (trailing_star_expansion)
+		*trailing_star_expansion = false;
+
 	return result;
 }
 
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index b89736ff1ea..85c05c7434c 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -47,7 +47,7 @@ static Node *transformAssignmentSubscripts(ParseState *pstate,
 static List *ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
 								 bool make_target_entry);
 static List *ExpandAllTables(ParseState *pstate, int location);
-static List *ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
+static Node *ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
 								   bool make_target_entry, ParseExprKind exprKind);
 static List *ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 							   int sublevels_up, int location,
@@ -133,6 +133,7 @@ transformTargetList(ParseState *pstate, List *targetlist,
 	foreach(o_target, targetlist)
 	{
 		ResTarget  *res = (ResTarget *) lfirst(o_target);
+		Node	   *transformed = NULL;
 
 		/*
 		 * Check for "something.*".  Depending on the complexity of the
@@ -161,13 +162,19 @@ transformTargetList(ParseState *pstate, List *targetlist,
 
 				if (IsA(llast(ind->indirection), A_Star))
 				{
-					/* It is something.*, expand into multiple items */
-					p_target = list_concat(p_target,
-										   ExpandIndirectionStar(pstate,
-																 ind,
-																 true,
-																 exprKind));
-					continue;
+					Node	   *columns = ExpandIndirectionStar(pstate,
+																ind,
+																true,
+																exprKind);
+
+					if (IsA(columns, List))
+					{
+						/* It is something.*, expand into multiple items */
+						p_target = list_concat(p_target, (List *) columns);
+						continue;
+					}
+
+					transformed = (Node *) columns;
 				}
 			}
 		}
@@ -179,7 +186,7 @@ transformTargetList(ParseState *pstate, List *targetlist,
 		p_target = lappend(p_target,
 						   transformTargetEntry(pstate,
 												res->val,
-												NULL,
+												transformed,
 												exprKind,
 												res->name,
 												false));
@@ -250,10 +257,15 @@ transformExpressionList(ParseState *pstate, List *exprlist,
 
 			if (IsA(llast(ind->indirection), A_Star))
 			{
-				/* It is something.*, expand into multiple items */
-				result = list_concat(result,
-									 ExpandIndirectionStar(pstate, ind,
-														   false, exprKind));
+				Node	   *cols = ExpandIndirectionStar(pstate, ind,
+														 false, exprKind);
+
+				if (!cols || IsA(cols, List))
+					/* It is something.*, expand into multiple items */
+					result = list_concat(result, (List *) cols);
+				else
+					result = lappend(result, cols);
+
 				continue;
 			}
 		}
@@ -1344,22 +1356,30 @@ ExpandAllTables(ParseState *pstate, int location)
  * For robustness, we use a separate "make_target_entry" flag to control
  * this rather than relying on exprKind.
  */
-static List *
+static Node *
 ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
 					  bool make_target_entry, ParseExprKind exprKind)
 {
 	Node	   *expr;
+	ParseExprKind sv_expr_kind;
+	bool		trailing_star_expansion = false;
+
+	/* Save and restore identity of expression type we're parsing */
+	Assert(exprKind != EXPR_KIND_NONE);
+	sv_expr_kind = pstate->p_expr_kind;
+	pstate->p_expr_kind = exprKind;
 
 	/* Strip off the '*' to create a reference to the rowtype object */
-	ind = copyObject(ind);
-	ind->indirection = list_truncate(ind->indirection,
-									 list_length(ind->indirection) - 1);
+	expr = transformIndirection(pstate, ind, &trailing_star_expansion);
+
+	pstate->p_expr_kind = sv_expr_kind;
 
-	/* And transform that */
-	expr = transformExpr(pstate, (Node *) ind, exprKind);
+	/* '*' was consumed by generic type subscripting */
+	if (!trailing_star_expansion)
+		return expr;
 
 	/* Expand the rowtype expression into individual fields */
-	return ExpandRowReference(pstate, expr, make_target_entry);
+	return (Node *) ExpandRowReference(pstate, expr, make_target_entry);
 }
 
 /*
@@ -1784,13 +1804,18 @@ FigureColnameInternal(Node *node, char **name)
 				char	   *fname = NULL;
 				ListCell   *l;
 
-				/* find last field name, if any, ignoring "*" and subscripts */
+				/*
+				 * find last field name, if any, ignoring subscripts, and use
+				 * '?column?' when there's a trailing '*'.
+				 */
 				foreach(l, ind->indirection)
 				{
 					Node	   *i = lfirst(l);
 
 					if (IsA(i, String))
 						fname = strVal(i);
+					else if (IsA(i, A_Star))
+						fname = "?column?";
 				}
 				if (fname)
 				{
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index 369f40ec044..374040b3b4e 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -144,7 +144,7 @@ jsonb_check_jsonpath_needed(List *indirection, bool isSlice)
 	{
 		Node	   *accessor = lfirst(lc);
 
-		if (IsA(accessor, String))
+		if (IsA(accessor, String) || IsA(accessor, A_Star))
 			return true;
 		else
 			Assert(IsA(accessor, A_Indices));
@@ -355,6 +355,16 @@ jsonb_subscript_make_jsonpath(ParseState *pstate, List **indirection, Subscripti
 				}
 			}
 		}
+		else if (IsA(accessor, A_Star))
+		{
+			Star *star_node;
+			jpi = make_jsonpath_item(jpiAnyKey);
+
+			star_node = makeNode(Star);
+			star_node->type = T_Star;
+
+			sbsref->refupperindexpr = lappend(sbsref->refupperindexpr, star_node);
+		}
 		else
 		{
 			/*
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index baa3ae97d57..ace0eff52e2 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -13010,7 +13010,11 @@ printSubscripts(SubscriptingRef *sbsref, deparse_context *context)
 	{
 		Node *upper = (Node *) lfirst(uplist_item);
 
-		if (upper && IsA(upper, FieldAccessorExpr))
+		if (upper && IsA(upper, Star))
+		{
+			appendStringInfoString(buf, ".*");
+		}
+		else if (upper && IsA(upper, FieldAccessorExpr))
 		{
 			FieldAccessorExpr *fae = (FieldAccessorExpr *) upper;
 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 7e89621bd65..7e418830f22 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -2419,4 +2419,16 @@ typedef struct FieldAccessorExpr
 	Oid			faecollid pg_node_attr(query_jumble_ignore);
 }			FieldAccessorExpr;
 
+/*
+ * Star - represents a wildcard member accessor (e.g., ".*") used in JSONB simplified accessor.
+ *
+ * This node serves as a syntactic placeholder in the expression tree and does not get evaluated, hence it
+ * has no associated type or collation.
+ */
+typedef struct Star
+{
+	NodeTag		type;
+}			Star;
+
+
 #endif							/* PRIMNODES_H */
diff --git a/src/include/parser/parse_expr.h b/src/include/parser/parse_expr.h
index efbaff8e710..c9f6a7724c0 100644
--- a/src/include/parser/parse_expr.h
+++ b/src/include/parser/parse_expr.h
@@ -22,4 +22,7 @@ extern Node *transformExpr(ParseState *pstate, Node *expr, ParseExprKind exprKin
 
 extern const char *ParseExprKindName(ParseExprKind exprKind);
 
+extern Node *transformIndirection(ParseState *pstate, A_Indirection *ind,
+								  bool *trailing_star_expansion);
+
 #endif							/* PARSE_EXPR_H */
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.c b/src/interfaces/ecpg/test/expected/sql-sqljson.c
index 935b47a3b9a..585d9b14445 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.c
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.c
@@ -515,9 +515,9 @@ if (sqlca.sqlcode < 0) sqlprint();}
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 145 "sqljson.pgc"
 
-	// error
+	printf("Found json=%s\n", json);
 
-	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )", ECPGt_EOIT, 
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . * . x )", ECPGt_EOIT, 
 	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
 	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 148 "sqljson.pgc"
@@ -527,7 +527,7 @@ if (sqlca.sqlcode < 0) sqlprint();}
 
 	printf("Found json=%s\n", json);
 
-	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ : ] )", ECPGt_EOIT, 
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )", ECPGt_EOIT, 
 	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
 	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 151 "sqljson.pgc"
@@ -537,12 +537,22 @@ if (sqlca.sqlcode < 0) sqlprint();}
 
 	printf("Found json=%s\n", json);
 
-  { ECPGdisconnect(__LINE__, "CURRENT");
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ : ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 154 "sqljson.pgc"
 
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 154 "sqljson.pgc"
 
+	printf("Found json=%s\n", json);
+
+  { ECPGdisconnect(__LINE__, "CURRENT");
+#line 157 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 157 "sqljson.pgc"
+
 
   return 0;
 }
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
index f3f899c6d87..4b9088545d6 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
@@ -347,22 +347,19 @@ SQL error: schema "jsonb" does not exist on line 121
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 145: using PQexec
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_check_PQresult on line 145: bad response - ERROR:  row expansion via "*" is not supported here
-LINE 1: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x...
-                        ^
+[NO_PID]: ecpg_process_output on line 145: correctly got 1 tuples with 1 fields
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: raising sqlstate 0A000 (sqlcode -400): row expansion via "*" is not supported here on line 145
-[NO_PID]: sqlca: code: -400, state: 0A000
-SQL error: row expansion via "*" is not supported here on line 145
-[NO_PID]: ecpg_execute on line 148: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: ecpg_get_data on line 145: RESULT: [{"b": 1, "c": 2}, [{"x": 1}, {"x": [12, {"y": 1}]}]] offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 148: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . * . x ); with 0 parameter(s) on connection ecpg1_regression
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 148: using PQexec
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_process_output on line 148: correctly got 1 tuples with 1 fields
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_get_data on line 148: RESULT: [{"x": 1}, {"x": [12, {"y": 1}]}] offset: -1; array: no
+[NO_PID]: ecpg_get_data on line 148: RESULT: [1, [12, {"y": 1}]] offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_execute on line 151: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: ecpg_execute on line 151: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] ); with 0 parameter(s) on connection ecpg1_regression
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 151: using PQexec
 [NO_PID]: sqlca: code: 0, state: 00000
@@ -370,5 +367,13 @@ SQL error: row expansion via "*" is not supported here on line 145
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_get_data on line 151: RESULT: [{"x": 1}, {"x": [12, {"y": 1}]}] offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 154: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 154: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 154: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 154: RESULT: [{"x": 1}, {"x": [12, {"y": 1}]}] offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_finish: connection ecpg1_regression closed
 [NO_PID]: sqlca: code: 0, state: 00000
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
index d01a8457f01..145dc95d430 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
@@ -36,5 +36,7 @@ Found json={"x": 1}
 Found json=[1, [12, {"y": 1}]]
 Found json={"x": 1}
 Found json=[12, {"y": 1}]
+Found json=[{"b": 1, "c": 2}, [{"x": 1}, {"x": [12, {"y": 1}]}]]
+Found json=[1, [12, {"y": 1}]]
 Found json=[{"x": 1}, {"x": [12, {"y": 1}]}]
 Found json=[{"x": 1}, {"x": [12, {"y": 1}]}]
diff --git a/src/interfaces/ecpg/test/sql/sqljson.pgc b/src/interfaces/ecpg/test/sql/sqljson.pgc
index 9423d25fd0b..2af50b5da4b 100644
--- a/src/interfaces/ecpg/test/sql/sqljson.pgc
+++ b/src/interfaces/ecpg/test/sql/sqljson.pgc
@@ -143,7 +143,10 @@ EXEC SQL END DECLARE SECTION;
 	printf("Found json=%s\n", json);
 
 	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).*) INTO :json;
-	// error
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).*.x) INTO :json;
+	printf("Found json=%s\n", json);
 
 	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb)['b'][0:]) INTO :json;
 	printf("Found json=%s\n", json);
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 45f4ae7b15d..d883f0edc6b 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -6064,8 +6064,175 @@ SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
  {"y": "YYY", "z": "ZZZ"}
 (1 row)
 
-SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
-ERROR:  type jsonb is not composite
+/* wild card member access */
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+                 ?column?                  
+-------------------------------------------
+ ["c", "d", "f", {"y": "yyy", "z": "zzz"}]
+(1 row)
+
+SELECT (jb).* FROM test_jsonb_dot_notation;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (jb).* FROM test_jsonb_dot_notation t;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (jb).* FROM test_jsonb_dot_notation;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (t.jb).* FROM test_jsonb_dot_notation;
+ERROR:  missing FROM-clause entry for table "t"
+LINE 1: SELECT (t.jb).* FROM test_jsonb_dot_notation;
+                ^
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+                 ?column?                  
+-------------------------------------------
+ ["c", "d", "f", {"y": "yyy", "z": "zzz"}]
+(1 row)
+
+SELECT (jb).a.*.b FROM test_jsonb_dot_notation;
+ b 
+---
+ 
+(1 row)
+
+SELECT (jb).a.*.x FROM test_jsonb_dot_notation;
+ x 
+---
+ 
+(1 row)
+
+SELECT (jb).a.*.y FROM test_jsonb_dot_notation;
+   y   
+-------
+ "yyy"
+(1 row)
+
+SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+    ?column?    
+----------------
+ ["yyy", "zzz"]
+(1 row)
+
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT (jb).*.x FROM test_jsonb_dot_notation t;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+ x 
+---
+ 
+(1 row)
+
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+ x 
+---
+ 
+(1 row)
+
+SELECT ((jb).*)[:].x FROM test_jsonb_dot_notation t;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT (jb).*.x.* FROM test_jsonb_dot_notation;
+           ?column?           
+------------------------------
+ ["yyy", "zzz", "YYY", "ZZZ"]
+(1 row)
+
+SELECT (jb).*.x.y FROM test_jsonb_dot_notation;
+       y        
+----------------
+ ["yyy", "YYY"]
+(1 row)
+
+SELECT (jb).*.x.z FROM test_jsonb_dot_notation;
+       z        
+----------------
+ ["zzz", "ZZZ"]
+(1 row)
+
+SELECT (jb).*.*.y FROM test_jsonb_dot_notation;
+       y        
+----------------
+ ["yyy", "YYY"]
+(1 row)
+
+SELECT (jb).*.*.* FROM test_jsonb_dot_notation;
+           ?column?           
+------------------------------
+ ["yyy", "zzz", "YYY", "ZZZ"]
+(1 row)
+
+SELECT (jb).*.*.*.* FROM test_jsonb_dot_notation;
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT (jb).a.b.c.* FROM test_jsonb_dot_notation;
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+    ?column?    
+----------------
+ ["yyy", "zzz"]
+(1 row)
+
+SELECT (jb).a.*[:].* FROM test_jsonb_dot_notation;
+    ?column?    
+----------------
+ ["yyy", "zzz"]
+(1 row)
+
+SELECT (jb).a.*[*].* FROM test_jsonb_dot_notation; -- not supported
+ERROR:  syntax error at or near "*"
+LINE 1: SELECT (jb).a.*[*].* FROM test_jsonb_dot_notation;
+                        ^
+SELECT (jb).a.**.x FROM test_jsonb_dot_notation; -- not supported
+ERROR:  syntax error at or near "**"
+LINE 1: SELECT (jb).a.**.x FROM test_jsonb_dot_notation;
+                      ^
 -- explains should work
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
                   QUERY PLAN                  
@@ -6093,6 +6260,45 @@ SELECT (jb).a[1] FROM test_jsonb_dot_notation;
  2
 (1 row)
 
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.* FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb).a.*
+(2 rows)
+
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+                 ?column?                  
+-------------------------------------------
+ ["c", "d", "f", {"y": "yyy", "z": "zzz"}]
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:].* FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb).a.*[:].*
+(2 rows)
+
+SELECT (jb).a.*[1:].* FROM test_jsonb_dot_notation;
+ ?column? 
+----------
+ 
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb).a.*[:2].*.b
+(2 rows)
+
+SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
+ b 
+---
+ 
+(1 row)
+
 -- views should work
 CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
 \sv test_jsonb_dot_notation_v1
@@ -6121,6 +6327,17 @@ SELECT * from v3;
  "yyy"
 (1 row)
 
+CREATE VIEW v4 AS SELECT (jb).a.*[:].* FROM test_jsonb_dot_notation;
+\sv v4
+CREATE OR REPLACE VIEW public.v4 AS
+ SELECT (jb).a.*[:].* AS "?column?"
+   FROM test_jsonb_dot_notation
+SELECT * from v4;
+    ?column?    
+----------------
+ ["yyy", "zzz"]
+(1 row)
+
 -- mixed syntax
 DROP VIEW test_jsonb_dot_notation_v1;
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
@@ -6356,4 +6573,5 @@ NOTICE:  [2]
 -- clean up
 DROP VIEW v2;
 DROP VIEW v3;
+DROP VIEW v4;
 DROP TABLE test_jsonb_dot_notation;
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index d3e91e2ed45..45769c8634b 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1631,13 +1631,49 @@ SELECT (jb).a[2:].b FROM test_jsonb_dot_notation;
 SELECT (jb).a[:2].b FROM test_jsonb_dot_notation;
 SELECT (jb).a[:].b FROM test_jsonb_dot_notation;
 SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
-SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
+
+/* wild card member access */
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+SELECT (jb).* FROM test_jsonb_dot_notation;
+SELECT (jb).* FROM test_jsonb_dot_notation t;
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+SELECT (jb).* FROM test_jsonb_dot_notation;
+SELECT (t.jb).* FROM test_jsonb_dot_notation;
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.b FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.x FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.y FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+SELECT (jb).*.x FROM test_jsonb_dot_notation t;
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+SELECT ((jb).*)[:].x FROM test_jsonb_dot_notation t;
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+SELECT (jb).*.x.* FROM test_jsonb_dot_notation;
+SELECT (jb).*.x.y FROM test_jsonb_dot_notation;
+SELECT (jb).*.x.z FROM test_jsonb_dot_notation;
+SELECT (jb).*.*.y FROM test_jsonb_dot_notation;
+SELECT (jb).*.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).*.*.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.b.c.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*[:].* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*[*].* FROM test_jsonb_dot_notation; -- not supported
+SELECT (jb).a.**.x FROM test_jsonb_dot_notation; -- not supported
 
 -- explains should work
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
 SELECT (t.jb).a FROM test_jsonb_dot_notation t;
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
 SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:].* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*[1:].* FROM test_jsonb_dot_notation;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
+SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
 
 -- views should work
 CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
@@ -1648,6 +1684,9 @@ SELECT * from v2;
 CREATE VIEW v3 AS SELECT (jb).a[:3].x.y[-1:] FROM test_jsonb_dot_notation;
 \sv v3
 SELECT * from v3;
+CREATE VIEW v4 AS SELECT (jb).a.*[:].* FROM test_jsonb_dot_notation;
+\sv v4
+SELECT * from v4;
 
 -- mixed syntax
 DROP VIEW test_jsonb_dot_notation_v1;
@@ -1749,4 +1788,5 @@ $$ LANGUAGE plpgsql;
 -- clean up
 DROP VIEW v2;
 DROP VIEW v3;
+DROP VIEW v4;
 DROP TABLE test_jsonb_dot_notation;
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v15-0001-Allow-transformation-of-only-a-sublist-of-subscr.patch (9.0K, ../../CAK98qZ1XFee_x1uQFcwJkZCSVcPBy-A1ib3N5Ls2uDdZviKVOw@mail.gmail.com/4-v15-0001-Allow-transformation-of-only-a-sublist-of-subscr.patch)
  download | inline diff:
From ea54bbbf1f71acda68e173e25244e462b01f32c3 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v15 1/7] Allow transformation of only a sublist of subscripts

This is a preparation step for allowing subscripting containers to
transform only a prefix of an indirection list and modify the list
in-place by removing the processed elements. Currently, all elements
are consumed, and the list is set to NIL after transformation.

In the following commit, subscripting containers will gain the
flexibility to stop transformation when encountering an unsupported
indirection and return the remaining indirections to the caller.

Reviewed-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Jian He <[email protected]>
---
 contrib/hstore/hstore_subs.c      | 10 ++++++----
 src/backend/parser/parse_expr.c   |  9 ++++-----
 src/backend/parser/parse_node.c   |  4 ++--
 src/backend/parser/parse_target.c |  2 +-
 src/backend/utils/adt/arraysubs.c |  6 ++++--
 src/backend/utils/adt/jsonbsubs.c |  6 ++++--
 src/include/nodes/subscripting.h  |  7 ++++++-
 src/include/parser/parse_node.h   |  2 +-
 8 files changed, 28 insertions(+), 18 deletions(-)

diff --git a/contrib/hstore/hstore_subs.c b/contrib/hstore/hstore_subs.c
index 3d03f66fa0d..1b29543ab67 100644
--- a/contrib/hstore/hstore_subs.c
+++ b/contrib/hstore/hstore_subs.c
@@ -40,7 +40,7 @@
  */
 static void
 hstore_subscript_transform(SubscriptingRef *sbsref,
-						   List *indirection,
+						   List **indirection,
 						   ParseState *pstate,
 						   bool isSlice,
 						   bool isAssignment)
@@ -49,15 +49,15 @@ hstore_subscript_transform(SubscriptingRef *sbsref,
 	Node	   *subexpr;
 
 	/* We support only single-subscript, non-slice cases */
-	if (isSlice || list_length(indirection) != 1)
+	if (isSlice || list_length(*indirection) != 1)
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("hstore allows only one subscript"),
 				 parser_errposition(pstate,
-									exprLocation((Node *) indirection))));
+									exprLocation((Node *) *indirection))));
 
 	/* Transform the subscript expression to type text */
-	ai = linitial_node(A_Indices, indirection);
+	ai = linitial_node(A_Indices, *indirection);
 	Assert(ai->uidx != NULL && ai->lidx == NULL && !ai->is_slice);
 
 	subexpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
@@ -81,6 +81,8 @@ hstore_subscript_transform(SubscriptingRef *sbsref,
 	/* Determine the result type of the subscripting operation; always text */
 	sbsref->refrestype = TEXTOID;
 	sbsref->reftypmod = -1;
+
+	*indirection = NIL;
 }
 
 /*
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index e1979a80c19..6e8fd42c612 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -465,14 +465,13 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 			Assert(IsA(n, String));
 
 			/* process subscripts before this field selection */
-			if (subscripts)
+			while (subscripts)
 				result = (Node *) transformContainerSubscripts(pstate,
 															   result,
 															   exprType(result),
 															   exprTypmod(result),
-															   subscripts,
+															   &subscripts,
 															   false);
-			subscripts = NIL;
 
 			newresult = ParseFuncOrColumn(pstate,
 										  list_make1(n),
@@ -487,12 +486,12 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		}
 	}
 	/* process trailing subscripts, if any */
-	if (subscripts)
+	while (subscripts)
 		result = (Node *) transformContainerSubscripts(pstate,
 													   result,
 													   exprType(result),
 													   exprTypmod(result),
-													   subscripts,
+													   &subscripts,
 													   false);
 
 	return result;
diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c
index 203b7a32178..f05baa50a15 100644
--- a/src/backend/parser/parse_node.c
+++ b/src/backend/parser/parse_node.c
@@ -244,7 +244,7 @@ transformContainerSubscripts(ParseState *pstate,
 							 Node *containerBase,
 							 Oid containerType,
 							 int32 containerTypMod,
-							 List *indirection,
+							 List **indirection,
 							 bool isAssignment)
 {
 	SubscriptingRef *sbsref;
@@ -280,7 +280,7 @@ transformContainerSubscripts(ParseState *pstate,
 	 * element.  If any of the items are slice specifiers (lower:upper), then
 	 * the subscript expression means a container slice operation.
 	 */
-	foreach(idx, indirection)
+	foreach(idx, *indirection)
 	{
 		A_Indices  *ai = lfirst_node(A_Indices, idx);
 
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 905c975d83b..a097736229a 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -935,7 +935,7 @@ transformAssignmentSubscripts(ParseState *pstate,
 										  basenode,
 										  containerType,
 										  containerTypMod,
-										  subscripts,
+										  &subscripts,
 										  true);
 
 	typeNeeded = sbsref->refrestype;
diff --git a/src/backend/utils/adt/arraysubs.c b/src/backend/utils/adt/arraysubs.c
index 2940fb8e8d7..234c2c278c1 100644
--- a/src/backend/utils/adt/arraysubs.c
+++ b/src/backend/utils/adt/arraysubs.c
@@ -54,7 +54,7 @@ typedef struct ArraySubWorkspace
  */
 static void
 array_subscript_transform(SubscriptingRef *sbsref,
-						  List *indirection,
+						  List **indirection,
 						  ParseState *pstate,
 						  bool isSlice,
 						  bool isAssignment)
@@ -71,7 +71,7 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	 * indirection items to slices by treating the single subscript as the
 	 * upper bound and supplying an assumed lower bound of 1.
 	 */
-	foreach(idx, indirection)
+	foreach(idx, *indirection)
 	{
 		A_Indices  *ai = lfirst_node(A_Indices, idx);
 		Node	   *subexpr;
@@ -152,6 +152,8 @@ array_subscript_transform(SubscriptingRef *sbsref,
 						list_length(upperIndexpr), MAXDIM)));
 	/* We need not check lowerIndexpr separately */
 
+	*indirection = NIL;
+
 	/*
 	 * Determine the result type of the subscripting operation.  It's the same
 	 * as the array type if we're slicing, else it's the element type.  In
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index de64d498512..8ad6aa1ad4f 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -41,7 +41,7 @@ typedef struct JsonbSubWorkspace
  */
 static void
 jsonb_subscript_transform(SubscriptingRef *sbsref,
-						  List *indirection,
+						  List **indirection,
 						  ParseState *pstate,
 						  bool isSlice,
 						  bool isAssignment)
@@ -53,7 +53,7 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	 * Transform and convert the subscript expressions. Jsonb subscripting
 	 * does not support slices, look only and the upper index.
 	 */
-	foreach(idx, indirection)
+	foreach(idx, *indirection)
 	{
 		A_Indices  *ai = lfirst_node(A_Indices, idx);
 		Node	   *subExpr;
@@ -159,6 +159,8 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	/* Determine the result type of the subscripting operation; always jsonb */
 	sbsref->refrestype = JSONBOID;
 	sbsref->reftypmod = -1;
+
+	*indirection = NIL;
 }
 
 /*
diff --git a/src/include/nodes/subscripting.h b/src/include/nodes/subscripting.h
index 234e8ad8012..5d576af346f 100644
--- a/src/include/nodes/subscripting.h
+++ b/src/include/nodes/subscripting.h
@@ -71,6 +71,11 @@ struct SubscriptExecSteps;
  * does not care to support slicing, it can just throw an error if isSlice.)
  * See array_subscript_transform() for sample code.
  *
+ * The transform method receives a pointer to a list of raw indirections.
+ * This allows the method to parse a sublist of the indirections (typically
+ * the prefix) and modify the original list in place, enabling the caller to
+ * either process the remaining indirections differently or raise an error.
+ *
  * The transform method is also responsible for identifying the result type
  * of the subscripting operation.  At call, refcontainertype and reftypmod
  * describe the container type (this will be a base type not a domain), and
@@ -93,7 +98,7 @@ struct SubscriptExecSteps;
  * assignment must return.
  */
 typedef void (*SubscriptTransform) (SubscriptingRef *sbsref,
-									List *indirection,
+									List **indirection,
 									struct ParseState *pstate,
 									bool isSlice,
 									bool isAssignment);
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index f7d07c84542..58a4b9df157 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -361,7 +361,7 @@ extern SubscriptingRef *transformContainerSubscripts(ParseState *pstate,
 													 Node *containerBase,
 													 Oid containerType,
 													 int32 containerTypMod,
-													 List *indirection,
+													 List **indirection,
 													 bool isAssignment);
 extern Const *make_const(ParseState *pstate, A_Const *aconst);
 
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v15-0004-Extract-coerce_jsonpath_subscript.patch (5.8K, ../../CAK98qZ1XFee_x1uQFcwJkZCSVcPBy-A1ib3N5Ls2uDdZviKVOw@mail.gmail.com/5-v15-0004-Extract-coerce_jsonpath_subscript.patch)
  download | inline diff:
From 4935f0ea669c544941ad5f8e353549aa4923fd99 Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v15 4/7] Extract coerce_jsonpath_subscript()

This is a preparation step for a future commit that will reuse the
aforementioned function.

Co-authored-by: Nikita Glukhov <[email protected]>
Co-authored-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Jian He <[email protected]>
---
 src/backend/utils/adt/jsonbsubs.c | 130 +++++++++++++++---------------
 1 file changed, 65 insertions(+), 65 deletions(-)

diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index a0d38a0fd80..f944d1544ca 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -32,6 +32,69 @@ typedef struct JsonbSubWorkspace
 	Datum	   *index;			/* Subscript values in Datum format */
 } JsonbSubWorkspace;
 
+static Node *
+coerce_jsonpath_subscript_to_int4_or_text(ParseState *pstate, Node *subExpr)
+{
+	Oid			subExprType = exprType(subExpr);
+	Oid			targetType = InvalidOid;
+
+	if (subExprType != UNKNOWNOID)
+	{
+		Oid			targets[2] = {INT4OID, TEXTOID};
+
+		/*
+		 * Jsonb can handle multiple subscript types, but cases when a
+		 * subscript could be coerced to multiple target types must be
+		 * avoided, similar to overloaded functions. It could be possibly
+		 * extend with jsonpath in the future.
+		 */
+		for (int i = 0; i < 2; i++)
+		{
+			if (can_coerce_type(1, &subExprType, &targets[i], COERCION_IMPLICIT))
+			{
+				/*
+				 * One type has already succeeded, it means there are two
+				 * coercion targets possible, failure.
+				 */
+				if (OidIsValid(targetType))
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
+							 errhint("jsonb subscript must be coercible to only one type, integer or text."),
+							 parser_errposition(pstate, exprLocation(subExpr))));
+
+				targetType = targets[i];
+			}
+		}
+
+		/*
+		 * No suitable types were found, failure.
+		 */
+		if (!OidIsValid(targetType))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
+					 errhint("jsonb subscript must be coercible to either integer or text."),
+					 parser_errposition(pstate, exprLocation(subExpr))));
+	}
+	else
+		targetType = TEXTOID;
+
+	/*
+	 * We known from can_coerce_type that coercion will succeed, so
+	 * coerce_type could be used. Note the implicit coercion context, which is
+	 * required to handle subscripts of different types, similar to overloaded
+	 * functions.
+	 */
+	subExpr = coerce_type(pstate,
+						  subExpr, subExprType,
+						  targetType, -1,
+						  COERCION_IMPLICIT,
+						  COERCE_IMPLICIT_CAST,
+						  -1);
+
+	return subExpr;
+}
 
 /*
  * Finish parse analysis of a SubscriptingRef expression for a jsonb.
@@ -51,7 +114,7 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 
 	/*
 	 * Transform and convert the subscript expressions. Jsonb subscripting
-	 * does not support slices, look only and the upper index.
+	 * does not support slices, look only at the upper index.
 	 */
 	foreach(idx, *indirection)
 	{
@@ -75,71 +138,8 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 
 		if (ai->uidx)
 		{
-			Oid			subExprType = InvalidOid,
-						targetType = UNKNOWNOID;
-
 			subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
-			subExprType = exprType(subExpr);
-
-			if (subExprType != UNKNOWNOID)
-			{
-				Oid			targets[2] = {INT4OID, TEXTOID};
-
-				/*
-				 * Jsonb can handle multiple subscript types, but cases when a
-				 * subscript could be coerced to multiple target types must be
-				 * avoided, similar to overloaded functions. It could be
-				 * possibly extend with jsonpath in the future.
-				 */
-				for (int i = 0; i < 2; i++)
-				{
-					if (can_coerce_type(1, &subExprType, &targets[i], COERCION_IMPLICIT))
-					{
-						/*
-						 * One type has already succeeded, it means there are
-						 * two coercion targets possible, failure.
-						 */
-						if (targetType != UNKNOWNOID)
-							ereport(ERROR,
-									(errcode(ERRCODE_DATATYPE_MISMATCH),
-									 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
-									 errhint("jsonb subscript must be coercible to only one type, integer or text."),
-									 parser_errposition(pstate, exprLocation(subExpr))));
-
-						targetType = targets[i];
-					}
-				}
-
-				/*
-				 * No suitable types were found, failure.
-				 */
-				if (targetType == UNKNOWNOID)
-					ereport(ERROR,
-							(errcode(ERRCODE_DATATYPE_MISMATCH),
-							 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
-							 errhint("jsonb subscript must be coercible to either integer or text."),
-							 parser_errposition(pstate, exprLocation(subExpr))));
-			}
-			else
-				targetType = TEXTOID;
-
-			/*
-			 * We known from can_coerce_type that coercion will succeed, so
-			 * coerce_type could be used. Note the implicit coercion context,
-			 * which is required to handle subscripts of different types,
-			 * similar to overloaded functions.
-			 */
-			subExpr = coerce_type(pstate,
-								  subExpr, subExprType,
-								  targetType, -1,
-								  COERCION_IMPLICIT,
-								  COERCE_IMPLICIT_CAST,
-								  -1);
-			if (subExpr == NULL)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("jsonb subscript must have text type"),
-						 parser_errposition(pstate, exprLocation(subExpr))));
+			subExpr = coerce_jsonpath_subscript_to_int4_or_text(pstate, subExpr);
 		}
 		else
 		{
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v15-0006-Implement-Jsonb-subscripting-with-slicing.patch (18.2K, ../../CAK98qZ1XFee_x1uQFcwJkZCSVcPBy-A1ib3N5Ls2uDdZviKVOw@mail.gmail.com/6-v15-0006-Implement-Jsonb-subscripting-with-slicing.patch)
  download | inline diff:
From 811c288392f1193d8ff38315879a59090c4e4da6 Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v15 6/7] Implement Jsonb subscripting with slicing

Previously, slicing was not supported for jsonb subscripting. This commit
implements subscripting with slicing as part of the JSON simplified accessor
syntax specified in SQL:2023.

Co-authored-by: Nikita Glukhov <[email protected]>
Co-authored-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Mark Dilger <[email protected]>
Reviewed-by: Jian He <[email protected]>
Reviewed-by: Vik Fearing <[email protected]>
Reviewed-by: Nikita Malakhov <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Tested-by: Mark Dilger <[email protected]>
Tested-by: Jian He <[email protected]>
---
 src/backend/utils/adt/jsonbsubs.c             |  45 ++++--
 .../ecpg/test/expected/sql-sqljson.c          |  16 +-
 .../ecpg/test/expected/sql-sqljson.stderr     |  26 ++--
 .../ecpg/test/expected/sql-sqljson.stdout     |   3 +
 src/interfaces/ecpg/test/sql/sqljson.pgc      |   7 +-
 src/test/regress/expected/jsonb.out           | 145 ++++++++++++++++--
 src/test/regress/sql/jsonb.sql                |  53 ++++++-
 7 files changed, 250 insertions(+), 45 deletions(-)

diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index 485ad967aba..369f40ec044 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -207,9 +207,10 @@ make_jsonpath_item_int(int32 val, List **exprs)
  * - pstate: parse state context
  * - expr: input expression node
  * - exprs: list of expression nodes (updated in place)
+ * - no_error: returns NULL when the data type doesn't match. Otherwise, emits an ERROR.
  */
 static JsonPathParseItem *
-make_jsonpath_item_expr(ParseState *pstate, Node *expr, List **exprs)
+make_jsonpath_item_expr(ParseState *pstate, Node *expr, List **exprs, bool no_error)
 {
 	Const	   *cnst;
 
@@ -222,7 +223,14 @@ make_jsonpath_item_expr(ParseState *pstate, Node *expr, List **exprs)
 			return make_jsonpath_item_int(DatumGetInt32(cnst->constvalue), exprs);
 	}
 
-	return NULL;
+	if (no_error)
+		return NULL;
+	else
+		ereport(ERROR,
+				errcode(ERRCODE_DATATYPE_MISMATCH),
+				errmsg("only non-null integer constants are supported for jsonb simplified accessor subscripting"),
+				errhint("use int data type for subscripting with slicing."),
+				parser_errposition(pstate, exprLocation(expr)));
 }
 
 /*
@@ -285,13 +293,12 @@ jsonb_subscript_make_jsonpath(ParseState *pstate, List **indirection, Subscripti
 		else if (IsA(accessor, A_Indices))
 		{
 			A_Indices  *ai = castNode(A_Indices, accessor);
+			JsonPathParseItem *jpi_from = NULL;
 
 			if (!ai->is_slice)
 			{
-				JsonPathParseItem *jpi_from = NULL;
-
 				Assert(ai->uidx && !ai->lidx);
-				jpi_from = make_jsonpath_item_expr(pstate, ai->uidx, &sbsref->refupperindexpr);
+				jpi_from = make_jsonpath_item_expr(pstate, ai->uidx, &sbsref->refupperindexpr, true);
 				if (jpi_from == NULL)
 				{
 					/*
@@ -318,22 +325,34 @@ jsonb_subscript_make_jsonpath(ParseState *pstate, List **indirection, Subscripti
 					 */
 					break;
 				}
+			}
 
-				jpi = make_jsonpath_item(jpiIndexArray);
-				jpi->value.array.nelems = 1;
-				jpi->value.array.elems = palloc(sizeof(jpi->value.array.elems[0]));
+			jpi = make_jsonpath_item(jpiIndexArray);
+			jpi->value.array.nelems = 1;
+			jpi->value.array.elems = palloc(sizeof(jpi->value.array.elems[0]));
 
+			if (!ai->is_slice)
+			{
 				jpi->value.array.elems[0].from = jpi_from;
 				jpi->value.array.elems[0].to = NULL;
 			}
 			else
 			{
-				Node	   *expr = ai->uidx ? ai->uidx : ai->lidx;
+				while (list_length(sbsref->reflowerindexpr) < list_length(sbsref->refupperindexpr))
+					sbsref->reflowerindexpr = lappend(sbsref->reflowerindexpr, NULL);
+
+				if (ai->lidx)
+					jpi->value.array.elems[0].from = make_jsonpath_item_expr(pstate, ai->lidx, &sbsref->reflowerindexpr, false);
+				else
+					jpi->value.array.elems[0].from = make_jsonpath_item_int(0, &sbsref->reflowerindexpr);
 
-				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("jsonb subscript does not support slices"),
-						 parser_errposition(pstate, exprLocation(expr))));
+				if (ai->uidx)
+					jpi->value.array.elems[0].to = make_jsonpath_item_expr(pstate, ai->uidx, &sbsref->refupperindexpr, false);
+				else
+				{
+					jpi->value.array.elems[0].to = make_jsonpath_item(jpiLast);
+					sbsref->refupperindexpr = lappend(sbsref->refupperindexpr, NULL);
+				}
 			}
 		}
 		else
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.c b/src/interfaces/ecpg/test/expected/sql-sqljson.c
index e6a7ece6dab..935b47a3b9a 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.c
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.c
@@ -505,7 +505,7 @@ if (sqlca.sqlcode < 0) sqlprint();}
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 142 "sqljson.pgc"
 
-	// error
+	printf("Found json=%s\n", json);
 
 	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . * )", ECPGt_EOIT, 
 	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
@@ -525,14 +525,24 @@ if (sqlca.sqlcode < 0) sqlprint();}
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 148 "sqljson.pgc"
 
-	// error
+	printf("Found json=%s\n", json);
 
-  { ECPGdisconnect(__LINE__, "CURRENT");
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ : ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 151 "sqljson.pgc"
 
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 151 "sqljson.pgc"
 
+	printf("Found json=%s\n", json);
+
+  { ECPGdisconnect(__LINE__, "CURRENT");
+#line 154 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 154 "sqljson.pgc"
+
 
   return 0;
 }
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
index 19f8c58af06..f3f899c6d87 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
@@ -339,13 +339,10 @@ SQL error: schema "jsonb" does not exist on line 121
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 142: using PQexec
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_check_PQresult on line 142: bad response - ERROR:  jsonb subscript does not support slices
-LINE 1: ..., {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] )
-                                                                ^
+[NO_PID]: ecpg_process_output on line 142: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 142: RESULT: [12, {"y": 1}] offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: raising sqlstate 42804 (sqlcode -400): jsonb subscript does not support slices on line 142
-[NO_PID]: sqlca: code: -400, state: 42804
-SQL error: jsonb subscript does not support slices on line 142
 [NO_PID]: ecpg_execute on line 145: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . * ); with 0 parameter(s) on connection ecpg1_regression
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 145: using PQexec
@@ -361,12 +358,17 @@ SQL error: row expansion via "*" is not supported here on line 145
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 148: using PQexec
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_check_PQresult on line 148: bad response - ERROR:  jsonb subscript does not support slices
-LINE 1: ...x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )
-                                                                ^
+[NO_PID]: ecpg_process_output on line 148: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 148: RESULT: [{"x": 1}, {"x": [12, {"y": 1}]}] offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 151: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 151: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 151: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 151: RESULT: [{"x": 1}, {"x": [12, {"y": 1}]}] offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: raising sqlstate 42804 (sqlcode -400): jsonb subscript does not support slices on line 148
-[NO_PID]: sqlca: code: -400, state: 42804
-SQL error: jsonb subscript does not support slices on line 148
 [NO_PID]: ecpg_finish: connection ecpg1_regression closed
 [NO_PID]: sqlca: code: 0, state: 00000
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
index 442d36931f1..d01a8457f01 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
@@ -35,3 +35,6 @@ Found json=null
 Found json={"x": 1}
 Found json=[1, [12, {"y": 1}]]
 Found json={"x": 1}
+Found json=[12, {"y": 1}]
+Found json=[{"x": 1}, {"x": [12, {"y": 1}]}]
+Found json=[{"x": 1}, {"x": [12, {"y": 1}]}]
diff --git a/src/interfaces/ecpg/test/sql/sqljson.pgc b/src/interfaces/ecpg/test/sql/sqljson.pgc
index 57a9bff424d..9423d25fd0b 100644
--- a/src/interfaces/ecpg/test/sql/sqljson.pgc
+++ b/src/interfaces/ecpg/test/sql/sqljson.pgc
@@ -140,13 +140,16 @@ EXEC SQL END DECLARE SECTION;
 	printf("Found json=%s\n", json);
 
 	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b[1].x[0:]) INTO :json;
-	// error
+	printf("Found json=%s\n", json);
 
 	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).*) INTO :json;
 	// error
 
 	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb)['b'][0:]) INTO :json;
-	// error
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b[:]) INTO :json;
+	printf("Found json=%s\n", json);
 
   EXEC SQL DISCONNECT;
 
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index d596e712372..45f4ae7b15d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5247,23 +5247,34 @@ select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b
 (1 row)
 
 select ('{"a": 1}'::jsonb)['a':'b']; -- fails
-ERROR:  jsonb subscript does not support slices
+ERROR:  only non-null integer constants are supported for jsonb simplified accessor subscripting
 LINE 1: select ('{"a": 1}'::jsonb)['a':'b'];
-                                       ^
+                                   ^
+HINT:  use int data type for subscripting with slicing.
 select ('[1, "2", null]'::jsonb)[1:2];
-ERROR:  jsonb subscript does not support slices
-LINE 1: select ('[1, "2", null]'::jsonb)[1:2];
-                                           ^
+    jsonb    
+-------------
+ ["2", null]
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[:2];
-ERROR:  jsonb subscript does not support slices
-LINE 1: select ('[1, "2", null]'::jsonb)[:2];
-                                          ^
+     jsonb      
+----------------
+ [1, "2", null]
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[1:];
-ERROR:  jsonb subscript does not support slices
-LINE 1: select ('[1, "2", null]'::jsonb)[1:];
-                                         ^
+    jsonb    
+-------------
+ ["2", null]
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[:];
-ERROR:  jsonb subscript does not support slices
+     jsonb      
+----------------
+ [1, "2", null]
+(1 row)
+
 create TEMP TABLE test_jsonb_subscript (
        id int,
        test_json jsonb
@@ -6017,8 +6028,42 @@ SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
  
 (1 row)
 
-SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t; -- fails
-ERROR:  jsonb subscript does not support slices
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
+            x             
+--------------------------
+ {"y": "YYY", "z": "ZZZ"}
+(1 row)
+
+SELECT (jb).a[2:3].b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT (jb).a[2:].b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT (jb).a[:2].b FROM test_jsonb_dot_notation;
+  b  
+-----
+ "c"
+(1 row)
+
+SELECT (jb).a[:].b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
+            x             
+--------------------------
+ {"y": "YYY", "z": "ZZZ"}
+(1 row)
+
 SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
 ERROR:  type jsonb is not composite
 -- explains should work
@@ -6054,6 +6099,28 @@ CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_d
 CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
  SELECT (jb).a[3].x.y AS y
    FROM test_jsonb_dot_notation
+CREATE VIEW v2 AS SELECT (jb).a[3:].x.y[:-1] FROM test_jsonb_dot_notation;
+\sv v2
+CREATE OR REPLACE VIEW public.v2 AS
+ SELECT (jb).a[3:].x.y[0:'-1'::integer] AS y
+   FROM test_jsonb_dot_notation
+SELECT * from v2;
+ y 
+---
+ 
+(1 row)
+
+CREATE VIEW v3 AS SELECT (jb).a[:3].x.y[-1:] FROM test_jsonb_dot_notation;
+\sv v3
+CREATE OR REPLACE VIEW public.v3 AS
+ SELECT (jb).a[0:3].x.y['-1'::integer:] AS y
+   FROM test_jsonb_dot_notation
+SELECT * from v3;
+   y   
+-------
+ "yyy"
+(1 row)
+
 -- mixed syntax
 DROP VIEW test_jsonb_dot_notation_v1;
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
@@ -6238,5 +6305,55 @@ SELECT * from test_jsonb_dot_notation_v1;
 (1 row)
 
 DROP VIEW public.test_jsonb_dot_notation_v1;
+-- jsonb array access in plpgsql
+DO $$
+    DECLARE
+        a jsonb := '[1,2,3,4,5,6,7]'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := a[2:];
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+NOTICE:  [1, 2, 3, 4, 5, 6, 7]
+NOTICE:  [3, 4, 5, 6, 7]
+NOTICE:  [5, 6, 7]
+NOTICE:  7
+-- jsonb dot access in plpgsql
+DO $$
+    DECLARE
+        a jsonb := '{"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := COALESCE(a."NU", a[2]); -- fails
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+NOTICE:  {"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}
+ERROR:  missing FROM-clause entry for table "a"
+LINE 1: a := COALESCE(a."NU", a[2])
+                      ^
+QUERY:  a := COALESCE(a."NU", a[2])
+CONTEXT:  PL/pgSQL function inline_code_block line 8 at assignment
+DO $$
+    DECLARE
+        a jsonb := '{"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := COALESCE((a)."NU", a[2]); -- succeeds
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+NOTICE:  {"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}
+NOTICE:  [{"": [[3]]}, [6], [2], "bCi"]
+NOTICE:  [2]
 -- clean up
+DROP VIEW v2;
+DROP VIEW v3;
 DROP TABLE test_jsonb_dot_notation;
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 5907cad69fd..d3e91e2ed45 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1625,7 +1625,12 @@ SELECT (jb).a[2].b FROM test_jsonb_dot_notation;
 SELECT (jb).a.x.y FROM test_jsonb_dot_notation;
 SELECT (jb).b.x.z FROM test_jsonb_dot_notation;
 SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
-SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t; -- fails
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
+SELECT (jb).a[2:3].b FROM test_jsonb_dot_notation;
+SELECT (jb).a[2:].b FROM test_jsonb_dot_notation;
+SELECT (jb).a[:2].b FROM test_jsonb_dot_notation;
+SELECT (jb).a[:].b FROM test_jsonb_dot_notation;
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
 SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
 
 -- explains should work
@@ -1637,6 +1642,12 @@ SELECT (jb).a[1] FROM test_jsonb_dot_notation;
 -- views should work
 CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
 \sv test_jsonb_dot_notation_v1
+CREATE VIEW v2 AS SELECT (jb).a[3:].x.y[:-1] FROM test_jsonb_dot_notation;
+\sv v2
+SELECT * from v2;
+CREATE VIEW v3 AS SELECT (jb).a[:3].x.y[-1:] FROM test_jsonb_dot_notation;
+\sv v3
+SELECT * from v3;
 
 -- mixed syntax
 DROP VIEW test_jsonb_dot_notation_v1;
@@ -1697,5 +1708,45 @@ SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
 SELECT * from test_jsonb_dot_notation_v1;
 DROP VIEW public.test_jsonb_dot_notation_v1;
 
+-- jsonb array access in plpgsql
+DO $$
+    DECLARE
+        a jsonb := '[1,2,3,4,5,6,7]'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := a[2:];
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+
+-- jsonb dot access in plpgsql
+DO $$
+    DECLARE
+        a jsonb := '{"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := COALESCE(a."NU", a[2]); -- fails
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+
+DO $$
+    DECLARE
+        a jsonb := '{"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := COALESCE((a)."NU", a[2]); -- succeeds
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+
 -- clean up
+DROP VIEW v2;
+DROP VIEW v3;
 DROP TABLE test_jsonb_dot_notation;
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v15-0005-Implement-read-only-dot-notation-for-jsonb.patch (63.1K, ../../CAK98qZ1XFee_x1uQFcwJkZCSVcPBy-A1ib3N5Ls2uDdZviKVOw@mail.gmail.com/7-v15-0005-Implement-read-only-dot-notation-for-jsonb.patch)
  download | inline diff:
From 0d2ddc492cd9698ce3a14a4fe61d47960abeac23 Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v15 5/7] Implement read-only dot notation for jsonb

This patch introduces JSONB member access using dot notation that
aligns with the JSON simplified accessor specified in SQL:2023.

Examples:

-- Setup
create table t(x int, y jsonb);
insert into t select 1, '{"a": 1, "b": 42}'::jsonb;
insert into t select 1, '{"a": 2, "b": {"c": 42}}'::jsonb;
insert into t select 1, '{"a": 3, "b": {"c": "42"}, "d":[11, 12]}'::jsonb;

-- Existing syntax in PostgreSQL that predates the SQL standard:
select (t.y)->'b' from t;
select (t.y)->'b'->'c' from t;
select (t.y)->'d'->0 from t;

-- JSON simplified accessor specified by the SQL standard:
select (t.y).b from t;
select (t.y).b.c from t;
select (t.y).d[0] from t;

The SQL standard states that simplified access is equivalent to:
JSON_QUERY (VEP, 'lax $.JC' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR)

where:
  VEP = <value expression primary>
  JC = <JSON simplified accessor op chain>

For example, the JSON_QUERY equivalents of the above queries are:

select json_query(y, 'lax $.b' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t;
select json_query(y, 'lax $.b.c' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t;
select json_query(y, 'lax $.d[0]' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t;

Implementation details:

Extends the existing container subscripting interface to support
container-specific information, specifically a JSONPath expression for
jsonb.

During query transformation, if dot-notation is present, constructs a
JSONPath expression representing the access chain.

During execution, if a JSONPath expression is present in
JsonbSubWorkspace, executes it via JsonPathQuery().

Does not transform accessors directly into JSON_QUERY during
transformation to preserve the original query structure for EXPLAIN
and CREATE VIEW.

Co-authored-by: Nikita Glukhov <[email protected]>
Co-authored-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Mark Dilger <[email protected]>
Reviewed-by: Jian He <[email protected]>
Reviewed-by: Vik Fearing <[email protected]>
Reviewed-by: Nikita Malakhov <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Tested-by: Jelte Fennema-Nio <[email protected]>
---
 src/backend/catalog/sql_features.txt          |   4 +-
 src/backend/executor/execExpr.c               |  76 ++--
 src/backend/nodes/nodeFuncs.c                 |  12 +
 src/backend/utils/adt/jsonbsubs.c             | 368 ++++++++++++++--
 src/backend/utils/adt/ruleutils.c             |  43 +-
 src/include/nodes/primnodes.h                 |  54 ++-
 .../ecpg/test/expected/sql-sqljson.c          | 112 ++++-
 .../ecpg/test/expected/sql-sqljson.stderr     | 100 +++++
 .../ecpg/test/expected/sql-sqljson.stdout     |   7 +
 src/interfaces/ecpg/test/sql/sqljson.pgc      |  33 ++
 src/test/regress/expected/jsonb.out           | 413 +++++++++++++++++-
 src/test/regress/sql/jsonb.sql                | 113 ++++-
 src/tools/pgindent/typedefs.list              |   1 +
 13 files changed, 1237 insertions(+), 99 deletions(-)

diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_features.txt
index ebe85337c28..457e993305e 100644
--- a/src/backend/catalog/sql_features.txt
+++ b/src/backend/catalog/sql_features.txt
@@ -568,8 +568,8 @@ T838	JSON_TABLE: PLAN DEFAULT clause			NO
 T839	Formatted cast of datetimes to/from character strings			NO	
 T840	Hex integer literals in SQL/JSON path language			YES	
 T851	SQL/JSON: optional keywords for default syntax			YES	
-T860	SQL/JSON simplified accessor: column reference only			NO	
-T861	SQL/JSON simplified accessor: case-sensitive JSON member accessor			NO	
+T860	SQL/JSON simplified accessor: column reference only			YES	
+T861	SQL/JSON simplified accessor: case-sensitive JSON member accessor			YES	
 T862	SQL/JSON simplified accessor: wildcard member accessor			NO	
 T863	SQL/JSON simplified accessor: single-quoted string literal as member accessor			NO	
 T864	SQL/JSON simplified accessor			NO	
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index f1569879b52..eac31b097e4 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -3320,50 +3320,54 @@ ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref,
 								   state->steps_len - 1);
 	}
 
-	/* Evaluate upper subscripts */
-	i = 0;
-	foreach(lc, sbsref->refupperindexpr)
+	/* Evaluate upper subscripts, unless refjsonbpath is used for execution */
+	if (!sbsref->refjsonbpath)
 	{
-		Expr	   *e = (Expr *) lfirst(lc);
+		i = 0;
+		foreach(lc, sbsref->refupperindexpr) {
+			Expr *e = (Expr *) lfirst(lc);
 
-		/* When slicing, individual subscript bounds can be omitted */
-		if (!e)
-		{
-			sbsrefstate->upperprovided[i] = false;
-			sbsrefstate->upperindexnull[i] = true;
-		}
-		else
-		{
-			sbsrefstate->upperprovided[i] = true;
-			/* Each subscript is evaluated into appropriate array entry */
-			ExecInitExprRec(e, state,
-							&sbsrefstate->upperindex[i],
-							&sbsrefstate->upperindexnull[i]);
+			/* When slicing, individual subscript bounds can be omitted */
+			if (!e)
+			{
+				sbsrefstate->upperprovided[i] = false;
+				sbsrefstate->upperindexnull[i] = true;
+			}
+			else {
+				sbsrefstate->upperprovided[i] = true;
+				/* Each subscript is evaluated into appropriate array entry */
+				ExecInitExprRec(e, state,
+								&sbsrefstate->upperindex[i],
+								&sbsrefstate->upperindexnull[i]);
+			}
+			i++;
 		}
-		i++;
 	}
 
-	/* Evaluate lower subscripts similarly */
-	i = 0;
-	foreach(lc, sbsref->reflowerindexpr)
+	/* Evaluate lower subscripts similarly, unless refjsonbpath is used for execution  */
+	if (!sbsref->refjsonbpath)
 	{
-		Expr	   *e = (Expr *) lfirst(lc);
-
-		/* When slicing, individual subscript bounds can be omitted */
-		if (!e)
+		i = 0;
+		foreach(lc, sbsref->reflowerindexpr)
 		{
-			sbsrefstate->lowerprovided[i] = false;
-			sbsrefstate->lowerindexnull[i] = true;
-		}
-		else
-		{
-			sbsrefstate->lowerprovided[i] = true;
-			/* Each subscript is evaluated into appropriate array entry */
-			ExecInitExprRec(e, state,
-							&sbsrefstate->lowerindex[i],
-							&sbsrefstate->lowerindexnull[i]);
+			Expr	   *e = (Expr *) lfirst(lc);
+
+			/* When slicing, individual subscript bounds can be omitted */
+			if (!e)
+			{
+				sbsrefstate->lowerprovided[i] = false;
+				sbsrefstate->lowerindexnull[i] = true;
+			}
+			else
+			{
+				sbsrefstate->lowerprovided[i] = true;
+				/* Each subscript is evaluated into appropriate array entry */
+				ExecInitExprRec(e, state,
+								&sbsrefstate->lowerindex[i],
+								&sbsrefstate->lowerindexnull[i]);
+			}
+			i++;
 		}
-		i++;
 	}
 
 	/* SBSREF_SUBSCRIPTS checks and converts all the subscripts at once */
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 7bc823507f1..d1bd575d9bd 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -284,6 +284,13 @@ exprType(const Node *expr)
 		case T_PlaceHolderVar:
 			type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_FieldAccessorExpr:
+			/*
+			 * FieldAccessorExpr is not evaluable. Treat it as TEXT for collation,
+			 * deparsing, and similar purposes, since it represents a JSON field name.
+			 */
+			type = TEXTOID;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			type = InvalidOid;	/* keep compiler quiet */
@@ -1146,6 +1153,9 @@ exprSetCollation(Node *expr, Oid collation)
 		case T_MergeSupportFunc:
 			((MergeSupportFunc *) expr)->msfcollid = collation;
 			break;
+		case T_FieldAccessorExpr:
+			((FieldAccessorExpr *) expr)->faecollid = collation;
+			break;
 		case T_SubscriptingRef:
 			((SubscriptingRef *) expr)->refcollid = collation;
 			break;
@@ -2129,6 +2139,7 @@ expression_tree_walker_impl(Node *node,
 		case T_SortGroupClause:
 		case T_CTESearchClause:
 		case T_MergeSupportFunc:
+		case T_FieldAccessorExpr:
 			/* primitive node types with no expression subnodes */
 			break;
 		case T_WithCheckOption:
@@ -3008,6 +3019,7 @@ expression_tree_mutator_impl(Node *node,
 		case T_SortGroupClause:
 		case T_CTESearchClause:
 		case T_MergeSupportFunc:
+		case T_FieldAccessorExpr:
 			return copyObject(node);
 		case T_WithCheckOption:
 			{
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index f944d1544ca..485ad967aba 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -15,21 +15,30 @@
 #include "postgres.h"
 
 #include "executor/execExpr.h"
+#include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "nodes/subscripting.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_expr.h"
 #include "utils/builtins.h"
 #include "utils/jsonb.h"
+#include "utils/jsonpath.h"
 
 
-/* SubscriptingRefState.workspace for jsonb subscripting execution */
+/*
+ * SubscriptingRefState.workspace for generic jsonb subscripting execution.
+ *
+ * Stores state for both jsonb simple subscripting and dot notation access.
+ * Dot notation additionally uses `jsonpath` for JsonPath evaluation.
+ */
 typedef struct JsonbSubWorkspace
 {
 	bool		expectArray;	/* jsonb root is expected to be an array */
 	Oid		   *indexOid;		/* OID of coerced subscript expression, could
 								 * be only integer or text */
 	Datum	   *index;			/* Subscript values in Datum format */
+	JsonPath   *jsonpath;		/* JsonPath for dot notation execution via
+								 * JsonPathQuery() */
 } JsonbSubWorkspace;
 
 static Node *
@@ -96,6 +105,268 @@ coerce_jsonpath_subscript_to_int4_or_text(ParseState *pstate, Node *subExpr)
 	return subExpr;
 }
 
+/*
+ * During transformation, determine whether to build a JsonPath
+ * for JsonPathQuery() execution.
+ *
+ * JsonPath is needed if the indirection list includes:
+ * - String-based access (dot notation)
+ * - Slice-based subscripting (when isSlice is true)
+ *
+ * Otherwise, simple jsonb subscripting is enough.
+ */
+static bool
+jsonb_check_jsonpath_needed(List *indirection, bool isSlice)
+{
+	ListCell   *lc;
+
+#ifdef USE_ASSERT_CHECKING
+	/* Verify consistency between isSlice parameter and is_slice field */
+	bool		foundSlice = false;
+
+	foreach(lc, indirection)
+	{
+		Node	   *accessor = lfirst(lc);
+
+		if (IsA(accessor, A_Indices) && castNode(A_Indices, accessor)->is_slice)
+		{
+			foundSlice = true;
+			break;
+		}
+	}
+	Assert(foundSlice == isSlice);
+#endif
+
+	if (isSlice)
+		return true;
+
+	foreach(lc, indirection)
+	{
+		Node	   *accessor = lfirst(lc);
+
+		if (IsA(accessor, String))
+			return true;
+		else
+			Assert(IsA(accessor, A_Indices));
+	}
+
+	return false;
+}
+
+/*
+ * Helper functions for constructing JsonPath expressions.
+ *
+ * The make_jsonpath_item_* functions create various types of JsonPathParseItem
+ * nodes, which are used to build JsonPath expressions for jsonb simplified
+ * accessor.
+ */
+
+static JsonPathParseItem *
+make_jsonpath_item(JsonPathItemType type)
+{
+	JsonPathParseItem *v = palloc(sizeof(*v));
+
+	v->type = type;
+	v->next = NULL;
+
+	return v;
+}
+
+/*
+ * Build a JsonPathParseItem for a constant integer value.
+ *
+ * This function constructs a jpiNumeric item for use in JsonPath,
+ * and appends a matching Const(INT4) node to the given expression list
+ * for use in EXPLAIN, views, etc.
+ *
+ * Parameters:
+ * - val: integer constant value
+ * - exprs: list of expression nodes (updated in place)
+ */
+static JsonPathParseItem *
+make_jsonpath_item_int(int32 val, List **exprs)
+{
+	JsonPathParseItem *jpi = make_jsonpath_item(jpiNumeric);
+
+	jpi->value.numeric =
+		DatumGetNumeric(DirectFunctionCall1(int4_numeric, Int32GetDatum(val)));
+
+	*exprs = lappend(*exprs, makeConst(INT4OID, -1, InvalidOid, 4,
+									   Int32GetDatum(val), false, true));
+
+	return jpi;
+}
+
+/*
+ * Convert a constant integer expression into a JsonPathParseItem.
+ *
+ * The input expression must be a non-null constant of type INT4. Returns NULL otherwise.
+ * The function extracts the value and delegates it to make_jsonpath_item_int().
+ *
+ * Parameters:
+ * - pstate: parse state context
+ * - expr: input expression node
+ * - exprs: list of expression nodes (updated in place)
+ */
+static JsonPathParseItem *
+make_jsonpath_item_expr(ParseState *pstate, Node *expr, List **exprs)
+{
+	Const	   *cnst;
+
+	expr = transformExpr(pstate, expr, pstate->p_expr_kind);
+
+	if (IsA(expr, Const))
+	{
+		cnst = (Const *) expr;
+		if (cnst->consttype == INT4OID && !(cnst->constisnull))
+			return make_jsonpath_item_int(DatumGetInt32(cnst->constvalue), exprs);
+	}
+
+	return NULL;
+}
+
+/*
+ * Constructs a JsonPath expression from a list of indirections.
+ * This function is used when jsonb subscripting involves dot notation,
+ * requiring JsonPath-based evaluation.
+ *
+ * The function modifies the indirection list in place, removing processed
+ * elements as it converts them into JsonPath components, as follows:
+ * - String keys (dot notation) -> jpiKey items.
+ * - Array indices -> jpiIndexArray items.
+ *
+ * In addition to building the JsonPath expression, this function populates
+ * the following fields of the given SubscriptingRef:
+ * - refjsonbpath: the generated JsonPath
+ * - refupperindexpr: upper index expressions (object keys or array indexes)
+ * - reflowerindexpr: lower index expressions, remains NIL as slices are not yet supported.
+ *
+ * Parameters:
+ * - pstate: Parse state context.
+ * - indirection: List of subscripting expressions (modified in-place).
+ * - sbsref: SubscriptingRef node to update
+ */
+static void
+jsonb_subscript_make_jsonpath(ParseState *pstate, List **indirection, SubscriptingRef *sbsref)
+{
+	JsonPathParseResult jpres;
+	JsonPathParseItem *path = make_jsonpath_item(jpiRoot);
+	ListCell   *lc;
+	Datum		jsp;
+	int			pathlen = 0;
+
+	sbsref->refupperindexpr = NIL;
+	sbsref->reflowerindexpr = NIL;
+	sbsref->refjsonbpath = NULL;
+
+	jpres.expr = path;
+	jpres.lax = true;
+
+	foreach(lc, *indirection)
+	{
+		Node	   *accessor = lfirst(lc);
+		JsonPathParseItem *jpi;
+
+		if (IsA(accessor, String))
+		{
+			char	   *field = strVal(accessor);
+			FieldAccessorExpr *accessor_expr;
+
+			jpi = make_jsonpath_item(jpiKey);
+			jpi->value.string.val = field;
+			jpi->value.string.len = strlen(field);
+
+			accessor_expr = makeNode(FieldAccessorExpr);
+			accessor_expr->type = T_FieldAccessorExpr;
+			accessor_expr->fieldname = field;
+
+			sbsref->refupperindexpr = lappend(sbsref->refupperindexpr, accessor_expr);
+		}
+		else if (IsA(accessor, A_Indices))
+		{
+			A_Indices  *ai = castNode(A_Indices, accessor);
+
+			if (!ai->is_slice)
+			{
+				JsonPathParseItem *jpi_from = NULL;
+
+				Assert(ai->uidx && !ai->lidx);
+				jpi_from = make_jsonpath_item_expr(pstate, ai->uidx, &sbsref->refupperindexpr);
+				if (jpi_from == NULL)
+				{
+					/*
+					 * Break out of the loop if the subscript is not a
+					 * non-null integer constant, so that we can fall back to
+					 * jsonb subscripting logic.
+					 *
+					 * This is needed to handle cases with mixed usage of SQL
+					 * standard json simplified accessor syntax and PostgreSQL
+					 * jsonb subscripting syntax, e.g:
+					 *
+					 * select (jb).a['b'].c from jsonb_table;
+					 *
+					 * where dot-notation (.a and .c) is the SQL standard json
+					 * simplified accessor syntax, and the ['b'] subscript is
+					 * the PostgreSQL jsonb subscripting syntax, because 'b'
+					 * is not a non-null constant integer and cannot be used
+					 * for json array access.
+					 *
+					 * In this case, we cannot create a JsonPath item, so we
+					 * break out of the loop and let
+					 * jsonb_subscript_transform() handle this indirection as
+					 * a PostgreSQL jsonb subscript.
+					 */
+					break;
+				}
+
+				jpi = make_jsonpath_item(jpiIndexArray);
+				jpi->value.array.nelems = 1;
+				jpi->value.array.elems = palloc(sizeof(jpi->value.array.elems[0]));
+
+				jpi->value.array.elems[0].from = jpi_from;
+				jpi->value.array.elems[0].to = NULL;
+			}
+			else
+			{
+				Node	   *expr = ai->uidx ? ai->uidx : ai->lidx;
+
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("jsonb subscript does not support slices"),
+						 parser_errposition(pstate, exprLocation(expr))));
+			}
+		}
+		else
+		{
+			/*
+			 * Unexpected node type in indirection list. This should not
+			 * happen with current grammar, but we handle it defensively by
+			 * breaking out of the loop rather than crashing. In case of
+			 * future grammar changes that might introduce new node types,
+			 * this allows us to create a jsonpath from as many indirection
+			 * elements as we can and let transformIndirection() fallback to
+			 * alternative logic to handle the remaining indirection elements.
+			 */
+			Assert(false);		/* not reachable */
+			break;
+		}
+
+		/* append path item */
+		path->next = jpi;
+		path = jpi;
+		pathlen++;
+	}
+
+	if (pathlen == 0)
+		return;
+
+	*indirection = list_delete_first_n(*indirection, pathlen);
+
+	jsp = jsonPathFromParseResult(&jpres, 0, NULL);
+
+	sbsref->refjsonbpath = (Node *) makeConst(JSONPATHOID, -1, InvalidOid, -1, jsp, false, false);
+}
+
 /*
  * Finish parse analysis of a SubscriptingRef expression for a jsonb.
  *
@@ -112,47 +383,46 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	List	   *upperIndexpr = NIL;
 	ListCell   *idx;
 
+	/* Determine the result type of the subscripting operation; always jsonb */
+	sbsref->refrestype = JSONBOID;
+	sbsref->reftypmod = -1;
+
+	if (jsonb_check_jsonpath_needed(*indirection, isSlice))
+	{
+		jsonb_subscript_make_jsonpath(pstate, indirection, sbsref);
+		if (sbsref->refjsonbpath)
+			return;
+	}
+
 	/*
-	 * Transform and convert the subscript expressions. Jsonb subscripting
-	 * does not support slices, look only at the upper index.
+	 * We reach here only in two cases: (a) the JSON simplified accessor is
+	 * not needed at all (for example, a plain array subscript like [1] or
+	 * object key access like ['a']), or (b) jsonb_subscript_make_jsonpath()
+	 * was called but could not complete the JsonPath construction (for
+	 * example, when mixing dot notation with non-integer subscripts like
+	 * (jb)['a'].b where 'a' is not a constant integer).
+	 *
+	 * In both cases we fall back to pre-standard jsonb subscripting, coercing
+	 * each subscript to array index or object key as needed.
 	 */
 	foreach(idx, *indirection)
 	{
+		Node	   *i = lfirst(idx);
 		A_Indices  *ai;
 		Node	   *subExpr;
 
 		if (!IsA(lfirst(idx), A_Indices))
 			break;
 
-		ai = lfirst_node(A_Indices, idx);
+		ai = castNode(A_Indices, i);
 
-		if (isSlice)
-		{
-			Node	   *expr = ai->uidx ? ai->uidx : ai->lidx;
+		if (ai->is_slice)
+			break;
 
-			ereport(ERROR,
-					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("jsonb subscript does not support slices"),
-					 parser_errposition(pstate, exprLocation(expr))));
-		}
+		Assert(!ai->lidx && ai->uidx);
 
-		if (ai->uidx)
-		{
-			subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
-			subExpr = coerce_jsonpath_subscript_to_int4_or_text(pstate, subExpr);
-		}
-		else
-		{
-			/*
-			 * Slice with omitted upper bound. Should not happen as we already
-			 * errored out on slice earlier, but handle this just in case.
-			 */
-			Assert(isSlice && ai->is_slice);
-			ereport(ERROR,
-					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("jsonb subscript does not support slices"),
-					 parser_errposition(pstate, exprLocation(ai->uidx))));
-		}
+		subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
+		subExpr = coerce_jsonpath_subscript_to_int4_or_text(pstate, subExpr);
 
 		upperIndexpr = lappend(upperIndexpr, subExpr);
 	}
@@ -161,10 +431,6 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->refupperindexpr = upperIndexpr;
 	sbsref->reflowerindexpr = NIL;
 
-	/* Determine the result type of the subscripting operation; always jsonb */
-	sbsref->refrestype = JSONBOID;
-	sbsref->reftypmod = -1;
-
 	/* Remove processed elements */
 	if (upperIndexpr)
 		*indirection = list_delete_first_n(*indirection, list_length(upperIndexpr));
@@ -219,7 +485,7 @@ jsonb_subscript_check_subscripts(ExprState *state,
 			 * For jsonb fetch and assign functions we need to provide path in
 			 * text format. Convert if it's not already text.
 			 */
-			if (workspace->indexOid[i] == INT4OID)
+			if (!workspace->jsonpath && workspace->indexOid[i] == INT4OID)
 			{
 				Datum		datum = sbsrefstate->upperindex[i];
 				char	   *cs = DatumGetCString(DirectFunctionCall1(int4out, datum));
@@ -247,17 +513,32 @@ jsonb_subscript_fetch(ExprState *state,
 {
 	SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
 	JsonbSubWorkspace *workspace = (JsonbSubWorkspace *) sbsrefstate->workspace;
-	Jsonb	   *jsonbSource;
 
 	/* Should not get here if source jsonb (or any subscript) is null */
 	Assert(!(*op->resnull));
 
-	jsonbSource = DatumGetJsonbP(*op->resvalue);
-	*op->resvalue = jsonb_get_element(jsonbSource,
-									  workspace->index,
-									  sbsrefstate->numupper,
-									  op->resnull,
-									  false);
+	if (workspace->jsonpath)
+	{
+		bool		empty = false;
+		bool		error = false;
+
+		*op->resvalue = JsonPathQuery(*op->resvalue, workspace->jsonpath,
+									  JSW_CONDITIONAL,
+									  &empty, &error, NULL,
+									  NULL);
+
+		*op->resnull = empty || error;
+	}
+	else
+	{
+		Jsonb	   *jsonbSource = DatumGetJsonbP(*op->resvalue);
+
+		*op->resvalue = jsonb_get_element(jsonbSource,
+										  workspace->index,
+										  sbsrefstate->numupper,
+										  op->resnull,
+										  false);
+	}
 }
 
 /*
@@ -365,7 +646,7 @@ jsonb_exec_setup(const SubscriptingRef *sbsref,
 {
 	JsonbSubWorkspace *workspace;
 	ListCell   *lc;
-	int			nupper = sbsref->refupperindexpr->length;
+	int			nupper = list_length(sbsref->refupperindexpr);
 	char	   *ptr;
 
 	/* Allocate type-specific workspace with space for per-subscript data */
@@ -374,6 +655,9 @@ jsonb_exec_setup(const SubscriptingRef *sbsref,
 	workspace->expectArray = false;
 	ptr = ((char *) workspace) + MAXALIGN(sizeof(JsonbSubWorkspace));
 
+	if (sbsref->refjsonbpath)
+		workspace->jsonpath = DatumGetJsonPathP(castNode(Const, sbsref->refjsonbpath)->constvalue);
+
 	/*
 	 * This coding assumes sizeof(Datum) >= sizeof(Oid), else we might
 	 * misalign the indexOid pointer
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 3d6e6bdbfd2..baa3ae97d57 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9329,10 +9329,13 @@ get_rule_expr(Node *node, deparse_context *context,
 				 * Parenthesize the argument unless it's a simple Var or a
 				 * FieldSelect.  (In particular, if it's another
 				 * SubscriptingRef, we *must* parenthesize to avoid
-				 * confusion.)
+				 * confusion.) Always add parenthesis if JSON simplified
+				 * accessor is used, for now.
 				 */
-				need_parens = !IsA(sbsref->refexpr, Var) &&
-					!IsA(sbsref->refexpr, FieldSelect);
+				need_parens = (!IsA(sbsref->refexpr, Var) &&
+					!IsA(sbsref->refexpr, FieldSelect)) ||
+						sbsref->refjsonbpath;
+
 				if (need_parens)
 					appendStringInfoChar(buf, '(');
 				get_rule_expr((Node *) sbsref->refexpr, context, showimplicit);
@@ -13005,17 +13008,35 @@ printSubscripts(SubscriptingRef *sbsref, deparse_context *context)
 	lowlist_item = list_head(sbsref->reflowerindexpr);	/* could be NULL */
 	foreach(uplist_item, sbsref->refupperindexpr)
 	{
-		appendStringInfoChar(buf, '[');
-		if (lowlist_item)
+		Node *upper = (Node *) lfirst(uplist_item);
+
+		if (upper && IsA(upper, FieldAccessorExpr))
 		{
+			FieldAccessorExpr *fae = (FieldAccessorExpr *) upper;
+
+			/* Use dot-notation for field access */
+			appendStringInfoChar(buf, '.');
+			appendStringInfoString(buf, quote_identifier(fae->fieldname));
+
+			/* Skip matching low index — field access doesn't use slices */
+			if (lowlist_item)
+				lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
+		}
+		else
+		{
+			/* Use JSONB array subscripting */
+			appendStringInfoChar(buf, '[');
+			if (lowlist_item)
+			{
+				/* If subexpression is NULL, get_rule_expr prints nothing */
+				get_rule_expr((Node *) lfirst(lowlist_item), context, false);
+				appendStringInfoChar(buf, ':');
+				lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
+			}
 			/* If subexpression is NULL, get_rule_expr prints nothing */
-			get_rule_expr((Node *) lfirst(lowlist_item), context, false);
-			appendStringInfoChar(buf, ':');
-			lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
+			get_rule_expr(upper, context, false);
+			appendStringInfoChar(buf, ']');
 		}
-		/* If subexpression is NULL, get_rule_expr prints nothing */
-		get_rule_expr((Node *) lfirst(uplist_item), context, false);
-		appendStringInfoChar(buf, ']');
 	}
 }
 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 6dfca3cb35b..7e89621bd65 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -708,18 +708,30 @@ typedef struct SubscriptingRef
 	int32		reftypmod pg_node_attr(query_jumble_ignore);
 	/* collation of result, or InvalidOid if none */
 	Oid			refcollid pg_node_attr(query_jumble_ignore);
-	/* expressions that evaluate to upper container indexes */
+
+	/*
+	 * expressions that evaluate to upper container indexes or expressions
+	 * that are collected but not evaluated when refjsonbpath is set.
+	 */
 	List	   *refupperindexpr;
 
 	/*
-	 * expressions that evaluate to lower container indexes, or NIL for single
-	 * container element.
+	 * expressions that evaluate to lower container indexes, or NIL for a
+	 * single container element, or expressions that are collected but not
+	 * evaluated when refjsonbpath is set.
 	 */
 	List	   *reflowerindexpr;
 	/* the expression that evaluates to a container value */
 	Expr	   *refexpr;
 	/* expression for the source value, or NULL if fetch */
 	Expr	   *refassgnexpr;
+
+	/*
+	 * container-specific extra information, currently used only by jsonb.
+	 * stores a JsonPath expression when jsonb dot notation is used. NULL for
+	 * simple subscripting.
+	 */
+	Node	   *refjsonbpath;
 } SubscriptingRef;
 
 /*
@@ -2371,4 +2383,40 @@ typedef struct OnConflictExpr
 	List	   *exclRelTlist;	/* tlist of the EXCLUDED pseudo relation */
 } OnConflictExpr;
 
+/*
+ * FieldAccessorExpr - represents a single object member access using dot-notation
+ *		in JSON simplified accessor syntax (e.g., jsonb_col.a).
+ *
+ * These nodes appear as list elements in SubscriptingRef.refupperindexpr to
+ * indicate JSON object key access. They are not evaluable expressions by
+ * themselves but serve as placeholders to preserve source-level syntax for
+ * rule rewriting and deparsing (e.g., in EXPLAIN and view definitions).
+ * Execution is handled by the enclosing SubscriptingRef.
+ *
+ * If dot-notation is used in a SubscriptingRef, the JSON path is represented
+ * as a flat list of FieldAccessorExpr nodes (for object field access), Const
+ * nodes (for array indexes), and NULLs (for omitted slice bounds), rather than
+ * through nested expression trees.
+ *
+ * Note: The flat representation avoids nested FieldAccessorExpr chains,
+ * simplifying evaluation and enabling standard-compliant behavior such as
+ * conditional array wrapping. This avoids the need for position-aware
+ * wrapping/unwrapping logic during execution.
+ *
+ * For example, in the expression:
+ *		('{"a": [{"b": 1}]}'::jsonb).a[0].b
+ * the SubscriptingRef will contain:
+ *		- refexpr: the base expression (the jsonb value)
+ *		- refupperindexpr: [FieldAccessorExpr("a"), Const(0),
+ *			FieldAccessorExpr("b")]
+ *		- reflowerindexpr: [NULL, NULL, NULL] (slice lower bounds not used here)
+ */
+typedef struct FieldAccessorExpr
+{
+	NodeTag		type;
+	char	   *fieldname;		/* name of the JSONB object field accessed via
+								 * dot notation */
+	Oid			faecollid pg_node_attr(query_jumble_ignore);
+}			FieldAccessorExpr;
+
 #endif							/* PRIMNODES_H */
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.c b/src/interfaces/ecpg/test/expected/sql-sqljson.c
index 39221f9ea5d..e6a7ece6dab 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.c
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.c
@@ -417,12 +417,122 @@ if (sqlca.sqlcode < 0) sqlprint();}
 	  for (int i = 0; i < sizeof(is_json); i++)
 		  printf("Found is_json[%d]: %s\n", i, is_json[i] ? "true" : "false");
 
-  { ECPGdisconnect(__LINE__, "CURRENT");
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . \"a\" )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 118 "sqljson.pgc"
 
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 118 "sqljson.pgc"
 
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb . \"a\" )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 121 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 121 "sqljson.pgc"
+
+	// error
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . a )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 124 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 124 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . a . b )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 127 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 127 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( coalesce ( json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . c ) , 'null' ) )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 130 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 130 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ 0 ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 133 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 133 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b . x )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 136 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 136 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) [ 'b' ] [ 0 ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 139 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 139 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 142 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 142 "sqljson.pgc"
+
+	// error
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . * )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 145 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 145 "sqljson.pgc"
+
+	// error
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 148 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 148 "sqljson.pgc"
+
+	// error
+
+  { ECPGdisconnect(__LINE__, "CURRENT");
+#line 151 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 151 "sqljson.pgc"
+
 
   return 0;
 }
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
index e55a95dd711..19f8c58af06 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
@@ -268,5 +268,105 @@ SQL error: cannot use type jsonb in RETURNING clause of JSON_SERIALIZE() on line
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_get_data on line 102: RESULT: f offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 118: query: select json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . "a" ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 118: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 118: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 118: RESULT: {"b": 1, "c": 2} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 121: query: select json ( '{"a": {"b": 1, "c": 2}}' :: jsonb . "a" ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 121: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 121: bad response - ERROR:  schema "jsonb" does not exist
+LINE 1: select json ( '{"a": {"b": 1, "c": 2}}' :: jsonb . "a" )
+                                                   ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 3F000 (sqlcode -400): schema "jsonb" does not exist on line 121
+[NO_PID]: sqlca: code: -400, state: 3F000
+SQL error: schema "jsonb" does not exist on line 121
+[NO_PID]: ecpg_execute on line 124: query: select json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . a ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 124: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 124: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 124: RESULT: {"b": 1, "c": 2} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 127: query: select json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . a . b ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 127: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 127: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 127: RESULT: 1 offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 130: query: select json ( coalesce ( json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . c ) , 'null' ) ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 130: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 130: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 130: RESULT: null offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 133: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 0 ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 133: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 133: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 133: RESULT: {"x": 1} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 136: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b . x ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 136: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 136: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 136: RESULT: [1, [12, {"y": 1}]] offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 139: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 139: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 139: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 139: RESULT: {"x": 1} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 142: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 142: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 142: bad response - ERROR:  jsonb subscript does not support slices
+LINE 1: ..., {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] )
+                                                                ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 42804 (sqlcode -400): jsonb subscript does not support slices on line 142
+[NO_PID]: sqlca: code: -400, state: 42804
+SQL error: jsonb subscript does not support slices on line 142
+[NO_PID]: ecpg_execute on line 145: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . * ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 145: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 145: bad response - ERROR:  row expansion via "*" is not supported here
+LINE 1: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x...
+                        ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 0A000 (sqlcode -400): row expansion via "*" is not supported here on line 145
+[NO_PID]: sqlca: code: -400, state: 0A000
+SQL error: row expansion via "*" is not supported here on line 145
+[NO_PID]: ecpg_execute on line 148: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 148: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 148: bad response - ERROR:  jsonb subscript does not support slices
+LINE 1: ...x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )
+                                                                ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 42804 (sqlcode -400): jsonb subscript does not support slices on line 148
+[NO_PID]: sqlca: code: -400, state: 42804
+SQL error: jsonb subscript does not support slices on line 148
 [NO_PID]: ecpg_finish: connection ecpg1_regression closed
 [NO_PID]: sqlca: code: 0, state: 00000
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
index 83f8df13e5a..442d36931f1 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
@@ -28,3 +28,10 @@ Found is_json[4]: false
 Found is_json[5]: false
 Found is_json[6]: true
 Found is_json[7]: false
+Found json={"b": 1, "c": 2}
+Found json={"b": 1, "c": 2}
+Found json=1
+Found json=null
+Found json={"x": 1}
+Found json=[1, [12, {"y": 1}]]
+Found json={"x": 1}
diff --git a/src/interfaces/ecpg/test/sql/sqljson.pgc b/src/interfaces/ecpg/test/sql/sqljson.pgc
index ddcbcc3b3cb..57a9bff424d 100644
--- a/src/interfaces/ecpg/test/sql/sqljson.pgc
+++ b/src/interfaces/ecpg/test/sql/sqljson.pgc
@@ -115,6 +115,39 @@ EXEC SQL END DECLARE SECTION;
 	  for (int i = 0; i < sizeof(is_json); i++)
 		  printf("Found is_json[%d]: %s\n", i, is_json[i] ? "true" : "false");
 
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}}'::jsonb)."a") INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON('{"a": {"b": 1, "c": 2}}'::jsonb."a") INTO :json;
+	// error
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}}'::jsonb).a) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}}'::jsonb).a.b) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(COALESCE(JSON(('{"a": {"b": 1, "c": 2}}'::jsonb).c), 'null')) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b[0]) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b.x) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb)['b'][0]) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b[1].x[0:]) INTO :json;
+	// error
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).*) INTO :json;
+	// error
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb)['b'][0:]) INTO :json;
+	// error
+
   EXEC SQL DISCONNECT;
 
   return 0;
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5a1eb18aba2..d596e712372 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -4989,6 +4989,12 @@ select ('123'::jsonb)['a'];
  
 (1 row)
 
+select ('123'::jsonb).a;
+ a 
+---
+ 
+(1 row)
+
 select ('123'::jsonb)[0];
  jsonb 
 -------
@@ -5001,12 +5007,24 @@ select ('123'::jsonb)[NULL];
  
 (1 row)
 
+select ('123'::jsonb).NULL;
+ null 
+------
+ 
+(1 row)
+
 select ('{"a": 1}'::jsonb)['a'];
  jsonb 
 -------
  1
 (1 row)
 
+select ('{"a": 1}'::jsonb).a;
+ a 
+---
+ 1
+(1 row)
+
 select ('{"a": 1}'::jsonb)[0];
  jsonb 
 -------
@@ -5019,6 +5037,12 @@ select ('{"a": 1}'::jsonb)['not_exist'];
  
 (1 row)
 
+select ('{"a": 1}'::jsonb)."not_exist";
+ not_exist 
+-----------
+ 
+(1 row)
+
 select ('{"a": 1}'::jsonb)[NULL];
  jsonb 
 -------
@@ -5031,6 +5055,12 @@ select ('[1, "2", null]'::jsonb)['a'];
  
 (1 row)
 
+select ('[1, "2", null]'::jsonb).a;
+ a 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[0];
  jsonb 
 -------
@@ -5043,6 +5073,12 @@ select ('[1, "2", null]'::jsonb)['1'];
  "2"
 (1 row)
 
+select ('[1, "2", null]'::jsonb)."1";
+ 1 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[1.0];
 ERROR:  subscript type numeric is not supported
 LINE 1: select ('[1, "2", null]'::jsonb)[1.0];
@@ -5072,6 +5108,12 @@ select ('[1, "2", null]'::jsonb)[1]['a'];
  
 (1 row)
 
+select ('[1, "2", null]'::jsonb)[1].a;
+ a 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[1][0];
  jsonb 
 -------
@@ -5084,56 +5126,127 @@ select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['b'];
  "c"
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).b;
+  b  
+-----
+ "c"
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'];
    jsonb   
 -----------
  [1, 2, 3]
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d;
+     d     
+-----------
+ [1, 2, 3]
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'][1];
  jsonb 
 -------
  2
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d[1];
+ d 
+---
+ 2
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d']['a'];
  jsonb 
 -------
  
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['a'];
+ d 
+---
+ 
+(1 row)
+
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d.a;
+ a 
+---
+ 
+(1 row)
+
+select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d['a'][0];
+ d 
+---
+ 1
+(1 row)
+
+select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d.a[0];
+ a 
+---
+ 1
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1'];
      jsonb     
 ---------------
  {"a2": "aaa"}
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1;
+      a1       
+---------------
+ {"a2": "aaa"}
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2'];
  jsonb 
 -------
  "aaa"
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2;
+  a2   
+-------
+ "aaa"
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']['a3'];
  jsonb 
 -------
  
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2.a3;
+ a3 
+----
+ 
+(1 row)
+
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'];
          jsonb         
 -----------------------
  ["aaa", "bbb", "ccc"]
 (1 row)
 
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1;
+          b1           
+-----------------------
+ ["aaa", "bbb", "ccc"]
+(1 row)
+
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'][2];
  jsonb 
 -------
  "ccc"
 (1 row)
 
--- slices are not supported
-select ('{"a": 1}'::jsonb)['a':'b'];
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1[2];
+  b1   
+-------
+ "ccc"
+(1 row)
+
+select ('{"a": 1}'::jsonb)['a':'b']; -- fails
 ERROR:  jsonb subscript does not support slices
 LINE 1: select ('{"a": 1}'::jsonb)['a':'b'];
                                        ^
@@ -5831,3 +5944,299 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
  12345
 (1 row)
 
+-- dot notation
+CREATE TABLE test_jsonb_dot_notation AS
+SELECT '{"a": [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], "b": [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]}'::jsonb jb;
+SELECT (jb).a FROM test_jsonb_dot_notation;
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+SELECT (jb)."a" FROM test_jsonb_dot_notation; -- double quote should work
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+SELECT (jb).'a' FROM test_jsonb_dot_notation; -- single quote should not work
+ERROR:  syntax error at or near "'a'"
+LINE 1: SELECT (jb).'a' FROM test_jsonb_dot_notation;
+                    ^
+select (jb)[0].a from test_jsonb_dot_notation; -- returns same result as (jb).a
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+select (jb)[1].a from test_jsonb_dot_notation; -- returns NULL
+ a 
+---
+ 
+(1 row)
+
+SELECT (jb).b FROM test_jsonb_dot_notation;
+                         b                         
+---------------------------------------------------
+ [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]
+(1 row)
+
+SELECT (jb).c FROM test_jsonb_dot_notation;
+ c 
+---
+ 
+(1 row)
+
+SELECT (jb).a.b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT (jb).a[2].b FROM test_jsonb_dot_notation;
+  b  
+-----
+ "c"
+(1 row)
+
+SELECT (jb).a.x.y FROM test_jsonb_dot_notation;
+   y   
+-------
+ "yyy"
+(1 row)
+
+SELECT (jb).b.x.z FROM test_jsonb_dot_notation;
+   z   
+-------
+ "ZZZ"
+(1 row)
+
+SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
+ c 
+---
+ 
+(1 row)
+
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t; -- fails
+ERROR:  jsonb subscript does not support slices
+SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
+ERROR:  type jsonb is not composite
+-- explains should work
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+                  QUERY PLAN                  
+----------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation t
+   Output: (jb).a
+(2 rows)
+
+SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb).a[1]
+(2 rows)
+
+SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+ a 
+---
+ 2
+(1 row)
+
+-- views should work
+CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT (jb).a[3].x.y AS y
+   FROM test_jsonb_dot_notation
+-- mixed syntax
+DROP VIEW test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb['a'::text]).b
+(2 rows)
+
+SELECT (jb)['a'].b FROM test_jsonb_dot_notation; -- returns an array due to lax mode
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT (jb['a'::text]).b AS b
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: ((jb).a)['b'::text]
+(2 rows)
+
+SELECT (jb).a['b'] FROM test_jsonb_dot_notation; -- returns NULL due to strict mode
+ a 
+---
+ 
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT ((jb).a)['b'::text] AS a
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+ a 
+---
+ 
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: ((jb).b.x)['z'::text]
+(2 rows)
+
+SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation; -- warnings
+   x   
+-------
+ "ZZZ"
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT ((jb).b.x)['z'::text] AS x
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+   x   
+-------
+ "ZZZ"
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: ((jb['b'::text]).x)['z'::text]
+(2 rows)
+
+SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;  -- warnings
+   x   
+-------
+ "ZZZ"
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT ((jb['b'::text]).x)['z'::text] AS x
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+   x   
+-------
+ "ZZZ"
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (((jb).b)['x'::text]).z
+(2 rows)
+
+SELECT (jb).b['x'].z FROM test_jsonb_dot_notation; -- returns NULL
+ z 
+---
+ 
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT (((jb).b)['x'::text]).z AS z
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+ z 
+---
+ 
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: ((jb).b)['x'::text]['z'::text]
+(2 rows)
+
+SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation; -- returns NULL
+ b 
+---
+ 
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT ((jb).b)['x'::text]['z'::text] AS b
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+ b 
+---
+ 
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb['b'::text]['x'::text]).z
+(2 rows)
+
+SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation; -- returns NULL
+ z 
+---
+ 
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT (jb['b'::text]['x'::text]).z AS z
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+ z 
+---
+ 
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+-- clean up
+DROP TABLE test_jsonb_dot_notation;
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 57c11acddfe..5907cad69fd 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1304,33 +1304,51 @@ select jsonb_insert('{"a": {"b": "value"}}', '{a, b}', '"new_value"', true);
 
 -- jsonb subscript
 select ('123'::jsonb)['a'];
+select ('123'::jsonb).a;
 select ('123'::jsonb)[0];
 select ('123'::jsonb)[NULL];
+select ('123'::jsonb).NULL;
 select ('{"a": 1}'::jsonb)['a'];
+select ('{"a": 1}'::jsonb).a;
 select ('{"a": 1}'::jsonb)[0];
 select ('{"a": 1}'::jsonb)['not_exist'];
+select ('{"a": 1}'::jsonb)."not_exist";
 select ('{"a": 1}'::jsonb)[NULL];
 select ('[1, "2", null]'::jsonb)['a'];
+select ('[1, "2", null]'::jsonb).a;
 select ('[1, "2", null]'::jsonb)[0];
 select ('[1, "2", null]'::jsonb)['1'];
+select ('[1, "2", null]'::jsonb)."1";
 select ('[1, "2", null]'::jsonb)[1.0];
 select ('[1, "2", null]'::jsonb)[2];
 select ('[1, "2", null]'::jsonb)[3];
 select ('[1, "2", null]'::jsonb)[-2];
 select ('[1, "2", null]'::jsonb)[1]['a'];
+select ('[1, "2", null]'::jsonb)[1].a;
 select ('[1, "2", null]'::jsonb)[1][0];
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['b'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).b;
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d;
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'][1];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d[1];
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d']['a'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['a'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d.a;
+select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d['a'][0];
+select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d.a[0];
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1;
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2;
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']['a3'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2.a3;
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'];
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1;
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'][2];
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1[2];
 
--- slices are not supported
-select ('{"a": 1}'::jsonb)['a':'b'];
+select ('{"a": 1}'::jsonb)['a':'b']; -- fails
 select ('[1, "2", null]'::jsonb)[1:2];
 select ('[1, "2", null]'::jsonb)[:2];
 select ('[1, "2", null]'::jsonb)[1:];
@@ -1590,3 +1608,94 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::float8;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int2;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int4;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
+
+-- dot notation
+CREATE TABLE test_jsonb_dot_notation AS
+SELECT '{"a": [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], "b": [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]}'::jsonb jb;
+
+SELECT (jb).a FROM test_jsonb_dot_notation;
+SELECT (jb)."a" FROM test_jsonb_dot_notation; -- double quote should work
+SELECT (jb).'a' FROM test_jsonb_dot_notation; -- single quote should not work
+select (jb)[0].a from test_jsonb_dot_notation; -- returns same result as (jb).a
+select (jb)[1].a from test_jsonb_dot_notation; -- returns NULL
+SELECT (jb).b FROM test_jsonb_dot_notation;
+SELECT (jb).c FROM test_jsonb_dot_notation;
+SELECT (jb).a.b FROM test_jsonb_dot_notation;
+SELECT (jb).a[2].b FROM test_jsonb_dot_notation;
+SELECT (jb).a.x.y FROM test_jsonb_dot_notation;
+SELECT (jb).b.x.z FROM test_jsonb_dot_notation;
+SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t; -- fails
+SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
+
+-- explains should work
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+
+-- views should work
+CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+
+-- mixed syntax
+DROP VIEW test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
+SELECT (jb)['a'].b FROM test_jsonb_dot_notation; -- returns an array due to lax mode
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+SELECT (jb).a['b'] FROM test_jsonb_dot_notation; -- returns NULL due to strict mode
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation; -- warnings
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;  -- warnings
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+SELECT (jb).b['x'].z FROM test_jsonb_dot_notation; -- returns NULL
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation; -- returns NULL
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
+SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation; -- returns NULL
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+-- clean up
+DROP TABLE test_jsonb_dot_notation;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index a13e8162890..cc64642f399 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -805,6 +805,7 @@ FdwRoutine
 FetchDirection
 FetchDirectionKeywords
 FetchStmt
+FieldAccessorExpr
 FieldSelect
 FieldStore
 File
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v15-0003-Export-jsonPathFromParseResult.patch (2.7K, ../../CAK98qZ1XFee_x1uQFcwJkZCSVcPBy-A1ib3N5Ls2uDdZviKVOw@mail.gmail.com/8-v15-0003-Export-jsonPathFromParseResult.patch)
  download | inline diff:
From e7fdd48a97ed34fbeb0f4ead7b75146c1ca68081 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v15 3/7] Export jsonPathFromParseResult()

This is a preparation step for a future commit that will reuse the
aforementioned function.

Authored-by: Nikita Glukhov <[email protected]>
Reviewed-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Jian He <[email protected]>
---
 src/backend/utils/adt/jsonpath.c | 19 +++++++++++++++----
 src/include/utils/jsonpath.h     |  4 ++++
 2 files changed, 19 insertions(+), 4 deletions(-)

diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index 762f7e8a09d..976aee84cf2 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -166,15 +166,13 @@ jsonpath_send(PG_FUNCTION_ARGS)
  * Converts C-string to a jsonpath value.
  *
  * Uses jsonpath parser to turn string into an AST, then
- * flattenJsonPathParseItem() does second pass turning AST into binary
+ * jsonPathFromParseResult() does second pass turning AST into binary
  * representation of jsonpath.
  */
 static Datum
 jsonPathFromCstring(char *in, int len, struct Node *escontext)
 {
 	JsonPathParseResult *jsonpath = parsejsonpath(in, len, escontext);
-	JsonPath   *res;
-	StringInfoData buf;
 
 	if (SOFT_ERROR_OCCURRED(escontext))
 		return (Datum) 0;
@@ -185,8 +183,21 @@ jsonPathFromCstring(char *in, int len, struct Node *escontext)
 				 errmsg("invalid input syntax for type %s: \"%s\"", "jsonpath",
 						in)));
 
+	return jsonPathFromParseResult(jsonpath, 4 * len, escontext);
+}
+
+/*
+ * Converts jsonpath Abstract Syntax Tree (AST) into jsonpath value in binary.
+ */
+Datum
+jsonPathFromParseResult(JsonPathParseResult *jsonpath, int estimated_len,
+						struct Node *escontext)
+{
+	JsonPath   *res;
+	StringInfoData buf;
+
 	initStringInfo(&buf);
-	enlargeStringInfo(&buf, 4 * len /* estimation */ );
+	enlargeStringInfo(&buf, estimated_len);
 
 	appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
 
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index 23a76d233e9..e05941623e7 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -281,6 +281,10 @@ extern JsonPathParseResult *parsejsonpath(const char *str, int len,
 extern bool jspConvertRegexFlags(uint32 xflags, int *result,
 								 struct Node *escontext);
 
+extern Datum jsonPathFromParseResult(JsonPathParseResult *jsonpath,
+									 int estimated_len,
+									 struct Node *escontext);
+
 /*
  * Struct for details about external variables passed into jsonpath executor
  */
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v15-0002-Allow-Generic-Type-Subscripting-to-Accept-Dot-No.patch (10.5K, ../../CAK98qZ1XFee_x1uQFcwJkZCSVcPBy-A1ib3N5Ls2uDdZviKVOw@mail.gmail.com/9-v15-0002-Allow-Generic-Type-Subscripting-to-Accept-Dot-No.patch)
  download | inline diff:
From 491b42e8a459da1cff2768876fede98e36d681f9 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v15 2/7] Allow Generic Type Subscripting to Accept Dot
 Notation (.) as Input

This change extends generic type subscripting to recognize dot
notation (.) in addition to bracket notation ([]). While this does not
yet provide full support for dot notation, it enables subscripting
containers to process it in the future.

For now, container-specific transform functions only handle
subscripting indices and stop processing when encountering dot
notation. It is up to individual containers to decide how to transform
dot notation in subsequent updates.

Authored-by: Nikita Glukhov <[email protected]>
Reviewed-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Jian He <[email protected]>
---
 src/backend/parser/parse_expr.c   | 66 ++++++++++++++++++++-----------
 src/backend/parser/parse_node.c   | 41 +++++++++++++++++--
 src/backend/parser/parse_target.c |  3 +-
 src/backend/utils/adt/arraysubs.c | 13 ++++--
 src/backend/utils/adt/jsonbsubs.c | 11 +++++-
 src/include/parser/parse_node.h   |  3 +-
 6 files changed, 105 insertions(+), 32 deletions(-)

diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 6e8fd42c612..ff104c95311 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -441,8 +441,9 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 	ListCell   *i;
 
 	/*
-	 * We have to split any field-selection operations apart from
-	 * subscripting.  Adjacent A_Indices nodes have to be treated as a single
+	 * Combine field names and subscripts into a single indirection list, as
+	 * some subscripting containers, such as jsonb, support field access using
+	 * dot notation. Adjacent A_Indices nodes have to be treated as a single
 	 * multidimensional subscript operation.
 	 */
 	foreach(i, ind->indirection)
@@ -460,19 +461,43 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		}
 		else
 		{
-			Node	   *newresult;
-
 			Assert(IsA(n, String));
+			subscripts = lappend(subscripts, n);
+		}
+	}
+
+	while (subscripts)
+	{
+		/* try processing container subscripts first */
+		Node	   *newresult = (Node *)
+			transformContainerSubscripts(pstate,
+										 result,
+										 exprType(result),
+										 exprTypmod(result),
+										 &subscripts,
+										 false,
+										 true);
+
+		if (!newresult)
+		{
+			/*
+			 * generic subscripting failed; falling back to field selection
+			 * for a composite type.
+			 */
+			Node	   *n;
+
+			Assert(subscripts);
 
-			/* process subscripts before this field selection */
-			while (subscripts)
-				result = (Node *) transformContainerSubscripts(pstate,
-															   result,
-															   exprType(result),
-															   exprTypmod(result),
-															   &subscripts,
-															   false);
+			n = linitial(subscripts);
+
+			if (!IsA(n, String))
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("cannot subscript type %s because it does not support subscripting",
+								format_type_be(exprType(result))),
+						 parser_errposition(pstate, exprLocation(result))));
 
+			/* try to find function for field selection */
 			newresult = ParseFuncOrColumn(pstate,
 										  list_make1(n),
 										  list_make1(result),
@@ -480,19 +505,16 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 										  NULL,
 										  false,
 										  location);
-			if (newresult == NULL)
+
+			if (!newresult)
 				unknown_attribute(pstate, result, strVal(n), location);
-			result = newresult;
+
+			/* consume field select */
+			subscripts = list_delete_first(subscripts);
 		}
+
+		result = newresult;
 	}
-	/* process trailing subscripts, if any */
-	while (subscripts)
-		result = (Node *) transformContainerSubscripts(pstate,
-													   result,
-													   exprType(result),
-													   exprTypmod(result),
-													   &subscripts,
-													   false);
 
 	return result;
 }
diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c
index f05baa50a15..c44a5a0effd 100644
--- a/src/backend/parser/parse_node.c
+++ b/src/backend/parser/parse_node.c
@@ -238,6 +238,8 @@ transformContainerType(Oid *containerType, int32 *containerTypmod)
  * containerTypMod	typmod for the container
  * indirection		Untransformed list of subscripts (must not be NIL)
  * isAssignment		True if this will become a container assignment.
+ * noError			True for return NULL with no error, if the container type
+ * 					is not subscriptable.
  */
 SubscriptingRef *
 transformContainerSubscripts(ParseState *pstate,
@@ -245,13 +247,15 @@ transformContainerSubscripts(ParseState *pstate,
 							 Oid containerType,
 							 int32 containerTypMod,
 							 List **indirection,
-							 bool isAssignment)
+							 bool isAssignment,
+							 bool noError)
 {
 	SubscriptingRef *sbsref;
 	const SubscriptRoutines *sbsroutines;
 	Oid			elementType;
 	bool		isSlice = false;
 	ListCell   *idx;
+	int			indirection_length = list_length(*indirection);
 
 	/*
 	 * Determine the actual container type, smashing any domain.  In the
@@ -267,11 +271,16 @@ transformContainerSubscripts(ParseState *pstate,
 	 */
 	sbsroutines = getSubscriptingRoutines(containerType, &elementType);
 	if (!sbsroutines)
+	{
+		if (noError)
+			return NULL;
+
 		ereport(ERROR,
 				(errcode(ERRCODE_DATATYPE_MISMATCH),
 				 errmsg("cannot subscript type %s because it does not support subscripting",
 						format_type_be(containerType)),
 				 parser_errposition(pstate, exprLocation(containerBase))));
+	}
 
 	/*
 	 * Detect whether any of the indirection items are slice specifiers.
@@ -282,9 +291,9 @@ transformContainerSubscripts(ParseState *pstate,
 	 */
 	foreach(idx, *indirection)
 	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
+		Node	   *ai = lfirst(idx);
 
-		if (ai->is_slice)
+		if (IsA(ai, A_Indices) && castNode(A_Indices, ai)->is_slice)
 		{
 			isSlice = true;
 			break;
@@ -312,6 +321,32 @@ transformContainerSubscripts(ParseState *pstate,
 	sbsroutines->transform(sbsref, indirection, pstate,
 						   isSlice, isAssignment);
 
+	/*
+	 * Error out, if datatype failed to consume any indirection elements.
+	 */
+	if (list_length(*indirection) == indirection_length)
+	{
+		Node	   *ind = linitial(*indirection);
+
+		if (noError)
+			return NULL;
+
+		if (IsA(ind, String))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("type %s does not support dot notation",
+							format_type_be(containerType)),
+					 parser_errposition(pstate, exprLocation(containerBase))));
+		else if (IsA(ind, A_Indices))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("type %s does not support array subscripting",
+							format_type_be(containerType)),
+					 parser_errposition(pstate, exprLocation(containerBase))));
+		else
+			elog(ERROR, "invalid indirection operation: %d", nodeTag(ind));
+	}
+
 	/*
 	 * Verify we got a valid type (this defends, for example, against someone
 	 * using array_subscript_handler as typsubscript without setting typelem).
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index a097736229a..b89736ff1ea 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -936,7 +936,8 @@ transformAssignmentSubscripts(ParseState *pstate,
 										  containerType,
 										  containerTypMod,
 										  &subscripts,
-										  true);
+										  true,
+										  false);
 
 	typeNeeded = sbsref->refrestype;
 	typmodNeeded = sbsref->reftypmod;
diff --git a/src/backend/utils/adt/arraysubs.c b/src/backend/utils/adt/arraysubs.c
index 234c2c278c1..d03d3519dfd 100644
--- a/src/backend/utils/adt/arraysubs.c
+++ b/src/backend/utils/adt/arraysubs.c
@@ -62,6 +62,7 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	List	   *upperIndexpr = NIL;
 	List	   *lowerIndexpr = NIL;
 	ListCell   *idx;
+	int			ndim;
 
 	/*
 	 * Transform the subscript expressions, and separate upper and lower
@@ -73,9 +74,14 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	 */
 	foreach(idx, *indirection)
 	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
+		A_Indices  *ai;
 		Node	   *subexpr;
 
+		if (!IsA(lfirst(idx), A_Indices))
+			break;
+
+		ai = lfirst_node(A_Indices, idx);
+
 		if (isSlice)
 		{
 			if (ai->lidx)
@@ -145,14 +151,15 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->reflowerindexpr = lowerIndexpr;
 
 	/* Verify subscript list lengths are within implementation limit */
-	if (list_length(upperIndexpr) > MAXDIM)
+	ndim = list_length(upperIndexpr);
+	if (ndim > MAXDIM)
 		ereport(ERROR,
 				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 				 errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
 						list_length(upperIndexpr), MAXDIM)));
 	/* We need not check lowerIndexpr separately */
 
-	*indirection = NIL;
+	*indirection = list_delete_first_n(*indirection, ndim);
 
 	/*
 	 * Determine the result type of the subscripting operation.  It's the same
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index 8ad6aa1ad4f..a0d38a0fd80 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -55,9 +55,14 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	 */
 	foreach(idx, *indirection)
 	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
+		A_Indices  *ai;
 		Node	   *subExpr;
 
+		if (!IsA(lfirst(idx), A_Indices))
+			break;
+
+		ai = lfirst_node(A_Indices, idx);
+
 		if (isSlice)
 		{
 			Node	   *expr = ai->uidx ? ai->uidx : ai->lidx;
@@ -160,7 +165,9 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->refrestype = JSONBOID;
 	sbsref->reftypmod = -1;
 
-	*indirection = NIL;
+	/* Remove processed elements */
+	if (upperIndexpr)
+		*indirection = list_delete_first_n(*indirection, list_length(upperIndexpr));
 }
 
 /*
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 58a4b9df157..5cc3ce58c30 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -362,7 +362,8 @@ extern SubscriptingRef *transformContainerSubscripts(ParseState *pstate,
 													 Oid containerType,
 													 int32 containerTypMod,
 													 List **indirection,
-													 bool isAssignment);
+													 bool isAssignment,
+													 bool noError);
 extern Const *make_const(ParseState *pstate, A_Const *aconst);
 
 #endif							/* PARSE_NODE_H */
-- 
2.39.5 (Apple Git-154)



^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-06-24 13:29                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-07-09 08:01                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-07-10 08:53                                 ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-21 04:53                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-22 08:19                                     ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-22 19:33                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-25 08:09                                         ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-26 03:52                                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-29 07:22                                             ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-02 02:59                                               ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-03 02:16                                                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
@ 2025-09-03 03:56                                                   ` Chao Li <[email protected]>
  2025-09-09 15:14                                                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  1 sibling, 1 reply; 67+ messages in thread

From: Chao Li @ 2025-09-03 03:56 UTC (permalink / raw)
  To: Alexandra Wang <[email protected]>; +Cc: jian he <[email protected]>; Nikita Malakhov <[email protected]>; Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>

Hi Alex,

Thanks for addressing my comments. I have a few follow up comments:

> On Sep 3, 2025, at 10:16, Alexandra Wang <[email protected]> wrote:
> 
> 
> 
> I don't like the idea of removing the "bool isSlice" argument from the
> *SubscriptTransform() function pointer. This patch series should make
> only minimal changes to the subscripting API. While it's true that
> each implementation can easily determine the boolean value of isSlice
> itself, I don't see a clear benefit to changing the interface. As you
> noted, arrsubs cares about isSlice; and users can also define custom
> data types that support subscripting, so the interface is not limited
> to built-in types.
> 
> As the comment for *SubscriptTransform() explains:
>> (Of course, if the transform method
>> * does not care to support slicing, it can just throw an error if isSlice.)
> 
> It's possible that in the end the "isSlice" argument isn't needed at
> all, but I don’t think this patch set is the right place for that
> refactor.
> 

I agree we should keep the change minimum and tightly related to the core feature.

My original suggestion was to move

    /*
     * Detect whether any of the indirection items are slice specifiers.
     *
     * A list containing only simple subscripts refers to a single container
     * element.  If any of the items are slice specifiers (lower:upper), then
     * the subscript expression means a container slice operation.
     */
    foreach(idx, *indirection)
    {
        Node       *ai = lfirst(idx);

        if (IsA(ai, A_Indices) && castNode(A_Indices, ai)->is_slice)
        {
            isSlice = true;
            break;
        }
    }

To out of transformContainerSubscripts(). Because the function was called only once under “if”, now this patch change it to be called under “while”, which brings two issues:

 * Original it was O(n) as it was under “if”, now it is O(n2) as it is under “while”.
* Logically it feels wrong now. Because this loops over the entire indirection list to check is_slice, while the subsequent sbsroutines->transform() may only process a portion (prefix) of indirection list. Say, the 5th element is slice, but the first sbsroutines-transform() call will only process the first 3 elements of indirection list, then pass true to the first transform() call sounds wrong.

if we pull the loop out of transformContainerSubscripts(), then we have to add a new parameter “bool is_slice” to it. But after some researching, I found that making that change is more complicated than removing “is_slice” parameter from SubscriptTransform(). That’s why I ended up suggesting removing “is_slice” from SubscriptTransform().

Does that sound reasonable?

--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/






^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-06-24 13:29                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-07-09 08:01                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-07-10 08:53                                 ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-21 04:53                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-22 08:19                                     ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-22 19:33                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-25 08:09                                         ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-26 03:52                                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-29 07:22                                             ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-02 02:59                                               ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-03 02:16                                                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-09-03 03:56                                                   ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
@ 2025-09-09 15:14                                                     ` Alexandra Wang <[email protected]>
  2025-09-10 03:52                                                       ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  0 siblings, 1 reply; 67+ messages in thread

From: Alexandra Wang @ 2025-09-09 15:14 UTC (permalink / raw)
  To: Chao Li <[email protected]>; +Cc: jian he <[email protected]>; Nikita Malakhov <[email protected]>; Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>

Hi Chao,

Thank you so much for your review!

I’ve attached v16.

My current goal is to get consensus on 0001 - 0005 and move them toward
commitment. I’d also appreciate feedback on 0006 - 0007 as follow-up.

In v16, I addressed your feedback regarding the isSlice argument in
SubscriptTransform() -- I removed it. I’ve also addressed your three
other comments, sorry for missing them earlier!

I did not change anything about the jb[0] array accessor for jsonb
objects, will discuss that in a follow-up email.

On Tue, Sep 2, 2025 at 8:56 PM Chao Li <[email protected]> wrote:

> On Sep 3, 2025, at 10:16, Alexandra Wang <[email protected]>
> wrote:
> I don't like the idea of removing the "bool isSlice" argument from the
> *SubscriptTransform() function pointer. This patch series should make
> only minimal changes to the subscripting API. While it's true that
> each implementation can easily determine the boolean value of isSlice
> itself, I don't see a clear benefit to changing the interface. As you
> noted, arrsubs cares about isSlice; and users can also define custom
> data types that support subscripting, so the interface is not limited
> to built-in types.
>
> As the comment for *SubscriptTransform() explains:
>
>> (Of course, if the transform method
>> * does not care to support slicing, it can just throw an error if
>> isSlice.)
>
>
> It's possible that in the end the "isSlice" argument isn't needed at
> all, but I don’t think this patch set is the right place for that
> refactor.
>
> I agree we should keep the change minimum and tightly related to the core
> feature.
>
> My original suggestion was to move
>
> /*
> * Detect whether any of the indirection items are slice specifiers.
> *
> * A list containing only simple subscripts refers to a single container
> * element. If any of the items are slice specifiers (lower:upper), then
> * the subscript expression means a container slice operation.
> */
> foreach(idx, *indirection)
> {
> Node *ai = lfirst(idx);
>
> if (IsA(ai, A_Indices) && castNode(A_Indices, ai)->is_slice)
> {
> isSlice = true;
> break;
> }
> }
>
> To out of transformContainerSubscripts(). Because the function was called
> only once under “if”, now this patch change it to be called under “while”,
> which brings two issues:
>
>  * Original it was O(n) as it was under “if”, now it is O(n2) as it is
> under “while”.
> * Logically it feels wrong now. Because this loops over the entire
> indirection list to check is_slice, while the subsequent
> sbsroutines->transform() may only process a portion (prefix) of indirection
> list. Say, the 5th element is slice, but the first sbsroutines-transform()
> call will only process the first 3 elements of indirection list, then pass
> true to the first transform() call sounds wrong.
>
> if we pull the loop out of transformContainerSubscripts(), then we have to
> add a new parameter “bool is_slice” to it. But after some researching, I
> found that making that change is more complicated than removing “is_slice”
> parameter from SubscriptTransform(). That’s why I ended up suggesting
> removing “is_slice” from SubscriptTransform().
>
> Does that sound reasonable?
>

Yes, that makes total sense! The logical defect you found can indeed
cause bugs in arraysubs. In 0002, I’ve added a test in arrays.sql to
demonstrate the potential bug and updated the code accordingly:

    This change also updates the SubscriptTransform() API to remove the
    "bool isSlice" argument. Each container’s transform function now
    determines slice-ness itself, since it may only process a prefix of
    the indirection list. For example, array_subscript_transform() stops
    at dot notation, so "isSlice" should not depend on slice specifiers
    that appear afterward.

Thanks,
Alex


Attachments:

  [application/octet-stream] v16-0007-Implement-jsonb-wildcard-member-accessor.patch (31.5K, ../../CAK98qZ1xhjKfo_C-s_bnpkPJ4xkdqEwdRYq4pbFKM8fKqcfKZw@mail.gmail.com/3-v16-0007-Implement-jsonb-wildcard-member-accessor.patch)
  download | inline diff:
From 36a13b0df61799a37ea191ff4fe7b2e06f09f761 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v16 7/7] Implement jsonb wildcard member accessor

This commit adds support for wildcard member access in jsonb, as
specified by the JSON simplified accessor syntax in SQL:2023.

Co-authored-by: Nikita Glukhov <[email protected]>
Co-authored-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Mark Dilger <[email protected]>
Reviewed-by: Jian He <[email protected]>
Reviewed-by: Vik Fearing <[email protected]>
Reviewed-by: Nikita Malakhov <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Jelte Fennema-Nio <[email protected]>
Tested-by: Jelte Fennema-Nio <[email protected]>
---
 src/backend/nodes/nodeFuncs.c                 |   8 +
 src/backend/parser/gram.y                     |   2 +
 src/backend/parser/parse_expr.c               |  39 +--
 src/backend/parser/parse_target.c             |  67 ++++--
 src/backend/utils/adt/jsonbsubs.c             |  12 +-
 src/backend/utils/adt/ruleutils.c             |   6 +-
 src/include/nodes/primnodes.h                 |  12 +
 src/include/parser/parse_expr.h               |   3 +
 .../ecpg/test/expected/sql-sqljson.c          |  18 +-
 .../ecpg/test/expected/sql-sqljson.stderr     |  23 +-
 .../ecpg/test/expected/sql-sqljson.stdout     |   2 +
 src/interfaces/ecpg/test/sql/sqljson.pgc      |   5 +-
 src/test/regress/expected/jsonb.out           | 222 +++++++++++++++++-
 src/test/regress/sql/jsonb.sql                |  42 +++-
 14 files changed, 406 insertions(+), 55 deletions(-)

diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index d1bd575d9bd..5f3038a1c26 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -291,6 +291,9 @@ exprType(const Node *expr)
 			 */
 			type = TEXTOID;
 			break;
+		case T_Star:
+			type = UNKNOWNOID;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			type = InvalidOid;	/* keep compiler quiet */
@@ -1156,6 +1159,9 @@ exprSetCollation(Node *expr, Oid collation)
 		case T_FieldAccessorExpr:
 			((FieldAccessorExpr *) expr)->faecollid = collation;
 			break;
+		case T_Star:
+			Assert(!OidIsValid(collation));
+			break;
 		case T_SubscriptingRef:
 			((SubscriptingRef *) expr)->refcollid = collation;
 			break;
@@ -2140,6 +2146,7 @@ expression_tree_walker_impl(Node *node,
 		case T_CTESearchClause:
 		case T_MergeSupportFunc:
 		case T_FieldAccessorExpr:
+		case T_Star:
 			/* primitive node types with no expression subnodes */
 			break;
 		case T_WithCheckOption:
@@ -3020,6 +3027,7 @@ expression_tree_mutator_impl(Node *node,
 		case T_CTESearchClause:
 		case T_MergeSupportFunc:
 		case T_FieldAccessorExpr:
+		case T_Star:
 			return copyObject(node);
 		case T_WithCheckOption:
 			{
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9fd48acb1f8..c86ab6fd512 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -18980,6 +18980,7 @@ check_func_name(List *names, core_yyscan_t yyscanner)
 static List *
 check_indirection(List *indirection, core_yyscan_t yyscanner)
 {
+#if 0
 	ListCell   *l;
 
 	foreach(l, indirection)
@@ -18990,6 +18991,7 @@ check_indirection(List *indirection, core_yyscan_t yyscanner)
 				parser_yyerror("improper use of \"*\"");
 		}
 	}
+#endif
 	return indirection;
 }
 
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index f8a0617f823..38ac6503a22 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -73,7 +73,6 @@ static Node *transformColumnRef(ParseState *pstate, ColumnRef *cref);
 static Node *transformWholeRowRef(ParseState *pstate,
 								  ParseNamespaceItem *nsitem,
 								  int sublevels_up, int location);
-static Node *transformIndirection(ParseState *pstate, A_Indirection *ind);
 static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
 static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
 static Node *transformJsonObjectConstructor(ParseState *pstate,
@@ -157,7 +156,7 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 			break;
 
 		case T_A_Indirection:
-			result = transformIndirection(pstate, (A_Indirection *) expr);
+			result = transformIndirection(pstate, (A_Indirection *) expr, NULL);
 			break;
 
 		case T_A_ArrayExpr:
@@ -431,8 +430,9 @@ unknown_attribute(ParseState *pstate, Node *relref, const char *attname,
 	}
 }
 
-static Node *
-transformIndirection(ParseState *pstate, A_Indirection *ind)
+Node *
+transformIndirection(ParseState *pstate, A_Indirection *ind,
+					 bool *trailing_star_expansion)
 {
 	Node	   *last_srf = pstate->p_last_srf;
 	Node	   *result = transformExprRecurse(pstate, ind->arg);
@@ -450,16 +450,8 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 	{
 		Node	   *n = lfirst(i);
 
-		if (IsA(n, A_Indices) || IsA(n, String))
-			subscripts = lappend(subscripts, n);
-		else
-		{
-			Assert(IsA(n, A_Star));
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("row expansion via \"*\" is not supported here"),
-					 parser_errposition(pstate, location)));
-		}
+		Assert (IsA(n, A_Indices) || IsA(n, String) || IsA(n, A_Star));
+		subscripts = lappend(subscripts, n);
 	}
 
 	while (subscripts)
@@ -486,7 +478,21 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 
 			n = linitial(subscripts);
 
-			if (!IsA(n, String))
+			if (IsA(n, A_Star))
+			{
+				/* Success, if trailing star expansion is allowed */
+				if (trailing_star_expansion && list_length(subscripts) == 1)
+				{
+					*trailing_star_expansion = true;
+					return result;
+				}
+
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("row expansion via \"*\" is not supported here"),
+						 parser_errposition(pstate, location)));
+			}
+			else if (!IsA(n, String))
 				ereport(ERROR,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
 						 errmsg("cannot subscript type %s because it does not support subscripting",
@@ -512,6 +518,9 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		result = newresult;
 	}
 
+	if (trailing_star_expansion)
+		*trailing_star_expansion = false;
+
 	return result;
 }
 
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index b89736ff1ea..85c05c7434c 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -47,7 +47,7 @@ static Node *transformAssignmentSubscripts(ParseState *pstate,
 static List *ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
 								 bool make_target_entry);
 static List *ExpandAllTables(ParseState *pstate, int location);
-static List *ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
+static Node *ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
 								   bool make_target_entry, ParseExprKind exprKind);
 static List *ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 							   int sublevels_up, int location,
@@ -133,6 +133,7 @@ transformTargetList(ParseState *pstate, List *targetlist,
 	foreach(o_target, targetlist)
 	{
 		ResTarget  *res = (ResTarget *) lfirst(o_target);
+		Node	   *transformed = NULL;
 
 		/*
 		 * Check for "something.*".  Depending on the complexity of the
@@ -161,13 +162,19 @@ transformTargetList(ParseState *pstate, List *targetlist,
 
 				if (IsA(llast(ind->indirection), A_Star))
 				{
-					/* It is something.*, expand into multiple items */
-					p_target = list_concat(p_target,
-										   ExpandIndirectionStar(pstate,
-																 ind,
-																 true,
-																 exprKind));
-					continue;
+					Node	   *columns = ExpandIndirectionStar(pstate,
+																ind,
+																true,
+																exprKind);
+
+					if (IsA(columns, List))
+					{
+						/* It is something.*, expand into multiple items */
+						p_target = list_concat(p_target, (List *) columns);
+						continue;
+					}
+
+					transformed = (Node *) columns;
 				}
 			}
 		}
@@ -179,7 +186,7 @@ transformTargetList(ParseState *pstate, List *targetlist,
 		p_target = lappend(p_target,
 						   transformTargetEntry(pstate,
 												res->val,
-												NULL,
+												transformed,
 												exprKind,
 												res->name,
 												false));
@@ -250,10 +257,15 @@ transformExpressionList(ParseState *pstate, List *exprlist,
 
 			if (IsA(llast(ind->indirection), A_Star))
 			{
-				/* It is something.*, expand into multiple items */
-				result = list_concat(result,
-									 ExpandIndirectionStar(pstate, ind,
-														   false, exprKind));
+				Node	   *cols = ExpandIndirectionStar(pstate, ind,
+														 false, exprKind);
+
+				if (!cols || IsA(cols, List))
+					/* It is something.*, expand into multiple items */
+					result = list_concat(result, (List *) cols);
+				else
+					result = lappend(result, cols);
+
 				continue;
 			}
 		}
@@ -1344,22 +1356,30 @@ ExpandAllTables(ParseState *pstate, int location)
  * For robustness, we use a separate "make_target_entry" flag to control
  * this rather than relying on exprKind.
  */
-static List *
+static Node *
 ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
 					  bool make_target_entry, ParseExprKind exprKind)
 {
 	Node	   *expr;
+	ParseExprKind sv_expr_kind;
+	bool		trailing_star_expansion = false;
+
+	/* Save and restore identity of expression type we're parsing */
+	Assert(exprKind != EXPR_KIND_NONE);
+	sv_expr_kind = pstate->p_expr_kind;
+	pstate->p_expr_kind = exprKind;
 
 	/* Strip off the '*' to create a reference to the rowtype object */
-	ind = copyObject(ind);
-	ind->indirection = list_truncate(ind->indirection,
-									 list_length(ind->indirection) - 1);
+	expr = transformIndirection(pstate, ind, &trailing_star_expansion);
+
+	pstate->p_expr_kind = sv_expr_kind;
 
-	/* And transform that */
-	expr = transformExpr(pstate, (Node *) ind, exprKind);
+	/* '*' was consumed by generic type subscripting */
+	if (!trailing_star_expansion)
+		return expr;
 
 	/* Expand the rowtype expression into individual fields */
-	return ExpandRowReference(pstate, expr, make_target_entry);
+	return (Node *) ExpandRowReference(pstate, expr, make_target_entry);
 }
 
 /*
@@ -1784,13 +1804,18 @@ FigureColnameInternal(Node *node, char **name)
 				char	   *fname = NULL;
 				ListCell   *l;
 
-				/* find last field name, if any, ignoring "*" and subscripts */
+				/*
+				 * find last field name, if any, ignoring subscripts, and use
+				 * '?column?' when there's a trailing '*'.
+				 */
 				foreach(l, ind->indirection)
 				{
 					Node	   *i = lfirst(l);
 
 					if (IsA(i, String))
 						fname = strVal(i);
+					else if (IsA(i, A_Star))
+						fname = "?column?";
 				}
 				if (fname)
 				{
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index ee925a7b486..8ffd19ff966 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -124,7 +124,7 @@ jsonb_check_jsonpath_needed(List *indirection)
 	{
 		Node	   *accessor = lfirst(lc);
 
-		if (IsA(accessor, String))
+		if (IsA(accessor, String) || IsA(accessor, A_Star))
 			return true;
 		else
 		{
@@ -339,6 +339,16 @@ jsonb_subscript_make_jsonpath(ParseState *pstate, List **indirection, Subscripti
 				}
 			}
 		}
+		else if (IsA(accessor, A_Star))
+		{
+			Star *star_node;
+			jpi = make_jsonpath_item(jpiAnyKey);
+
+			star_node = makeNode(Star);
+			star_node->type = T_Star;
+
+			sbsref->refupperindexpr = lappend(sbsref->refupperindexpr, star_node);
+		}
 		else
 		{
 			/*
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index baa3ae97d57..ace0eff52e2 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -13010,7 +13010,11 @@ printSubscripts(SubscriptingRef *sbsref, deparse_context *context)
 	{
 		Node *upper = (Node *) lfirst(uplist_item);
 
-		if (upper && IsA(upper, FieldAccessorExpr))
+		if (upper && IsA(upper, Star))
+		{
+			appendStringInfoString(buf, ".*");
+		}
+		else if (upper && IsA(upper, FieldAccessorExpr))
 		{
 			FieldAccessorExpr *fae = (FieldAccessorExpr *) upper;
 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 7e89621bd65..7e418830f22 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -2419,4 +2419,16 @@ typedef struct FieldAccessorExpr
 	Oid			faecollid pg_node_attr(query_jumble_ignore);
 }			FieldAccessorExpr;
 
+/*
+ * Star - represents a wildcard member accessor (e.g., ".*") used in JSONB simplified accessor.
+ *
+ * This node serves as a syntactic placeholder in the expression tree and does not get evaluated, hence it
+ * has no associated type or collation.
+ */
+typedef struct Star
+{
+	NodeTag		type;
+}			Star;
+
+
 #endif							/* PRIMNODES_H */
diff --git a/src/include/parser/parse_expr.h b/src/include/parser/parse_expr.h
index efbaff8e710..c9f6a7724c0 100644
--- a/src/include/parser/parse_expr.h
+++ b/src/include/parser/parse_expr.h
@@ -22,4 +22,7 @@ extern Node *transformExpr(ParseState *pstate, Node *expr, ParseExprKind exprKin
 
 extern const char *ParseExprKindName(ParseExprKind exprKind);
 
+extern Node *transformIndirection(ParseState *pstate, A_Indirection *ind,
+								  bool *trailing_star_expansion);
+
 #endif							/* PARSE_EXPR_H */
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.c b/src/interfaces/ecpg/test/expected/sql-sqljson.c
index 935b47a3b9a..585d9b14445 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.c
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.c
@@ -515,9 +515,9 @@ if (sqlca.sqlcode < 0) sqlprint();}
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 145 "sqljson.pgc"
 
-	// error
+	printf("Found json=%s\n", json);
 
-	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )", ECPGt_EOIT, 
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . * . x )", ECPGt_EOIT, 
 	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
 	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 148 "sqljson.pgc"
@@ -527,7 +527,7 @@ if (sqlca.sqlcode < 0) sqlprint();}
 
 	printf("Found json=%s\n", json);
 
-	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ : ] )", ECPGt_EOIT, 
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )", ECPGt_EOIT, 
 	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
 	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 151 "sqljson.pgc"
@@ -537,12 +537,22 @@ if (sqlca.sqlcode < 0) sqlprint();}
 
 	printf("Found json=%s\n", json);
 
-  { ECPGdisconnect(__LINE__, "CURRENT");
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ : ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 154 "sqljson.pgc"
 
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 154 "sqljson.pgc"
 
+	printf("Found json=%s\n", json);
+
+  { ECPGdisconnect(__LINE__, "CURRENT");
+#line 157 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 157 "sqljson.pgc"
+
 
   return 0;
 }
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
index f3f899c6d87..4b9088545d6 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
@@ -347,22 +347,19 @@ SQL error: schema "jsonb" does not exist on line 121
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 145: using PQexec
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_check_PQresult on line 145: bad response - ERROR:  row expansion via "*" is not supported here
-LINE 1: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x...
-                        ^
+[NO_PID]: ecpg_process_output on line 145: correctly got 1 tuples with 1 fields
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: raising sqlstate 0A000 (sqlcode -400): row expansion via "*" is not supported here on line 145
-[NO_PID]: sqlca: code: -400, state: 0A000
-SQL error: row expansion via "*" is not supported here on line 145
-[NO_PID]: ecpg_execute on line 148: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: ecpg_get_data on line 145: RESULT: [{"b": 1, "c": 2}, [{"x": 1}, {"x": [12, {"y": 1}]}]] offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 148: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . * . x ); with 0 parameter(s) on connection ecpg1_regression
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 148: using PQexec
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_process_output on line 148: correctly got 1 tuples with 1 fields
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_get_data on line 148: RESULT: [{"x": 1}, {"x": [12, {"y": 1}]}] offset: -1; array: no
+[NO_PID]: ecpg_get_data on line 148: RESULT: [1, [12, {"y": 1}]] offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_execute on line 151: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: ecpg_execute on line 151: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] ); with 0 parameter(s) on connection ecpg1_regression
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 151: using PQexec
 [NO_PID]: sqlca: code: 0, state: 00000
@@ -370,5 +367,13 @@ SQL error: row expansion via "*" is not supported here on line 145
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_get_data on line 151: RESULT: [{"x": 1}, {"x": [12, {"y": 1}]}] offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 154: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 154: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 154: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 154: RESULT: [{"x": 1}, {"x": [12, {"y": 1}]}] offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_finish: connection ecpg1_regression closed
 [NO_PID]: sqlca: code: 0, state: 00000
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
index d01a8457f01..145dc95d430 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
@@ -36,5 +36,7 @@ Found json={"x": 1}
 Found json=[1, [12, {"y": 1}]]
 Found json={"x": 1}
 Found json=[12, {"y": 1}]
+Found json=[{"b": 1, "c": 2}, [{"x": 1}, {"x": [12, {"y": 1}]}]]
+Found json=[1, [12, {"y": 1}]]
 Found json=[{"x": 1}, {"x": [12, {"y": 1}]}]
 Found json=[{"x": 1}, {"x": [12, {"y": 1}]}]
diff --git a/src/interfaces/ecpg/test/sql/sqljson.pgc b/src/interfaces/ecpg/test/sql/sqljson.pgc
index 9423d25fd0b..2af50b5da4b 100644
--- a/src/interfaces/ecpg/test/sql/sqljson.pgc
+++ b/src/interfaces/ecpg/test/sql/sqljson.pgc
@@ -143,7 +143,10 @@ EXEC SQL END DECLARE SECTION;
 	printf("Found json=%s\n", json);
 
 	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).*) INTO :json;
-	// error
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).*.x) INTO :json;
+	printf("Found json=%s\n", json);
 
 	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb)['b'][0:]) INTO :json;
 	printf("Found json=%s\n", json);
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 2702edc9705..86b7f7677fc 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -6064,8 +6064,175 @@ SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
  {"y": "YYY", "z": "ZZZ"}
 (1 row)
 
-SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
-ERROR:  type jsonb is not composite
+/* wild card member access */
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+                 ?column?                  
+-------------------------------------------
+ ["c", "d", "f", {"y": "yyy", "z": "zzz"}]
+(1 row)
+
+SELECT (jb).* FROM test_jsonb_dot_notation;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (jb).* FROM test_jsonb_dot_notation t;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (jb).* FROM test_jsonb_dot_notation;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (t.jb).* FROM test_jsonb_dot_notation;
+ERROR:  missing FROM-clause entry for table "t"
+LINE 1: SELECT (t.jb).* FROM test_jsonb_dot_notation;
+                ^
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+                 ?column?                  
+-------------------------------------------
+ ["c", "d", "f", {"y": "yyy", "z": "zzz"}]
+(1 row)
+
+SELECT (jb).a.*.b FROM test_jsonb_dot_notation;
+ b 
+---
+ 
+(1 row)
+
+SELECT (jb).a.*.x FROM test_jsonb_dot_notation;
+ x 
+---
+ 
+(1 row)
+
+SELECT (jb).a.*.y FROM test_jsonb_dot_notation;
+   y   
+-------
+ "yyy"
+(1 row)
+
+SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+    ?column?    
+----------------
+ ["yyy", "zzz"]
+(1 row)
+
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT (jb).*.x FROM test_jsonb_dot_notation t;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+ x 
+---
+ 
+(1 row)
+
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+ x 
+---
+ 
+(1 row)
+
+SELECT ((jb).*)[:].x FROM test_jsonb_dot_notation t;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT (jb).*.x.* FROM test_jsonb_dot_notation;
+           ?column?           
+------------------------------
+ ["yyy", "zzz", "YYY", "ZZZ"]
+(1 row)
+
+SELECT (jb).*.x.y FROM test_jsonb_dot_notation;
+       y        
+----------------
+ ["yyy", "YYY"]
+(1 row)
+
+SELECT (jb).*.x.z FROM test_jsonb_dot_notation;
+       z        
+----------------
+ ["zzz", "ZZZ"]
+(1 row)
+
+SELECT (jb).*.*.y FROM test_jsonb_dot_notation;
+       y        
+----------------
+ ["yyy", "YYY"]
+(1 row)
+
+SELECT (jb).*.*.* FROM test_jsonb_dot_notation;
+           ?column?           
+------------------------------
+ ["yyy", "zzz", "YYY", "ZZZ"]
+(1 row)
+
+SELECT (jb).*.*.*.* FROM test_jsonb_dot_notation;
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT (jb).a.b.c.* FROM test_jsonb_dot_notation;
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+    ?column?    
+----------------
+ ["yyy", "zzz"]
+(1 row)
+
+SELECT (jb).a.*[:].* FROM test_jsonb_dot_notation;
+    ?column?    
+----------------
+ ["yyy", "zzz"]
+(1 row)
+
+SELECT (jb).a.*[*].* FROM test_jsonb_dot_notation; -- not supported
+ERROR:  syntax error at or near "*"
+LINE 1: SELECT (jb).a.*[*].* FROM test_jsonb_dot_notation;
+                        ^
+SELECT (jb).a.**.x FROM test_jsonb_dot_notation; -- not supported
+ERROR:  syntax error at or near "**"
+LINE 1: SELECT (jb).a.**.x FROM test_jsonb_dot_notation;
+                      ^
 -- explains should work
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
                   QUERY PLAN                  
@@ -6093,6 +6260,45 @@ SELECT (jb).a[1] FROM test_jsonb_dot_notation;
  2
 (1 row)
 
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.* FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb).a.*
+(2 rows)
+
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+                 ?column?                  
+-------------------------------------------
+ ["c", "d", "f", {"y": "yyy", "z": "zzz"}]
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:].* FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb).a.*[:].*
+(2 rows)
+
+SELECT (jb).a.*[1:].* FROM test_jsonb_dot_notation;
+ ?column? 
+----------
+ 
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb).a.*[:2].*.b
+(2 rows)
+
+SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
+ b 
+---
+ 
+(1 row)
+
 -- views should work
 CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
 \sv test_jsonb_dot_notation_v1
@@ -6121,6 +6327,17 @@ SELECT * from v3;
  "yyy"
 (1 row)
 
+CREATE VIEW v4 AS SELECT (jb).a.*[:].* FROM test_jsonb_dot_notation;
+\sv v4
+CREATE OR REPLACE VIEW public.v4 AS
+ SELECT (jb).a.*[:].* AS "?column?"
+   FROM test_jsonb_dot_notation
+SELECT * from v4;
+    ?column?    
+----------------
+ ["yyy", "zzz"]
+(1 row)
+
 -- mixed syntax
 DROP VIEW test_jsonb_dot_notation_v1;
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
@@ -6356,4 +6573,5 @@ NOTICE:  [2]
 -- clean up
 DROP VIEW v2;
 DROP VIEW v3;
+DROP VIEW v4;
 DROP TABLE test_jsonb_dot_notation;
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index de2c9cda094..07facc280d7 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1631,13 +1631,49 @@ SELECT (jb).a[2:].b FROM test_jsonb_dot_notation;
 SELECT (jb).a[:2].b FROM test_jsonb_dot_notation;
 SELECT (jb).a[:].b FROM test_jsonb_dot_notation;
 SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
-SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
+
+/* wild card member access */
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+SELECT (jb).* FROM test_jsonb_dot_notation;
+SELECT (jb).* FROM test_jsonb_dot_notation t;
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+SELECT (jb).* FROM test_jsonb_dot_notation;
+SELECT (t.jb).* FROM test_jsonb_dot_notation;
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.b FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.x FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.y FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+SELECT (jb).*.x FROM test_jsonb_dot_notation t;
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+SELECT ((jb).*)[:].x FROM test_jsonb_dot_notation t;
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+SELECT (jb).*.x.* FROM test_jsonb_dot_notation;
+SELECT (jb).*.x.y FROM test_jsonb_dot_notation;
+SELECT (jb).*.x.z FROM test_jsonb_dot_notation;
+SELECT (jb).*.*.y FROM test_jsonb_dot_notation;
+SELECT (jb).*.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).*.*.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.b.c.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*[:].* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*[*].* FROM test_jsonb_dot_notation; -- not supported
+SELECT (jb).a.**.x FROM test_jsonb_dot_notation; -- not supported
 
 -- explains should work
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
 SELECT (t.jb).a FROM test_jsonb_dot_notation t;
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
 SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:].* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*[1:].* FROM test_jsonb_dot_notation;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
+SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
 
 -- views should work
 CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
@@ -1648,6 +1684,9 @@ SELECT * from v2;
 CREATE VIEW v3 AS SELECT (jb).a[:3].x.y[-1:] FROM test_jsonb_dot_notation;
 \sv v3
 SELECT * from v3;
+CREATE VIEW v4 AS SELECT (jb).a.*[:].* FROM test_jsonb_dot_notation;
+\sv v4
+SELECT * from v4;
 
 -- mixed syntax
 DROP VIEW test_jsonb_dot_notation_v1;
@@ -1749,4 +1788,5 @@ $$ LANGUAGE plpgsql;
 -- clean up
 DROP VIEW v2;
 DROP VIEW v3;
+DROP VIEW v4;
 DROP TABLE test_jsonb_dot_notation;
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v16-0001-Allow-transformation-of-only-a-sublist-of-subscr.patch (9.0K, ../../CAK98qZ1xhjKfo_C-s_bnpkPJ4xkdqEwdRYq4pbFKM8fKqcfKZw@mail.gmail.com/4-v16-0001-Allow-transformation-of-only-a-sublist-of-subscr.patch)
  download | inline diff:
From bc6d63bb593ccf079390996cbbb3612751600fdc Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v16 1/7] Allow transformation of only a sublist of subscripts

This is a preparation step for allowing subscripting containers to
transform only a prefix of an indirection list and modify the list
in-place by removing the processed elements. Currently, all elements
are consumed, and the list is set to NIL after transformation.

In the following commit, subscripting containers will gain the
flexibility to stop transformation when encountering an unsupported
indirection and return the remaining indirections to the caller.

Reviewed-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Jian He <[email protected]>
Reviewed-by: Chao Li <[email protected]>
---
 contrib/hstore/hstore_subs.c      | 10 ++++++----
 src/backend/parser/parse_expr.c   |  9 ++++-----
 src/backend/parser/parse_node.c   |  4 ++--
 src/backend/parser/parse_target.c |  2 +-
 src/backend/utils/adt/arraysubs.c |  6 ++++--
 src/backend/utils/adt/jsonbsubs.c |  6 ++++--
 src/include/nodes/subscripting.h  |  7 ++++++-
 src/include/parser/parse_node.h   |  2 +-
 8 files changed, 28 insertions(+), 18 deletions(-)

diff --git a/contrib/hstore/hstore_subs.c b/contrib/hstore/hstore_subs.c
index 3d03f66fa0d..1b29543ab67 100644
--- a/contrib/hstore/hstore_subs.c
+++ b/contrib/hstore/hstore_subs.c
@@ -40,7 +40,7 @@
  */
 static void
 hstore_subscript_transform(SubscriptingRef *sbsref,
-						   List *indirection,
+						   List **indirection,
 						   ParseState *pstate,
 						   bool isSlice,
 						   bool isAssignment)
@@ -49,15 +49,15 @@ hstore_subscript_transform(SubscriptingRef *sbsref,
 	Node	   *subexpr;
 
 	/* We support only single-subscript, non-slice cases */
-	if (isSlice || list_length(indirection) != 1)
+	if (isSlice || list_length(*indirection) != 1)
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("hstore allows only one subscript"),
 				 parser_errposition(pstate,
-									exprLocation((Node *) indirection))));
+									exprLocation((Node *) *indirection))));
 
 	/* Transform the subscript expression to type text */
-	ai = linitial_node(A_Indices, indirection);
+	ai = linitial_node(A_Indices, *indirection);
 	Assert(ai->uidx != NULL && ai->lidx == NULL && !ai->is_slice);
 
 	subexpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
@@ -81,6 +81,8 @@ hstore_subscript_transform(SubscriptingRef *sbsref,
 	/* Determine the result type of the subscripting operation; always text */
 	sbsref->refrestype = TEXTOID;
 	sbsref->reftypmod = -1;
+
+	*indirection = NIL;
 }
 
 /*
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index e1979a80c19..6e8fd42c612 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -465,14 +465,13 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 			Assert(IsA(n, String));
 
 			/* process subscripts before this field selection */
-			if (subscripts)
+			while (subscripts)
 				result = (Node *) transformContainerSubscripts(pstate,
 															   result,
 															   exprType(result),
 															   exprTypmod(result),
-															   subscripts,
+															   &subscripts,
 															   false);
-			subscripts = NIL;
 
 			newresult = ParseFuncOrColumn(pstate,
 										  list_make1(n),
@@ -487,12 +486,12 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		}
 	}
 	/* process trailing subscripts, if any */
-	if (subscripts)
+	while (subscripts)
 		result = (Node *) transformContainerSubscripts(pstate,
 													   result,
 													   exprType(result),
 													   exprTypmod(result),
-													   subscripts,
+													   &subscripts,
 													   false);
 
 	return result;
diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c
index 203b7a32178..f05baa50a15 100644
--- a/src/backend/parser/parse_node.c
+++ b/src/backend/parser/parse_node.c
@@ -244,7 +244,7 @@ transformContainerSubscripts(ParseState *pstate,
 							 Node *containerBase,
 							 Oid containerType,
 							 int32 containerTypMod,
-							 List *indirection,
+							 List **indirection,
 							 bool isAssignment)
 {
 	SubscriptingRef *sbsref;
@@ -280,7 +280,7 @@ transformContainerSubscripts(ParseState *pstate,
 	 * element.  If any of the items are slice specifiers (lower:upper), then
 	 * the subscript expression means a container slice operation.
 	 */
-	foreach(idx, indirection)
+	foreach(idx, *indirection)
 	{
 		A_Indices  *ai = lfirst_node(A_Indices, idx);
 
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 905c975d83b..a097736229a 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -935,7 +935,7 @@ transformAssignmentSubscripts(ParseState *pstate,
 										  basenode,
 										  containerType,
 										  containerTypMod,
-										  subscripts,
+										  &subscripts,
 										  true);
 
 	typeNeeded = sbsref->refrestype;
diff --git a/src/backend/utils/adt/arraysubs.c b/src/backend/utils/adt/arraysubs.c
index 2940fb8e8d7..234c2c278c1 100644
--- a/src/backend/utils/adt/arraysubs.c
+++ b/src/backend/utils/adt/arraysubs.c
@@ -54,7 +54,7 @@ typedef struct ArraySubWorkspace
  */
 static void
 array_subscript_transform(SubscriptingRef *sbsref,
-						  List *indirection,
+						  List **indirection,
 						  ParseState *pstate,
 						  bool isSlice,
 						  bool isAssignment)
@@ -71,7 +71,7 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	 * indirection items to slices by treating the single subscript as the
 	 * upper bound and supplying an assumed lower bound of 1.
 	 */
-	foreach(idx, indirection)
+	foreach(idx, *indirection)
 	{
 		A_Indices  *ai = lfirst_node(A_Indices, idx);
 		Node	   *subexpr;
@@ -152,6 +152,8 @@ array_subscript_transform(SubscriptingRef *sbsref,
 						list_length(upperIndexpr), MAXDIM)));
 	/* We need not check lowerIndexpr separately */
 
+	*indirection = NIL;
+
 	/*
 	 * Determine the result type of the subscripting operation.  It's the same
 	 * as the array type if we're slicing, else it's the element type.  In
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index de64d498512..8ad6aa1ad4f 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -41,7 +41,7 @@ typedef struct JsonbSubWorkspace
  */
 static void
 jsonb_subscript_transform(SubscriptingRef *sbsref,
-						  List *indirection,
+						  List **indirection,
 						  ParseState *pstate,
 						  bool isSlice,
 						  bool isAssignment)
@@ -53,7 +53,7 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	 * Transform and convert the subscript expressions. Jsonb subscripting
 	 * does not support slices, look only and the upper index.
 	 */
-	foreach(idx, indirection)
+	foreach(idx, *indirection)
 	{
 		A_Indices  *ai = lfirst_node(A_Indices, idx);
 		Node	   *subExpr;
@@ -159,6 +159,8 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	/* Determine the result type of the subscripting operation; always jsonb */
 	sbsref->refrestype = JSONBOID;
 	sbsref->reftypmod = -1;
+
+	*indirection = NIL;
 }
 
 /*
diff --git a/src/include/nodes/subscripting.h b/src/include/nodes/subscripting.h
index 234e8ad8012..df988a85fad 100644
--- a/src/include/nodes/subscripting.h
+++ b/src/include/nodes/subscripting.h
@@ -71,6 +71,11 @@ struct SubscriptExecSteps;
  * does not care to support slicing, it can just throw an error if isSlice.)
  * See array_subscript_transform() for sample code.
  *
+ * The transform method receives a pointer to a list of raw indirections.
+ * It may parse a sublist (typically the prefix) of these indirections and
+ * modify the original list in place, allowing the caller to handle any
+ * remaining indirections differently or to raise an error as needed.
+ *
  * The transform method is also responsible for identifying the result type
  * of the subscripting operation.  At call, refcontainertype and reftypmod
  * describe the container type (this will be a base type not a domain), and
@@ -93,7 +98,7 @@ struct SubscriptExecSteps;
  * assignment must return.
  */
 typedef void (*SubscriptTransform) (SubscriptingRef *sbsref,
-									List *indirection,
+									List **indirection,
 									struct ParseState *pstate,
 									bool isSlice,
 									bool isAssignment);
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index f7d07c84542..58a4b9df157 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -361,7 +361,7 @@ extern SubscriptingRef *transformContainerSubscripts(ParseState *pstate,
 													 Node *containerBase,
 													 Oid containerType,
 													 int32 containerTypMod,
-													 List *indirection,
+													 List **indirection,
 													 bool isAssignment);
 extern Const *make_const(ParseState *pstate, A_Const *aconst);
 
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v16-0004-Extract-coerce_jsonpath_subscript.patch (5.8K, ../../CAK98qZ1xhjKfo_C-s_bnpkPJ4xkdqEwdRYq4pbFKM8fKqcfKZw@mail.gmail.com/5-v16-0004-Extract-coerce_jsonpath_subscript.patch)
  download | inline diff:
From b2a3ca6c6d57d2209eff0654381c3983ad924208 Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v16 4/7] Extract coerce_jsonpath_subscript()

This is a preparation step for a future commit that will reuse the
aforementioned function.

Co-authored-by: Nikita Glukhov <[email protected]>
Co-authored-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Jian He <[email protected]>
---
 src/backend/utils/adt/jsonbsubs.c | 130 +++++++++++++++---------------
 1 file changed, 65 insertions(+), 65 deletions(-)

diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index ec8e82dc629..1c586e960f3 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -32,6 +32,69 @@ typedef struct JsonbSubWorkspace
 	Datum	   *index;			/* Subscript values in Datum format */
 } JsonbSubWorkspace;
 
+static Node *
+coerce_jsonpath_subscript_to_int4_or_text(ParseState *pstate, Node *subExpr)
+{
+	Oid			subExprType = exprType(subExpr);
+	Oid			targetType = InvalidOid;
+
+	if (subExprType != UNKNOWNOID)
+	{
+		Oid			targets[2] = {INT4OID, TEXTOID};
+
+		/*
+		 * Jsonb can handle multiple subscript types, but cases when a
+		 * subscript could be coerced to multiple target types must be
+		 * avoided, similar to overloaded functions. It could be possibly
+		 * extend with jsonpath in the future.
+		 */
+		for (int i = 0; i < 2; i++)
+		{
+			if (can_coerce_type(1, &subExprType, &targets[i], COERCION_IMPLICIT))
+			{
+				/*
+				 * One type has already succeeded, it means there are two
+				 * coercion targets possible, failure.
+				 */
+				if (OidIsValid(targetType))
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
+							 errhint("jsonb subscript must be coercible to only one type, integer or text."),
+							 parser_errposition(pstate, exprLocation(subExpr))));
+
+				targetType = targets[i];
+			}
+		}
+
+		/*
+		 * No suitable types were found, failure.
+		 */
+		if (!OidIsValid(targetType))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
+					 errhint("jsonb subscript must be coercible to either integer or text."),
+					 parser_errposition(pstate, exprLocation(subExpr))));
+	}
+	else
+		targetType = TEXTOID;
+
+	/*
+	 * We known from can_coerce_type that coercion will succeed, so
+	 * coerce_type could be used. Note the implicit coercion context, which is
+	 * required to handle subscripts of different types, similar to overloaded
+	 * functions.
+	 */
+	subExpr = coerce_type(pstate,
+						  subExpr, subExprType,
+						  targetType, -1,
+						  COERCION_IMPLICIT,
+						  COERCE_IMPLICIT_CAST,
+						  -1);
+
+	return subExpr;
+}
 
 /*
  * Finish parse analysis of a SubscriptingRef expression for a jsonb.
@@ -50,7 +113,7 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 
 	/*
 	 * Transform and convert the subscript expressions. Jsonb subscripting
-	 * does not support slices, look only and the upper index.
+	 * does not support slices, look only at the upper index.
 	 */
 	foreach(idx, *indirection)
 	{
@@ -74,71 +137,8 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 
 		if (ai->uidx)
 		{
-			Oid			subExprType = InvalidOid,
-						targetType = UNKNOWNOID;
-
 			subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
-			subExprType = exprType(subExpr);
-
-			if (subExprType != UNKNOWNOID)
-			{
-				Oid			targets[2] = {INT4OID, TEXTOID};
-
-				/*
-				 * Jsonb can handle multiple subscript types, but cases when a
-				 * subscript could be coerced to multiple target types must be
-				 * avoided, similar to overloaded functions. It could be
-				 * possibly extend with jsonpath in the future.
-				 */
-				for (int i = 0; i < 2; i++)
-				{
-					if (can_coerce_type(1, &subExprType, &targets[i], COERCION_IMPLICIT))
-					{
-						/*
-						 * One type has already succeeded, it means there are
-						 * two coercion targets possible, failure.
-						 */
-						if (targetType != UNKNOWNOID)
-							ereport(ERROR,
-									(errcode(ERRCODE_DATATYPE_MISMATCH),
-									 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
-									 errhint("jsonb subscript must be coercible to only one type, integer or text."),
-									 parser_errposition(pstate, exprLocation(subExpr))));
-
-						targetType = targets[i];
-					}
-				}
-
-				/*
-				 * No suitable types were found, failure.
-				 */
-				if (targetType == UNKNOWNOID)
-					ereport(ERROR,
-							(errcode(ERRCODE_DATATYPE_MISMATCH),
-							 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
-							 errhint("jsonb subscript must be coercible to either integer or text."),
-							 parser_errposition(pstate, exprLocation(subExpr))));
-			}
-			else
-				targetType = TEXTOID;
-
-			/*
-			 * We known from can_coerce_type that coercion will succeed, so
-			 * coerce_type could be used. Note the implicit coercion context,
-			 * which is required to handle subscripts of different types,
-			 * similar to overloaded functions.
-			 */
-			subExpr = coerce_type(pstate,
-								  subExpr, subExprType,
-								  targetType, -1,
-								  COERCION_IMPLICIT,
-								  COERCE_IMPLICIT_CAST,
-								  -1);
-			if (subExpr == NULL)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("jsonb subscript must have text type"),
-						 parser_errposition(pstate, exprLocation(subExpr))));
+			subExpr = coerce_jsonpath_subscript_to_int4_or_text(pstate, subExpr);
 		}
 		else
 		{
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v16-0006-Implement-Jsonb-subscripting-with-slicing.patch (21.5K, ../../CAK98qZ1xhjKfo_C-s_bnpkPJ4xkdqEwdRYq4pbFKM8fKqcfKZw@mail.gmail.com/6-v16-0006-Implement-Jsonb-subscripting-with-slicing.patch)
  download | inline diff:
From 7331551c2b7469381ce51f3825f9ec2c1b653c32 Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v16 6/7] Implement Jsonb subscripting with slicing

Previously, slicing was not supported for jsonb subscripting. This commit
implements subscripting with slicing as part of the JSON simplified accessor
syntax specified in SQL:2023.

Co-authored-by: Nikita Glukhov <[email protected]>
Co-authored-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Mark Dilger <[email protected]>
Reviewed-by: Jian He <[email protected]>
Reviewed-by: Vik Fearing <[email protected]>
Reviewed-by: Nikita Malakhov <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Tested-by: Mark Dilger <[email protected]>
Tested-by: Jian He <[email protected]>
---
 src/backend/utils/adt/jsonbsubs.c             | 119 ++++++++------
 .../ecpg/test/expected/sql-sqljson.c          |  16 +-
 .../ecpg/test/expected/sql-sqljson.stderr     |  26 ++--
 .../ecpg/test/expected/sql-sqljson.stdout     |   3 +
 src/interfaces/ecpg/test/sql/sqljson.pgc      |   7 +-
 src/test/regress/expected/jsonb.out           | 145 ++++++++++++++++--
 src/test/regress/sql/jsonb.sql                |  53 ++++++-
 7 files changed, 286 insertions(+), 83 deletions(-)

diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index 62553d54060..ee925a7b486 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -111,7 +111,7 @@ coerce_jsonpath_subscript_to_int4_or_text(ParseState *pstate, Node *subExpr)
  *
  * JsonPath is needed if the indirection list includes:
  * - String-based access (dot notation)
- * - Slice-based subscripting (when isSlice is true)
+ * - Slice-based subscripting
  *
  * Otherwise, simple jsonb subscripting is enough.
  */
@@ -127,7 +127,11 @@ jsonb_check_jsonpath_needed(List *indirection)
 		if (IsA(accessor, String))
 			return true;
 		else
+		{
 			Assert(IsA(accessor, A_Indices));
+			if (castNode(A_Indices, accessor)->is_slice)
+				return true;
+		}
 	}
 
 	return false;
@@ -152,20 +156,45 @@ make_jsonpath_item(JsonPathItemType type)
 	return v;
 }
 
+/*
+ * Build a JsonPathParseItem for a constant integer value.
+ *
+ * This function constructs a jpiNumeric item for use in JsonPath,
+ * and appends a matching Const(INT4) node to the given expression list
+ * for use in EXPLAIN, views, etc.
+ *
+ * Parameters:
+ * - val: integer constant value
+ * - exprs: list of expression nodes (updated in place)
+ */
+static JsonPathParseItem *
+make_jsonpath_item_int(int32 val, List **exprs)
+{
+	JsonPathParseItem *jpi = make_jsonpath_item(jpiNumeric);
+
+	jpi->value.numeric =
+		DatumGetNumeric(DirectFunctionCall1(int4_numeric, Int32GetDatum(val)));
+
+	*exprs = lappend(*exprs, makeConst(INT4OID, -1, InvalidOid, 4,
+									   Int32GetDatum(val), false, true));
+
+	return jpi;
+}
+
 /*
  * Convert a constant integer expression into a JsonPathParseItem.
  *
  * The input expression must be a non-null constant of type INT4. Returns NULL otherwise.
- * This function constructs a jpiNumeric item for use in JsonPath and appends a matching
- * Const(INT4) node to the given expression list for use in EXPLAIN, views, etc.
+ * The function extracts the value and delegates it to make_jsonpath_item_int().
  *
  * Parameters:
  * - pstate: parse state context
  * - expr: input expression node
  * - exprs: list of expression nodes (updated in place)
+ * - no_error: returns NULL when the data type doesn't match. Otherwise, emits an ERROR.
  */
 static JsonPathParseItem *
-make_jsonpath_item_expr(ParseState *pstate, Node *expr, List **exprs)
+make_jsonpath_item_expr(ParseState *pstate, Node *expr, List **exprs, bool no_error)
 {
 	Const	   *cnst;
 
@@ -175,20 +204,17 @@ make_jsonpath_item_expr(ParseState *pstate, Node *expr, List **exprs)
 	{
 		cnst = (Const *) expr;
 		if (cnst->consttype == INT4OID && !(cnst->constisnull))
-		{
-			JsonPathParseItem *jpi = make_jsonpath_item(jpiNumeric);
-
-			jpi->value.numeric =
-				DatumGetNumeric(DirectFunctionCall1(int4_numeric, Int32GetDatum(cnst->constvalue)));
-
-			*exprs = lappend(*exprs, makeConst(INT4OID, -1, InvalidOid, 4,
-											   Int32GetDatum(cnst->constvalue), false, true));
-
-			return jpi;
-		}
+			return make_jsonpath_item_int(DatumGetInt32(cnst->constvalue), exprs);
 	}
 
-	return NULL;
+	if (no_error)
+		return NULL;
+	else
+		ereport(ERROR,
+				errcode(ERRCODE_DATATYPE_MISMATCH),
+				errmsg("only non-null integer constants are supported for jsonb simplified accessor subscripting"),
+				errhint("use int data type for subscripting with slicing."),
+				parser_errposition(pstate, exprLocation(expr)));
 }
 
 /*
@@ -251,13 +277,12 @@ jsonb_subscript_make_jsonpath(ParseState *pstate, List **indirection, Subscripti
 		else if (IsA(accessor, A_Indices))
 		{
 			A_Indices  *ai = castNode(A_Indices, accessor);
+			JsonPathParseItem *jpi_from = NULL;
 
 			if (!ai->is_slice)
 			{
-				JsonPathParseItem *jpi_from = NULL;
-
 				Assert(ai->uidx && !ai->lidx);
-				jpi_from = make_jsonpath_item_expr(pstate, ai->uidx, &sbsref->refupperindexpr);
+				jpi_from = make_jsonpath_item_expr(pstate, ai->uidx, &sbsref->refupperindexpr, true);
 				if (jpi_from == NULL)
 				{
 					/*
@@ -284,22 +309,34 @@ jsonb_subscript_make_jsonpath(ParseState *pstate, List **indirection, Subscripti
 					 */
 					break;
 				}
+			}
 
-				jpi = make_jsonpath_item(jpiIndexArray);
-				jpi->value.array.nelems = 1;
-				jpi->value.array.elems = palloc(sizeof(jpi->value.array.elems[0]));
+			jpi = make_jsonpath_item(jpiIndexArray);
+			jpi->value.array.nelems = 1;
+			jpi->value.array.elems = palloc(sizeof(jpi->value.array.elems[0]));
 
+			if (!ai->is_slice)
+			{
 				jpi->value.array.elems[0].from = jpi_from;
 				jpi->value.array.elems[0].to = NULL;
 			}
 			else
 			{
-				Node	   *expr = ai->uidx ? ai->uidx : ai->lidx;
+				while (list_length(sbsref->reflowerindexpr) < list_length(sbsref->refupperindexpr))
+					sbsref->reflowerindexpr = lappend(sbsref->reflowerindexpr, NULL);
+
+				if (ai->lidx)
+					jpi->value.array.elems[0].from = make_jsonpath_item_expr(pstate, ai->lidx, &sbsref->reflowerindexpr, false);
+				else
+					jpi->value.array.elems[0].from = make_jsonpath_item_int(0, &sbsref->reflowerindexpr);
 
-				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("jsonb subscript does not support slices"),
-						 parser_errposition(pstate, exprLocation(expr))));
+				if (ai->uidx)
+					jpi->value.array.elems[0].to = make_jsonpath_item_expr(pstate, ai->uidx, &sbsref->refupperindexpr, false);
+				else
+				{
+					jpi->value.array.elems[0].to = make_jsonpath_item(jpiLast);
+					sbsref->refupperindexpr = lappend(sbsref->refupperindexpr, NULL);
+				}
 			}
 		}
 		else
@@ -381,32 +418,12 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 		ai = lfirst_node(A_Indices, idx);
 
 		if (ai->is_slice)
-		{
-			Node	   *expr = ai->uidx ? ai->uidx : ai->lidx;
+			break;
 
-			ereport(ERROR,
-					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("jsonb subscript does not support slices"),
-					 parser_errposition(pstate, exprLocation(expr))));
-		}
+		Assert(!ai->lidx && ai->uidx);
 
-		if (ai->uidx)
-		{
-			subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
-			subExpr = coerce_jsonpath_subscript_to_int4_or_text(pstate, subExpr);
-		}
-		else
-		{
-			/*
-			 * Slice with omitted upper bound. Should not happen as we already
-			 * errored out on slice earlier, but handle this just in case.
-			 */
-			Assert(ai->is_slice);
-			ereport(ERROR,
-					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("jsonb subscript does not support slices"),
-					 parser_errposition(pstate, exprLocation(ai->uidx))));
-		}
+		subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
+		subExpr = coerce_jsonpath_subscript_to_int4_or_text(pstate, subExpr);
 
 		upperIndexpr = lappend(upperIndexpr, subExpr);
 	}
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.c b/src/interfaces/ecpg/test/expected/sql-sqljson.c
index e6a7ece6dab..935b47a3b9a 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.c
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.c
@@ -505,7 +505,7 @@ if (sqlca.sqlcode < 0) sqlprint();}
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 142 "sqljson.pgc"
 
-	// error
+	printf("Found json=%s\n", json);
 
 	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . * )", ECPGt_EOIT, 
 	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
@@ -525,14 +525,24 @@ if (sqlca.sqlcode < 0) sqlprint();}
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 148 "sqljson.pgc"
 
-	// error
+	printf("Found json=%s\n", json);
 
-  { ECPGdisconnect(__LINE__, "CURRENT");
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ : ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 151 "sqljson.pgc"
 
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 151 "sqljson.pgc"
 
+	printf("Found json=%s\n", json);
+
+  { ECPGdisconnect(__LINE__, "CURRENT");
+#line 154 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 154 "sqljson.pgc"
+
 
   return 0;
 }
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
index 19f8c58af06..f3f899c6d87 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
@@ -339,13 +339,10 @@ SQL error: schema "jsonb" does not exist on line 121
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 142: using PQexec
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_check_PQresult on line 142: bad response - ERROR:  jsonb subscript does not support slices
-LINE 1: ..., {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] )
-                                                                ^
+[NO_PID]: ecpg_process_output on line 142: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 142: RESULT: [12, {"y": 1}] offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: raising sqlstate 42804 (sqlcode -400): jsonb subscript does not support slices on line 142
-[NO_PID]: sqlca: code: -400, state: 42804
-SQL error: jsonb subscript does not support slices on line 142
 [NO_PID]: ecpg_execute on line 145: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . * ); with 0 parameter(s) on connection ecpg1_regression
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 145: using PQexec
@@ -361,12 +358,17 @@ SQL error: row expansion via "*" is not supported here on line 145
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 148: using PQexec
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_check_PQresult on line 148: bad response - ERROR:  jsonb subscript does not support slices
-LINE 1: ...x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )
-                                                                ^
+[NO_PID]: ecpg_process_output on line 148: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 148: RESULT: [{"x": 1}, {"x": [12, {"y": 1}]}] offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 151: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 151: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 151: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 151: RESULT: [{"x": 1}, {"x": [12, {"y": 1}]}] offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: raising sqlstate 42804 (sqlcode -400): jsonb subscript does not support slices on line 148
-[NO_PID]: sqlca: code: -400, state: 42804
-SQL error: jsonb subscript does not support slices on line 148
 [NO_PID]: ecpg_finish: connection ecpg1_regression closed
 [NO_PID]: sqlca: code: 0, state: 00000
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
index 442d36931f1..d01a8457f01 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
@@ -35,3 +35,6 @@ Found json=null
 Found json={"x": 1}
 Found json=[1, [12, {"y": 1}]]
 Found json={"x": 1}
+Found json=[12, {"y": 1}]
+Found json=[{"x": 1}, {"x": [12, {"y": 1}]}]
+Found json=[{"x": 1}, {"x": [12, {"y": 1}]}]
diff --git a/src/interfaces/ecpg/test/sql/sqljson.pgc b/src/interfaces/ecpg/test/sql/sqljson.pgc
index 57a9bff424d..9423d25fd0b 100644
--- a/src/interfaces/ecpg/test/sql/sqljson.pgc
+++ b/src/interfaces/ecpg/test/sql/sqljson.pgc
@@ -140,13 +140,16 @@ EXEC SQL END DECLARE SECTION;
 	printf("Found json=%s\n", json);
 
 	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b[1].x[0:]) INTO :json;
-	// error
+	printf("Found json=%s\n", json);
 
 	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).*) INTO :json;
 	// error
 
 	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb)['b'][0:]) INTO :json;
-	// error
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b[:]) INTO :json;
+	printf("Found json=%s\n", json);
 
   EXEC SQL DISCONNECT;
 
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 37d71b23d5d..2702edc9705 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5247,23 +5247,34 @@ select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b
 (1 row)
 
 select ('{"a": 1}'::jsonb)['a':'b']; -- fails
-ERROR:  jsonb subscript does not support slices
+ERROR:  only non-null integer constants are supported for jsonb simplified accessor subscripting
 LINE 1: select ('{"a": 1}'::jsonb)['a':'b'];
-                                       ^
+                                   ^
+HINT:  use int data type for subscripting with slicing.
 select ('[1, "2", null]'::jsonb)[1:2];
-ERROR:  jsonb subscript does not support slices
-LINE 1: select ('[1, "2", null]'::jsonb)[1:2];
-                                           ^
+    jsonb    
+-------------
+ ["2", null]
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[:2];
-ERROR:  jsonb subscript does not support slices
-LINE 1: select ('[1, "2", null]'::jsonb)[:2];
-                                          ^
+     jsonb      
+----------------
+ [1, "2", null]
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[1:];
-ERROR:  jsonb subscript does not support slices
-LINE 1: select ('[1, "2", null]'::jsonb)[1:];
-                                         ^
+    jsonb    
+-------------
+ ["2", null]
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[:];
-ERROR:  jsonb subscript does not support slices
+     jsonb      
+----------------
+ [1, "2", null]
+(1 row)
+
 create TEMP TABLE test_jsonb_subscript (
        id int,
        test_json jsonb
@@ -6017,8 +6028,42 @@ SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
  
 (1 row)
 
-SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t; -- fails
-ERROR:  jsonb subscript does not support slices
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
+            x             
+--------------------------
+ {"y": "YYY", "z": "ZZZ"}
+(1 row)
+
+SELECT (jb).a[2:3].b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT (jb).a[2:].b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT (jb).a[:2].b FROM test_jsonb_dot_notation;
+  b  
+-----
+ "c"
+(1 row)
+
+SELECT (jb).a[:].b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
+            x             
+--------------------------
+ {"y": "YYY", "z": "ZZZ"}
+(1 row)
+
 SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
 ERROR:  type jsonb is not composite
 -- explains should work
@@ -6054,6 +6099,28 @@ CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_d
 CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
  SELECT (jb).a[3].x.y AS y
    FROM test_jsonb_dot_notation
+CREATE VIEW v2 AS SELECT (jb).a[3:].x.y[:-1] FROM test_jsonb_dot_notation;
+\sv v2
+CREATE OR REPLACE VIEW public.v2 AS
+ SELECT (jb).a[3:].x.y[0:'-1'::integer] AS y
+   FROM test_jsonb_dot_notation
+SELECT * from v2;
+ y 
+---
+ 
+(1 row)
+
+CREATE VIEW v3 AS SELECT (jb).a[:3].x.y[-1:] FROM test_jsonb_dot_notation;
+\sv v3
+CREATE OR REPLACE VIEW public.v3 AS
+ SELECT (jb).a[0:3].x.y['-1'::integer:] AS y
+   FROM test_jsonb_dot_notation
+SELECT * from v3;
+   y   
+-------
+ "yyy"
+(1 row)
+
 -- mixed syntax
 DROP VIEW test_jsonb_dot_notation_v1;
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
@@ -6238,5 +6305,55 @@ SELECT * from test_jsonb_dot_notation_v1;
 (1 row)
 
 DROP VIEW public.test_jsonb_dot_notation_v1;
+-- jsonb array access in plpgsql
+DO $$
+    DECLARE
+        a jsonb := '[1,2,3,4,5,6,7]'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := a[2:];
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+NOTICE:  [1, 2, 3, 4, 5, 6, 7]
+NOTICE:  [3, 4, 5, 6, 7]
+NOTICE:  [5, 6, 7]
+NOTICE:  7
+-- jsonb dot access in plpgsql
+DO $$
+    DECLARE
+        a jsonb := '{"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := COALESCE(a."NU", a[2]); -- fails
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+NOTICE:  {"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}
+ERROR:  missing FROM-clause entry for table "a"
+LINE 1: a := COALESCE(a."NU", a[2])
+                      ^
+QUERY:  a := COALESCE(a."NU", a[2])
+CONTEXT:  PL/pgSQL function inline_code_block line 8 at assignment
+DO $$
+    DECLARE
+        a jsonb := '{"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := COALESCE((a)."NU", a[2]); -- succeeds
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+NOTICE:  {"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}
+NOTICE:  [{"": [[3]]}, [6], [2], "bCi"]
+NOTICE:  [2]
 -- clean up
+DROP VIEW v2;
+DROP VIEW v3;
 DROP TABLE test_jsonb_dot_notation;
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 5fc53e1c9aa..de2c9cda094 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1625,7 +1625,12 @@ SELECT (jb).a[2].b FROM test_jsonb_dot_notation;
 SELECT (jb).a.x.y FROM test_jsonb_dot_notation;
 SELECT (jb).b.x.z FROM test_jsonb_dot_notation;
 SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
-SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t; -- fails
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
+SELECT (jb).a[2:3].b FROM test_jsonb_dot_notation;
+SELECT (jb).a[2:].b FROM test_jsonb_dot_notation;
+SELECT (jb).a[:2].b FROM test_jsonb_dot_notation;
+SELECT (jb).a[:].b FROM test_jsonb_dot_notation;
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
 SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
 
 -- explains should work
@@ -1637,6 +1642,12 @@ SELECT (jb).a[1] FROM test_jsonb_dot_notation;
 -- views should work
 CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
 \sv test_jsonb_dot_notation_v1
+CREATE VIEW v2 AS SELECT (jb).a[3:].x.y[:-1] FROM test_jsonb_dot_notation;
+\sv v2
+SELECT * from v2;
+CREATE VIEW v3 AS SELECT (jb).a[:3].x.y[-1:] FROM test_jsonb_dot_notation;
+\sv v3
+SELECT * from v3;
 
 -- mixed syntax
 DROP VIEW test_jsonb_dot_notation_v1;
@@ -1697,5 +1708,45 @@ SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
 SELECT * from test_jsonb_dot_notation_v1;
 DROP VIEW public.test_jsonb_dot_notation_v1;
 
+-- jsonb array access in plpgsql
+DO $$
+    DECLARE
+        a jsonb := '[1,2,3,4,5,6,7]'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := a[2:];
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+
+-- jsonb dot access in plpgsql
+DO $$
+    DECLARE
+        a jsonb := '{"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := COALESCE(a."NU", a[2]); -- fails
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+
+DO $$
+    DECLARE
+        a jsonb := '{"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := COALESCE((a)."NU", a[2]); -- succeeds
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+
 -- clean up
+DROP VIEW v2;
+DROP VIEW v3;
 DROP TABLE test_jsonb_dot_notation;
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v16-0005-Implement-read-only-dot-notation-for-jsonb.patch (61.2K, ../../CAK98qZ1xhjKfo_C-s_bnpkPJ4xkdqEwdRYq4pbFKM8fKqcfKZw@mail.gmail.com/7-v16-0005-Implement-read-only-dot-notation-for-jsonb.patch)
  download | inline diff:
From 2254eed23236ab8968eb5e7753b0e2e975f48251 Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v16 5/7] Implement read-only dot notation for jsonb

This patch introduces JSONB member access using dot notation that
aligns with the JSON simplified accessor specified in SQL:2023.

Examples:

-- Setup
create table t(x int, y jsonb);
insert into t select 1, '{"a": 1, "b": 42}'::jsonb;
insert into t select 1, '{"a": 2, "b": {"c": 42}}'::jsonb;
insert into t select 1, '{"a": 3, "b": {"c": "42"}, "d":[11, 12]}'::jsonb;

-- Existing syntax in PostgreSQL that predates the SQL standard:
select (t.y)->'b' from t;
select (t.y)->'b'->'c' from t;
select (t.y)->'d'->0 from t;

-- JSON simplified accessor specified by the SQL standard:
select (t.y).b from t;
select (t.y).b.c from t;
select (t.y).d[0] from t;

The SQL standard states that simplified access is equivalent to:
JSON_QUERY (VEP, 'lax $.JC' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR)

where:
  VEP = <value expression primary>
  JC = <JSON simplified accessor op chain>

For example, the JSON_QUERY equivalents of the above queries are:

select json_query(y, 'lax $.b' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t;
select json_query(y, 'lax $.b.c' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t;
select json_query(y, 'lax $.d[0]' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t;

Implementation details:

This patch extends the existing container subscripting interface to
support container-specific information, namely a JSONPath expression
for jsonb.

During query transformation, if dot-notation is present, a JSONPath
expression is constructed to represent the access chain.

Then during execution, if a JSONPath expression is present in
JsonbSubWorkspace, executes it via JsonPathQuery().

Note that we cannot simply rewrite the accessors into JSON_QUERY()
during transformation, because the original query structure must be
preserved for EXPLAIN and CREATE VIEW.

Co-authored-by: Nikita Glukhov <[email protected]>
Co-authored-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Mark Dilger <[email protected]>
Reviewed-by: Jian He <[email protected]>
Reviewed-by: Vik Fearing <[email protected]>
Reviewed-by: Nikita Malakhov <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Tested-by: Jelte Fennema-Nio <[email protected]>
---
 src/backend/catalog/sql_features.txt          |   4 +-
 src/backend/executor/execExpr.c               |  81 ++--
 src/backend/nodes/nodeFuncs.c                 |  12 +
 src/backend/utils/adt/jsonbsubs.c             | 301 ++++++++++++-
 src/backend/utils/adt/ruleutils.c             |  43 +-
 src/include/nodes/primnodes.h                 |  54 ++-
 .../ecpg/test/expected/sql-sqljson.c          | 112 ++++-
 .../ecpg/test/expected/sql-sqljson.stderr     | 100 +++++
 .../ecpg/test/expected/sql-sqljson.stdout     |   7 +
 src/interfaces/ecpg/test/sql/sqljson.pgc      |  33 ++
 src/test/regress/expected/jsonb.out           | 413 +++++++++++++++++-
 src/test/regress/sql/jsonb.sql                | 113 ++++-
 src/tools/pgindent/typedefs.list              |   1 +
 13 files changed, 1201 insertions(+), 73 deletions(-)

diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_features.txt
index ebe85337c28..457e993305e 100644
--- a/src/backend/catalog/sql_features.txt
+++ b/src/backend/catalog/sql_features.txt
@@ -568,8 +568,8 @@ T838	JSON_TABLE: PLAN DEFAULT clause			NO
 T839	Formatted cast of datetimes to/from character strings			NO	
 T840	Hex integer literals in SQL/JSON path language			YES	
 T851	SQL/JSON: optional keywords for default syntax			YES	
-T860	SQL/JSON simplified accessor: column reference only			NO	
-T861	SQL/JSON simplified accessor: case-sensitive JSON member accessor			NO	
+T860	SQL/JSON simplified accessor: column reference only			YES	
+T861	SQL/JSON simplified accessor: case-sensitive JSON member accessor			YES	
 T862	SQL/JSON simplified accessor: wildcard member accessor			NO	
 T863	SQL/JSON simplified accessor: single-quoted string literal as member accessor			NO	
 T864	SQL/JSON simplified accessor			NO	
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index f1569879b52..385c8d0cefe 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -3320,50 +3320,59 @@ ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref,
 								   state->steps_len - 1);
 	}
 
-	/* Evaluate upper subscripts */
-	i = 0;
-	foreach(lc, sbsref->refupperindexpr)
+	/* Evaluate upper subscripts, unless refjsonbpath is used for execution */
+	if (!sbsref->refjsonbpath)
 	{
-		Expr	   *e = (Expr *) lfirst(lc);
-
-		/* When slicing, individual subscript bounds can be omitted */
-		if (!e)
-		{
-			sbsrefstate->upperprovided[i] = false;
-			sbsrefstate->upperindexnull[i] = true;
-		}
-		else
+		i = 0;
+		foreach(lc, sbsref->refupperindexpr)
 		{
-			sbsrefstate->upperprovided[i] = true;
-			/* Each subscript is evaluated into appropriate array entry */
-			ExecInitExprRec(e, state,
-							&sbsrefstate->upperindex[i],
-							&sbsrefstate->upperindexnull[i]);
+			Expr *e = (Expr *) lfirst(lc);
+
+			/* When slicing, individual subscript bounds can be omitted */
+			if (!e)
+			{
+				sbsrefstate->upperprovided[i] = false;
+				sbsrefstate->upperindexnull[i] = true;
+			}
+			else
+			{
+				sbsrefstate->upperprovided[i] = true;
+				/* Each subscript is evaluated into appropriate array entry */
+				ExecInitExprRec(e, state,
+								&sbsrefstate->upperindex[i],
+								&sbsrefstate->upperindexnull[i]);
+			}
+			i++;
 		}
-		i++;
 	}
 
-	/* Evaluate lower subscripts similarly */
-	i = 0;
-	foreach(lc, sbsref->reflowerindexpr)
+	/*
+	 * Evaluate lower subscripts similarly, unless refjsonbpath is used for
+	 * execution
+	 */
+	if (!sbsref->refjsonbpath)
 	{
-		Expr	   *e = (Expr *) lfirst(lc);
-
-		/* When slicing, individual subscript bounds can be omitted */
-		if (!e)
-		{
-			sbsrefstate->lowerprovided[i] = false;
-			sbsrefstate->lowerindexnull[i] = true;
-		}
-		else
+		i = 0;
+		foreach(lc, sbsref->reflowerindexpr)
 		{
-			sbsrefstate->lowerprovided[i] = true;
-			/* Each subscript is evaluated into appropriate array entry */
-			ExecInitExprRec(e, state,
-							&sbsrefstate->lowerindex[i],
-							&sbsrefstate->lowerindexnull[i]);
+			Expr	   *e = (Expr *) lfirst(lc);
+
+			/* When slicing, individual subscript bounds can be omitted */
+			if (!e)
+			{
+				sbsrefstate->lowerprovided[i] = false;
+				sbsrefstate->lowerindexnull[i] = true;
+			}
+			else
+			{
+				sbsrefstate->lowerprovided[i] = true;
+				/* Each subscript is evaluated into appropriate array entry */
+				ExecInitExprRec(e, state,
+								&sbsrefstate->lowerindex[i],
+								&sbsrefstate->lowerindexnull[i]);
+			}
+			i++;
 		}
-		i++;
 	}
 
 	/* SBSREF_SUBSCRIPTS checks and converts all the subscripts at once */
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 7bc823507f1..d1bd575d9bd 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -284,6 +284,13 @@ exprType(const Node *expr)
 		case T_PlaceHolderVar:
 			type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_FieldAccessorExpr:
+			/*
+			 * FieldAccessorExpr is not evaluable. Treat it as TEXT for collation,
+			 * deparsing, and similar purposes, since it represents a JSON field name.
+			 */
+			type = TEXTOID;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			type = InvalidOid;	/* keep compiler quiet */
@@ -1146,6 +1153,9 @@ exprSetCollation(Node *expr, Oid collation)
 		case T_MergeSupportFunc:
 			((MergeSupportFunc *) expr)->msfcollid = collation;
 			break;
+		case T_FieldAccessorExpr:
+			((FieldAccessorExpr *) expr)->faecollid = collation;
+			break;
 		case T_SubscriptingRef:
 			((SubscriptingRef *) expr)->refcollid = collation;
 			break;
@@ -2129,6 +2139,7 @@ expression_tree_walker_impl(Node *node,
 		case T_SortGroupClause:
 		case T_CTESearchClause:
 		case T_MergeSupportFunc:
+		case T_FieldAccessorExpr:
 			/* primitive node types with no expression subnodes */
 			break;
 		case T_WithCheckOption:
@@ -3008,6 +3019,7 @@ expression_tree_mutator_impl(Node *node,
 		case T_SortGroupClause:
 		case T_CTESearchClause:
 		case T_MergeSupportFunc:
+		case T_FieldAccessorExpr:
 			return copyObject(node);
 		case T_WithCheckOption:
 			{
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index 1c586e960f3..62553d54060 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -15,21 +15,30 @@
 #include "postgres.h"
 
 #include "executor/execExpr.h"
+#include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "nodes/subscripting.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_expr.h"
 #include "utils/builtins.h"
 #include "utils/jsonb.h"
+#include "utils/jsonpath.h"
 
 
-/* SubscriptingRefState.workspace for jsonb subscripting execution */
+/*
+ * SubscriptingRefState.workspace for generic jsonb subscripting execution.
+ *
+ * Stores state for both jsonb simple subscripting and dot notation access.
+ * Dot notation additionally uses `jsonpath` for JsonPath evaluation.
+ */
 typedef struct JsonbSubWorkspace
 {
 	bool		expectArray;	/* jsonb root is expected to be an array */
 	Oid		   *indexOid;		/* OID of coerced subscript expression, could
 								 * be only integer or text */
 	Datum	   *index;			/* Subscript values in Datum format */
+	JsonPath   *jsonpath;		/* JsonPath for dot notation execution via
+								 * JsonPathQuery() */
 } JsonbSubWorkspace;
 
 static Node *
@@ -96,6 +105,234 @@ coerce_jsonpath_subscript_to_int4_or_text(ParseState *pstate, Node *subExpr)
 	return subExpr;
 }
 
+/*
+ * During transformation, determine whether to build a JsonPath
+ * for JsonPathQuery() execution.
+ *
+ * JsonPath is needed if the indirection list includes:
+ * - String-based access (dot notation)
+ * - Slice-based subscripting (when isSlice is true)
+ *
+ * Otherwise, simple jsonb subscripting is enough.
+ */
+static bool
+jsonb_check_jsonpath_needed(List *indirection)
+{
+	ListCell   *lc;
+
+	foreach(lc, indirection)
+	{
+		Node	   *accessor = lfirst(lc);
+
+		if (IsA(accessor, String))
+			return true;
+		else
+			Assert(IsA(accessor, A_Indices));
+	}
+
+	return false;
+}
+
+/*
+ * Helper functions for constructing JsonPath expressions.
+ *
+ * The make_jsonpath_item_* functions create various types of JsonPathParseItem
+ * nodes, which are used to build JsonPath expressions for jsonb simplified
+ * accessor.
+ */
+
+static JsonPathParseItem *
+make_jsonpath_item(JsonPathItemType type)
+{
+	JsonPathParseItem *v = palloc(sizeof(*v));
+
+	v->type = type;
+	v->next = NULL;
+
+	return v;
+}
+
+/*
+ * Convert a constant integer expression into a JsonPathParseItem.
+ *
+ * The input expression must be a non-null constant of type INT4. Returns NULL otherwise.
+ * This function constructs a jpiNumeric item for use in JsonPath and appends a matching
+ * Const(INT4) node to the given expression list for use in EXPLAIN, views, etc.
+ *
+ * Parameters:
+ * - pstate: parse state context
+ * - expr: input expression node
+ * - exprs: list of expression nodes (updated in place)
+ */
+static JsonPathParseItem *
+make_jsonpath_item_expr(ParseState *pstate, Node *expr, List **exprs)
+{
+	Const	   *cnst;
+
+	expr = transformExpr(pstate, expr, pstate->p_expr_kind);
+
+	if (IsA(expr, Const))
+	{
+		cnst = (Const *) expr;
+		if (cnst->consttype == INT4OID && !(cnst->constisnull))
+		{
+			JsonPathParseItem *jpi = make_jsonpath_item(jpiNumeric);
+
+			jpi->value.numeric =
+				DatumGetNumeric(DirectFunctionCall1(int4_numeric, Int32GetDatum(cnst->constvalue)));
+
+			*exprs = lappend(*exprs, makeConst(INT4OID, -1, InvalidOid, 4,
+											   Int32GetDatum(cnst->constvalue), false, true));
+
+			return jpi;
+		}
+	}
+
+	return NULL;
+}
+
+/*
+ * Constructs a JsonPath expression from a list of indirections.
+ * This function is used when jsonb subscripting involves dot notation,
+ * requiring JsonPath-based evaluation.
+ *
+ * The function modifies the indirection list in place, removing processed
+ * elements as it converts them into JsonPath components, as follows:
+ * - String keys (dot notation) -> jpiKey items.
+ * - Array indices -> jpiIndexArray items.
+ *
+ * In addition to building the JsonPath expression, this function populates
+ * the following fields of the given SubscriptingRef:
+ * - refjsonbpath: the generated JsonPath
+ * - refupperindexpr: upper index expressions (object keys or array indexes)
+ * - reflowerindexpr: lower index expressions, remains NIL as slices are not supported.
+ *
+ * Parameters:
+ * - pstate: Parse state context.
+ * - indirection: List of subscripting expressions (modified in-place).
+ * - sbsref: SubscriptingRef node to update
+ */
+static void
+jsonb_subscript_make_jsonpath(ParseState *pstate, List **indirection, SubscriptingRef *sbsref)
+{
+	JsonPathParseResult jpres;
+	JsonPathParseItem *path = make_jsonpath_item(jpiRoot);
+	ListCell   *lc;
+	Datum		jsp;
+	int			pathlen = 0;
+
+	sbsref->refupperindexpr = NIL;
+	sbsref->reflowerindexpr = NIL;
+	sbsref->refjsonbpath = NULL;
+
+	jpres.expr = path;
+	jpres.lax = true;
+
+	foreach(lc, *indirection)
+	{
+		Node	   *accessor = lfirst(lc);
+		JsonPathParseItem *jpi;
+
+		if (IsA(accessor, String))
+		{
+			char	   *field = strVal(accessor);
+			FieldAccessorExpr *accessor_expr;
+
+			jpi = make_jsonpath_item(jpiKey);
+			jpi->value.string.val = field;
+			jpi->value.string.len = strlen(field);
+
+			accessor_expr = makeNode(FieldAccessorExpr);
+			accessor_expr->type = T_FieldAccessorExpr;
+			accessor_expr->fieldname = field;
+
+			sbsref->refupperindexpr = lappend(sbsref->refupperindexpr, accessor_expr);
+		}
+		else if (IsA(accessor, A_Indices))
+		{
+			A_Indices  *ai = castNode(A_Indices, accessor);
+
+			if (!ai->is_slice)
+			{
+				JsonPathParseItem *jpi_from = NULL;
+
+				Assert(ai->uidx && !ai->lidx);
+				jpi_from = make_jsonpath_item_expr(pstate, ai->uidx, &sbsref->refupperindexpr);
+				if (jpi_from == NULL)
+				{
+					/*
+					 * Break out of the loop if the subscript is not a
+					 * non-null integer constant, so that we can fall back to
+					 * jsonb subscripting logic.
+					 *
+					 * This is needed to handle cases with mixed usage of SQL
+					 * standard json simplified accessor syntax and PostgreSQL
+					 * jsonb subscripting syntax, e.g:
+					 *
+					 * select (jb).a['b'].c from jsonb_table;
+					 *
+					 * where dot-notation (.a and .c) is the SQL standard json
+					 * simplified accessor syntax, and the ['b'] subscript is
+					 * the PostgreSQL jsonb subscripting syntax, because 'b'
+					 * is not a non-null constant integer and cannot be used
+					 * for json array access.
+					 *
+					 * In this case, we cannot create a JsonPath item, so we
+					 * break out of the loop and let
+					 * jsonb_subscript_transform() handle this indirection as
+					 * a PostgreSQL jsonb subscript.
+					 */
+					break;
+				}
+
+				jpi = make_jsonpath_item(jpiIndexArray);
+				jpi->value.array.nelems = 1;
+				jpi->value.array.elems = palloc(sizeof(jpi->value.array.elems[0]));
+
+				jpi->value.array.elems[0].from = jpi_from;
+				jpi->value.array.elems[0].to = NULL;
+			}
+			else
+			{
+				Node	   *expr = ai->uidx ? ai->uidx : ai->lidx;
+
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("jsonb subscript does not support slices"),
+						 parser_errposition(pstate, exprLocation(expr))));
+			}
+		}
+		else
+		{
+			/*
+			 * Unexpected node type in indirection list. This should not
+			 * happen with current grammar, but we handle it defensively by
+			 * breaking out of the loop rather than crashing. In case of
+			 * future grammar changes that might introduce new node types,
+			 * this allows us to create a jsonpath from as many indirection
+			 * elements as we can and let transformIndirection() fallback to
+			 * alternative logic to handle the remaining indirection elements.
+			 */
+			Assert(false);		/* not reachable */
+			break;
+		}
+
+		/* append path item */
+		path->next = jpi;
+		path = jpi;
+		pathlen++;
+	}
+
+	if (pathlen == 0)
+		return;
+
+	*indirection = list_delete_first_n(*indirection, pathlen);
+
+	jsp = jsonPathFromParseResult(&jpres, 0, NULL);
+
+	sbsref->refjsonbpath = (Node *) makeConst(JSONPATHOID, -1, InvalidOid, -1, jsp, false, false);
+}
+
 /*
  * Finish parse analysis of a SubscriptingRef expression for a jsonb.
  *
@@ -111,9 +348,27 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	List	   *upperIndexpr = NIL;
 	ListCell   *idx;
 
+	/* Determine the result type of the subscripting operation; always jsonb */
+	sbsref->refrestype = JSONBOID;
+	sbsref->reftypmod = -1;
+
+	if (jsonb_check_jsonpath_needed(*indirection))
+	{
+		jsonb_subscript_make_jsonpath(pstate, indirection, sbsref);
+		if (sbsref->refjsonbpath)
+			return;
+	}
+
 	/*
-	 * Transform and convert the subscript expressions. Jsonb subscripting
-	 * does not support slices, look only at the upper index.
+	 * We reach here only in two cases: (a) the JSON simplified accessor is
+	 * not needed at all (for example, a plain array subscript like [1] or
+	 * object key access like ['a']), or (b) jsonb_subscript_make_jsonpath()
+	 * was called but could not complete the JsonPath construction (for
+	 * example, when mixing dot notation with non-integer subscripts like
+	 * (jb)['a'].b where 'a' is not a constant integer).
+	 *
+	 * In both cases we fall back to pre-standard jsonb subscripting, coercing
+	 * each subscript to array index or object key as needed.
 	 */
 	foreach(idx, *indirection)
 	{
@@ -160,10 +415,6 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->refupperindexpr = upperIndexpr;
 	sbsref->reflowerindexpr = NIL;
 
-	/* Determine the result type of the subscripting operation; always jsonb */
-	sbsref->refrestype = JSONBOID;
-	sbsref->reftypmod = -1;
-
 	/* Remove processed elements */
 	if (upperIndexpr)
 		*indirection = list_delete_first_n(*indirection, list_length(upperIndexpr));
@@ -218,7 +469,7 @@ jsonb_subscript_check_subscripts(ExprState *state,
 			 * For jsonb fetch and assign functions we need to provide path in
 			 * text format. Convert if it's not already text.
 			 */
-			if (workspace->indexOid[i] == INT4OID)
+			if (!workspace->jsonpath && workspace->indexOid[i] == INT4OID)
 			{
 				Datum		datum = sbsrefstate->upperindex[i];
 				char	   *cs = DatumGetCString(DirectFunctionCall1(int4out, datum));
@@ -246,17 +497,32 @@ jsonb_subscript_fetch(ExprState *state,
 {
 	SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
 	JsonbSubWorkspace *workspace = (JsonbSubWorkspace *) sbsrefstate->workspace;
-	Jsonb	   *jsonbSource;
 
 	/* Should not get here if source jsonb (or any subscript) is null */
 	Assert(!(*op->resnull));
 
-	jsonbSource = DatumGetJsonbP(*op->resvalue);
-	*op->resvalue = jsonb_get_element(jsonbSource,
-									  workspace->index,
-									  sbsrefstate->numupper,
-									  op->resnull,
-									  false);
+	if (workspace->jsonpath)
+	{
+		bool		empty = false;
+		bool		error = false;
+
+		*op->resvalue = JsonPathQuery(*op->resvalue, workspace->jsonpath,
+									  JSW_CONDITIONAL,
+									  &empty, &error, NULL,
+									  NULL);
+
+		*op->resnull = empty || error;
+	}
+	else
+	{
+		Jsonb	   *jsonbSource = DatumGetJsonbP(*op->resvalue);
+
+		*op->resvalue = jsonb_get_element(jsonbSource,
+										  workspace->index,
+										  sbsrefstate->numupper,
+										  op->resnull,
+										  false);
+	}
 }
 
 /*
@@ -364,7 +630,7 @@ jsonb_exec_setup(const SubscriptingRef *sbsref,
 {
 	JsonbSubWorkspace *workspace;
 	ListCell   *lc;
-	int			nupper = sbsref->refupperindexpr->length;
+	int			nupper = list_length(sbsref->refupperindexpr);
 	char	   *ptr;
 
 	/* Allocate type-specific workspace with space for per-subscript data */
@@ -373,6 +639,9 @@ jsonb_exec_setup(const SubscriptingRef *sbsref,
 	workspace->expectArray = false;
 	ptr = ((char *) workspace) + MAXALIGN(sizeof(JsonbSubWorkspace));
 
+	if (sbsref->refjsonbpath)
+		workspace->jsonpath = DatumGetJsonPathP(castNode(Const, sbsref->refjsonbpath)->constvalue);
+
 	/*
 	 * This coding assumes sizeof(Datum) >= sizeof(Oid), else we might
 	 * misalign the indexOid pointer
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 3d6e6bdbfd2..baa3ae97d57 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9329,10 +9329,13 @@ get_rule_expr(Node *node, deparse_context *context,
 				 * Parenthesize the argument unless it's a simple Var or a
 				 * FieldSelect.  (In particular, if it's another
 				 * SubscriptingRef, we *must* parenthesize to avoid
-				 * confusion.)
+				 * confusion.) Always add parenthesis if JSON simplified
+				 * accessor is used, for now.
 				 */
-				need_parens = !IsA(sbsref->refexpr, Var) &&
-					!IsA(sbsref->refexpr, FieldSelect);
+				need_parens = (!IsA(sbsref->refexpr, Var) &&
+					!IsA(sbsref->refexpr, FieldSelect)) ||
+						sbsref->refjsonbpath;
+
 				if (need_parens)
 					appendStringInfoChar(buf, '(');
 				get_rule_expr((Node *) sbsref->refexpr, context, showimplicit);
@@ -13005,17 +13008,35 @@ printSubscripts(SubscriptingRef *sbsref, deparse_context *context)
 	lowlist_item = list_head(sbsref->reflowerindexpr);	/* could be NULL */
 	foreach(uplist_item, sbsref->refupperindexpr)
 	{
-		appendStringInfoChar(buf, '[');
-		if (lowlist_item)
+		Node *upper = (Node *) lfirst(uplist_item);
+
+		if (upper && IsA(upper, FieldAccessorExpr))
 		{
+			FieldAccessorExpr *fae = (FieldAccessorExpr *) upper;
+
+			/* Use dot-notation for field access */
+			appendStringInfoChar(buf, '.');
+			appendStringInfoString(buf, quote_identifier(fae->fieldname));
+
+			/* Skip matching low index — field access doesn't use slices */
+			if (lowlist_item)
+				lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
+		}
+		else
+		{
+			/* Use JSONB array subscripting */
+			appendStringInfoChar(buf, '[');
+			if (lowlist_item)
+			{
+				/* If subexpression is NULL, get_rule_expr prints nothing */
+				get_rule_expr((Node *) lfirst(lowlist_item), context, false);
+				appendStringInfoChar(buf, ':');
+				lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
+			}
 			/* If subexpression is NULL, get_rule_expr prints nothing */
-			get_rule_expr((Node *) lfirst(lowlist_item), context, false);
-			appendStringInfoChar(buf, ':');
-			lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
+			get_rule_expr(upper, context, false);
+			appendStringInfoChar(buf, ']');
 		}
-		/* If subexpression is NULL, get_rule_expr prints nothing */
-		get_rule_expr((Node *) lfirst(uplist_item), context, false);
-		appendStringInfoChar(buf, ']');
 	}
 }
 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 6dfca3cb35b..7e89621bd65 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -708,18 +708,30 @@ typedef struct SubscriptingRef
 	int32		reftypmod pg_node_attr(query_jumble_ignore);
 	/* collation of result, or InvalidOid if none */
 	Oid			refcollid pg_node_attr(query_jumble_ignore);
-	/* expressions that evaluate to upper container indexes */
+
+	/*
+	 * expressions that evaluate to upper container indexes or expressions
+	 * that are collected but not evaluated when refjsonbpath is set.
+	 */
 	List	   *refupperindexpr;
 
 	/*
-	 * expressions that evaluate to lower container indexes, or NIL for single
-	 * container element.
+	 * expressions that evaluate to lower container indexes, or NIL for a
+	 * single container element, or expressions that are collected but not
+	 * evaluated when refjsonbpath is set.
 	 */
 	List	   *reflowerindexpr;
 	/* the expression that evaluates to a container value */
 	Expr	   *refexpr;
 	/* expression for the source value, or NULL if fetch */
 	Expr	   *refassgnexpr;
+
+	/*
+	 * container-specific extra information, currently used only by jsonb.
+	 * stores a JsonPath expression when jsonb dot notation is used. NULL for
+	 * simple subscripting.
+	 */
+	Node	   *refjsonbpath;
 } SubscriptingRef;
 
 /*
@@ -2371,4 +2383,40 @@ typedef struct OnConflictExpr
 	List	   *exclRelTlist;	/* tlist of the EXCLUDED pseudo relation */
 } OnConflictExpr;
 
+/*
+ * FieldAccessorExpr - represents a single object member access using dot-notation
+ *		in JSON simplified accessor syntax (e.g., jsonb_col.a).
+ *
+ * These nodes appear as list elements in SubscriptingRef.refupperindexpr to
+ * indicate JSON object key access. They are not evaluable expressions by
+ * themselves but serve as placeholders to preserve source-level syntax for
+ * rule rewriting and deparsing (e.g., in EXPLAIN and view definitions).
+ * Execution is handled by the enclosing SubscriptingRef.
+ *
+ * If dot-notation is used in a SubscriptingRef, the JSON path is represented
+ * as a flat list of FieldAccessorExpr nodes (for object field access), Const
+ * nodes (for array indexes), and NULLs (for omitted slice bounds), rather than
+ * through nested expression trees.
+ *
+ * Note: The flat representation avoids nested FieldAccessorExpr chains,
+ * simplifying evaluation and enabling standard-compliant behavior such as
+ * conditional array wrapping. This avoids the need for position-aware
+ * wrapping/unwrapping logic during execution.
+ *
+ * For example, in the expression:
+ *		('{"a": [{"b": 1}]}'::jsonb).a[0].b
+ * the SubscriptingRef will contain:
+ *		- refexpr: the base expression (the jsonb value)
+ *		- refupperindexpr: [FieldAccessorExpr("a"), Const(0),
+ *			FieldAccessorExpr("b")]
+ *		- reflowerindexpr: [NULL, NULL, NULL] (slice lower bounds not used here)
+ */
+typedef struct FieldAccessorExpr
+{
+	NodeTag		type;
+	char	   *fieldname;		/* name of the JSONB object field accessed via
+								 * dot notation */
+	Oid			faecollid pg_node_attr(query_jumble_ignore);
+}			FieldAccessorExpr;
+
 #endif							/* PRIMNODES_H */
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.c b/src/interfaces/ecpg/test/expected/sql-sqljson.c
index 39221f9ea5d..e6a7ece6dab 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.c
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.c
@@ -417,12 +417,122 @@ if (sqlca.sqlcode < 0) sqlprint();}
 	  for (int i = 0; i < sizeof(is_json); i++)
 		  printf("Found is_json[%d]: %s\n", i, is_json[i] ? "true" : "false");
 
-  { ECPGdisconnect(__LINE__, "CURRENT");
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . \"a\" )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 118 "sqljson.pgc"
 
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 118 "sqljson.pgc"
 
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb . \"a\" )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 121 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 121 "sqljson.pgc"
+
+	// error
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . a )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 124 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 124 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . a . b )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 127 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 127 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( coalesce ( json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . c ) , 'null' ) )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 130 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 130 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ 0 ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 133 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 133 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b . x )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 136 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 136 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) [ 'b' ] [ 0 ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 139 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 139 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 142 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 142 "sqljson.pgc"
+
+	// error
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . * )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 145 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 145 "sqljson.pgc"
+
+	// error
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 148 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 148 "sqljson.pgc"
+
+	// error
+
+  { ECPGdisconnect(__LINE__, "CURRENT");
+#line 151 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 151 "sqljson.pgc"
+
 
   return 0;
 }
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
index e55a95dd711..19f8c58af06 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
@@ -268,5 +268,105 @@ SQL error: cannot use type jsonb in RETURNING clause of JSON_SERIALIZE() on line
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_get_data on line 102: RESULT: f offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 118: query: select json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . "a" ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 118: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 118: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 118: RESULT: {"b": 1, "c": 2} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 121: query: select json ( '{"a": {"b": 1, "c": 2}}' :: jsonb . "a" ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 121: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 121: bad response - ERROR:  schema "jsonb" does not exist
+LINE 1: select json ( '{"a": {"b": 1, "c": 2}}' :: jsonb . "a" )
+                                                   ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 3F000 (sqlcode -400): schema "jsonb" does not exist on line 121
+[NO_PID]: sqlca: code: -400, state: 3F000
+SQL error: schema "jsonb" does not exist on line 121
+[NO_PID]: ecpg_execute on line 124: query: select json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . a ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 124: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 124: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 124: RESULT: {"b": 1, "c": 2} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 127: query: select json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . a . b ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 127: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 127: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 127: RESULT: 1 offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 130: query: select json ( coalesce ( json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . c ) , 'null' ) ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 130: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 130: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 130: RESULT: null offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 133: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 0 ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 133: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 133: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 133: RESULT: {"x": 1} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 136: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b . x ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 136: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 136: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 136: RESULT: [1, [12, {"y": 1}]] offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 139: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 139: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 139: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 139: RESULT: {"x": 1} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 142: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 142: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 142: bad response - ERROR:  jsonb subscript does not support slices
+LINE 1: ..., {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] )
+                                                                ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 42804 (sqlcode -400): jsonb subscript does not support slices on line 142
+[NO_PID]: sqlca: code: -400, state: 42804
+SQL error: jsonb subscript does not support slices on line 142
+[NO_PID]: ecpg_execute on line 145: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . * ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 145: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 145: bad response - ERROR:  row expansion via "*" is not supported here
+LINE 1: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x...
+                        ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 0A000 (sqlcode -400): row expansion via "*" is not supported here on line 145
+[NO_PID]: sqlca: code: -400, state: 0A000
+SQL error: row expansion via "*" is not supported here on line 145
+[NO_PID]: ecpg_execute on line 148: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 148: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 148: bad response - ERROR:  jsonb subscript does not support slices
+LINE 1: ...x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )
+                                                                ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 42804 (sqlcode -400): jsonb subscript does not support slices on line 148
+[NO_PID]: sqlca: code: -400, state: 42804
+SQL error: jsonb subscript does not support slices on line 148
 [NO_PID]: ecpg_finish: connection ecpg1_regression closed
 [NO_PID]: sqlca: code: 0, state: 00000
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
index 83f8df13e5a..442d36931f1 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
@@ -28,3 +28,10 @@ Found is_json[4]: false
 Found is_json[5]: false
 Found is_json[6]: true
 Found is_json[7]: false
+Found json={"b": 1, "c": 2}
+Found json={"b": 1, "c": 2}
+Found json=1
+Found json=null
+Found json={"x": 1}
+Found json=[1, [12, {"y": 1}]]
+Found json={"x": 1}
diff --git a/src/interfaces/ecpg/test/sql/sqljson.pgc b/src/interfaces/ecpg/test/sql/sqljson.pgc
index ddcbcc3b3cb..57a9bff424d 100644
--- a/src/interfaces/ecpg/test/sql/sqljson.pgc
+++ b/src/interfaces/ecpg/test/sql/sqljson.pgc
@@ -115,6 +115,39 @@ EXEC SQL END DECLARE SECTION;
 	  for (int i = 0; i < sizeof(is_json); i++)
 		  printf("Found is_json[%d]: %s\n", i, is_json[i] ? "true" : "false");
 
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}}'::jsonb)."a") INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON('{"a": {"b": 1, "c": 2}}'::jsonb."a") INTO :json;
+	// error
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}}'::jsonb).a) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}}'::jsonb).a.b) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(COALESCE(JSON(('{"a": {"b": 1, "c": 2}}'::jsonb).c), 'null')) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b[0]) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b.x) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb)['b'][0]) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b[1].x[0:]) INTO :json;
+	// error
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).*) INTO :json;
+	// error
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb)['b'][0:]) INTO :json;
+	// error
+
   EXEC SQL DISCONNECT;
 
   return 0;
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5a1eb18aba2..37d71b23d5d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -4989,6 +4989,12 @@ select ('123'::jsonb)['a'];
  
 (1 row)
 
+select ('123'::jsonb).a;
+ a 
+---
+ 
+(1 row)
+
 select ('123'::jsonb)[0];
  jsonb 
 -------
@@ -5001,12 +5007,24 @@ select ('123'::jsonb)[NULL];
  
 (1 row)
 
+select ('123'::jsonb).NULL;
+ null 
+------
+ 
+(1 row)
+
 select ('{"a": 1}'::jsonb)['a'];
  jsonb 
 -------
  1
 (1 row)
 
+select ('{"a": 1}'::jsonb).a;
+ a 
+---
+ 1
+(1 row)
+
 select ('{"a": 1}'::jsonb)[0];
  jsonb 
 -------
@@ -5019,6 +5037,12 @@ select ('{"a": 1}'::jsonb)['not_exist'];
  
 (1 row)
 
+select ('{"a": 1}'::jsonb)."not_exist";
+ not_exist 
+-----------
+ 
+(1 row)
+
 select ('{"a": 1}'::jsonb)[NULL];
  jsonb 
 -------
@@ -5031,6 +5055,12 @@ select ('[1, "2", null]'::jsonb)['a'];
  
 (1 row)
 
+select ('[1, "2", null]'::jsonb).a;
+ a 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[0];
  jsonb 
 -------
@@ -5043,6 +5073,12 @@ select ('[1, "2", null]'::jsonb)['1'];
  "2"
 (1 row)
 
+select ('[1, "2", null]'::jsonb)."1";
+ 1 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[1.0];
 ERROR:  subscript type numeric is not supported
 LINE 1: select ('[1, "2", null]'::jsonb)[1.0];
@@ -5072,6 +5108,12 @@ select ('[1, "2", null]'::jsonb)[1]['a'];
  
 (1 row)
 
+select ('[1, "2", null]'::jsonb)[1].a;
+ a 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[1][0];
  jsonb 
 -------
@@ -5084,56 +5126,127 @@ select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['b'];
  "c"
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).b;
+  b  
+-----
+ "c"
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'];
    jsonb   
 -----------
  [1, 2, 3]
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d;
+     d     
+-----------
+ [1, 2, 3]
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'][1];
  jsonb 
 -------
  2
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d[1];
+ d 
+---
+ 2
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d']['a'];
  jsonb 
 -------
  
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['a'];
+ d 
+---
+ 
+(1 row)
+
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d.a;
+ a 
+---
+ 
+(1 row)
+
+select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d['a'][0];
+ d 
+---
+ 1
+(1 row)
+
+select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d.a[0];
+ a 
+---
+ 1
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1'];
      jsonb     
 ---------------
  {"a2": "aaa"}
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1;
+      a1       
+---------------
+ {"a2": "aaa"}
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2'];
  jsonb 
 -------
  "aaa"
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2;
+  a2   
+-------
+ "aaa"
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']['a3'];
  jsonb 
 -------
  
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2.a3;
+ a3 
+----
+ 
+(1 row)
+
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'];
          jsonb         
 -----------------------
  ["aaa", "bbb", "ccc"]
 (1 row)
 
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1;
+          b1           
+-----------------------
+ ["aaa", "bbb", "ccc"]
+(1 row)
+
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'][2];
  jsonb 
 -------
  "ccc"
 (1 row)
 
--- slices are not supported
-select ('{"a": 1}'::jsonb)['a':'b'];
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1[2];
+  b1   
+-------
+ "ccc"
+(1 row)
+
+select ('{"a": 1}'::jsonb)['a':'b']; -- fails
 ERROR:  jsonb subscript does not support slices
 LINE 1: select ('{"a": 1}'::jsonb)['a':'b'];
                                        ^
@@ -5831,3 +5944,299 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
  12345
 (1 row)
 
+-- dot notation
+CREATE TABLE test_jsonb_dot_notation AS
+SELECT '{"a": [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], "b": [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]}'::jsonb jb;
+SELECT (jb).a FROM test_jsonb_dot_notation;
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+SELECT (jb)."a" FROM test_jsonb_dot_notation; -- double quote should work
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+SELECT (jb).'a' FROM test_jsonb_dot_notation; -- single quote should not work
+ERROR:  syntax error at or near "'a'"
+LINE 1: SELECT (jb).'a' FROM test_jsonb_dot_notation;
+                    ^
+select (jb)[0].a from test_jsonb_dot_notation; -- returns same result as (jb).a
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+select (jb)[1].a from test_jsonb_dot_notation; -- returns NULL
+ a 
+---
+ 
+(1 row)
+
+SELECT (jb).b FROM test_jsonb_dot_notation;
+                         b                         
+---------------------------------------------------
+ [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]
+(1 row)
+
+SELECT (jb).c FROM test_jsonb_dot_notation;
+ c 
+---
+ 
+(1 row)
+
+SELECT (jb).a.b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT (jb).a[2].b FROM test_jsonb_dot_notation;
+  b  
+-----
+ "c"
+(1 row)
+
+SELECT (jb).a.x.y FROM test_jsonb_dot_notation;
+   y   
+-------
+ "yyy"
+(1 row)
+
+SELECT (jb).b.x.z FROM test_jsonb_dot_notation;
+   z   
+-------
+ "ZZZ"
+(1 row)
+
+SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
+ c 
+---
+ 
+(1 row)
+
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t; -- fails
+ERROR:  jsonb subscript does not support slices
+SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
+ERROR:  type jsonb is not composite
+-- explains should work
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+                  QUERY PLAN                  
+----------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation t
+   Output: (jb).a
+(2 rows)
+
+SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb).a[1]
+(2 rows)
+
+SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+ a 
+---
+ 2
+(1 row)
+
+-- views should work
+CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT (jb).a[3].x.y AS y
+   FROM test_jsonb_dot_notation
+-- mixed syntax
+DROP VIEW test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb['a'::text]).b
+(2 rows)
+
+SELECT (jb)['a'].b FROM test_jsonb_dot_notation; -- returns an array due to lax mode
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT (jb['a'::text]).b AS b
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: ((jb).a)['b'::text]
+(2 rows)
+
+SELECT (jb).a['b'] FROM test_jsonb_dot_notation; -- returns NULL because ['b'] looks for strict match in an object
+ a 
+---
+ 
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT ((jb).a)['b'::text] AS a
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+ a 
+---
+ 
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: ((jb).b.x)['z'::text]
+(2 rows)
+
+SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation; -- warnings
+   x   
+-------
+ "ZZZ"
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT ((jb).b.x)['z'::text] AS x
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+   x   
+-------
+ "ZZZ"
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: ((jb['b'::text]).x)['z'::text]
+(2 rows)
+
+SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;  -- warnings
+   x   
+-------
+ "ZZZ"
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT ((jb['b'::text]).x)['z'::text] AS x
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+   x   
+-------
+ "ZZZ"
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (((jb).b)['x'::text]).z
+(2 rows)
+
+SELECT (jb).b['x'].z FROM test_jsonb_dot_notation; -- returns NULL
+ z 
+---
+ 
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT (((jb).b)['x'::text]).z AS z
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+ z 
+---
+ 
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: ((jb).b)['x'::text]['z'::text]
+(2 rows)
+
+SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation; -- returns NULL
+ b 
+---
+ 
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT ((jb).b)['x'::text]['z'::text] AS b
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+ b 
+---
+ 
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb['b'::text]['x'::text]).z
+(2 rows)
+
+SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation; -- returns NULL
+ z 
+---
+ 
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT (jb['b'::text]['x'::text]).z AS z
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+ z 
+---
+ 
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+-- clean up
+DROP TABLE test_jsonb_dot_notation;
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 57c11acddfe..5fc53e1c9aa 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1304,33 +1304,51 @@ select jsonb_insert('{"a": {"b": "value"}}', '{a, b}', '"new_value"', true);
 
 -- jsonb subscript
 select ('123'::jsonb)['a'];
+select ('123'::jsonb).a;
 select ('123'::jsonb)[0];
 select ('123'::jsonb)[NULL];
+select ('123'::jsonb).NULL;
 select ('{"a": 1}'::jsonb)['a'];
+select ('{"a": 1}'::jsonb).a;
 select ('{"a": 1}'::jsonb)[0];
 select ('{"a": 1}'::jsonb)['not_exist'];
+select ('{"a": 1}'::jsonb)."not_exist";
 select ('{"a": 1}'::jsonb)[NULL];
 select ('[1, "2", null]'::jsonb)['a'];
+select ('[1, "2", null]'::jsonb).a;
 select ('[1, "2", null]'::jsonb)[0];
 select ('[1, "2", null]'::jsonb)['1'];
+select ('[1, "2", null]'::jsonb)."1";
 select ('[1, "2", null]'::jsonb)[1.0];
 select ('[1, "2", null]'::jsonb)[2];
 select ('[1, "2", null]'::jsonb)[3];
 select ('[1, "2", null]'::jsonb)[-2];
 select ('[1, "2", null]'::jsonb)[1]['a'];
+select ('[1, "2", null]'::jsonb)[1].a;
 select ('[1, "2", null]'::jsonb)[1][0];
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['b'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).b;
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d;
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'][1];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d[1];
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d']['a'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['a'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d.a;
+select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d['a'][0];
+select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d.a[0];
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1;
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2;
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']['a3'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2.a3;
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'];
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1;
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'][2];
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1[2];
 
--- slices are not supported
-select ('{"a": 1}'::jsonb)['a':'b'];
+select ('{"a": 1}'::jsonb)['a':'b']; -- fails
 select ('[1, "2", null]'::jsonb)[1:2];
 select ('[1, "2", null]'::jsonb)[:2];
 select ('[1, "2", null]'::jsonb)[1:];
@@ -1590,3 +1608,94 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::float8;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int2;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int4;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
+
+-- dot notation
+CREATE TABLE test_jsonb_dot_notation AS
+SELECT '{"a": [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], "b": [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]}'::jsonb jb;
+
+SELECT (jb).a FROM test_jsonb_dot_notation;
+SELECT (jb)."a" FROM test_jsonb_dot_notation; -- double quote should work
+SELECT (jb).'a' FROM test_jsonb_dot_notation; -- single quote should not work
+select (jb)[0].a from test_jsonb_dot_notation; -- returns same result as (jb).a
+select (jb)[1].a from test_jsonb_dot_notation; -- returns NULL
+SELECT (jb).b FROM test_jsonb_dot_notation;
+SELECT (jb).c FROM test_jsonb_dot_notation;
+SELECT (jb).a.b FROM test_jsonb_dot_notation;
+SELECT (jb).a[2].b FROM test_jsonb_dot_notation;
+SELECT (jb).a.x.y FROM test_jsonb_dot_notation;
+SELECT (jb).b.x.z FROM test_jsonb_dot_notation;
+SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t; -- fails
+SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
+
+-- explains should work
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+
+-- views should work
+CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+
+-- mixed syntax
+DROP VIEW test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
+SELECT (jb)['a'].b FROM test_jsonb_dot_notation; -- returns an array due to lax mode
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+SELECT (jb).a['b'] FROM test_jsonb_dot_notation; -- returns NULL because ['b'] looks for strict match in an object
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation; -- warnings
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;  -- warnings
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+SELECT (jb).b['x'].z FROM test_jsonb_dot_notation; -- returns NULL
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation; -- returns NULL
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
+SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation; -- returns NULL
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+-- clean up
+DROP TABLE test_jsonb_dot_notation;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index a13e8162890..cc64642f399 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -805,6 +805,7 @@ FdwRoutine
 FetchDirection
 FetchDirectionKeywords
 FetchStmt
+FieldAccessorExpr
 FieldSelect
 FieldStore
 File
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v16-0003-Export-jsonPathFromParseResult.patch (2.7K, ../../CAK98qZ1xhjKfo_C-s_bnpkPJ4xkdqEwdRYq4pbFKM8fKqcfKZw@mail.gmail.com/8-v16-0003-Export-jsonPathFromParseResult.patch)
  download | inline diff:
From 7d6ed7066a7c621adaeb5338aa3f0ef1beb5aef8 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v16 3/7] Export jsonPathFromParseResult()

This is a preparation step for a future commit that will reuse the
aforementioned function.

Authored-by: Nikita Glukhov <[email protected]>
Reviewed-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Jian He <[email protected]>
---
 src/backend/utils/adt/jsonpath.c | 19 +++++++++++++++----
 src/include/utils/jsonpath.h     |  4 ++++
 2 files changed, 19 insertions(+), 4 deletions(-)

diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index 762f7e8a09d..976aee84cf2 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -166,15 +166,13 @@ jsonpath_send(PG_FUNCTION_ARGS)
  * Converts C-string to a jsonpath value.
  *
  * Uses jsonpath parser to turn string into an AST, then
- * flattenJsonPathParseItem() does second pass turning AST into binary
+ * jsonPathFromParseResult() does second pass turning AST into binary
  * representation of jsonpath.
  */
 static Datum
 jsonPathFromCstring(char *in, int len, struct Node *escontext)
 {
 	JsonPathParseResult *jsonpath = parsejsonpath(in, len, escontext);
-	JsonPath   *res;
-	StringInfoData buf;
 
 	if (SOFT_ERROR_OCCURRED(escontext))
 		return (Datum) 0;
@@ -185,8 +183,21 @@ jsonPathFromCstring(char *in, int len, struct Node *escontext)
 				 errmsg("invalid input syntax for type %s: \"%s\"", "jsonpath",
 						in)));
 
+	return jsonPathFromParseResult(jsonpath, 4 * len, escontext);
+}
+
+/*
+ * Converts jsonpath Abstract Syntax Tree (AST) into jsonpath value in binary.
+ */
+Datum
+jsonPathFromParseResult(JsonPathParseResult *jsonpath, int estimated_len,
+						struct Node *escontext)
+{
+	JsonPath   *res;
+	StringInfoData buf;
+
 	initStringInfo(&buf);
-	enlargeStringInfo(&buf, 4 * len /* estimation */ );
+	enlargeStringInfo(&buf, estimated_len);
 
 	appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
 
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index 23a76d233e9..e05941623e7 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -281,6 +281,10 @@ extern JsonPathParseResult *parsejsonpath(const char *str, int len,
 extern bool jspConvertRegexFlags(uint32 xflags, int *result,
 								 struct Node *escontext);
 
+extern Datum jsonPathFromParseResult(JsonPathParseResult *jsonpath,
+									 int estimated_len,
+									 struct Node *escontext);
+
 /*
  * Struct for details about external variables passed into jsonpath executor
  */
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v16-0002-Allow-Generic-Type-Subscripting-to-Accept-Dot-No.patch (18.0K, ../../CAK98qZ1xhjKfo_C-s_bnpkPJ4xkdqEwdRYq4pbFKM8fKqcfKZw@mail.gmail.com/9-v16-0002-Allow-Generic-Type-Subscripting-to-Accept-Dot-No.patch)
  download | inline diff:
From cc7bb2562a02cee163a308f4707f14f0ca4bfde3 Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Fri, 5 Sep 2025 14:17:37 -0700
Subject: [PATCH v16 2/7] Allow Generic Type Subscripting to Accept Dot
 Notation (.) as Input
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This change extends generic type subscripting to recognize dot
notation (.) in addition to bracket notation ([]). While this does not
yet provide full support for dot notation, it enables subscripting
containers to process it in the future.

For now, container-specific transform functions only handle
subscripting indices and stop processing when encountering dot
notation. It is up to individual containers to decide how to transform
dot notation in subsequent updates.

This change also updates the SubscriptTransform() API to remove the
"bool isSlice" argument. Each container’s transform function now
determines slice-ness itself, since it may only process a prefix of
the indirection list. For example, array_subscript_transform() stops
at dot notation, so "isSlice" should not depend on slice specifiers
that appear afterward.

Authored-by: Nikita Glukhov <[email protected]>
Authored-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Jian He <[email protected]>
Reviewed-by: Chao Li <[email protected]>
---
 contrib/hstore/hstore_subs.c         |  9 ++--
 src/backend/parser/parse_expr.c      | 68 ++++++++++++++++++----------
 src/backend/parser/parse_node.c      | 57 ++++++++++++++---------
 src/backend/parser/parse_target.c    |  3 +-
 src/backend/utils/adt/arraysubs.c    | 40 ++++++++++++++--
 src/backend/utils/adt/jsonbsubs.c    | 16 +++++--
 src/include/nodes/subscripting.h     |  3 +-
 src/include/parser/parse_node.h      |  3 +-
 src/test/regress/expected/arrays.out | 26 +++++++++--
 src/test/regress/sql/arrays.sql      |  7 ++-
 10 files changed, 162 insertions(+), 70 deletions(-)

diff --git a/contrib/hstore/hstore_subs.c b/contrib/hstore/hstore_subs.c
index 1b29543ab67..5492049af3b 100644
--- a/contrib/hstore/hstore_subs.c
+++ b/contrib/hstore/hstore_subs.c
@@ -42,14 +42,16 @@ static void
 hstore_subscript_transform(SubscriptingRef *sbsref,
 						   List **indirection,
 						   ParseState *pstate,
-						   bool isSlice,
 						   bool isAssignment)
 {
 	A_Indices  *ai;
 	Node	   *subexpr;
 
+	Assert(*indirection != NIL);
+	ai = linitial_node(A_Indices, *indirection);
+
 	/* We support only single-subscript, non-slice cases */
-	if (isSlice || list_length(*indirection) != 1)
+	if (ai->is_slice || list_length(*indirection) != 1)
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("hstore allows only one subscript"),
@@ -57,8 +59,7 @@ hstore_subscript_transform(SubscriptingRef *sbsref,
 									exprLocation((Node *) *indirection))));
 
 	/* Transform the subscript expression to type text */
-	ai = linitial_node(A_Indices, *indirection);
-	Assert(ai->uidx != NULL && ai->lidx == NULL && !ai->is_slice);
+	Assert(ai->uidx != NULL && ai->lidx == NULL);
 
 	subexpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
 	/* If it's not text already, try to coerce */
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 6e8fd42c612..f8a0617f823 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -441,38 +441,59 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 	ListCell   *i;
 
 	/*
-	 * We have to split any field-selection operations apart from
-	 * subscripting.  Adjacent A_Indices nodes have to be treated as a single
+	 * Combine field names and subscripts into a single indirection list, as
+	 * some subscripting containers, such as jsonb, support field access using
+	 * dot notation. Adjacent A_Indices nodes have to be treated as a single
 	 * multidimensional subscript operation.
 	 */
 	foreach(i, ind->indirection)
 	{
 		Node	   *n = lfirst(i);
 
-		if (IsA(n, A_Indices))
+		if (IsA(n, A_Indices) || IsA(n, String))
 			subscripts = lappend(subscripts, n);
-		else if (IsA(n, A_Star))
+		else
 		{
+			Assert(IsA(n, A_Star));
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("row expansion via \"*\" is not supported here"),
 					 parser_errposition(pstate, location)));
 		}
-		else
+	}
+
+	while (subscripts)
+	{
+		/* try processing container subscripts first */
+		Node	   *newresult = (Node *)
+			transformContainerSubscripts(pstate,
+										 result,
+										 exprType(result),
+										 exprTypmod(result),
+										 &subscripts,
+										 false,
+										 true);
+
+		if (!newresult)
 		{
-			Node	   *newresult;
+			/*
+			 * generic subscripting failed; falling back to field selection
+			 * for a composite type.
+			 */
+			Node	   *n;
+
+			Assert(subscripts);
 
-			Assert(IsA(n, String));
+			n = linitial(subscripts);
 
-			/* process subscripts before this field selection */
-			while (subscripts)
-				result = (Node *) transformContainerSubscripts(pstate,
-															   result,
-															   exprType(result),
-															   exprTypmod(result),
-															   &subscripts,
-															   false);
+			if (!IsA(n, String))
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("cannot subscript type %s because it does not support subscripting",
+								format_type_be(exprType(result))),
+						 parser_errposition(pstate, exprLocation(result))));
 
+			/* try to find function for field selection */
 			newresult = ParseFuncOrColumn(pstate,
 										  list_make1(n),
 										  list_make1(result),
@@ -480,19 +501,16 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 										  NULL,
 										  false,
 										  location);
-			if (newresult == NULL)
+
+			if (!newresult)
 				unknown_attribute(pstate, result, strVal(n), location);
-			result = newresult;
+
+			/* consume field select */
+			subscripts = list_delete_first(subscripts);
 		}
+
+		result = newresult;
 	}
-	/* process trailing subscripts, if any */
-	while (subscripts)
-		result = (Node *) transformContainerSubscripts(pstate,
-													   result,
-													   exprType(result),
-													   exprTypmod(result),
-													   &subscripts,
-													   false);
 
 	return result;
 }
diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c
index f05baa50a15..d7b23688b9b 100644
--- a/src/backend/parser/parse_node.c
+++ b/src/backend/parser/parse_node.c
@@ -238,6 +238,8 @@ transformContainerType(Oid *containerType, int32 *containerTypmod)
  * containerTypMod	typmod for the container
  * indirection		Untransformed list of subscripts (must not be NIL)
  * isAssignment		True if this will become a container assignment.
+ * noError			True for return NULL with no error, if the container type
+ * 					is not subscriptable.
  */
 SubscriptingRef *
 transformContainerSubscripts(ParseState *pstate,
@@ -245,13 +247,13 @@ transformContainerSubscripts(ParseState *pstate,
 							 Oid containerType,
 							 int32 containerTypMod,
 							 List **indirection,
-							 bool isAssignment)
+							 bool isAssignment,
+							 bool noError)
 {
 	SubscriptingRef *sbsref;
 	const SubscriptRoutines *sbsroutines;
 	Oid			elementType;
-	bool		isSlice = false;
-	ListCell   *idx;
+	int			indirection_length = list_length(*indirection);
 
 	/*
 	 * Determine the actual container type, smashing any domain.  In the
@@ -267,28 +269,15 @@ transformContainerSubscripts(ParseState *pstate,
 	 */
 	sbsroutines = getSubscriptingRoutines(containerType, &elementType);
 	if (!sbsroutines)
+	{
+		if (noError)
+			return NULL;
+
 		ereport(ERROR,
 				(errcode(ERRCODE_DATATYPE_MISMATCH),
 				 errmsg("cannot subscript type %s because it does not support subscripting",
 						format_type_be(containerType)),
 				 parser_errposition(pstate, exprLocation(containerBase))));
-
-	/*
-	 * Detect whether any of the indirection items are slice specifiers.
-	 *
-	 * A list containing only simple subscripts refers to a single container
-	 * element.  If any of the items are slice specifiers (lower:upper), then
-	 * the subscript expression means a container slice operation.
-	 */
-	foreach(idx, *indirection)
-	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
-
-		if (ai->is_slice)
-		{
-			isSlice = true;
-			break;
-		}
 	}
 
 	/*
@@ -310,7 +299,33 @@ transformContainerSubscripts(ParseState *pstate,
 	 * determine the subscripting result type.
 	 */
 	sbsroutines->transform(sbsref, indirection, pstate,
-						   isSlice, isAssignment);
+						   isAssignment);
+
+	/*
+	 * Error out, if datatype failed to consume any indirection elements.
+	 */
+	if (list_length(*indirection) == indirection_length)
+	{
+		Node	   *ind = linitial(*indirection);
+
+		if (noError)
+			return NULL;
+
+		if (IsA(ind, String))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("type %s does not support dot notation",
+							format_type_be(containerType)),
+					 parser_errposition(pstate, exprLocation(containerBase))));
+		else if (IsA(ind, A_Indices))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("type %s does not support array subscripting",
+							format_type_be(containerType)),
+					 parser_errposition(pstate, exprLocation(containerBase))));
+		else
+			elog(ERROR, "invalid indirection operation: %d", nodeTag(ind));
+	}
 
 	/*
 	 * Verify we got a valid type (this defends, for example, against someone
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index a097736229a..b89736ff1ea 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -936,7 +936,8 @@ transformAssignmentSubscripts(ParseState *pstate,
 										  containerType,
 										  containerTypMod,
 										  &subscripts,
-										  true);
+										  true,
+										  false);
 
 	typeNeeded = sbsref->refrestype;
 	typmodNeeded = sbsref->reftypmod;
diff --git a/src/backend/utils/adt/arraysubs.c b/src/backend/utils/adt/arraysubs.c
index 234c2c278c1..378b6bf16cb 100644
--- a/src/backend/utils/adt/arraysubs.c
+++ b/src/backend/utils/adt/arraysubs.c
@@ -56,12 +56,38 @@ static void
 array_subscript_transform(SubscriptingRef *sbsref,
 						  List **indirection,
 						  ParseState *pstate,
-						  bool isSlice,
 						  bool isAssignment)
 {
 	List	   *upperIndexpr = NIL;
 	List	   *lowerIndexpr = NIL;
 	ListCell   *idx;
+	int			ndim;
+	bool		isSlice = false;
+
+	/*
+	 * Detect whether any of the indirection items are slice specifiers.
+	 *
+	 * A list containing only simple subscripts refers to a single container
+	 * element.  If any of the items are slice specifiers (lower:upper), then
+	 * the subscript expression means a container slice operation.
+	 */
+	foreach(idx, *indirection)
+	{
+		Node	   *ai = lfirst(idx);
+
+		/*
+		 * We should not inspect slice specifiers beyond an indirection node
+		 * type that we don't support.
+		 */
+		if (!IsA(ai, A_Indices))
+			break;
+
+		if (castNode(A_Indices, ai)->is_slice)
+		{
+			isSlice = true;
+			break;
+		}
+	}
 
 	/*
 	 * Transform the subscript expressions, and separate upper and lower
@@ -73,9 +99,14 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	 */
 	foreach(idx, *indirection)
 	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
+		A_Indices  *ai;
 		Node	   *subexpr;
 
+		if (!IsA(lfirst(idx), A_Indices))
+			break;
+
+		ai = lfirst_node(A_Indices, idx);
+
 		if (isSlice)
 		{
 			if (ai->lidx)
@@ -145,14 +176,15 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->reflowerindexpr = lowerIndexpr;
 
 	/* Verify subscript list lengths are within implementation limit */
-	if (list_length(upperIndexpr) > MAXDIM)
+	ndim = list_length(upperIndexpr);
+	if (ndim > MAXDIM)
 		ereport(ERROR,
 				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 				 errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
 						list_length(upperIndexpr), MAXDIM)));
 	/* We need not check lowerIndexpr separately */
 
-	*indirection = NIL;
+	*indirection = list_delete_first_n(*indirection, ndim);
 
 	/*
 	 * Determine the result type of the subscripting operation.  It's the same
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index 8ad6aa1ad4f..ec8e82dc629 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -43,7 +43,6 @@ static void
 jsonb_subscript_transform(SubscriptingRef *sbsref,
 						  List **indirection,
 						  ParseState *pstate,
-						  bool isSlice,
 						  bool isAssignment)
 {
 	List	   *upperIndexpr = NIL;
@@ -55,10 +54,15 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	 */
 	foreach(idx, *indirection)
 	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
+		A_Indices  *ai;
 		Node	   *subExpr;
 
-		if (isSlice)
+		if (!IsA(lfirst(idx), A_Indices))
+			break;
+
+		ai = lfirst_node(A_Indices, idx);
+
+		if (ai->is_slice)
 		{
 			Node	   *expr = ai->uidx ? ai->uidx : ai->lidx;
 
@@ -142,7 +146,7 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 			 * Slice with omitted upper bound. Should not happen as we already
 			 * errored out on slice earlier, but handle this just in case.
 			 */
-			Assert(isSlice && ai->is_slice);
+			Assert(ai->is_slice);
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
 					 errmsg("jsonb subscript does not support slices"),
@@ -160,7 +164,9 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->refrestype = JSONBOID;
 	sbsref->reftypmod = -1;
 
-	*indirection = NIL;
+	/* Remove processed elements */
+	if (upperIndexpr)
+		*indirection = list_delete_first_n(*indirection, list_length(upperIndexpr));
 }
 
 /*
diff --git a/src/include/nodes/subscripting.h b/src/include/nodes/subscripting.h
index df988a85fad..14ef414a180 100644
--- a/src/include/nodes/subscripting.h
+++ b/src/include/nodes/subscripting.h
@@ -68,7 +68,7 @@ struct SubscriptExecSteps;
  * same length as refupperindexpr for a slice operation.  Insert NULLs
  * (that is, an empty parse tree, not a null Const node) for any omitted
  * subscripts in a slice operation.  (Of course, if the transform method
- * does not care to support slicing, it can just throw an error if isSlice.)
+ * does not care to support slicing, it can just throw an error.)
  * See array_subscript_transform() for sample code.
  *
  * The transform method receives a pointer to a list of raw indirections.
@@ -100,7 +100,6 @@ struct SubscriptExecSteps;
 typedef void (*SubscriptTransform) (SubscriptingRef *sbsref,
 									List **indirection,
 									struct ParseState *pstate,
-									bool isSlice,
 									bool isAssignment);
 
 /*
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 58a4b9df157..5cc3ce58c30 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -362,7 +362,8 @@ extern SubscriptingRef *transformContainerSubscripts(ParseState *pstate,
 													 Oid containerType,
 													 int32 containerTypMod,
 													 List **indirection,
-													 bool isAssignment);
+													 bool isAssignment,
+													 bool noError);
 extern Const *make_const(ParseState *pstate, A_Const *aconst);
 
 #endif							/* PARSE_NODE_H */
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index b815473f414..78dd9e71bf3 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -1782,17 +1782,17 @@ SELECT max(f1), min(f1), max(f2), min(f2), max(f3), min(f3) FROM arraggtest;
 (1 row)
 
 -- A few simple tests for arrays of composite types
-create type comptype as (f1 int, f2 text);
+create type comptype as (f1 int, f2 text, f3 int[]);
 create table comptable (c1 comptype, c2 comptype[]);
 -- XXX would like to not have to specify row() construct types here ...
 insert into comptable
-  values (row(1,'foo'), array[row(2,'bar')::comptype, row(3,'baz')::comptype]);
+  values (row(1,'foo',array[10,20]), array[row(2,'bar',array[30,40])::comptype, row(3,'baz',array[50,60])::comptype]);
 -- check that implicitly named array type _comptype isn't a problem
 create type _comptype as enum('fooey');
 select * from comptable;
-   c1    |          c2           
----------+-----------------------
- (1,foo) | {"(2,bar)","(3,baz)"}
+        c1         |                      c2                       
+-------------------+-----------------------------------------------
+ (1,foo,"{10,20}") | {"(2,bar,\"{30,40}\")","(3,baz,\"{50,60}\")"}
 (1 row)
 
 select c2[2].f2 from comptable;
@@ -1801,6 +1801,22 @@ select c2[2].f2 from comptable;
  baz
 (1 row)
 
+select c2[2].f3 from comptable;
+   f3    
+---------
+ {50,60}
+(1 row)
+
+select c2[2].f3[1:2] from comptable;
+   f3    
+---------
+ {50,60}
+(1 row)
+
+select c2[1:2].f3[1:2] from comptable;
+ERROR:  column notation .f3 applied to type comptype[], which is not a composite type
+LINE 1: select c2[1:2].f3[1:2] from comptable;
+               ^
 drop type _comptype;
 drop table comptable;
 drop type comptype;
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 47d62c1d38d..450389831a0 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -555,19 +555,22 @@ SELECT max(f1), min(f1), max(f2), min(f2), max(f3), min(f3) FROM arraggtest;
 
 -- A few simple tests for arrays of composite types
 
-create type comptype as (f1 int, f2 text);
+create type comptype as (f1 int, f2 text, f3 int[]);
 
 create table comptable (c1 comptype, c2 comptype[]);
 
 -- XXX would like to not have to specify row() construct types here ...
 insert into comptable
-  values (row(1,'foo'), array[row(2,'bar')::comptype, row(3,'baz')::comptype]);
+  values (row(1,'foo',array[10,20]), array[row(2,'bar',array[30,40])::comptype, row(3,'baz',array[50,60])::comptype]);
 
 -- check that implicitly named array type _comptype isn't a problem
 create type _comptype as enum('fooey');
 
 select * from comptable;
 select c2[2].f2 from comptable;
+select c2[2].f3 from comptable;
+select c2[2].f3[1:2] from comptable;
+select c2[1:2].f3[1:2] from comptable;
 
 drop type _comptype;
 drop table comptable;
-- 
2.39.5 (Apple Git-154)



^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-06-24 13:29                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-07-09 08:01                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-07-10 08:53                                 ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-21 04:53                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-22 08:19                                     ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-22 19:33                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-25 08:09                                         ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-26 03:52                                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-29 07:22                                             ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-02 02:59                                               ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-03 02:16                                                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-09-03 03:56                                                   ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-09 15:14                                                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
@ 2025-09-10 03:52                                                       ` Chao Li <[email protected]>
  2025-09-10 16:40                                                         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  0 siblings, 1 reply; 67+ messages in thread

From: Chao Li @ 2025-09-10 03:52 UTC (permalink / raw)
  To: Alexandra Wang <[email protected]>; +Cc: jian he <[email protected]>; Nikita Malakhov <[email protected]>; Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>


> <v16-0007-Implement-jsonb-wildcard-member-accessor.patch><v16-0001-Allow-transformation-of-only-a-sublist-of-subscr.patch><v16-0004-Extract-coerce_jsonpath_subscript.patch><v16-0006-Implement-Jsonb-subscripting-with-slicing.patch><v16-0005-Implement-read-only-dot-notation-for-jsonb.patch><v16-0003-Export-jsonPathFromParseResult.patch><v16-0002-Allow-Generic-Type-Subscripting-to-Accept-Dot-No.patch>


A few more comment for v16.

1 - 0002
```
--- a/contrib/hstore/hstore_subs.c
+++ b/contrib/hstore/hstore_subs.c

-	if (isSlice || list_length(*indirection) != 1)
+	if (ai->is_slice || list_length(*indirection) != 1)
```

We should put list_length() check before ai->is_slace. Because when indirection length is greater than 1, it take a single check; other it may need to check the both conditions.

2 - 0002 - also in hstore_subs.c 
```
   *indirection = NIL;
```

We should do “list_delete_first_n(indirection, 1)”.

3 - 0003
```
+Datum
+jsonPathFromParseResult(JsonPathParseResult *jsonpath, int estimated_len,
+						struct Node *escontext)
+{
```

`jsonpath` is not changed in this function, so it should be defined as a const: “const JsonPathParseResult *jsonpath”. 

4 - 0004
```
+		Oid			targets[2] = {INT4OID, TEXTOID};
+
+		/*
+		 * Jsonb can handle multiple subscript types, but cases when a
+		 * subscript could be coerced to multiple target types must be
+		 * avoided, similar to overloaded functions. It could be possibly
+		 * extend with jsonpath in the future.
+		 */
+		for (int i = 0; i < 2; i++)
```

This is a nit optimization. We can do:

Oid targets[] = {INT4OID, TEXTOID};
For (int I = 0; I < sizeof(targets) / sizeof(targets[0]); I ++) 

This way lets the compiler to decide length of “targets”. So that avoids hard code “2” in “for”. If we need to add more elements to “targets”, we can just add it without updating “for”.

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/






^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-06-24 13:29                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-07-09 08:01                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-07-10 08:53                                 ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-21 04:53                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-22 08:19                                     ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-22 19:33                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-25 08:09                                         ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-26 03:52                                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-29 07:22                                             ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-02 02:59                                               ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-03 02:16                                                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-09-03 03:56                                                   ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-09 15:14                                                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-09-10 03:52                                                       ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
@ 2025-09-10 16:40                                                         ` Alexandra Wang <[email protected]>
  0 siblings, 0 replies; 67+ messages in thread

From: Alexandra Wang @ 2025-09-10 16:40 UTC (permalink / raw)
  To: Chao Li <[email protected]>; +Cc: jian he <[email protected]>; Nikita Malakhov <[email protected]>; Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>

Hi Chao,

On Tue, Sep 9, 2025 at 8:52 PM Chao Li <[email protected]> wrote:

>
> <v16-0007-Implement-jsonb-wildcard-member-accessor.patch>
> <v16-0001-Allow-transformation-of-only-a-sublist-of-subscr.patch>
> <v16-0004-Extract-coerce_jsonpath_subscript.patch>
> <v16-0006-Implement-Jsonb-subscripting-with-slicing.patch>
> <v16-0005-Implement-read-only-dot-notation-for-jsonb.patch>
> <v16-0003-Export-jsonPathFromParseResult.patch>
> <v16-0002-Allow-Generic-Type-Subscripting-to-Accept-Dot-No.patch>
>
>
> A few more comment for v16.
>
> 1 - 0002
> ```
> --- a/contrib/hstore/hstore_subs.c
> +++ b/contrib/hstore/hstore_subs.c
>
> - if (isSlice || list_length(*indirection) != 1)
> + if (ai->is_slice || list_length(*indirection) != 1)
> ```
>
> We should put list_length() check before ai->is_slace. Because when
> indirection length is greater than 1, it take a single check; other it may
> need to check the both conditions.
>
> 2 - 0002 - also in hstore_subs.c
> ```
>    *indirection = NIL;
> ```
>
> We should do “list_delete_first_n(indirection, 1)”.
>
> 3 - 0003
> ```
> +Datum
> +jsonPathFromParseResult(JsonPathParseResult *jsonpath, int estimated_len,
> + struct Node *escontext)
> +{
> ```
>
> `jsonpath` is not changed in this function, so it should be defined as a
> const: “const JsonPathParseResult *jsonpath”.
>
> 4 - 0004
> ```
> + Oid targets[2] = {INT4OID, TEXTOID};
> +
> + /*
> + * Jsonb can handle multiple subscript types, but cases when a
> + * subscript could be coerced to multiple target types must be
> + * avoided, similar to overloaded functions. It could be possibly
> + * extend with jsonpath in the future.
> + */
> + for (int i = 0; i < 2; i++)
> ```
>
> This is a nit optimization. We can do:
>
> Oid targets[] = {INT4OID, TEXTOID};
> For (int I = 0; I < sizeof(targets) / sizeof(targets[0]); I ++)
>
> This way lets the compiler to decide length of “targets”. So that avoids
> hard code “2” in “for”. If we need to add more elements to “targets”, we
> can just add it without updating “for”.
>

Thanks for the feedback! Here’s v17. I’ve addressed 1 - 3 exactly as
you suggested. For 4, I made the targets array const and used the
lengthof macro to determine the array length.

Best,
Alex


Attachments:

  [application/octet-stream] v17-0007-Implement-jsonb-wildcard-member-accessor.patch (31.5K, ../../CAK98qZ1FK1ZhH_uyqg02_n7Q5gnPAooC8zUdFAx3_XQiBdXo6Q@mail.gmail.com/3-v17-0007-Implement-jsonb-wildcard-member-accessor.patch)
  download | inline diff:
From ef0292c845114bf30139f8c138f3f83ebe2d765b Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v17 7/7] Implement jsonb wildcard member accessor

This commit adds support for wildcard member access in jsonb, as
specified by the JSON simplified accessor syntax in SQL:2023.

Co-authored-by: Nikita Glukhov <[email protected]>
Co-authored-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Mark Dilger <[email protected]>
Reviewed-by: Jian He <[email protected]>
Reviewed-by: Vik Fearing <[email protected]>
Reviewed-by: Nikita Malakhov <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Jelte Fennema-Nio <[email protected]>
Tested-by: Jelte Fennema-Nio <[email protected]>
---
 src/backend/nodes/nodeFuncs.c                 |   8 +
 src/backend/parser/gram.y                     |   2 +
 src/backend/parser/parse_expr.c               |  39 +--
 src/backend/parser/parse_target.c             |  67 ++++--
 src/backend/utils/adt/jsonbsubs.c             |  12 +-
 src/backend/utils/adt/ruleutils.c             |   6 +-
 src/include/nodes/primnodes.h                 |  12 +
 src/include/parser/parse_expr.h               |   3 +
 .../ecpg/test/expected/sql-sqljson.c          |  18 +-
 .../ecpg/test/expected/sql-sqljson.stderr     |  23 +-
 .../ecpg/test/expected/sql-sqljson.stdout     |   2 +
 src/interfaces/ecpg/test/sql/sqljson.pgc      |   5 +-
 src/test/regress/expected/jsonb.out           | 222 +++++++++++++++++-
 src/test/regress/sql/jsonb.sql                |  42 +++-
 14 files changed, 406 insertions(+), 55 deletions(-)

diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index d1bd575d9bd..5f3038a1c26 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -291,6 +291,9 @@ exprType(const Node *expr)
 			 */
 			type = TEXTOID;
 			break;
+		case T_Star:
+			type = UNKNOWNOID;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			type = InvalidOid;	/* keep compiler quiet */
@@ -1156,6 +1159,9 @@ exprSetCollation(Node *expr, Oid collation)
 		case T_FieldAccessorExpr:
 			((FieldAccessorExpr *) expr)->faecollid = collation;
 			break;
+		case T_Star:
+			Assert(!OidIsValid(collation));
+			break;
 		case T_SubscriptingRef:
 			((SubscriptingRef *) expr)->refcollid = collation;
 			break;
@@ -2140,6 +2146,7 @@ expression_tree_walker_impl(Node *node,
 		case T_CTESearchClause:
 		case T_MergeSupportFunc:
 		case T_FieldAccessorExpr:
+		case T_Star:
 			/* primitive node types with no expression subnodes */
 			break;
 		case T_WithCheckOption:
@@ -3020,6 +3027,7 @@ expression_tree_mutator_impl(Node *node,
 		case T_CTESearchClause:
 		case T_MergeSupportFunc:
 		case T_FieldAccessorExpr:
+		case T_Star:
 			return copyObject(node);
 		case T_WithCheckOption:
 			{
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9fd48acb1f8..c86ab6fd512 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -18980,6 +18980,7 @@ check_func_name(List *names, core_yyscan_t yyscanner)
 static List *
 check_indirection(List *indirection, core_yyscan_t yyscanner)
 {
+#if 0
 	ListCell   *l;
 
 	foreach(l, indirection)
@@ -18990,6 +18991,7 @@ check_indirection(List *indirection, core_yyscan_t yyscanner)
 				parser_yyerror("improper use of \"*\"");
 		}
 	}
+#endif
 	return indirection;
 }
 
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index f8a0617f823..38ac6503a22 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -73,7 +73,6 @@ static Node *transformColumnRef(ParseState *pstate, ColumnRef *cref);
 static Node *transformWholeRowRef(ParseState *pstate,
 								  ParseNamespaceItem *nsitem,
 								  int sublevels_up, int location);
-static Node *transformIndirection(ParseState *pstate, A_Indirection *ind);
 static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
 static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
 static Node *transformJsonObjectConstructor(ParseState *pstate,
@@ -157,7 +156,7 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 			break;
 
 		case T_A_Indirection:
-			result = transformIndirection(pstate, (A_Indirection *) expr);
+			result = transformIndirection(pstate, (A_Indirection *) expr, NULL);
 			break;
 
 		case T_A_ArrayExpr:
@@ -431,8 +430,9 @@ unknown_attribute(ParseState *pstate, Node *relref, const char *attname,
 	}
 }
 
-static Node *
-transformIndirection(ParseState *pstate, A_Indirection *ind)
+Node *
+transformIndirection(ParseState *pstate, A_Indirection *ind,
+					 bool *trailing_star_expansion)
 {
 	Node	   *last_srf = pstate->p_last_srf;
 	Node	   *result = transformExprRecurse(pstate, ind->arg);
@@ -450,16 +450,8 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 	{
 		Node	   *n = lfirst(i);
 
-		if (IsA(n, A_Indices) || IsA(n, String))
-			subscripts = lappend(subscripts, n);
-		else
-		{
-			Assert(IsA(n, A_Star));
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("row expansion via \"*\" is not supported here"),
-					 parser_errposition(pstate, location)));
-		}
+		Assert (IsA(n, A_Indices) || IsA(n, String) || IsA(n, A_Star));
+		subscripts = lappend(subscripts, n);
 	}
 
 	while (subscripts)
@@ -486,7 +478,21 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 
 			n = linitial(subscripts);
 
-			if (!IsA(n, String))
+			if (IsA(n, A_Star))
+			{
+				/* Success, if trailing star expansion is allowed */
+				if (trailing_star_expansion && list_length(subscripts) == 1)
+				{
+					*trailing_star_expansion = true;
+					return result;
+				}
+
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("row expansion via \"*\" is not supported here"),
+						 parser_errposition(pstate, location)));
+			}
+			else if (!IsA(n, String))
 				ereport(ERROR,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
 						 errmsg("cannot subscript type %s because it does not support subscripting",
@@ -512,6 +518,9 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		result = newresult;
 	}
 
+	if (trailing_star_expansion)
+		*trailing_star_expansion = false;
+
 	return result;
 }
 
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index b89736ff1ea..85c05c7434c 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -47,7 +47,7 @@ static Node *transformAssignmentSubscripts(ParseState *pstate,
 static List *ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
 								 bool make_target_entry);
 static List *ExpandAllTables(ParseState *pstate, int location);
-static List *ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
+static Node *ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
 								   bool make_target_entry, ParseExprKind exprKind);
 static List *ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 							   int sublevels_up, int location,
@@ -133,6 +133,7 @@ transformTargetList(ParseState *pstate, List *targetlist,
 	foreach(o_target, targetlist)
 	{
 		ResTarget  *res = (ResTarget *) lfirst(o_target);
+		Node	   *transformed = NULL;
 
 		/*
 		 * Check for "something.*".  Depending on the complexity of the
@@ -161,13 +162,19 @@ transformTargetList(ParseState *pstate, List *targetlist,
 
 				if (IsA(llast(ind->indirection), A_Star))
 				{
-					/* It is something.*, expand into multiple items */
-					p_target = list_concat(p_target,
-										   ExpandIndirectionStar(pstate,
-																 ind,
-																 true,
-																 exprKind));
-					continue;
+					Node	   *columns = ExpandIndirectionStar(pstate,
+																ind,
+																true,
+																exprKind);
+
+					if (IsA(columns, List))
+					{
+						/* It is something.*, expand into multiple items */
+						p_target = list_concat(p_target, (List *) columns);
+						continue;
+					}
+
+					transformed = (Node *) columns;
 				}
 			}
 		}
@@ -179,7 +186,7 @@ transformTargetList(ParseState *pstate, List *targetlist,
 		p_target = lappend(p_target,
 						   transformTargetEntry(pstate,
 												res->val,
-												NULL,
+												transformed,
 												exprKind,
 												res->name,
 												false));
@@ -250,10 +257,15 @@ transformExpressionList(ParseState *pstate, List *exprlist,
 
 			if (IsA(llast(ind->indirection), A_Star))
 			{
-				/* It is something.*, expand into multiple items */
-				result = list_concat(result,
-									 ExpandIndirectionStar(pstate, ind,
-														   false, exprKind));
+				Node	   *cols = ExpandIndirectionStar(pstate, ind,
+														 false, exprKind);
+
+				if (!cols || IsA(cols, List))
+					/* It is something.*, expand into multiple items */
+					result = list_concat(result, (List *) cols);
+				else
+					result = lappend(result, cols);
+
 				continue;
 			}
 		}
@@ -1344,22 +1356,30 @@ ExpandAllTables(ParseState *pstate, int location)
  * For robustness, we use a separate "make_target_entry" flag to control
  * this rather than relying on exprKind.
  */
-static List *
+static Node *
 ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
 					  bool make_target_entry, ParseExprKind exprKind)
 {
 	Node	   *expr;
+	ParseExprKind sv_expr_kind;
+	bool		trailing_star_expansion = false;
+
+	/* Save and restore identity of expression type we're parsing */
+	Assert(exprKind != EXPR_KIND_NONE);
+	sv_expr_kind = pstate->p_expr_kind;
+	pstate->p_expr_kind = exprKind;
 
 	/* Strip off the '*' to create a reference to the rowtype object */
-	ind = copyObject(ind);
-	ind->indirection = list_truncate(ind->indirection,
-									 list_length(ind->indirection) - 1);
+	expr = transformIndirection(pstate, ind, &trailing_star_expansion);
+
+	pstate->p_expr_kind = sv_expr_kind;
 
-	/* And transform that */
-	expr = transformExpr(pstate, (Node *) ind, exprKind);
+	/* '*' was consumed by generic type subscripting */
+	if (!trailing_star_expansion)
+		return expr;
 
 	/* Expand the rowtype expression into individual fields */
-	return ExpandRowReference(pstate, expr, make_target_entry);
+	return (Node *) ExpandRowReference(pstate, expr, make_target_entry);
 }
 
 /*
@@ -1784,13 +1804,18 @@ FigureColnameInternal(Node *node, char **name)
 				char	   *fname = NULL;
 				ListCell   *l;
 
-				/* find last field name, if any, ignoring "*" and subscripts */
+				/*
+				 * find last field name, if any, ignoring subscripts, and use
+				 * '?column?' when there's a trailing '*'.
+				 */
 				foreach(l, ind->indirection)
 				{
 					Node	   *i = lfirst(l);
 
 					if (IsA(i, String))
 						fname = strVal(i);
+					else if (IsA(i, A_Star))
+						fname = "?column?";
 				}
 				if (fname)
 				{
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index 4762f6bd11c..1dcde658739 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -124,7 +124,7 @@ jsonb_check_jsonpath_needed(List *indirection)
 	{
 		Node	   *accessor = lfirst(lc);
 
-		if (IsA(accessor, String))
+		if (IsA(accessor, String) || IsA(accessor, A_Star))
 			return true;
 		else
 		{
@@ -339,6 +339,16 @@ jsonb_subscript_make_jsonpath(ParseState *pstate, List **indirection, Subscripti
 				}
 			}
 		}
+		else if (IsA(accessor, A_Star))
+		{
+			Star *star_node;
+			jpi = make_jsonpath_item(jpiAnyKey);
+
+			star_node = makeNode(Star);
+			star_node->type = T_Star;
+
+			sbsref->refupperindexpr = lappend(sbsref->refupperindexpr, star_node);
+		}
 		else
 		{
 			/*
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index baa3ae97d57..ace0eff52e2 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -13010,7 +13010,11 @@ printSubscripts(SubscriptingRef *sbsref, deparse_context *context)
 	{
 		Node *upper = (Node *) lfirst(uplist_item);
 
-		if (upper && IsA(upper, FieldAccessorExpr))
+		if (upper && IsA(upper, Star))
+		{
+			appendStringInfoString(buf, ".*");
+		}
+		else if (upper && IsA(upper, FieldAccessorExpr))
 		{
 			FieldAccessorExpr *fae = (FieldAccessorExpr *) upper;
 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 7e89621bd65..7e418830f22 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -2419,4 +2419,16 @@ typedef struct FieldAccessorExpr
 	Oid			faecollid pg_node_attr(query_jumble_ignore);
 }			FieldAccessorExpr;
 
+/*
+ * Star - represents a wildcard member accessor (e.g., ".*") used in JSONB simplified accessor.
+ *
+ * This node serves as a syntactic placeholder in the expression tree and does not get evaluated, hence it
+ * has no associated type or collation.
+ */
+typedef struct Star
+{
+	NodeTag		type;
+}			Star;
+
+
 #endif							/* PRIMNODES_H */
diff --git a/src/include/parser/parse_expr.h b/src/include/parser/parse_expr.h
index efbaff8e710..c9f6a7724c0 100644
--- a/src/include/parser/parse_expr.h
+++ b/src/include/parser/parse_expr.h
@@ -22,4 +22,7 @@ extern Node *transformExpr(ParseState *pstate, Node *expr, ParseExprKind exprKin
 
 extern const char *ParseExprKindName(ParseExprKind exprKind);
 
+extern Node *transformIndirection(ParseState *pstate, A_Indirection *ind,
+								  bool *trailing_star_expansion);
+
 #endif							/* PARSE_EXPR_H */
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.c b/src/interfaces/ecpg/test/expected/sql-sqljson.c
index 935b47a3b9a..585d9b14445 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.c
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.c
@@ -515,9 +515,9 @@ if (sqlca.sqlcode < 0) sqlprint();}
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 145 "sqljson.pgc"
 
-	// error
+	printf("Found json=%s\n", json);
 
-	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )", ECPGt_EOIT, 
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . * . x )", ECPGt_EOIT, 
 	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
 	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 148 "sqljson.pgc"
@@ -527,7 +527,7 @@ if (sqlca.sqlcode < 0) sqlprint();}
 
 	printf("Found json=%s\n", json);
 
-	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ : ] )", ECPGt_EOIT, 
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )", ECPGt_EOIT, 
 	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
 	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 151 "sqljson.pgc"
@@ -537,12 +537,22 @@ if (sqlca.sqlcode < 0) sqlprint();}
 
 	printf("Found json=%s\n", json);
 
-  { ECPGdisconnect(__LINE__, "CURRENT");
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ : ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 154 "sqljson.pgc"
 
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 154 "sqljson.pgc"
 
+	printf("Found json=%s\n", json);
+
+  { ECPGdisconnect(__LINE__, "CURRENT");
+#line 157 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 157 "sqljson.pgc"
+
 
   return 0;
 }
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
index f3f899c6d87..4b9088545d6 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
@@ -347,22 +347,19 @@ SQL error: schema "jsonb" does not exist on line 121
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 145: using PQexec
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_check_PQresult on line 145: bad response - ERROR:  row expansion via "*" is not supported here
-LINE 1: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x...
-                        ^
+[NO_PID]: ecpg_process_output on line 145: correctly got 1 tuples with 1 fields
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: raising sqlstate 0A000 (sqlcode -400): row expansion via "*" is not supported here on line 145
-[NO_PID]: sqlca: code: -400, state: 0A000
-SQL error: row expansion via "*" is not supported here on line 145
-[NO_PID]: ecpg_execute on line 148: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: ecpg_get_data on line 145: RESULT: [{"b": 1, "c": 2}, [{"x": 1}, {"x": [12, {"y": 1}]}]] offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 148: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . * . x ); with 0 parameter(s) on connection ecpg1_regression
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 148: using PQexec
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_process_output on line 148: correctly got 1 tuples with 1 fields
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_get_data on line 148: RESULT: [{"x": 1}, {"x": [12, {"y": 1}]}] offset: -1; array: no
+[NO_PID]: ecpg_get_data on line 148: RESULT: [1, [12, {"y": 1}]] offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_execute on line 151: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: ecpg_execute on line 151: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] ); with 0 parameter(s) on connection ecpg1_regression
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 151: using PQexec
 [NO_PID]: sqlca: code: 0, state: 00000
@@ -370,5 +367,13 @@ SQL error: row expansion via "*" is not supported here on line 145
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_get_data on line 151: RESULT: [{"x": 1}, {"x": [12, {"y": 1}]}] offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 154: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 154: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 154: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 154: RESULT: [{"x": 1}, {"x": [12, {"y": 1}]}] offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_finish: connection ecpg1_regression closed
 [NO_PID]: sqlca: code: 0, state: 00000
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
index d01a8457f01..145dc95d430 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
@@ -36,5 +36,7 @@ Found json={"x": 1}
 Found json=[1, [12, {"y": 1}]]
 Found json={"x": 1}
 Found json=[12, {"y": 1}]
+Found json=[{"b": 1, "c": 2}, [{"x": 1}, {"x": [12, {"y": 1}]}]]
+Found json=[1, [12, {"y": 1}]]
 Found json=[{"x": 1}, {"x": [12, {"y": 1}]}]
 Found json=[{"x": 1}, {"x": [12, {"y": 1}]}]
diff --git a/src/interfaces/ecpg/test/sql/sqljson.pgc b/src/interfaces/ecpg/test/sql/sqljson.pgc
index 9423d25fd0b..2af50b5da4b 100644
--- a/src/interfaces/ecpg/test/sql/sqljson.pgc
+++ b/src/interfaces/ecpg/test/sql/sqljson.pgc
@@ -143,7 +143,10 @@ EXEC SQL END DECLARE SECTION;
 	printf("Found json=%s\n", json);
 
 	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).*) INTO :json;
-	// error
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).*.x) INTO :json;
+	printf("Found json=%s\n", json);
 
 	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb)['b'][0:]) INTO :json;
 	printf("Found json=%s\n", json);
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 2702edc9705..86b7f7677fc 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -6064,8 +6064,175 @@ SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
  {"y": "YYY", "z": "ZZZ"}
 (1 row)
 
-SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
-ERROR:  type jsonb is not composite
+/* wild card member access */
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+                 ?column?                  
+-------------------------------------------
+ ["c", "d", "f", {"y": "yyy", "z": "zzz"}]
+(1 row)
+
+SELECT (jb).* FROM test_jsonb_dot_notation;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (jb).* FROM test_jsonb_dot_notation t;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (jb).* FROM test_jsonb_dot_notation;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (t.jb).* FROM test_jsonb_dot_notation;
+ERROR:  missing FROM-clause entry for table "t"
+LINE 1: SELECT (t.jb).* FROM test_jsonb_dot_notation;
+                ^
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+                                                           ?column?                                                           
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+                 ?column?                  
+-------------------------------------------
+ ["c", "d", "f", {"y": "yyy", "z": "zzz"}]
+(1 row)
+
+SELECT (jb).a.*.b FROM test_jsonb_dot_notation;
+ b 
+---
+ 
+(1 row)
+
+SELECT (jb).a.*.x FROM test_jsonb_dot_notation;
+ x 
+---
+ 
+(1 row)
+
+SELECT (jb).a.*.y FROM test_jsonb_dot_notation;
+   y   
+-------
+ "yyy"
+(1 row)
+
+SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+    ?column?    
+----------------
+ ["yyy", "zzz"]
+(1 row)
+
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT (jb).*.x FROM test_jsonb_dot_notation t;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+ x 
+---
+ 
+(1 row)
+
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+ x 
+---
+ 
+(1 row)
+
+SELECT ((jb).*)[:].x FROM test_jsonb_dot_notation t;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
+SELECT (jb).*.x.* FROM test_jsonb_dot_notation;
+           ?column?           
+------------------------------
+ ["yyy", "zzz", "YYY", "ZZZ"]
+(1 row)
+
+SELECT (jb).*.x.y FROM test_jsonb_dot_notation;
+       y        
+----------------
+ ["yyy", "YYY"]
+(1 row)
+
+SELECT (jb).*.x.z FROM test_jsonb_dot_notation;
+       z        
+----------------
+ ["zzz", "ZZZ"]
+(1 row)
+
+SELECT (jb).*.*.y FROM test_jsonb_dot_notation;
+       y        
+----------------
+ ["yyy", "YYY"]
+(1 row)
+
+SELECT (jb).*.*.* FROM test_jsonb_dot_notation;
+           ?column?           
+------------------------------
+ ["yyy", "zzz", "YYY", "ZZZ"]
+(1 row)
+
+SELECT (jb).*.*.*.* FROM test_jsonb_dot_notation;
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT (jb).a.b.c.* FROM test_jsonb_dot_notation;
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+    ?column?    
+----------------
+ ["yyy", "zzz"]
+(1 row)
+
+SELECT (jb).a.*[:].* FROM test_jsonb_dot_notation;
+    ?column?    
+----------------
+ ["yyy", "zzz"]
+(1 row)
+
+SELECT (jb).a.*[*].* FROM test_jsonb_dot_notation; -- not supported
+ERROR:  syntax error at or near "*"
+LINE 1: SELECT (jb).a.*[*].* FROM test_jsonb_dot_notation;
+                        ^
+SELECT (jb).a.**.x FROM test_jsonb_dot_notation; -- not supported
+ERROR:  syntax error at or near "**"
+LINE 1: SELECT (jb).a.**.x FROM test_jsonb_dot_notation;
+                      ^
 -- explains should work
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
                   QUERY PLAN                  
@@ -6093,6 +6260,45 @@ SELECT (jb).a[1] FROM test_jsonb_dot_notation;
  2
 (1 row)
 
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.* FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb).a.*
+(2 rows)
+
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+                 ?column?                  
+-------------------------------------------
+ ["c", "d", "f", {"y": "yyy", "z": "zzz"}]
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:].* FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb).a.*[:].*
+(2 rows)
+
+SELECT (jb).a.*[1:].* FROM test_jsonb_dot_notation;
+ ?column? 
+----------
+ 
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb).a.*[:2].*.b
+(2 rows)
+
+SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
+ b 
+---
+ 
+(1 row)
+
 -- views should work
 CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
 \sv test_jsonb_dot_notation_v1
@@ -6121,6 +6327,17 @@ SELECT * from v3;
  "yyy"
 (1 row)
 
+CREATE VIEW v4 AS SELECT (jb).a.*[:].* FROM test_jsonb_dot_notation;
+\sv v4
+CREATE OR REPLACE VIEW public.v4 AS
+ SELECT (jb).a.*[:].* AS "?column?"
+   FROM test_jsonb_dot_notation
+SELECT * from v4;
+    ?column?    
+----------------
+ ["yyy", "zzz"]
+(1 row)
+
 -- mixed syntax
 DROP VIEW test_jsonb_dot_notation_v1;
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
@@ -6356,4 +6573,5 @@ NOTICE:  [2]
 -- clean up
 DROP VIEW v2;
 DROP VIEW v3;
+DROP VIEW v4;
 DROP TABLE test_jsonb_dot_notation;
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index de2c9cda094..07facc280d7 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1631,13 +1631,49 @@ SELECT (jb).a[2:].b FROM test_jsonb_dot_notation;
 SELECT (jb).a[:2].b FROM test_jsonb_dot_notation;
 SELECT (jb).a[:].b FROM test_jsonb_dot_notation;
 SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
-SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
+
+/* wild card member access */
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+SELECT (jb).* FROM test_jsonb_dot_notation;
+SELECT (jb).* FROM test_jsonb_dot_notation t;
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+SELECT (jb).* FROM test_jsonb_dot_notation;
+SELECT (t.jb).* FROM test_jsonb_dot_notation;
+SELECT (t.jb).* FROM test_jsonb_dot_notation t;
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.b FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.x FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.y FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+SELECT (jb).*.x FROM test_jsonb_dot_notation t;
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
+SELECT ((jb).*)[:].x FROM test_jsonb_dot_notation t;
+SELECT (jb).*.x FROM test_jsonb_dot_notation;
+SELECT (jb).*.x.* FROM test_jsonb_dot_notation;
+SELECT (jb).*.x.y FROM test_jsonb_dot_notation;
+SELECT (jb).*.x.z FROM test_jsonb_dot_notation;
+SELECT (jb).*.*.y FROM test_jsonb_dot_notation;
+SELECT (jb).*.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).*.*.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.b.c.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*[:].* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*[*].* FROM test_jsonb_dot_notation; -- not supported
+SELECT (jb).a.**.x FROM test_jsonb_dot_notation; -- not supported
 
 -- explains should work
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
 SELECT (t.jb).a FROM test_jsonb_dot_notation t;
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
 SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.* FROM test_jsonb_dot_notation;
+SELECT (jb).a.* FROM test_jsonb_dot_notation;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:].* FROM test_jsonb_dot_notation;
+SELECT (jb).a.*[1:].* FROM test_jsonb_dot_notation;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
+SELECT (jb).a.*[1:2].*.b FROM test_jsonb_dot_notation;
 
 -- views should work
 CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
@@ -1648,6 +1684,9 @@ SELECT * from v2;
 CREATE VIEW v3 AS SELECT (jb).a[:3].x.y[-1:] FROM test_jsonb_dot_notation;
 \sv v3
 SELECT * from v3;
+CREATE VIEW v4 AS SELECT (jb).a.*[:].* FROM test_jsonb_dot_notation;
+\sv v4
+SELECT * from v4;
 
 -- mixed syntax
 DROP VIEW test_jsonb_dot_notation_v1;
@@ -1749,4 +1788,5 @@ $$ LANGUAGE plpgsql;
 -- clean up
 DROP VIEW v2;
 DROP VIEW v3;
+DROP VIEW v4;
 DROP TABLE test_jsonb_dot_notation;
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v17-0001-Allow-transformation-of-only-a-sublist-of-subscr.patch (9.0K, ../../CAK98qZ1FK1ZhH_uyqg02_n7Q5gnPAooC8zUdFAx3_XQiBdXo6Q@mail.gmail.com/4-v17-0001-Allow-transformation-of-only-a-sublist-of-subscr.patch)
  download | inline diff:
From 15f69ce64cc58b1b96836aaa5e37515e470bf9da Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v17 1/7] Allow transformation of only a sublist of subscripts

This is a preparation step for allowing subscripting containers to
transform only a prefix of an indirection list and modify the list
in-place by removing the processed elements. Currently, all elements
are consumed, and the list is set to NIL after transformation.

In the following commit, subscripting containers will gain the
flexibility to stop transformation when encountering an unsupported
indirection and return the remaining indirections to the caller.

Reviewed-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Jian He <[email protected]>
Reviewed-by: Chao Li <[email protected]>
---
 contrib/hstore/hstore_subs.c      | 10 ++++++----
 src/backend/parser/parse_expr.c   |  9 ++++-----
 src/backend/parser/parse_node.c   |  4 ++--
 src/backend/parser/parse_target.c |  2 +-
 src/backend/utils/adt/arraysubs.c |  6 ++++--
 src/backend/utils/adt/jsonbsubs.c |  6 ++++--
 src/include/nodes/subscripting.h  |  7 ++++++-
 src/include/parser/parse_node.h   |  2 +-
 8 files changed, 28 insertions(+), 18 deletions(-)

diff --git a/contrib/hstore/hstore_subs.c b/contrib/hstore/hstore_subs.c
index 3d03f66fa0d..1b29543ab67 100644
--- a/contrib/hstore/hstore_subs.c
+++ b/contrib/hstore/hstore_subs.c
@@ -40,7 +40,7 @@
  */
 static void
 hstore_subscript_transform(SubscriptingRef *sbsref,
-						   List *indirection,
+						   List **indirection,
 						   ParseState *pstate,
 						   bool isSlice,
 						   bool isAssignment)
@@ -49,15 +49,15 @@ hstore_subscript_transform(SubscriptingRef *sbsref,
 	Node	   *subexpr;
 
 	/* We support only single-subscript, non-slice cases */
-	if (isSlice || list_length(indirection) != 1)
+	if (isSlice || list_length(*indirection) != 1)
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("hstore allows only one subscript"),
 				 parser_errposition(pstate,
-									exprLocation((Node *) indirection))));
+									exprLocation((Node *) *indirection))));
 
 	/* Transform the subscript expression to type text */
-	ai = linitial_node(A_Indices, indirection);
+	ai = linitial_node(A_Indices, *indirection);
 	Assert(ai->uidx != NULL && ai->lidx == NULL && !ai->is_slice);
 
 	subexpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
@@ -81,6 +81,8 @@ hstore_subscript_transform(SubscriptingRef *sbsref,
 	/* Determine the result type of the subscripting operation; always text */
 	sbsref->refrestype = TEXTOID;
 	sbsref->reftypmod = -1;
+
+	*indirection = NIL;
 }
 
 /*
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index e1979a80c19..6e8fd42c612 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -465,14 +465,13 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 			Assert(IsA(n, String));
 
 			/* process subscripts before this field selection */
-			if (subscripts)
+			while (subscripts)
 				result = (Node *) transformContainerSubscripts(pstate,
 															   result,
 															   exprType(result),
 															   exprTypmod(result),
-															   subscripts,
+															   &subscripts,
 															   false);
-			subscripts = NIL;
 
 			newresult = ParseFuncOrColumn(pstate,
 										  list_make1(n),
@@ -487,12 +486,12 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		}
 	}
 	/* process trailing subscripts, if any */
-	if (subscripts)
+	while (subscripts)
 		result = (Node *) transformContainerSubscripts(pstate,
 													   result,
 													   exprType(result),
 													   exprTypmod(result),
-													   subscripts,
+													   &subscripts,
 													   false);
 
 	return result;
diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c
index 203b7a32178..f05baa50a15 100644
--- a/src/backend/parser/parse_node.c
+++ b/src/backend/parser/parse_node.c
@@ -244,7 +244,7 @@ transformContainerSubscripts(ParseState *pstate,
 							 Node *containerBase,
 							 Oid containerType,
 							 int32 containerTypMod,
-							 List *indirection,
+							 List **indirection,
 							 bool isAssignment)
 {
 	SubscriptingRef *sbsref;
@@ -280,7 +280,7 @@ transformContainerSubscripts(ParseState *pstate,
 	 * element.  If any of the items are slice specifiers (lower:upper), then
 	 * the subscript expression means a container slice operation.
 	 */
-	foreach(idx, indirection)
+	foreach(idx, *indirection)
 	{
 		A_Indices  *ai = lfirst_node(A_Indices, idx);
 
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 905c975d83b..a097736229a 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -935,7 +935,7 @@ transformAssignmentSubscripts(ParseState *pstate,
 										  basenode,
 										  containerType,
 										  containerTypMod,
-										  subscripts,
+										  &subscripts,
 										  true);
 
 	typeNeeded = sbsref->refrestype;
diff --git a/src/backend/utils/adt/arraysubs.c b/src/backend/utils/adt/arraysubs.c
index 2940fb8e8d7..234c2c278c1 100644
--- a/src/backend/utils/adt/arraysubs.c
+++ b/src/backend/utils/adt/arraysubs.c
@@ -54,7 +54,7 @@ typedef struct ArraySubWorkspace
  */
 static void
 array_subscript_transform(SubscriptingRef *sbsref,
-						  List *indirection,
+						  List **indirection,
 						  ParseState *pstate,
 						  bool isSlice,
 						  bool isAssignment)
@@ -71,7 +71,7 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	 * indirection items to slices by treating the single subscript as the
 	 * upper bound and supplying an assumed lower bound of 1.
 	 */
-	foreach(idx, indirection)
+	foreach(idx, *indirection)
 	{
 		A_Indices  *ai = lfirst_node(A_Indices, idx);
 		Node	   *subexpr;
@@ -152,6 +152,8 @@ array_subscript_transform(SubscriptingRef *sbsref,
 						list_length(upperIndexpr), MAXDIM)));
 	/* We need not check lowerIndexpr separately */
 
+	*indirection = NIL;
+
 	/*
 	 * Determine the result type of the subscripting operation.  It's the same
 	 * as the array type if we're slicing, else it's the element type.  In
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index e8626d3b4fc..5ead693a3b2 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -41,7 +41,7 @@ typedef struct JsonbSubWorkspace
  */
 static void
 jsonb_subscript_transform(SubscriptingRef *sbsref,
-						  List *indirection,
+						  List **indirection,
 						  ParseState *pstate,
 						  bool isSlice,
 						  bool isAssignment)
@@ -53,7 +53,7 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	 * Transform and convert the subscript expressions. Jsonb subscripting
 	 * does not support slices, look only at the upper index.
 	 */
-	foreach(idx, indirection)
+	foreach(idx, *indirection)
 	{
 		A_Indices  *ai = lfirst_node(A_Indices, idx);
 		Node	   *subExpr;
@@ -159,6 +159,8 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	/* Determine the result type of the subscripting operation; always jsonb */
 	sbsref->refrestype = JSONBOID;
 	sbsref->reftypmod = -1;
+
+	*indirection = NIL;
 }
 
 /*
diff --git a/src/include/nodes/subscripting.h b/src/include/nodes/subscripting.h
index 234e8ad8012..df988a85fad 100644
--- a/src/include/nodes/subscripting.h
+++ b/src/include/nodes/subscripting.h
@@ -71,6 +71,11 @@ struct SubscriptExecSteps;
  * does not care to support slicing, it can just throw an error if isSlice.)
  * See array_subscript_transform() for sample code.
  *
+ * The transform method receives a pointer to a list of raw indirections.
+ * It may parse a sublist (typically the prefix) of these indirections and
+ * modify the original list in place, allowing the caller to handle any
+ * remaining indirections differently or to raise an error as needed.
+ *
  * The transform method is also responsible for identifying the result type
  * of the subscripting operation.  At call, refcontainertype and reftypmod
  * describe the container type (this will be a base type not a domain), and
@@ -93,7 +98,7 @@ struct SubscriptExecSteps;
  * assignment must return.
  */
 typedef void (*SubscriptTransform) (SubscriptingRef *sbsref,
-									List *indirection,
+									List **indirection,
 									struct ParseState *pstate,
 									bool isSlice,
 									bool isAssignment);
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index f7d07c84542..58a4b9df157 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -361,7 +361,7 @@ extern SubscriptingRef *transformContainerSubscripts(ParseState *pstate,
 													 Node *containerBase,
 													 Oid containerType,
 													 int32 containerTypMod,
-													 List *indirection,
+													 List **indirection,
 													 bool isAssignment);
 extern Const *make_const(ParseState *pstate, A_Const *aconst);
 
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v17-0006-Implement-Jsonb-subscripting-with-slicing.patch (21.5K, ../../CAK98qZ1FK1ZhH_uyqg02_n7Q5gnPAooC8zUdFAx3_XQiBdXo6Q@mail.gmail.com/5-v17-0006-Implement-Jsonb-subscripting-with-slicing.patch)
  download | inline diff:
From 2d32ec307203bdc07013c57d9f0cd89adcbd4b35 Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v17 6/7] Implement Jsonb subscripting with slicing

Previously, slicing was not supported for jsonb subscripting. This commit
implements subscripting with slicing as part of the JSON simplified accessor
syntax specified in SQL:2023.

Co-authored-by: Nikita Glukhov <[email protected]>
Co-authored-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Mark Dilger <[email protected]>
Reviewed-by: Jian He <[email protected]>
Reviewed-by: Vik Fearing <[email protected]>
Reviewed-by: Nikita Malakhov <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Tested-by: Mark Dilger <[email protected]>
Tested-by: Jian He <[email protected]>
---
 src/backend/utils/adt/jsonbsubs.c             | 119 ++++++++------
 .../ecpg/test/expected/sql-sqljson.c          |  16 +-
 .../ecpg/test/expected/sql-sqljson.stderr     |  26 ++--
 .../ecpg/test/expected/sql-sqljson.stdout     |   3 +
 src/interfaces/ecpg/test/sql/sqljson.pgc      |   7 +-
 src/test/regress/expected/jsonb.out           | 145 ++++++++++++++++--
 src/test/regress/sql/jsonb.sql                |  53 ++++++-
 7 files changed, 286 insertions(+), 83 deletions(-)

diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index ccc5f3c6e94..4762f6bd11c 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -111,7 +111,7 @@ coerce_jsonpath_subscript_to_int4_or_text(ParseState *pstate, Node *subExpr)
  *
  * JsonPath is needed if the indirection list includes:
  * - String-based access (dot notation)
- * - Slice-based subscripting (when isSlice is true)
+ * - Slice-based subscripting
  *
  * Otherwise, simple jsonb subscripting is enough.
  */
@@ -127,7 +127,11 @@ jsonb_check_jsonpath_needed(List *indirection)
 		if (IsA(accessor, String))
 			return true;
 		else
+		{
 			Assert(IsA(accessor, A_Indices));
+			if (castNode(A_Indices, accessor)->is_slice)
+				return true;
+		}
 	}
 
 	return false;
@@ -152,20 +156,45 @@ make_jsonpath_item(JsonPathItemType type)
 	return v;
 }
 
+/*
+ * Build a JsonPathParseItem for a constant integer value.
+ *
+ * This function constructs a jpiNumeric item for use in JsonPath,
+ * and appends a matching Const(INT4) node to the given expression list
+ * for use in EXPLAIN, views, etc.
+ *
+ * Parameters:
+ * - val: integer constant value
+ * - exprs: list of expression nodes (updated in place)
+ */
+static JsonPathParseItem *
+make_jsonpath_item_int(int32 val, List **exprs)
+{
+	JsonPathParseItem *jpi = make_jsonpath_item(jpiNumeric);
+
+	jpi->value.numeric =
+		DatumGetNumeric(DirectFunctionCall1(int4_numeric, Int32GetDatum(val)));
+
+	*exprs = lappend(*exprs, makeConst(INT4OID, -1, InvalidOid, 4,
+									   Int32GetDatum(val), false, true));
+
+	return jpi;
+}
+
 /*
  * Convert a constant integer expression into a JsonPathParseItem.
  *
  * The input expression must be a non-null constant of type INT4. Returns NULL otherwise.
- * This function constructs a jpiNumeric item for use in JsonPath and appends a matching
- * Const(INT4) node to the given expression list for use in EXPLAIN, views, etc.
+ * The function extracts the value and delegates it to make_jsonpath_item_int().
  *
  * Parameters:
  * - pstate: parse state context
  * - expr: input expression node
  * - exprs: list of expression nodes (updated in place)
+ * - no_error: returns NULL when the data type doesn't match. Otherwise, emits an ERROR.
  */
 static JsonPathParseItem *
-make_jsonpath_item_expr(ParseState *pstate, Node *expr, List **exprs)
+make_jsonpath_item_expr(ParseState *pstate, Node *expr, List **exprs, bool no_error)
 {
 	Const	   *cnst;
 
@@ -175,20 +204,17 @@ make_jsonpath_item_expr(ParseState *pstate, Node *expr, List **exprs)
 	{
 		cnst = (Const *) expr;
 		if (cnst->consttype == INT4OID && !(cnst->constisnull))
-		{
-			JsonPathParseItem *jpi = make_jsonpath_item(jpiNumeric);
-
-			jpi->value.numeric =
-				DatumGetNumeric(DirectFunctionCall1(int4_numeric, Int32GetDatum(cnst->constvalue)));
-
-			*exprs = lappend(*exprs, makeConst(INT4OID, -1, InvalidOid, 4,
-											   Int32GetDatum(cnst->constvalue), false, true));
-
-			return jpi;
-		}
+			return make_jsonpath_item_int(DatumGetInt32(cnst->constvalue), exprs);
 	}
 
-	return NULL;
+	if (no_error)
+		return NULL;
+	else
+		ereport(ERROR,
+				errcode(ERRCODE_DATATYPE_MISMATCH),
+				errmsg("only non-null integer constants are supported for jsonb simplified accessor subscripting"),
+				errhint("use int data type for subscripting with slicing."),
+				parser_errposition(pstate, exprLocation(expr)));
 }
 
 /*
@@ -251,13 +277,12 @@ jsonb_subscript_make_jsonpath(ParseState *pstate, List **indirection, Subscripti
 		else if (IsA(accessor, A_Indices))
 		{
 			A_Indices  *ai = castNode(A_Indices, accessor);
+			JsonPathParseItem *jpi_from = NULL;
 
 			if (!ai->is_slice)
 			{
-				JsonPathParseItem *jpi_from = NULL;
-
 				Assert(ai->uidx && !ai->lidx);
-				jpi_from = make_jsonpath_item_expr(pstate, ai->uidx, &sbsref->refupperindexpr);
+				jpi_from = make_jsonpath_item_expr(pstate, ai->uidx, &sbsref->refupperindexpr, true);
 				if (jpi_from == NULL)
 				{
 					/*
@@ -284,22 +309,34 @@ jsonb_subscript_make_jsonpath(ParseState *pstate, List **indirection, Subscripti
 					 */
 					break;
 				}
+			}
 
-				jpi = make_jsonpath_item(jpiIndexArray);
-				jpi->value.array.nelems = 1;
-				jpi->value.array.elems = palloc(sizeof(jpi->value.array.elems[0]));
+			jpi = make_jsonpath_item(jpiIndexArray);
+			jpi->value.array.nelems = 1;
+			jpi->value.array.elems = palloc(sizeof(jpi->value.array.elems[0]));
 
+			if (!ai->is_slice)
+			{
 				jpi->value.array.elems[0].from = jpi_from;
 				jpi->value.array.elems[0].to = NULL;
 			}
 			else
 			{
-				Node	   *expr = ai->uidx ? ai->uidx : ai->lidx;
+				while (list_length(sbsref->reflowerindexpr) < list_length(sbsref->refupperindexpr))
+					sbsref->reflowerindexpr = lappend(sbsref->reflowerindexpr, NULL);
+
+				if (ai->lidx)
+					jpi->value.array.elems[0].from = make_jsonpath_item_expr(pstate, ai->lidx, &sbsref->reflowerindexpr, false);
+				else
+					jpi->value.array.elems[0].from = make_jsonpath_item_int(0, &sbsref->reflowerindexpr);
 
-				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("jsonb subscript does not support slices"),
-						 parser_errposition(pstate, exprLocation(expr))));
+				if (ai->uidx)
+					jpi->value.array.elems[0].to = make_jsonpath_item_expr(pstate, ai->uidx, &sbsref->refupperindexpr, false);
+				else
+				{
+					jpi->value.array.elems[0].to = make_jsonpath_item(jpiLast);
+					sbsref->refupperindexpr = lappend(sbsref->refupperindexpr, NULL);
+				}
 			}
 		}
 		else
@@ -381,32 +418,12 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 		ai = lfirst_node(A_Indices, idx);
 
 		if (ai->is_slice)
-		{
-			Node	   *expr = ai->uidx ? ai->uidx : ai->lidx;
+			break;
 
-			ereport(ERROR,
-					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("jsonb subscript does not support slices"),
-					 parser_errposition(pstate, exprLocation(expr))));
-		}
+		Assert(!ai->lidx && ai->uidx);
 
-		if (ai->uidx)
-		{
-			subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
-			subExpr = coerce_jsonpath_subscript_to_int4_or_text(pstate, subExpr);
-		}
-		else
-		{
-			/*
-			 * Slice with omitted upper bound. Should not happen as we already
-			 * errored out on slice earlier, but handle this just in case.
-			 */
-			Assert(ai->is_slice);
-			ereport(ERROR,
-					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("jsonb subscript does not support slices"),
-					 parser_errposition(pstate, exprLocation(ai->uidx))));
-		}
+		subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
+		subExpr = coerce_jsonpath_subscript_to_int4_or_text(pstate, subExpr);
 
 		upperIndexpr = lappend(upperIndexpr, subExpr);
 	}
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.c b/src/interfaces/ecpg/test/expected/sql-sqljson.c
index e6a7ece6dab..935b47a3b9a 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.c
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.c
@@ -505,7 +505,7 @@ if (sqlca.sqlcode < 0) sqlprint();}
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 142 "sqljson.pgc"
 
-	// error
+	printf("Found json=%s\n", json);
 
 	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . * )", ECPGt_EOIT, 
 	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
@@ -525,14 +525,24 @@ if (sqlca.sqlcode < 0) sqlprint();}
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 148 "sqljson.pgc"
 
-	// error
+	printf("Found json=%s\n", json);
 
-  { ECPGdisconnect(__LINE__, "CURRENT");
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ : ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 151 "sqljson.pgc"
 
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 151 "sqljson.pgc"
 
+	printf("Found json=%s\n", json);
+
+  { ECPGdisconnect(__LINE__, "CURRENT");
+#line 154 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 154 "sqljson.pgc"
+
 
   return 0;
 }
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
index 19f8c58af06..f3f899c6d87 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
@@ -339,13 +339,10 @@ SQL error: schema "jsonb" does not exist on line 121
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 142: using PQexec
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_check_PQresult on line 142: bad response - ERROR:  jsonb subscript does not support slices
-LINE 1: ..., {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] )
-                                                                ^
+[NO_PID]: ecpg_process_output on line 142: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 142: RESULT: [12, {"y": 1}] offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: raising sqlstate 42804 (sqlcode -400): jsonb subscript does not support slices on line 142
-[NO_PID]: sqlca: code: -400, state: 42804
-SQL error: jsonb subscript does not support slices on line 142
 [NO_PID]: ecpg_execute on line 145: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . * ); with 0 parameter(s) on connection ecpg1_regression
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 145: using PQexec
@@ -361,12 +358,17 @@ SQL error: row expansion via "*" is not supported here on line 145
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_execute on line 148: using PQexec
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_check_PQresult on line 148: bad response - ERROR:  jsonb subscript does not support slices
-LINE 1: ...x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )
-                                                                ^
+[NO_PID]: ecpg_process_output on line 148: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 148: RESULT: [{"x": 1}, {"x": [12, {"y": 1}]}] offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 151: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 151: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 151: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 151: RESULT: [{"x": 1}, {"x": [12, {"y": 1}]}] offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: raising sqlstate 42804 (sqlcode -400): jsonb subscript does not support slices on line 148
-[NO_PID]: sqlca: code: -400, state: 42804
-SQL error: jsonb subscript does not support slices on line 148
 [NO_PID]: ecpg_finish: connection ecpg1_regression closed
 [NO_PID]: sqlca: code: 0, state: 00000
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
index 442d36931f1..d01a8457f01 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
@@ -35,3 +35,6 @@ Found json=null
 Found json={"x": 1}
 Found json=[1, [12, {"y": 1}]]
 Found json={"x": 1}
+Found json=[12, {"y": 1}]
+Found json=[{"x": 1}, {"x": [12, {"y": 1}]}]
+Found json=[{"x": 1}, {"x": [12, {"y": 1}]}]
diff --git a/src/interfaces/ecpg/test/sql/sqljson.pgc b/src/interfaces/ecpg/test/sql/sqljson.pgc
index 57a9bff424d..9423d25fd0b 100644
--- a/src/interfaces/ecpg/test/sql/sqljson.pgc
+++ b/src/interfaces/ecpg/test/sql/sqljson.pgc
@@ -140,13 +140,16 @@ EXEC SQL END DECLARE SECTION;
 	printf("Found json=%s\n", json);
 
 	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b[1].x[0:]) INTO :json;
-	// error
+	printf("Found json=%s\n", json);
 
 	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).*) INTO :json;
 	// error
 
 	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb)['b'][0:]) INTO :json;
-	// error
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b[:]) INTO :json;
+	printf("Found json=%s\n", json);
 
   EXEC SQL DISCONNECT;
 
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 37d71b23d5d..2702edc9705 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5247,23 +5247,34 @@ select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b
 (1 row)
 
 select ('{"a": 1}'::jsonb)['a':'b']; -- fails
-ERROR:  jsonb subscript does not support slices
+ERROR:  only non-null integer constants are supported for jsonb simplified accessor subscripting
 LINE 1: select ('{"a": 1}'::jsonb)['a':'b'];
-                                       ^
+                                   ^
+HINT:  use int data type for subscripting with slicing.
 select ('[1, "2", null]'::jsonb)[1:2];
-ERROR:  jsonb subscript does not support slices
-LINE 1: select ('[1, "2", null]'::jsonb)[1:2];
-                                           ^
+    jsonb    
+-------------
+ ["2", null]
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[:2];
-ERROR:  jsonb subscript does not support slices
-LINE 1: select ('[1, "2", null]'::jsonb)[:2];
-                                          ^
+     jsonb      
+----------------
+ [1, "2", null]
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[1:];
-ERROR:  jsonb subscript does not support slices
-LINE 1: select ('[1, "2", null]'::jsonb)[1:];
-                                         ^
+    jsonb    
+-------------
+ ["2", null]
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[:];
-ERROR:  jsonb subscript does not support slices
+     jsonb      
+----------------
+ [1, "2", null]
+(1 row)
+
 create TEMP TABLE test_jsonb_subscript (
        id int,
        test_json jsonb
@@ -6017,8 +6028,42 @@ SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
  
 (1 row)
 
-SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t; -- fails
-ERROR:  jsonb subscript does not support slices
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
+            x             
+--------------------------
+ {"y": "YYY", "z": "ZZZ"}
+(1 row)
+
+SELECT (jb).a[2:3].b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT (jb).a[2:].b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT (jb).a[:2].b FROM test_jsonb_dot_notation;
+  b  
+-----
+ "c"
+(1 row)
+
+SELECT (jb).a[:].b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
+            x             
+--------------------------
+ {"y": "YYY", "z": "ZZZ"}
+(1 row)
+
 SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
 ERROR:  type jsonb is not composite
 -- explains should work
@@ -6054,6 +6099,28 @@ CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_d
 CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
  SELECT (jb).a[3].x.y AS y
    FROM test_jsonb_dot_notation
+CREATE VIEW v2 AS SELECT (jb).a[3:].x.y[:-1] FROM test_jsonb_dot_notation;
+\sv v2
+CREATE OR REPLACE VIEW public.v2 AS
+ SELECT (jb).a[3:].x.y[0:'-1'::integer] AS y
+   FROM test_jsonb_dot_notation
+SELECT * from v2;
+ y 
+---
+ 
+(1 row)
+
+CREATE VIEW v3 AS SELECT (jb).a[:3].x.y[-1:] FROM test_jsonb_dot_notation;
+\sv v3
+CREATE OR REPLACE VIEW public.v3 AS
+ SELECT (jb).a[0:3].x.y['-1'::integer:] AS y
+   FROM test_jsonb_dot_notation
+SELECT * from v3;
+   y   
+-------
+ "yyy"
+(1 row)
+
 -- mixed syntax
 DROP VIEW test_jsonb_dot_notation_v1;
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
@@ -6238,5 +6305,55 @@ SELECT * from test_jsonb_dot_notation_v1;
 (1 row)
 
 DROP VIEW public.test_jsonb_dot_notation_v1;
+-- jsonb array access in plpgsql
+DO $$
+    DECLARE
+        a jsonb := '[1,2,3,4,5,6,7]'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := a[2:];
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+NOTICE:  [1, 2, 3, 4, 5, 6, 7]
+NOTICE:  [3, 4, 5, 6, 7]
+NOTICE:  [5, 6, 7]
+NOTICE:  7
+-- jsonb dot access in plpgsql
+DO $$
+    DECLARE
+        a jsonb := '{"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := COALESCE(a."NU", a[2]); -- fails
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+NOTICE:  {"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}
+ERROR:  missing FROM-clause entry for table "a"
+LINE 1: a := COALESCE(a."NU", a[2])
+                      ^
+QUERY:  a := COALESCE(a."NU", a[2])
+CONTEXT:  PL/pgSQL function inline_code_block line 8 at assignment
+DO $$
+    DECLARE
+        a jsonb := '{"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := COALESCE((a)."NU", a[2]); -- succeeds
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+NOTICE:  {"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}
+NOTICE:  [{"": [[3]]}, [6], [2], "bCi"]
+NOTICE:  [2]
 -- clean up
+DROP VIEW v2;
+DROP VIEW v3;
 DROP TABLE test_jsonb_dot_notation;
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 5fc53e1c9aa..de2c9cda094 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1625,7 +1625,12 @@ SELECT (jb).a[2].b FROM test_jsonb_dot_notation;
 SELECT (jb).a.x.y FROM test_jsonb_dot_notation;
 SELECT (jb).b.x.z FROM test_jsonb_dot_notation;
 SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
-SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t; -- fails
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
+SELECT (jb).a[2:3].b FROM test_jsonb_dot_notation;
+SELECT (jb).a[2:].b FROM test_jsonb_dot_notation;
+SELECT (jb).a[:2].b FROM test_jsonb_dot_notation;
+SELECT (jb).a[:].b FROM test_jsonb_dot_notation;
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t;
 SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
 
 -- explains should work
@@ -1637,6 +1642,12 @@ SELECT (jb).a[1] FROM test_jsonb_dot_notation;
 -- views should work
 CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
 \sv test_jsonb_dot_notation_v1
+CREATE VIEW v2 AS SELECT (jb).a[3:].x.y[:-1] FROM test_jsonb_dot_notation;
+\sv v2
+SELECT * from v2;
+CREATE VIEW v3 AS SELECT (jb).a[:3].x.y[-1:] FROM test_jsonb_dot_notation;
+\sv v3
+SELECT * from v3;
 
 -- mixed syntax
 DROP VIEW test_jsonb_dot_notation_v1;
@@ -1697,5 +1708,45 @@ SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
 SELECT * from test_jsonb_dot_notation_v1;
 DROP VIEW public.test_jsonb_dot_notation_v1;
 
+-- jsonb array access in plpgsql
+DO $$
+    DECLARE
+        a jsonb := '[1,2,3,4,5,6,7]'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := a[2:];
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+
+-- jsonb dot access in plpgsql
+DO $$
+    DECLARE
+        a jsonb := '{"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := COALESCE(a."NU", a[2]); -- fails
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+
+DO $$
+    DECLARE
+        a jsonb := '{"": 6, "NU": [{"": [[3]]}, [6], [2], "bCi"], "aaf": [-6, -8]}'::jsonb;
+    BEGIN
+        WHILE a IS NOT NULL
+            LOOP
+                RAISE NOTICE '%', a;
+                a := COALESCE((a)."NU", a[2]); -- succeeds
+            END LOOP;
+    END
+$$ LANGUAGE plpgsql;
+
 -- clean up
+DROP VIEW v2;
+DROP VIEW v3;
 DROP TABLE test_jsonb_dot_notation;
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v17-0004-Extract-coerce_jsonpath_subscript.patch (5.5K, ../../CAK98qZ1FK1ZhH_uyqg02_n7Q5gnPAooC8zUdFAx3_XQiBdXo6Q@mail.gmail.com/6-v17-0004-Extract-coerce_jsonpath_subscript.patch)
  download | inline diff:
From 16585b8f90f657aecc7be0d5c8856d45c9318c7a Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v17 4/7] Extract coerce_jsonpath_subscript()

This is a preparation step for a future commit that will reuse the
aforementioned function.

Co-authored-by: Nikita Glukhov <[email protected]>
Co-authored-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Jian He <[email protected]>
---
 src/backend/utils/adt/jsonbsubs.c | 128 +++++++++++++++---------------
 1 file changed, 64 insertions(+), 64 deletions(-)

diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index 2aa410f605b..8852c27a198 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -32,6 +32,69 @@ typedef struct JsonbSubWorkspace
 	Datum	   *index;			/* Subscript values in Datum format */
 } JsonbSubWorkspace;
 
+static Node *
+coerce_jsonpath_subscript_to_int4_or_text(ParseState *pstate, Node *subExpr)
+{
+	Oid			subExprType = exprType(subExpr);
+	Oid			targetType = InvalidOid;
+
+	if (subExprType != UNKNOWNOID)
+	{
+		const Oid	targets[] = {INT4OID, TEXTOID};
+
+		/*
+		 * Jsonb can handle multiple subscript types, but cases when a
+		 * subscript could be coerced to multiple target types must be
+		 * avoided, similar to overloaded functions. It could be possibly
+		 * extend with jsonpath in the future.
+		 */
+		for (int i = 0; i < lengthof(targets); i++)
+		{
+			if (can_coerce_type(1, &subExprType, &targets[i], COERCION_IMPLICIT))
+			{
+				/*
+				 * One type has already succeeded, it means there are two
+				 * coercion targets possible, failure.
+				 */
+				if (OidIsValid(targetType))
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
+							 errhint("jsonb subscript must be coercible to only one type, integer or text."),
+							 parser_errposition(pstate, exprLocation(subExpr))));
+
+				targetType = targets[i];
+			}
+		}
+
+		/*
+		 * No suitable types were found, failure.
+		 */
+		if (!OidIsValid(targetType))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
+					 errhint("jsonb subscript must be coercible to either integer or text."),
+					 parser_errposition(pstate, exprLocation(subExpr))));
+	}
+	else
+		targetType = TEXTOID;
+
+	/*
+	 * We known from can_coerce_type that coercion will succeed, so
+	 * coerce_type could be used. Note the implicit coercion context, which is
+	 * required to handle subscripts of different types, similar to overloaded
+	 * functions.
+	 */
+	subExpr = coerce_type(pstate,
+						  subExpr, subExprType,
+						  targetType, -1,
+						  COERCION_IMPLICIT,
+						  COERCE_IMPLICIT_CAST,
+						  -1);
+
+	return subExpr;
+}
 
 /*
  * Finish parse analysis of a SubscriptingRef expression for a jsonb.
@@ -74,71 +137,8 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 
 		if (ai->uidx)
 		{
-			Oid			subExprType = InvalidOid,
-						targetType = UNKNOWNOID;
-
 			subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
-			subExprType = exprType(subExpr);
-
-			if (subExprType != UNKNOWNOID)
-			{
-				Oid			targets[2] = {INT4OID, TEXTOID};
-
-				/*
-				 * Jsonb can handle multiple subscript types, but cases when a
-				 * subscript could be coerced to multiple target types must be
-				 * avoided, similar to overloaded functions. It could be
-				 * possibly extend with jsonpath in the future.
-				 */
-				for (int i = 0; i < 2; i++)
-				{
-					if (can_coerce_type(1, &subExprType, &targets[i], COERCION_IMPLICIT))
-					{
-						/*
-						 * One type has already succeeded, it means there are
-						 * two coercion targets possible, failure.
-						 */
-						if (targetType != UNKNOWNOID)
-							ereport(ERROR,
-									(errcode(ERRCODE_DATATYPE_MISMATCH),
-									 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
-									 errhint("jsonb subscript must be coercible to only one type, integer or text."),
-									 parser_errposition(pstate, exprLocation(subExpr))));
-
-						targetType = targets[i];
-					}
-				}
-
-				/*
-				 * No suitable types were found, failure.
-				 */
-				if (targetType == UNKNOWNOID)
-					ereport(ERROR,
-							(errcode(ERRCODE_DATATYPE_MISMATCH),
-							 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
-							 errhint("jsonb subscript must be coercible to either integer or text."),
-							 parser_errposition(pstate, exprLocation(subExpr))));
-			}
-			else
-				targetType = TEXTOID;
-
-			/*
-			 * We known from can_coerce_type that coercion will succeed, so
-			 * coerce_type could be used. Note the implicit coercion context,
-			 * which is required to handle subscripts of different types,
-			 * similar to overloaded functions.
-			 */
-			subExpr = coerce_type(pstate,
-								  subExpr, subExprType,
-								  targetType, -1,
-								  COERCION_IMPLICIT,
-								  COERCE_IMPLICIT_CAST,
-								  -1);
-			if (subExpr == NULL)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("jsonb subscript must have text type"),
-						 parser_errposition(pstate, exprLocation(subExpr))));
+			subExpr = coerce_jsonpath_subscript_to_int4_or_text(pstate, subExpr);
 		}
 		else
 		{
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v17-0005-Implement-read-only-dot-notation-for-jsonb.patch (61.2K, ../../CAK98qZ1FK1ZhH_uyqg02_n7Q5gnPAooC8zUdFAx3_XQiBdXo6Q@mail.gmail.com/7-v17-0005-Implement-read-only-dot-notation-for-jsonb.patch)
  download | inline diff:
From c25a81c6e8a45d7e4c6f803561e66ba9af7e964b Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v17 5/7] Implement read-only dot notation for jsonb

This patch introduces JSONB member access using dot notation that
aligns with the JSON simplified accessor specified in SQL:2023.

Examples:

-- Setup
create table t(x int, y jsonb);
insert into t select 1, '{"a": 1, "b": 42}'::jsonb;
insert into t select 1, '{"a": 2, "b": {"c": 42}}'::jsonb;
insert into t select 1, '{"a": 3, "b": {"c": "42"}, "d":[11, 12]}'::jsonb;

-- Existing syntax in PostgreSQL that predates the SQL standard:
select (t.y)->'b' from t;
select (t.y)->'b'->'c' from t;
select (t.y)->'d'->0 from t;

-- JSON simplified accessor specified by the SQL standard:
select (t.y).b from t;
select (t.y).b.c from t;
select (t.y).d[0] from t;

The SQL standard states that simplified access is equivalent to:
JSON_QUERY (VEP, 'lax $.JC' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR)

where:
  VEP = <value expression primary>
  JC = <JSON simplified accessor op chain>

For example, the JSON_QUERY equivalents of the above queries are:

select json_query(y, 'lax $.b' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t;
select json_query(y, 'lax $.b.c' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t;
select json_query(y, 'lax $.d[0]' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t;

Implementation details:

This patch extends the existing container subscripting interface to
support container-specific information, namely a JSONPath expression
for jsonb.

During query transformation, if dot-notation is present, a JSONPath
expression is constructed to represent the access chain.

Then during execution, if a JSONPath expression is present in
JsonbSubWorkspace, executes it via JsonPathQuery().

Note that we cannot simply rewrite the accessors into JSON_QUERY()
during transformation, because the original query structure must be
preserved for EXPLAIN and CREATE VIEW.

Co-authored-by: Nikita Glukhov <[email protected]>
Co-authored-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Mark Dilger <[email protected]>
Reviewed-by: Jian He <[email protected]>
Reviewed-by: Vik Fearing <[email protected]>
Reviewed-by: Nikita Malakhov <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Tested-by: Jelte Fennema-Nio <[email protected]>
---
 src/backend/catalog/sql_features.txt          |   4 +-
 src/backend/executor/execExpr.c               |  81 ++--
 src/backend/nodes/nodeFuncs.c                 |  12 +
 src/backend/utils/adt/jsonbsubs.c             | 301 ++++++++++++-
 src/backend/utils/adt/ruleutils.c             |  43 +-
 src/include/nodes/primnodes.h                 |  54 ++-
 .../ecpg/test/expected/sql-sqljson.c          | 112 ++++-
 .../ecpg/test/expected/sql-sqljson.stderr     | 100 +++++
 .../ecpg/test/expected/sql-sqljson.stdout     |   7 +
 src/interfaces/ecpg/test/sql/sqljson.pgc      |  33 ++
 src/test/regress/expected/jsonb.out           | 413 +++++++++++++++++-
 src/test/regress/sql/jsonb.sql                | 113 ++++-
 src/tools/pgindent/typedefs.list              |   1 +
 13 files changed, 1201 insertions(+), 73 deletions(-)

diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_features.txt
index ebe85337c28..457e993305e 100644
--- a/src/backend/catalog/sql_features.txt
+++ b/src/backend/catalog/sql_features.txt
@@ -568,8 +568,8 @@ T838	JSON_TABLE: PLAN DEFAULT clause			NO
 T839	Formatted cast of datetimes to/from character strings			NO	
 T840	Hex integer literals in SQL/JSON path language			YES	
 T851	SQL/JSON: optional keywords for default syntax			YES	
-T860	SQL/JSON simplified accessor: column reference only			NO	
-T861	SQL/JSON simplified accessor: case-sensitive JSON member accessor			NO	
+T860	SQL/JSON simplified accessor: column reference only			YES	
+T861	SQL/JSON simplified accessor: case-sensitive JSON member accessor			YES	
 T862	SQL/JSON simplified accessor: wildcard member accessor			NO	
 T863	SQL/JSON simplified accessor: single-quoted string literal as member accessor			NO	
 T864	SQL/JSON simplified accessor			NO	
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index f1569879b52..385c8d0cefe 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -3320,50 +3320,59 @@ ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref,
 								   state->steps_len - 1);
 	}
 
-	/* Evaluate upper subscripts */
-	i = 0;
-	foreach(lc, sbsref->refupperindexpr)
+	/* Evaluate upper subscripts, unless refjsonbpath is used for execution */
+	if (!sbsref->refjsonbpath)
 	{
-		Expr	   *e = (Expr *) lfirst(lc);
-
-		/* When slicing, individual subscript bounds can be omitted */
-		if (!e)
-		{
-			sbsrefstate->upperprovided[i] = false;
-			sbsrefstate->upperindexnull[i] = true;
-		}
-		else
+		i = 0;
+		foreach(lc, sbsref->refupperindexpr)
 		{
-			sbsrefstate->upperprovided[i] = true;
-			/* Each subscript is evaluated into appropriate array entry */
-			ExecInitExprRec(e, state,
-							&sbsrefstate->upperindex[i],
-							&sbsrefstate->upperindexnull[i]);
+			Expr *e = (Expr *) lfirst(lc);
+
+			/* When slicing, individual subscript bounds can be omitted */
+			if (!e)
+			{
+				sbsrefstate->upperprovided[i] = false;
+				sbsrefstate->upperindexnull[i] = true;
+			}
+			else
+			{
+				sbsrefstate->upperprovided[i] = true;
+				/* Each subscript is evaluated into appropriate array entry */
+				ExecInitExprRec(e, state,
+								&sbsrefstate->upperindex[i],
+								&sbsrefstate->upperindexnull[i]);
+			}
+			i++;
 		}
-		i++;
 	}
 
-	/* Evaluate lower subscripts similarly */
-	i = 0;
-	foreach(lc, sbsref->reflowerindexpr)
+	/*
+	 * Evaluate lower subscripts similarly, unless refjsonbpath is used for
+	 * execution
+	 */
+	if (!sbsref->refjsonbpath)
 	{
-		Expr	   *e = (Expr *) lfirst(lc);
-
-		/* When slicing, individual subscript bounds can be omitted */
-		if (!e)
-		{
-			sbsrefstate->lowerprovided[i] = false;
-			sbsrefstate->lowerindexnull[i] = true;
-		}
-		else
+		i = 0;
+		foreach(lc, sbsref->reflowerindexpr)
 		{
-			sbsrefstate->lowerprovided[i] = true;
-			/* Each subscript is evaluated into appropriate array entry */
-			ExecInitExprRec(e, state,
-							&sbsrefstate->lowerindex[i],
-							&sbsrefstate->lowerindexnull[i]);
+			Expr	   *e = (Expr *) lfirst(lc);
+
+			/* When slicing, individual subscript bounds can be omitted */
+			if (!e)
+			{
+				sbsrefstate->lowerprovided[i] = false;
+				sbsrefstate->lowerindexnull[i] = true;
+			}
+			else
+			{
+				sbsrefstate->lowerprovided[i] = true;
+				/* Each subscript is evaluated into appropriate array entry */
+				ExecInitExprRec(e, state,
+								&sbsrefstate->lowerindex[i],
+								&sbsrefstate->lowerindexnull[i]);
+			}
+			i++;
 		}
-		i++;
 	}
 
 	/* SBSREF_SUBSCRIPTS checks and converts all the subscripts at once */
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 7bc823507f1..d1bd575d9bd 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -284,6 +284,13 @@ exprType(const Node *expr)
 		case T_PlaceHolderVar:
 			type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_FieldAccessorExpr:
+			/*
+			 * FieldAccessorExpr is not evaluable. Treat it as TEXT for collation,
+			 * deparsing, and similar purposes, since it represents a JSON field name.
+			 */
+			type = TEXTOID;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			type = InvalidOid;	/* keep compiler quiet */
@@ -1146,6 +1153,9 @@ exprSetCollation(Node *expr, Oid collation)
 		case T_MergeSupportFunc:
 			((MergeSupportFunc *) expr)->msfcollid = collation;
 			break;
+		case T_FieldAccessorExpr:
+			((FieldAccessorExpr *) expr)->faecollid = collation;
+			break;
 		case T_SubscriptingRef:
 			((SubscriptingRef *) expr)->refcollid = collation;
 			break;
@@ -2129,6 +2139,7 @@ expression_tree_walker_impl(Node *node,
 		case T_SortGroupClause:
 		case T_CTESearchClause:
 		case T_MergeSupportFunc:
+		case T_FieldAccessorExpr:
 			/* primitive node types with no expression subnodes */
 			break;
 		case T_WithCheckOption:
@@ -3008,6 +3019,7 @@ expression_tree_mutator_impl(Node *node,
 		case T_SortGroupClause:
 		case T_CTESearchClause:
 		case T_MergeSupportFunc:
+		case T_FieldAccessorExpr:
 			return copyObject(node);
 		case T_WithCheckOption:
 			{
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index 8852c27a198..ccc5f3c6e94 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -15,21 +15,30 @@
 #include "postgres.h"
 
 #include "executor/execExpr.h"
+#include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "nodes/subscripting.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_expr.h"
 #include "utils/builtins.h"
 #include "utils/jsonb.h"
+#include "utils/jsonpath.h"
 
 
-/* SubscriptingRefState.workspace for jsonb subscripting execution */
+/*
+ * SubscriptingRefState.workspace for generic jsonb subscripting execution.
+ *
+ * Stores state for both jsonb simple subscripting and dot notation access.
+ * Dot notation additionally uses `jsonpath` for JsonPath evaluation.
+ */
 typedef struct JsonbSubWorkspace
 {
 	bool		expectArray;	/* jsonb root is expected to be an array */
 	Oid		   *indexOid;		/* OID of coerced subscript expression, could
 								 * be only integer or text */
 	Datum	   *index;			/* Subscript values in Datum format */
+	JsonPath   *jsonpath;		/* JsonPath for dot notation execution via
+								 * JsonPathQuery() */
 } JsonbSubWorkspace;
 
 static Node *
@@ -96,6 +105,234 @@ coerce_jsonpath_subscript_to_int4_or_text(ParseState *pstate, Node *subExpr)
 	return subExpr;
 }
 
+/*
+ * During transformation, determine whether to build a JsonPath
+ * for JsonPathQuery() execution.
+ *
+ * JsonPath is needed if the indirection list includes:
+ * - String-based access (dot notation)
+ * - Slice-based subscripting (when isSlice is true)
+ *
+ * Otherwise, simple jsonb subscripting is enough.
+ */
+static bool
+jsonb_check_jsonpath_needed(List *indirection)
+{
+	ListCell   *lc;
+
+	foreach(lc, indirection)
+	{
+		Node	   *accessor = lfirst(lc);
+
+		if (IsA(accessor, String))
+			return true;
+		else
+			Assert(IsA(accessor, A_Indices));
+	}
+
+	return false;
+}
+
+/*
+ * Helper functions for constructing JsonPath expressions.
+ *
+ * The make_jsonpath_item_* functions create various types of JsonPathParseItem
+ * nodes, which are used to build JsonPath expressions for jsonb simplified
+ * accessor.
+ */
+
+static JsonPathParseItem *
+make_jsonpath_item(JsonPathItemType type)
+{
+	JsonPathParseItem *v = palloc(sizeof(*v));
+
+	v->type = type;
+	v->next = NULL;
+
+	return v;
+}
+
+/*
+ * Convert a constant integer expression into a JsonPathParseItem.
+ *
+ * The input expression must be a non-null constant of type INT4. Returns NULL otherwise.
+ * This function constructs a jpiNumeric item for use in JsonPath and appends a matching
+ * Const(INT4) node to the given expression list for use in EXPLAIN, views, etc.
+ *
+ * Parameters:
+ * - pstate: parse state context
+ * - expr: input expression node
+ * - exprs: list of expression nodes (updated in place)
+ */
+static JsonPathParseItem *
+make_jsonpath_item_expr(ParseState *pstate, Node *expr, List **exprs)
+{
+	Const	   *cnst;
+
+	expr = transformExpr(pstate, expr, pstate->p_expr_kind);
+
+	if (IsA(expr, Const))
+	{
+		cnst = (Const *) expr;
+		if (cnst->consttype == INT4OID && !(cnst->constisnull))
+		{
+			JsonPathParseItem *jpi = make_jsonpath_item(jpiNumeric);
+
+			jpi->value.numeric =
+				DatumGetNumeric(DirectFunctionCall1(int4_numeric, Int32GetDatum(cnst->constvalue)));
+
+			*exprs = lappend(*exprs, makeConst(INT4OID, -1, InvalidOid, 4,
+											   Int32GetDatum(cnst->constvalue), false, true));
+
+			return jpi;
+		}
+	}
+
+	return NULL;
+}
+
+/*
+ * Constructs a JsonPath expression from a list of indirections.
+ * This function is used when jsonb subscripting involves dot notation,
+ * requiring JsonPath-based evaluation.
+ *
+ * The function modifies the indirection list in place, removing processed
+ * elements as it converts them into JsonPath components, as follows:
+ * - String keys (dot notation) -> jpiKey items.
+ * - Array indices -> jpiIndexArray items.
+ *
+ * In addition to building the JsonPath expression, this function populates
+ * the following fields of the given SubscriptingRef:
+ * - refjsonbpath: the generated JsonPath
+ * - refupperindexpr: upper index expressions (object keys or array indexes)
+ * - reflowerindexpr: lower index expressions, remains NIL as slices are not supported.
+ *
+ * Parameters:
+ * - pstate: Parse state context.
+ * - indirection: List of subscripting expressions (modified in-place).
+ * - sbsref: SubscriptingRef node to update
+ */
+static void
+jsonb_subscript_make_jsonpath(ParseState *pstate, List **indirection, SubscriptingRef *sbsref)
+{
+	JsonPathParseResult jpres;
+	JsonPathParseItem *path = make_jsonpath_item(jpiRoot);
+	ListCell   *lc;
+	Datum		jsp;
+	int			pathlen = 0;
+
+	sbsref->refupperindexpr = NIL;
+	sbsref->reflowerindexpr = NIL;
+	sbsref->refjsonbpath = NULL;
+
+	jpres.expr = path;
+	jpres.lax = true;
+
+	foreach(lc, *indirection)
+	{
+		Node	   *accessor = lfirst(lc);
+		JsonPathParseItem *jpi;
+
+		if (IsA(accessor, String))
+		{
+			char	   *field = strVal(accessor);
+			FieldAccessorExpr *accessor_expr;
+
+			jpi = make_jsonpath_item(jpiKey);
+			jpi->value.string.val = field;
+			jpi->value.string.len = strlen(field);
+
+			accessor_expr = makeNode(FieldAccessorExpr);
+			accessor_expr->type = T_FieldAccessorExpr;
+			accessor_expr->fieldname = field;
+
+			sbsref->refupperindexpr = lappend(sbsref->refupperindexpr, accessor_expr);
+		}
+		else if (IsA(accessor, A_Indices))
+		{
+			A_Indices  *ai = castNode(A_Indices, accessor);
+
+			if (!ai->is_slice)
+			{
+				JsonPathParseItem *jpi_from = NULL;
+
+				Assert(ai->uidx && !ai->lidx);
+				jpi_from = make_jsonpath_item_expr(pstate, ai->uidx, &sbsref->refupperindexpr);
+				if (jpi_from == NULL)
+				{
+					/*
+					 * Break out of the loop if the subscript is not a
+					 * non-null integer constant, so that we can fall back to
+					 * jsonb subscripting logic.
+					 *
+					 * This is needed to handle cases with mixed usage of SQL
+					 * standard json simplified accessor syntax and PostgreSQL
+					 * jsonb subscripting syntax, e.g:
+					 *
+					 * select (jb).a['b'].c from jsonb_table;
+					 *
+					 * where dot-notation (.a and .c) is the SQL standard json
+					 * simplified accessor syntax, and the ['b'] subscript is
+					 * the PostgreSQL jsonb subscripting syntax, because 'b'
+					 * is not a non-null constant integer and cannot be used
+					 * for json array access.
+					 *
+					 * In this case, we cannot create a JsonPath item, so we
+					 * break out of the loop and let
+					 * jsonb_subscript_transform() handle this indirection as
+					 * a PostgreSQL jsonb subscript.
+					 */
+					break;
+				}
+
+				jpi = make_jsonpath_item(jpiIndexArray);
+				jpi->value.array.nelems = 1;
+				jpi->value.array.elems = palloc(sizeof(jpi->value.array.elems[0]));
+
+				jpi->value.array.elems[0].from = jpi_from;
+				jpi->value.array.elems[0].to = NULL;
+			}
+			else
+			{
+				Node	   *expr = ai->uidx ? ai->uidx : ai->lidx;
+
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("jsonb subscript does not support slices"),
+						 parser_errposition(pstate, exprLocation(expr))));
+			}
+		}
+		else
+		{
+			/*
+			 * Unexpected node type in indirection list. This should not
+			 * happen with current grammar, but we handle it defensively by
+			 * breaking out of the loop rather than crashing. In case of
+			 * future grammar changes that might introduce new node types,
+			 * this allows us to create a jsonpath from as many indirection
+			 * elements as we can and let transformIndirection() fallback to
+			 * alternative logic to handle the remaining indirection elements.
+			 */
+			Assert(false);		/* not reachable */
+			break;
+		}
+
+		/* append path item */
+		path->next = jpi;
+		path = jpi;
+		pathlen++;
+	}
+
+	if (pathlen == 0)
+		return;
+
+	*indirection = list_delete_first_n(*indirection, pathlen);
+
+	jsp = jsonPathFromParseResult(&jpres, 0, NULL);
+
+	sbsref->refjsonbpath = (Node *) makeConst(JSONPATHOID, -1, InvalidOid, -1, jsp, false, false);
+}
+
 /*
  * Finish parse analysis of a SubscriptingRef expression for a jsonb.
  *
@@ -111,9 +348,27 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	List	   *upperIndexpr = NIL;
 	ListCell   *idx;
 
+	/* Determine the result type of the subscripting operation; always jsonb */
+	sbsref->refrestype = JSONBOID;
+	sbsref->reftypmod = -1;
+
+	if (jsonb_check_jsonpath_needed(*indirection))
+	{
+		jsonb_subscript_make_jsonpath(pstate, indirection, sbsref);
+		if (sbsref->refjsonbpath)
+			return;
+	}
+
 	/*
-	 * Transform and convert the subscript expressions. Jsonb subscripting
-	 * does not support slices, look only at the upper index.
+	 * We reach here only in two cases: (a) the JSON simplified accessor is
+	 * not needed at all (for example, a plain array subscript like [1] or
+	 * object key access like ['a']), or (b) jsonb_subscript_make_jsonpath()
+	 * was called but could not complete the JsonPath construction (for
+	 * example, when mixing dot notation with non-integer subscripts like
+	 * (jb)['a'].b where 'a' is not a constant integer).
+	 *
+	 * In both cases we fall back to pre-standard jsonb subscripting, coercing
+	 * each subscript to array index or object key as needed.
 	 */
 	foreach(idx, *indirection)
 	{
@@ -160,10 +415,6 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->refupperindexpr = upperIndexpr;
 	sbsref->reflowerindexpr = NIL;
 
-	/* Determine the result type of the subscripting operation; always jsonb */
-	sbsref->refrestype = JSONBOID;
-	sbsref->reftypmod = -1;
-
 	/* Remove processed elements */
 	if (upperIndexpr)
 		*indirection = list_delete_first_n(*indirection, list_length(upperIndexpr));
@@ -218,7 +469,7 @@ jsonb_subscript_check_subscripts(ExprState *state,
 			 * For jsonb fetch and assign functions we need to provide path in
 			 * text format. Convert if it's not already text.
 			 */
-			if (workspace->indexOid[i] == INT4OID)
+			if (!workspace->jsonpath && workspace->indexOid[i] == INT4OID)
 			{
 				Datum		datum = sbsrefstate->upperindex[i];
 				char	   *cs = DatumGetCString(DirectFunctionCall1(int4out, datum));
@@ -246,17 +497,32 @@ jsonb_subscript_fetch(ExprState *state,
 {
 	SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
 	JsonbSubWorkspace *workspace = (JsonbSubWorkspace *) sbsrefstate->workspace;
-	Jsonb	   *jsonbSource;
 
 	/* Should not get here if source jsonb (or any subscript) is null */
 	Assert(!(*op->resnull));
 
-	jsonbSource = DatumGetJsonbP(*op->resvalue);
-	*op->resvalue = jsonb_get_element(jsonbSource,
-									  workspace->index,
-									  sbsrefstate->numupper,
-									  op->resnull,
-									  false);
+	if (workspace->jsonpath)
+	{
+		bool		empty = false;
+		bool		error = false;
+
+		*op->resvalue = JsonPathQuery(*op->resvalue, workspace->jsonpath,
+									  JSW_CONDITIONAL,
+									  &empty, &error, NULL,
+									  NULL);
+
+		*op->resnull = empty || error;
+	}
+	else
+	{
+		Jsonb	   *jsonbSource = DatumGetJsonbP(*op->resvalue);
+
+		*op->resvalue = jsonb_get_element(jsonbSource,
+										  workspace->index,
+										  sbsrefstate->numupper,
+										  op->resnull,
+										  false);
+	}
 }
 
 /*
@@ -364,7 +630,7 @@ jsonb_exec_setup(const SubscriptingRef *sbsref,
 {
 	JsonbSubWorkspace *workspace;
 	ListCell   *lc;
-	int			nupper = sbsref->refupperindexpr->length;
+	int			nupper = list_length(sbsref->refupperindexpr);
 	char	   *ptr;
 
 	/* Allocate type-specific workspace with space for per-subscript data */
@@ -373,6 +639,9 @@ jsonb_exec_setup(const SubscriptingRef *sbsref,
 	workspace->expectArray = false;
 	ptr = ((char *) workspace) + MAXALIGN(sizeof(JsonbSubWorkspace));
 
+	if (sbsref->refjsonbpath)
+		workspace->jsonpath = DatumGetJsonPathP(castNode(Const, sbsref->refjsonbpath)->constvalue);
+
 	/*
 	 * This coding assumes sizeof(Datum) >= sizeof(Oid), else we might
 	 * misalign the indexOid pointer
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 3d6e6bdbfd2..baa3ae97d57 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9329,10 +9329,13 @@ get_rule_expr(Node *node, deparse_context *context,
 				 * Parenthesize the argument unless it's a simple Var or a
 				 * FieldSelect.  (In particular, if it's another
 				 * SubscriptingRef, we *must* parenthesize to avoid
-				 * confusion.)
+				 * confusion.) Always add parenthesis if JSON simplified
+				 * accessor is used, for now.
 				 */
-				need_parens = !IsA(sbsref->refexpr, Var) &&
-					!IsA(sbsref->refexpr, FieldSelect);
+				need_parens = (!IsA(sbsref->refexpr, Var) &&
+					!IsA(sbsref->refexpr, FieldSelect)) ||
+						sbsref->refjsonbpath;
+
 				if (need_parens)
 					appendStringInfoChar(buf, '(');
 				get_rule_expr((Node *) sbsref->refexpr, context, showimplicit);
@@ -13005,17 +13008,35 @@ printSubscripts(SubscriptingRef *sbsref, deparse_context *context)
 	lowlist_item = list_head(sbsref->reflowerindexpr);	/* could be NULL */
 	foreach(uplist_item, sbsref->refupperindexpr)
 	{
-		appendStringInfoChar(buf, '[');
-		if (lowlist_item)
+		Node *upper = (Node *) lfirst(uplist_item);
+
+		if (upper && IsA(upper, FieldAccessorExpr))
 		{
+			FieldAccessorExpr *fae = (FieldAccessorExpr *) upper;
+
+			/* Use dot-notation for field access */
+			appendStringInfoChar(buf, '.');
+			appendStringInfoString(buf, quote_identifier(fae->fieldname));
+
+			/* Skip matching low index — field access doesn't use slices */
+			if (lowlist_item)
+				lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
+		}
+		else
+		{
+			/* Use JSONB array subscripting */
+			appendStringInfoChar(buf, '[');
+			if (lowlist_item)
+			{
+				/* If subexpression is NULL, get_rule_expr prints nothing */
+				get_rule_expr((Node *) lfirst(lowlist_item), context, false);
+				appendStringInfoChar(buf, ':');
+				lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
+			}
 			/* If subexpression is NULL, get_rule_expr prints nothing */
-			get_rule_expr((Node *) lfirst(lowlist_item), context, false);
-			appendStringInfoChar(buf, ':');
-			lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
+			get_rule_expr(upper, context, false);
+			appendStringInfoChar(buf, ']');
 		}
-		/* If subexpression is NULL, get_rule_expr prints nothing */
-		get_rule_expr((Node *) lfirst(uplist_item), context, false);
-		appendStringInfoChar(buf, ']');
 	}
 }
 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 6dfca3cb35b..7e89621bd65 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -708,18 +708,30 @@ typedef struct SubscriptingRef
 	int32		reftypmod pg_node_attr(query_jumble_ignore);
 	/* collation of result, or InvalidOid if none */
 	Oid			refcollid pg_node_attr(query_jumble_ignore);
-	/* expressions that evaluate to upper container indexes */
+
+	/*
+	 * expressions that evaluate to upper container indexes or expressions
+	 * that are collected but not evaluated when refjsonbpath is set.
+	 */
 	List	   *refupperindexpr;
 
 	/*
-	 * expressions that evaluate to lower container indexes, or NIL for single
-	 * container element.
+	 * expressions that evaluate to lower container indexes, or NIL for a
+	 * single container element, or expressions that are collected but not
+	 * evaluated when refjsonbpath is set.
 	 */
 	List	   *reflowerindexpr;
 	/* the expression that evaluates to a container value */
 	Expr	   *refexpr;
 	/* expression for the source value, or NULL if fetch */
 	Expr	   *refassgnexpr;
+
+	/*
+	 * container-specific extra information, currently used only by jsonb.
+	 * stores a JsonPath expression when jsonb dot notation is used. NULL for
+	 * simple subscripting.
+	 */
+	Node	   *refjsonbpath;
 } SubscriptingRef;
 
 /*
@@ -2371,4 +2383,40 @@ typedef struct OnConflictExpr
 	List	   *exclRelTlist;	/* tlist of the EXCLUDED pseudo relation */
 } OnConflictExpr;
 
+/*
+ * FieldAccessorExpr - represents a single object member access using dot-notation
+ *		in JSON simplified accessor syntax (e.g., jsonb_col.a).
+ *
+ * These nodes appear as list elements in SubscriptingRef.refupperindexpr to
+ * indicate JSON object key access. They are not evaluable expressions by
+ * themselves but serve as placeholders to preserve source-level syntax for
+ * rule rewriting and deparsing (e.g., in EXPLAIN and view definitions).
+ * Execution is handled by the enclosing SubscriptingRef.
+ *
+ * If dot-notation is used in a SubscriptingRef, the JSON path is represented
+ * as a flat list of FieldAccessorExpr nodes (for object field access), Const
+ * nodes (for array indexes), and NULLs (for omitted slice bounds), rather than
+ * through nested expression trees.
+ *
+ * Note: The flat representation avoids nested FieldAccessorExpr chains,
+ * simplifying evaluation and enabling standard-compliant behavior such as
+ * conditional array wrapping. This avoids the need for position-aware
+ * wrapping/unwrapping logic during execution.
+ *
+ * For example, in the expression:
+ *		('{"a": [{"b": 1}]}'::jsonb).a[0].b
+ * the SubscriptingRef will contain:
+ *		- refexpr: the base expression (the jsonb value)
+ *		- refupperindexpr: [FieldAccessorExpr("a"), Const(0),
+ *			FieldAccessorExpr("b")]
+ *		- reflowerindexpr: [NULL, NULL, NULL] (slice lower bounds not used here)
+ */
+typedef struct FieldAccessorExpr
+{
+	NodeTag		type;
+	char	   *fieldname;		/* name of the JSONB object field accessed via
+								 * dot notation */
+	Oid			faecollid pg_node_attr(query_jumble_ignore);
+}			FieldAccessorExpr;
+
 #endif							/* PRIMNODES_H */
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.c b/src/interfaces/ecpg/test/expected/sql-sqljson.c
index 39221f9ea5d..e6a7ece6dab 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.c
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.c
@@ -417,12 +417,122 @@ if (sqlca.sqlcode < 0) sqlprint();}
 	  for (int i = 0; i < sizeof(is_json); i++)
 		  printf("Found is_json[%d]: %s\n", i, is_json[i] ? "true" : "false");
 
-  { ECPGdisconnect(__LINE__, "CURRENT");
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . \"a\" )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
 #line 118 "sqljson.pgc"
 
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 118 "sqljson.pgc"
 
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb . \"a\" )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 121 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 121 "sqljson.pgc"
+
+	// error
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . a )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 124 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 124 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . a . b )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 127 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 127 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( coalesce ( json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . c ) , 'null' ) )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 130 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 130 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ 0 ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 133 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 133 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b . x )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 136 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 136 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) [ 'b' ] [ 0 ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 139 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 139 "sqljson.pgc"
+
+	printf("Found json=%s\n", json);
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 142 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 142 "sqljson.pgc"
+
+	// error
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . * )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 145 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 145 "sqljson.pgc"
+
+	// error
+
+	{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )", ECPGt_EOIT, 
+	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
+	ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 148 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 148 "sqljson.pgc"
+
+	// error
+
+  { ECPGdisconnect(__LINE__, "CURRENT");
+#line 151 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 151 "sqljson.pgc"
+
 
   return 0;
 }
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
index e55a95dd711..19f8c58af06 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
@@ -268,5 +268,105 @@ SQL error: cannot use type jsonb in RETURNING clause of JSON_SERIALIZE() on line
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_get_data on line 102: RESULT: f offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 118: query: select json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . "a" ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 118: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 118: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 118: RESULT: {"b": 1, "c": 2} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 121: query: select json ( '{"a": {"b": 1, "c": 2}}' :: jsonb . "a" ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 121: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 121: bad response - ERROR:  schema "jsonb" does not exist
+LINE 1: select json ( '{"a": {"b": 1, "c": 2}}' :: jsonb . "a" )
+                                                   ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 3F000 (sqlcode -400): schema "jsonb" does not exist on line 121
+[NO_PID]: sqlca: code: -400, state: 3F000
+SQL error: schema "jsonb" does not exist on line 121
+[NO_PID]: ecpg_execute on line 124: query: select json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . a ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 124: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 124: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 124: RESULT: {"b": 1, "c": 2} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 127: query: select json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . a . b ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 127: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 127: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 127: RESULT: 1 offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 130: query: select json ( coalesce ( json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . c ) , 'null' ) ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 130: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 130: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 130: RESULT: null offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 133: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 0 ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 133: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 133: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 133: RESULT: {"x": 1} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 136: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b . x ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 136: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 136: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 136: RESULT: [1, [12, {"y": 1}]] offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 139: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 139: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 139: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 139: RESULT: {"x": 1} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 142: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 142: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 142: bad response - ERROR:  jsonb subscript does not support slices
+LINE 1: ..., {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] )
+                                                                ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 42804 (sqlcode -400): jsonb subscript does not support slices on line 142
+[NO_PID]: sqlca: code: -400, state: 42804
+SQL error: jsonb subscript does not support slices on line 142
+[NO_PID]: ecpg_execute on line 145: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . * ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 145: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 145: bad response - ERROR:  row expansion via "*" is not supported here
+LINE 1: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x...
+                        ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 0A000 (sqlcode -400): row expansion via "*" is not supported here on line 145
+[NO_PID]: sqlca: code: -400, state: 0A000
+SQL error: row expansion via "*" is not supported here on line 145
+[NO_PID]: ecpg_execute on line 148: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 148: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_check_PQresult on line 148: bad response - ERROR:  jsonb subscript does not support slices
+LINE 1: ...x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )
+                                                                ^
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlstate 42804 (sqlcode -400): jsonb subscript does not support slices on line 148
+[NO_PID]: sqlca: code: -400, state: 42804
+SQL error: jsonb subscript does not support slices on line 148
 [NO_PID]: ecpg_finish: connection ecpg1_regression closed
 [NO_PID]: sqlca: code: 0, state: 00000
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
index 83f8df13e5a..442d36931f1 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
@@ -28,3 +28,10 @@ Found is_json[4]: false
 Found is_json[5]: false
 Found is_json[6]: true
 Found is_json[7]: false
+Found json={"b": 1, "c": 2}
+Found json={"b": 1, "c": 2}
+Found json=1
+Found json=null
+Found json={"x": 1}
+Found json=[1, [12, {"y": 1}]]
+Found json={"x": 1}
diff --git a/src/interfaces/ecpg/test/sql/sqljson.pgc b/src/interfaces/ecpg/test/sql/sqljson.pgc
index ddcbcc3b3cb..57a9bff424d 100644
--- a/src/interfaces/ecpg/test/sql/sqljson.pgc
+++ b/src/interfaces/ecpg/test/sql/sqljson.pgc
@@ -115,6 +115,39 @@ EXEC SQL END DECLARE SECTION;
 	  for (int i = 0; i < sizeof(is_json); i++)
 		  printf("Found is_json[%d]: %s\n", i, is_json[i] ? "true" : "false");
 
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}}'::jsonb)."a") INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON('{"a": {"b": 1, "c": 2}}'::jsonb."a") INTO :json;
+	// error
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}}'::jsonb).a) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}}'::jsonb).a.b) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(COALESCE(JSON(('{"a": {"b": 1, "c": 2}}'::jsonb).c), 'null')) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b[0]) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b.x) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb)['b'][0]) INTO :json;
+	printf("Found json=%s\n", json);
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b[1].x[0:]) INTO :json;
+	// error
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).*) INTO :json;
+	// error
+
+	EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb)['b'][0:]) INTO :json;
+	// error
+
   EXEC SQL DISCONNECT;
 
   return 0;
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 5a1eb18aba2..37d71b23d5d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -4989,6 +4989,12 @@ select ('123'::jsonb)['a'];
  
 (1 row)
 
+select ('123'::jsonb).a;
+ a 
+---
+ 
+(1 row)
+
 select ('123'::jsonb)[0];
  jsonb 
 -------
@@ -5001,12 +5007,24 @@ select ('123'::jsonb)[NULL];
  
 (1 row)
 
+select ('123'::jsonb).NULL;
+ null 
+------
+ 
+(1 row)
+
 select ('{"a": 1}'::jsonb)['a'];
  jsonb 
 -------
  1
 (1 row)
 
+select ('{"a": 1}'::jsonb).a;
+ a 
+---
+ 1
+(1 row)
+
 select ('{"a": 1}'::jsonb)[0];
  jsonb 
 -------
@@ -5019,6 +5037,12 @@ select ('{"a": 1}'::jsonb)['not_exist'];
  
 (1 row)
 
+select ('{"a": 1}'::jsonb)."not_exist";
+ not_exist 
+-----------
+ 
+(1 row)
+
 select ('{"a": 1}'::jsonb)[NULL];
  jsonb 
 -------
@@ -5031,6 +5055,12 @@ select ('[1, "2", null]'::jsonb)['a'];
  
 (1 row)
 
+select ('[1, "2", null]'::jsonb).a;
+ a 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[0];
  jsonb 
 -------
@@ -5043,6 +5073,12 @@ select ('[1, "2", null]'::jsonb)['1'];
  "2"
 (1 row)
 
+select ('[1, "2", null]'::jsonb)."1";
+ 1 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[1.0];
 ERROR:  subscript type numeric is not supported
 LINE 1: select ('[1, "2", null]'::jsonb)[1.0];
@@ -5072,6 +5108,12 @@ select ('[1, "2", null]'::jsonb)[1]['a'];
  
 (1 row)
 
+select ('[1, "2", null]'::jsonb)[1].a;
+ a 
+---
+ 
+(1 row)
+
 select ('[1, "2", null]'::jsonb)[1][0];
  jsonb 
 -------
@@ -5084,56 +5126,127 @@ select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['b'];
  "c"
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).b;
+  b  
+-----
+ "c"
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'];
    jsonb   
 -----------
  [1, 2, 3]
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d;
+     d     
+-----------
+ [1, 2, 3]
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'][1];
  jsonb 
 -------
  2
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d[1];
+ d 
+---
+ 2
+(1 row)
+
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d']['a'];
  jsonb 
 -------
  
 (1 row)
 
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['a'];
+ d 
+---
+ 
+(1 row)
+
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d.a;
+ a 
+---
+ 
+(1 row)
+
+select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d['a'][0];
+ d 
+---
+ 1
+(1 row)
+
+select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d.a[0];
+ a 
+---
+ 1
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1'];
      jsonb     
 ---------------
  {"a2": "aaa"}
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1;
+      a1       
+---------------
+ {"a2": "aaa"}
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2'];
  jsonb 
 -------
  "aaa"
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2;
+  a2   
+-------
+ "aaa"
+(1 row)
+
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']['a3'];
  jsonb 
 -------
  
 (1 row)
 
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2.a3;
+ a3 
+----
+ 
+(1 row)
+
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'];
          jsonb         
 -----------------------
  ["aaa", "bbb", "ccc"]
 (1 row)
 
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1;
+          b1           
+-----------------------
+ ["aaa", "bbb", "ccc"]
+(1 row)
+
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'][2];
  jsonb 
 -------
  "ccc"
 (1 row)
 
--- slices are not supported
-select ('{"a": 1}'::jsonb)['a':'b'];
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1[2];
+  b1   
+-------
+ "ccc"
+(1 row)
+
+select ('{"a": 1}'::jsonb)['a':'b']; -- fails
 ERROR:  jsonb subscript does not support slices
 LINE 1: select ('{"a": 1}'::jsonb)['a':'b'];
                                        ^
@@ -5831,3 +5944,299 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
  12345
 (1 row)
 
+-- dot notation
+CREATE TABLE test_jsonb_dot_notation AS
+SELECT '{"a": [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], "b": [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]}'::jsonb jb;
+SELECT (jb).a FROM test_jsonb_dot_notation;
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+SELECT (jb)."a" FROM test_jsonb_dot_notation; -- double quote should work
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+SELECT (jb).'a' FROM test_jsonb_dot_notation; -- single quote should not work
+ERROR:  syntax error at or near "'a'"
+LINE 1: SELECT (jb).'a' FROM test_jsonb_dot_notation;
+                    ^
+select (jb)[0].a from test_jsonb_dot_notation; -- returns same result as (jb).a
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+select (jb)[1].a from test_jsonb_dot_notation; -- returns NULL
+ a 
+---
+ 
+(1 row)
+
+SELECT (jb).b FROM test_jsonb_dot_notation;
+                         b                         
+---------------------------------------------------
+ [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]
+(1 row)
+
+SELECT (jb).c FROM test_jsonb_dot_notation;
+ c 
+---
+ 
+(1 row)
+
+SELECT (jb).a.b FROM test_jsonb_dot_notation;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+SELECT (jb).a[2].b FROM test_jsonb_dot_notation;
+  b  
+-----
+ "c"
+(1 row)
+
+SELECT (jb).a.x.y FROM test_jsonb_dot_notation;
+   y   
+-------
+ "yyy"
+(1 row)
+
+SELECT (jb).b.x.z FROM test_jsonb_dot_notation;
+   z   
+-------
+ "ZZZ"
+(1 row)
+
+SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
+ c 
+---
+ 
+(1 row)
+
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t; -- fails
+ERROR:  jsonb subscript does not support slices
+SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
+ERROR:  type jsonb is not composite
+-- explains should work
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+                  QUERY PLAN                  
+----------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation t
+   Output: (jb).a
+(2 rows)
+
+SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+                                    a                                    
+-------------------------------------------------------------------------
+ [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}]
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb).a[1]
+(2 rows)
+
+SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+ a 
+---
+ 2
+(1 row)
+
+-- views should work
+CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT (jb).a[3].x.y AS y
+   FROM test_jsonb_dot_notation
+-- mixed syntax
+DROP VIEW test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb['a'::text]).b
+(2 rows)
+
+SELECT (jb)['a'].b FROM test_jsonb_dot_notation; -- returns an array due to lax mode
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT (jb['a'::text]).b AS b
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+     b      
+------------
+ ["c", "d"]
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: ((jb).a)['b'::text]
+(2 rows)
+
+SELECT (jb).a['b'] FROM test_jsonb_dot_notation; -- returns NULL because ['b'] looks for strict match in an object
+ a 
+---
+ 
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT ((jb).a)['b'::text] AS a
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+ a 
+---
+ 
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: ((jb).b.x)['z'::text]
+(2 rows)
+
+SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation; -- warnings
+   x   
+-------
+ "ZZZ"
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT ((jb).b.x)['z'::text] AS x
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+   x   
+-------
+ "ZZZ"
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: ((jb['b'::text]).x)['z'::text]
+(2 rows)
+
+SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;  -- warnings
+   x   
+-------
+ "ZZZ"
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT ((jb['b'::text]).x)['z'::text] AS x
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+   x   
+-------
+ "ZZZ"
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (((jb).b)['x'::text]).z
+(2 rows)
+
+SELECT (jb).b['x'].z FROM test_jsonb_dot_notation; -- returns NULL
+ z 
+---
+ 
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT (((jb).b)['x'::text]).z AS z
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+ z 
+---
+ 
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: ((jb).b)['x'::text]['z'::text]
+(2 rows)
+
+SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation; -- returns NULL
+ b 
+---
+ 
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT ((jb).b)['x'::text]['z'::text] AS b
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+ b 
+---
+ 
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: (jb['b'::text]['x'::text]).z
+(2 rows)
+
+SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation; -- returns NULL
+ z 
+---
+ 
+(1 row)
+
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS
+ SELECT (jb['b'::text]['x'::text]).z AS z
+   FROM test_jsonb_dot_notation
+SELECT * from test_jsonb_dot_notation_v1;
+ z 
+---
+ 
+(1 row)
+
+DROP VIEW public.test_jsonb_dot_notation_v1;
+-- clean up
+DROP TABLE test_jsonb_dot_notation;
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 57c11acddfe..5fc53e1c9aa 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1304,33 +1304,51 @@ select jsonb_insert('{"a": {"b": "value"}}', '{a, b}', '"new_value"', true);
 
 -- jsonb subscript
 select ('123'::jsonb)['a'];
+select ('123'::jsonb).a;
 select ('123'::jsonb)[0];
 select ('123'::jsonb)[NULL];
+select ('123'::jsonb).NULL;
 select ('{"a": 1}'::jsonb)['a'];
+select ('{"a": 1}'::jsonb).a;
 select ('{"a": 1}'::jsonb)[0];
 select ('{"a": 1}'::jsonb)['not_exist'];
+select ('{"a": 1}'::jsonb)."not_exist";
 select ('{"a": 1}'::jsonb)[NULL];
 select ('[1, "2", null]'::jsonb)['a'];
+select ('[1, "2", null]'::jsonb).a;
 select ('[1, "2", null]'::jsonb)[0];
 select ('[1, "2", null]'::jsonb)['1'];
+select ('[1, "2", null]'::jsonb)."1";
 select ('[1, "2", null]'::jsonb)[1.0];
 select ('[1, "2", null]'::jsonb)[2];
 select ('[1, "2", null]'::jsonb)[3];
 select ('[1, "2", null]'::jsonb)[-2];
 select ('[1, "2", null]'::jsonb)[1]['a'];
+select ('[1, "2", null]'::jsonb)[1].a;
 select ('[1, "2", null]'::jsonb)[1][0];
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['b'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).b;
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d;
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'][1];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d[1];
 select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d']['a'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['a'];
+select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d.a;
+select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d['a'][0];
+select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d.a[0];
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1;
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2;
 select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']['a3'];
+select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2.a3;
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'];
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1;
 select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'][2];
+select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1[2];
 
--- slices are not supported
-select ('{"a": 1}'::jsonb)['a':'b'];
+select ('{"a": 1}'::jsonb)['a':'b']; -- fails
 select ('[1, "2", null]'::jsonb)[1:2];
 select ('[1, "2", null]'::jsonb)[:2];
 select ('[1, "2", null]'::jsonb)[1:];
@@ -1590,3 +1608,94 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::float8;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int2;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int4;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
+
+-- dot notation
+CREATE TABLE test_jsonb_dot_notation AS
+SELECT '{"a": [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], "b": [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]}'::jsonb jb;
+
+SELECT (jb).a FROM test_jsonb_dot_notation;
+SELECT (jb)."a" FROM test_jsonb_dot_notation; -- double quote should work
+SELECT (jb).'a' FROM test_jsonb_dot_notation; -- single quote should not work
+select (jb)[0].a from test_jsonb_dot_notation; -- returns same result as (jb).a
+select (jb)[1].a from test_jsonb_dot_notation; -- returns NULL
+SELECT (jb).b FROM test_jsonb_dot_notation;
+SELECT (jb).c FROM test_jsonb_dot_notation;
+SELECT (jb).a.b FROM test_jsonb_dot_notation;
+SELECT (jb).a[2].b FROM test_jsonb_dot_notation;
+SELECT (jb).a.x.y FROM test_jsonb_dot_notation;
+SELECT (jb).b.x.z FROM test_jsonb_dot_notation;
+SELECT (jb).a.b.c FROM test_jsonb_dot_notation;
+SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t; -- fails
+SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails
+
+-- explains should work
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+SELECT (t.jb).a FROM test_jsonb_dot_notation t;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+SELECT (jb).a[1] FROM test_jsonb_dot_notation;
+
+-- views should work
+CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+
+-- mixed syntax
+DROP VIEW test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
+SELECT (jb)['a'].b FROM test_jsonb_dot_notation; -- returns an array due to lax mode
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['a'].b FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+SELECT (jb).a['b'] FROM test_jsonb_dot_notation; -- returns NULL because ['b'] looks for strict match in an object
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).a['b'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation; -- warnings
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;  -- warnings
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+SELECT (jb).b['x'].z FROM test_jsonb_dot_notation; -- returns NULL
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b['x'].z FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation; -- returns NULL
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
+SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation; -- returns NULL
+CREATE VIEW public.test_jsonb_dot_notation_v1 AS
+SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation;
+\sv test_jsonb_dot_notation_v1
+SELECT * from test_jsonb_dot_notation_v1;
+DROP VIEW public.test_jsonb_dot_notation_v1;
+
+-- clean up
+DROP TABLE test_jsonb_dot_notation;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index a13e8162890..cc64642f399 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -805,6 +805,7 @@ FdwRoutine
 FetchDirection
 FetchDirectionKeywords
 FetchStmt
+FieldAccessorExpr
 FieldSelect
 FieldStore
 File
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v17-0003-Export-jsonPathFromParseResult.patch (2.7K, ../../CAK98qZ1FK1ZhH_uyqg02_n7Q5gnPAooC8zUdFAx3_XQiBdXo6Q@mail.gmail.com/8-v17-0003-Export-jsonPathFromParseResult.patch)
  download | inline diff:
From 8692fccb6967f79736d457142f889a73dc2d05ba Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <[email protected]>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v17 3/7] Export jsonPathFromParseResult()

This is a preparation step for a future commit that will reuse the
aforementioned function.

Authored-by: Nikita Glukhov <[email protected]>
Reviewed-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Jian He <[email protected]>
---
 src/backend/utils/adt/jsonpath.c | 19 +++++++++++++++----
 src/include/utils/jsonpath.h     |  4 ++++
 2 files changed, 19 insertions(+), 4 deletions(-)

diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index 762f7e8a09d..c83774b2a16 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -166,15 +166,13 @@ jsonpath_send(PG_FUNCTION_ARGS)
  * Converts C-string to a jsonpath value.
  *
  * Uses jsonpath parser to turn string into an AST, then
- * flattenJsonPathParseItem() does second pass turning AST into binary
+ * jsonPathFromParseResult() does second pass turning AST into binary
  * representation of jsonpath.
  */
 static Datum
 jsonPathFromCstring(char *in, int len, struct Node *escontext)
 {
 	JsonPathParseResult *jsonpath = parsejsonpath(in, len, escontext);
-	JsonPath   *res;
-	StringInfoData buf;
 
 	if (SOFT_ERROR_OCCURRED(escontext))
 		return (Datum) 0;
@@ -185,8 +183,21 @@ jsonPathFromCstring(char *in, int len, struct Node *escontext)
 				 errmsg("invalid input syntax for type %s: \"%s\"", "jsonpath",
 						in)));
 
+	return jsonPathFromParseResult(jsonpath, 4 * len, escontext);
+}
+
+/*
+ * Converts jsonpath Abstract Syntax Tree (AST) into jsonpath value in binary.
+ */
+Datum
+jsonPathFromParseResult(const JsonPathParseResult *jsonpath, int estimated_len,
+						struct Node *escontext)
+{
+	JsonPath   *res;
+	StringInfoData buf;
+
 	initStringInfo(&buf);
-	enlargeStringInfo(&buf, 4 * len /* estimation */ );
+	enlargeStringInfo(&buf, estimated_len);
 
 	appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
 
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index 23a76d233e9..0958bc22bb6 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -281,6 +281,10 @@ extern JsonPathParseResult *parsejsonpath(const char *str, int len,
 extern bool jspConvertRegexFlags(uint32 xflags, int *result,
 								 struct Node *escontext);
 
+extern Datum jsonPathFromParseResult(const JsonPathParseResult *jsonpath,
+									 int estimated_len,
+									 struct Node *escontext);
+
 /*
  * Struct for details about external variables passed into jsonpath executor
  */
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v17-0002-Allow-Generic-Type-Subscripting-to-Accept-Dot-No.patch (18.2K, ../../CAK98qZ1FK1ZhH_uyqg02_n7Q5gnPAooC8zUdFAx3_XQiBdXo6Q@mail.gmail.com/9-v17-0002-Allow-Generic-Type-Subscripting-to-Accept-Dot-No.patch)
  download | inline diff:
From 31386c23ea14e2b86d1c22829f3650a85ec42d33 Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email protected]>
Date: Fri, 5 Sep 2025 14:17:37 -0700
Subject: [PATCH v17 2/7] Allow Generic Type Subscripting to Accept Dot
 Notation (.) as Input
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This change extends generic type subscripting to recognize dot
notation (.) in addition to bracket notation ([]). While this does not
yet provide full support for dot notation, it enables subscripting
containers to process it in the future.

For now, container-specific transform functions only handle
subscripting indices and stop processing when encountering dot
notation. It is up to individual containers to decide how to transform
dot notation in subsequent updates.

This change also updates the SubscriptTransform() API to remove the
"bool isSlice" argument. Each container’s transform function now
determines slice-ness itself, since it may only process a prefix of
the indirection list. For example, array_subscript_transform() stops
at dot notation, so "isSlice" should not depend on slice specifiers
that appear afterward.

Authored-by: Nikita Glukhov <[email protected]>
Authored-by: Alexandra Wang <[email protected]>
Reviewed-by: Andrew Dunstan <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Reviewed-by: Jian He <[email protected]>
Reviewed-by: Chao Li <[email protected]>
---
 contrib/hstore/hstore_subs.c         | 11 +++--
 src/backend/parser/parse_expr.c      | 68 ++++++++++++++++++----------
 src/backend/parser/parse_node.c      | 57 ++++++++++++++---------
 src/backend/parser/parse_target.c    |  3 +-
 src/backend/utils/adt/arraysubs.c    | 40 ++++++++++++++--
 src/backend/utils/adt/jsonbsubs.c    | 16 +++++--
 src/include/nodes/subscripting.h     |  3 +-
 src/include/parser/parse_node.h      |  3 +-
 src/test/regress/expected/arrays.out | 26 +++++++++--
 src/test/regress/sql/arrays.sql      |  7 ++-
 10 files changed, 163 insertions(+), 71 deletions(-)

diff --git a/contrib/hstore/hstore_subs.c b/contrib/hstore/hstore_subs.c
index 1b29543ab67..f008e5faf03 100644
--- a/contrib/hstore/hstore_subs.c
+++ b/contrib/hstore/hstore_subs.c
@@ -42,14 +42,16 @@ static void
 hstore_subscript_transform(SubscriptingRef *sbsref,
 						   List **indirection,
 						   ParseState *pstate,
-						   bool isSlice,
 						   bool isAssignment)
 {
 	A_Indices  *ai;
 	Node	   *subexpr;
 
+	Assert(*indirection != NIL);
+	ai = linitial_node(A_Indices, *indirection);
+
 	/* We support only single-subscript, non-slice cases */
-	if (isSlice || list_length(*indirection) != 1)
+	if (list_length(*indirection) != 1 || ai->is_slice)
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("hstore allows only one subscript"),
@@ -57,8 +59,7 @@ hstore_subscript_transform(SubscriptingRef *sbsref,
 									exprLocation((Node *) *indirection))));
 
 	/* Transform the subscript expression to type text */
-	ai = linitial_node(A_Indices, *indirection);
-	Assert(ai->uidx != NULL && ai->lidx == NULL && !ai->is_slice);
+	Assert(ai->uidx != NULL && ai->lidx == NULL);
 
 	subexpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
 	/* If it's not text already, try to coerce */
@@ -82,7 +83,7 @@ hstore_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->refrestype = TEXTOID;
 	sbsref->reftypmod = -1;
 
-	*indirection = NIL;
+	*indirection = list_delete_first_n(*indirection, 1);
 }
 
 /*
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 6e8fd42c612..f8a0617f823 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -441,38 +441,59 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 	ListCell   *i;
 
 	/*
-	 * We have to split any field-selection operations apart from
-	 * subscripting.  Adjacent A_Indices nodes have to be treated as a single
+	 * Combine field names and subscripts into a single indirection list, as
+	 * some subscripting containers, such as jsonb, support field access using
+	 * dot notation. Adjacent A_Indices nodes have to be treated as a single
 	 * multidimensional subscript operation.
 	 */
 	foreach(i, ind->indirection)
 	{
 		Node	   *n = lfirst(i);
 
-		if (IsA(n, A_Indices))
+		if (IsA(n, A_Indices) || IsA(n, String))
 			subscripts = lappend(subscripts, n);
-		else if (IsA(n, A_Star))
+		else
 		{
+			Assert(IsA(n, A_Star));
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("row expansion via \"*\" is not supported here"),
 					 parser_errposition(pstate, location)));
 		}
-		else
+	}
+
+	while (subscripts)
+	{
+		/* try processing container subscripts first */
+		Node	   *newresult = (Node *)
+			transformContainerSubscripts(pstate,
+										 result,
+										 exprType(result),
+										 exprTypmod(result),
+										 &subscripts,
+										 false,
+										 true);
+
+		if (!newresult)
 		{
-			Node	   *newresult;
+			/*
+			 * generic subscripting failed; falling back to field selection
+			 * for a composite type.
+			 */
+			Node	   *n;
+
+			Assert(subscripts);
 
-			Assert(IsA(n, String));
+			n = linitial(subscripts);
 
-			/* process subscripts before this field selection */
-			while (subscripts)
-				result = (Node *) transformContainerSubscripts(pstate,
-															   result,
-															   exprType(result),
-															   exprTypmod(result),
-															   &subscripts,
-															   false);
+			if (!IsA(n, String))
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("cannot subscript type %s because it does not support subscripting",
+								format_type_be(exprType(result))),
+						 parser_errposition(pstate, exprLocation(result))));
 
+			/* try to find function for field selection */
 			newresult = ParseFuncOrColumn(pstate,
 										  list_make1(n),
 										  list_make1(result),
@@ -480,19 +501,16 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 										  NULL,
 										  false,
 										  location);
-			if (newresult == NULL)
+
+			if (!newresult)
 				unknown_attribute(pstate, result, strVal(n), location);
-			result = newresult;
+
+			/* consume field select */
+			subscripts = list_delete_first(subscripts);
 		}
+
+		result = newresult;
 	}
-	/* process trailing subscripts, if any */
-	while (subscripts)
-		result = (Node *) transformContainerSubscripts(pstate,
-													   result,
-													   exprType(result),
-													   exprTypmod(result),
-													   &subscripts,
-													   false);
 
 	return result;
 }
diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c
index f05baa50a15..d7b23688b9b 100644
--- a/src/backend/parser/parse_node.c
+++ b/src/backend/parser/parse_node.c
@@ -238,6 +238,8 @@ transformContainerType(Oid *containerType, int32 *containerTypmod)
  * containerTypMod	typmod for the container
  * indirection		Untransformed list of subscripts (must not be NIL)
  * isAssignment		True if this will become a container assignment.
+ * noError			True for return NULL with no error, if the container type
+ * 					is not subscriptable.
  */
 SubscriptingRef *
 transformContainerSubscripts(ParseState *pstate,
@@ -245,13 +247,13 @@ transformContainerSubscripts(ParseState *pstate,
 							 Oid containerType,
 							 int32 containerTypMod,
 							 List **indirection,
-							 bool isAssignment)
+							 bool isAssignment,
+							 bool noError)
 {
 	SubscriptingRef *sbsref;
 	const SubscriptRoutines *sbsroutines;
 	Oid			elementType;
-	bool		isSlice = false;
-	ListCell   *idx;
+	int			indirection_length = list_length(*indirection);
 
 	/*
 	 * Determine the actual container type, smashing any domain.  In the
@@ -267,28 +269,15 @@ transformContainerSubscripts(ParseState *pstate,
 	 */
 	sbsroutines = getSubscriptingRoutines(containerType, &elementType);
 	if (!sbsroutines)
+	{
+		if (noError)
+			return NULL;
+
 		ereport(ERROR,
 				(errcode(ERRCODE_DATATYPE_MISMATCH),
 				 errmsg("cannot subscript type %s because it does not support subscripting",
 						format_type_be(containerType)),
 				 parser_errposition(pstate, exprLocation(containerBase))));
-
-	/*
-	 * Detect whether any of the indirection items are slice specifiers.
-	 *
-	 * A list containing only simple subscripts refers to a single container
-	 * element.  If any of the items are slice specifiers (lower:upper), then
-	 * the subscript expression means a container slice operation.
-	 */
-	foreach(idx, *indirection)
-	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
-
-		if (ai->is_slice)
-		{
-			isSlice = true;
-			break;
-		}
 	}
 
 	/*
@@ -310,7 +299,33 @@ transformContainerSubscripts(ParseState *pstate,
 	 * determine the subscripting result type.
 	 */
 	sbsroutines->transform(sbsref, indirection, pstate,
-						   isSlice, isAssignment);
+						   isAssignment);
+
+	/*
+	 * Error out, if datatype failed to consume any indirection elements.
+	 */
+	if (list_length(*indirection) == indirection_length)
+	{
+		Node	   *ind = linitial(*indirection);
+
+		if (noError)
+			return NULL;
+
+		if (IsA(ind, String))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("type %s does not support dot notation",
+							format_type_be(containerType)),
+					 parser_errposition(pstate, exprLocation(containerBase))));
+		else if (IsA(ind, A_Indices))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("type %s does not support array subscripting",
+							format_type_be(containerType)),
+					 parser_errposition(pstate, exprLocation(containerBase))));
+		else
+			elog(ERROR, "invalid indirection operation: %d", nodeTag(ind));
+	}
 
 	/*
 	 * Verify we got a valid type (this defends, for example, against someone
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index a097736229a..b89736ff1ea 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -936,7 +936,8 @@ transformAssignmentSubscripts(ParseState *pstate,
 										  containerType,
 										  containerTypMod,
 										  &subscripts,
-										  true);
+										  true,
+										  false);
 
 	typeNeeded = sbsref->refrestype;
 	typmodNeeded = sbsref->reftypmod;
diff --git a/src/backend/utils/adt/arraysubs.c b/src/backend/utils/adt/arraysubs.c
index 234c2c278c1..378b6bf16cb 100644
--- a/src/backend/utils/adt/arraysubs.c
+++ b/src/backend/utils/adt/arraysubs.c
@@ -56,12 +56,38 @@ static void
 array_subscript_transform(SubscriptingRef *sbsref,
 						  List **indirection,
 						  ParseState *pstate,
-						  bool isSlice,
 						  bool isAssignment)
 {
 	List	   *upperIndexpr = NIL;
 	List	   *lowerIndexpr = NIL;
 	ListCell   *idx;
+	int			ndim;
+	bool		isSlice = false;
+
+	/*
+	 * Detect whether any of the indirection items are slice specifiers.
+	 *
+	 * A list containing only simple subscripts refers to a single container
+	 * element.  If any of the items are slice specifiers (lower:upper), then
+	 * the subscript expression means a container slice operation.
+	 */
+	foreach(idx, *indirection)
+	{
+		Node	   *ai = lfirst(idx);
+
+		/*
+		 * We should not inspect slice specifiers beyond an indirection node
+		 * type that we don't support.
+		 */
+		if (!IsA(ai, A_Indices))
+			break;
+
+		if (castNode(A_Indices, ai)->is_slice)
+		{
+			isSlice = true;
+			break;
+		}
+	}
 
 	/*
 	 * Transform the subscript expressions, and separate upper and lower
@@ -73,9 +99,14 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	 */
 	foreach(idx, *indirection)
 	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
+		A_Indices  *ai;
 		Node	   *subexpr;
 
+		if (!IsA(lfirst(idx), A_Indices))
+			break;
+
+		ai = lfirst_node(A_Indices, idx);
+
 		if (isSlice)
 		{
 			if (ai->lidx)
@@ -145,14 +176,15 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->reflowerindexpr = lowerIndexpr;
 
 	/* Verify subscript list lengths are within implementation limit */
-	if (list_length(upperIndexpr) > MAXDIM)
+	ndim = list_length(upperIndexpr);
+	if (ndim > MAXDIM)
 		ereport(ERROR,
 				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 				 errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
 						list_length(upperIndexpr), MAXDIM)));
 	/* We need not check lowerIndexpr separately */
 
-	*indirection = NIL;
+	*indirection = list_delete_first_n(*indirection, ndim);
 
 	/*
 	 * Determine the result type of the subscripting operation.  It's the same
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index 5ead693a3b2..2aa410f605b 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -43,7 +43,6 @@ static void
 jsonb_subscript_transform(SubscriptingRef *sbsref,
 						  List **indirection,
 						  ParseState *pstate,
-						  bool isSlice,
 						  bool isAssignment)
 {
 	List	   *upperIndexpr = NIL;
@@ -55,10 +54,15 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	 */
 	foreach(idx, *indirection)
 	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
+		A_Indices  *ai;
 		Node	   *subExpr;
 
-		if (isSlice)
+		if (!IsA(lfirst(idx), A_Indices))
+			break;
+
+		ai = lfirst_node(A_Indices, idx);
+
+		if (ai->is_slice)
 		{
 			Node	   *expr = ai->uidx ? ai->uidx : ai->lidx;
 
@@ -142,7 +146,7 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 			 * Slice with omitted upper bound. Should not happen as we already
 			 * errored out on slice earlier, but handle this just in case.
 			 */
-			Assert(isSlice && ai->is_slice);
+			Assert(ai->is_slice);
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
 					 errmsg("jsonb subscript does not support slices"),
@@ -160,7 +164,9 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->refrestype = JSONBOID;
 	sbsref->reftypmod = -1;
 
-	*indirection = NIL;
+	/* Remove processed elements */
+	if (upperIndexpr)
+		*indirection = list_delete_first_n(*indirection, list_length(upperIndexpr));
 }
 
 /*
diff --git a/src/include/nodes/subscripting.h b/src/include/nodes/subscripting.h
index df988a85fad..14ef414a180 100644
--- a/src/include/nodes/subscripting.h
+++ b/src/include/nodes/subscripting.h
@@ -68,7 +68,7 @@ struct SubscriptExecSteps;
  * same length as refupperindexpr for a slice operation.  Insert NULLs
  * (that is, an empty parse tree, not a null Const node) for any omitted
  * subscripts in a slice operation.  (Of course, if the transform method
- * does not care to support slicing, it can just throw an error if isSlice.)
+ * does not care to support slicing, it can just throw an error.)
  * See array_subscript_transform() for sample code.
  *
  * The transform method receives a pointer to a list of raw indirections.
@@ -100,7 +100,6 @@ struct SubscriptExecSteps;
 typedef void (*SubscriptTransform) (SubscriptingRef *sbsref,
 									List **indirection,
 									struct ParseState *pstate,
-									bool isSlice,
 									bool isAssignment);
 
 /*
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 58a4b9df157..5cc3ce58c30 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -362,7 +362,8 @@ extern SubscriptingRef *transformContainerSubscripts(ParseState *pstate,
 													 Oid containerType,
 													 int32 containerTypMod,
 													 List **indirection,
-													 bool isAssignment);
+													 bool isAssignment,
+													 bool noError);
 extern Const *make_const(ParseState *pstate, A_Const *aconst);
 
 #endif							/* PARSE_NODE_H */
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index b815473f414..78dd9e71bf3 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -1782,17 +1782,17 @@ SELECT max(f1), min(f1), max(f2), min(f2), max(f3), min(f3) FROM arraggtest;
 (1 row)
 
 -- A few simple tests for arrays of composite types
-create type comptype as (f1 int, f2 text);
+create type comptype as (f1 int, f2 text, f3 int[]);
 create table comptable (c1 comptype, c2 comptype[]);
 -- XXX would like to not have to specify row() construct types here ...
 insert into comptable
-  values (row(1,'foo'), array[row(2,'bar')::comptype, row(3,'baz')::comptype]);
+  values (row(1,'foo',array[10,20]), array[row(2,'bar',array[30,40])::comptype, row(3,'baz',array[50,60])::comptype]);
 -- check that implicitly named array type _comptype isn't a problem
 create type _comptype as enum('fooey');
 select * from comptable;
-   c1    |          c2           
----------+-----------------------
- (1,foo) | {"(2,bar)","(3,baz)"}
+        c1         |                      c2                       
+-------------------+-----------------------------------------------
+ (1,foo,"{10,20}") | {"(2,bar,\"{30,40}\")","(3,baz,\"{50,60}\")"}
 (1 row)
 
 select c2[2].f2 from comptable;
@@ -1801,6 +1801,22 @@ select c2[2].f2 from comptable;
  baz
 (1 row)
 
+select c2[2].f3 from comptable;
+   f3    
+---------
+ {50,60}
+(1 row)
+
+select c2[2].f3[1:2] from comptable;
+   f3    
+---------
+ {50,60}
+(1 row)
+
+select c2[1:2].f3[1:2] from comptable;
+ERROR:  column notation .f3 applied to type comptype[], which is not a composite type
+LINE 1: select c2[1:2].f3[1:2] from comptable;
+               ^
 drop type _comptype;
 drop table comptable;
 drop type comptype;
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 47d62c1d38d..450389831a0 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -555,19 +555,22 @@ SELECT max(f1), min(f1), max(f2), min(f2), max(f3), min(f3) FROM arraggtest;
 
 -- A few simple tests for arrays of composite types
 
-create type comptype as (f1 int, f2 text);
+create type comptype as (f1 int, f2 text, f3 int[]);
 
 create table comptable (c1 comptype, c2 comptype[]);
 
 -- XXX would like to not have to specify row() construct types here ...
 insert into comptable
-  values (row(1,'foo'), array[row(2,'bar')::comptype, row(3,'baz')::comptype]);
+  values (row(1,'foo',array[10,20]), array[row(2,'bar',array[30,40])::comptype, row(3,'baz',array[50,60])::comptype]);
 
 -- check that implicitly named array type _comptype isn't a problem
 create type _comptype as enum('fooey');
 
 select * from comptable;
 select c2[2].f2 from comptable;
+select c2[2].f3 from comptable;
+select c2[2].f3[1:2] from comptable;
+select c2[1:2].f3[1:2] from comptable;
 
 drop type _comptype;
 drop table comptable;
-- 
2.39.5 (Apple Git-154)



^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
  2025-04-23 16:54                         ` Re: SQL:2023 JSON simplified accessor support Nikita Malakhov <[email protected]>
  2025-06-23 14:34                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-06-24 13:29                             ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-07-09 08:01                               ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-07-10 08:53                                 ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-21 04:53                                   ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-22 08:19                                     ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-22 19:33                                       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-25 08:09                                         ` Re: SQL:2023 JSON simplified accessor support jian he <[email protected]>
  2025-08-26 03:52                                           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-08-29 07:22                                             ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-02 02:59                                               ` Re: SQL:2023 JSON simplified accessor support Chao Li <[email protected]>
  2025-09-03 02:16                                                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
@ 2025-09-03 07:11                                                   ` Chao Li <[email protected]>
  1 sibling, 0 replies; 67+ messages in thread

From: Chao Li @ 2025-09-03 07:11 UTC (permalink / raw)
  To: Alexandra Wang <[email protected]>; +Cc: jian he <[email protected]>; Nikita Malakhov <[email protected]>; Vik Fearing <[email protected]>; Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>



> On Sep 3, 2025, at 10:16, Alexandra Wang <[email protected]> wrote:
> 
> 
> <v15-0007-Implement-jsonb-wildcard-member-accessor.patch><v15-0001-Allow-transformation-of-only-a-sublist-of-subscr.patch><v15-0004-Extract-coerce_jsonpath_subscript.patch><v15-0006-Implement-Jsonb-subscripting-with-slicing.patch><v15-0005-Implement-read-only-dot-notation-for-jsonb.patch><v15-0003-Export-jsonPathFromParseResult.patch><v15-0002-Allow-Generic-Type-Subscripting-to-Accept-Dot-No.patch>



I have a few more other small comments:

1 - 0002
```
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 6e8fd42c612..ff104c95311 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -441,8 +441,9 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 	ListCell   *i;
 
 	/*
-	 * We have to split any field-selection operations apart from
-	 * subscripting.  Adjacent A_Indices nodes have to be treated as a single
+	 * Combine field names and subscripts into a single indirection list, as
+	 * some subscripting containers, such as jsonb, support field access using
+	 * dot notation. Adjacent A_Indices nodes have to be treated as a single
 	 * multidimensional subscript operation.
 	 */
 	foreach(i, ind->indirection)
@@ -460,19 +461,43 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		}
 		else
 		{
-			Node	   *newresult;
-
 			Assert(IsA(n, String));
+			subscripts = lappend(subscripts, n);
+		}
+	}
```

I raised this comment in my previous email, I guess you missed this one.

With this change, the 3 clauses are quite similar, the if-elseif-else can be simplified as:

     If (!IsA(n, A_Indices) && !Is_A(n, A_Start))
          Assert(IsA(n, String));
     subscripts = lappend(subscripts, n)

2 - 0001
```
diff --git a/src/include/nodes/subscripting.h b/src/include/nodes/subscripting.h
index 234e8ad8012..5d576af346f 100644
--- a/src/include/nodes/subscripting.h
+++ b/src/include/nodes/subscripting.h
@@ -71,6 +71,11 @@ struct SubscriptExecSteps;
  * does not care to support slicing, it can just throw an error if isSlice.)
  * See array_subscript_transform() for sample code.
  *
+ * The transform method receives a pointer to a list of raw indirections.
+ * This allows the method to parse a sublist of the indirections (typically
+ * the prefix) and modify the original list in place, enabling the caller to
+ * either process the remaining indirections differently or raise an error.
+ *
  * The transform method is also responsible for identifying the result type
```

This is nit comment about the wording. “This allows the method to parse a sublist..." sounds like more from the patch perspective. It’s more suitable for git commit comments, but code comment, I feel it’s better to be more general, for example:

* The transform method receives a pointer to a list of raw indirections.
 * It can parse a sublist (typically the prefix) of these indirections and
 * modify the original list in place, allowing the caller to either handle
 * the remaining indirections differently or raise an error.

3 - 0005
```
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index f1569879b52..eac31b097e4 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -3320,50 +3320,54 @@ ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref,
 								   state->steps_len - 1);
 	}
 
+			/* When slicing, individual subscript bounds can be omitted */
+			if (!e)
+			{
+				sbsrefstate->upperprovided[i] = false;
+				sbsrefstate->upperindexnull[i] = true;
+			}
+			else {
+				sbsrefstate->upperprovided[i] = true;
```

This is also a nit comment about code format. “{" should be put to the next line of “else” according to other existing code.

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/






^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-27 14:27                       ` Re: SQL:2023 JSON simplified accessor support Vik Fearing <[email protected]>
@ 2025-06-23 14:22                         ` Alexandra Wang <[email protected]>
  1 sibling, 0 replies; 67+ messages in thread

From: Alexandra Wang @ 2025-06-23 14:22 UTC (permalink / raw)
  To: Vik Fearing <[email protected]>; +Cc: Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>; jian he <[email protected]>

Hi Vik,

On Thu, Mar 27, 2025 at 3:27 PM Vik Fearing <[email protected]> wrote:

>
> I am reviewing this from a feature perspective and not from a code
> perspective.  On the whole, this looks good to me from a standards point
> of view.
>

Thank you so much for reviewing!

On Thu, Mar 27, 2025 at 3:27 PM Vik Fearing <[email protected]> wrote:

> There are two things that stand out to me about this feature:
>
>
> 1) I am not seeing the ** syntax in the standard, so does it need to be
> signaled as not supported?  Perhaps I am just overlooking something.
>

I think you’re referring to patch v11-0008, which adds the ** token in
the lexer. You’re right — the ** syntax is not mentioned at all in the
SQL standard. I added lexer support for ** based on earlier feedback
from Mark Dilger. See his comment below:

On Thu, Mar 13, 2025 at 3:02 PM Alexandra Wang <[email protected]>
wrote:

> On Tue, Mar 4, 2025 at 8:05 AM Mark Dilger <[email protected]>
> wrote:
>
>>
>>
> +SELECT (jb).a.**.x FROM test_jsonb_dot_notation; -- not supported
>> +ERROR:  syntax error at or near "**"
>> +LINE 1: SELECT (jb).a.**.x FROM test_jsonb_dot_notation;
>>
>> I wonder if it would be better to have the parser handle this case and
>> raise a ERRCODE_FEATURE_NOT_SUPPORTED instead?
>>
>
> In 0008 I added a new token named "DOUBLE_ASTERISK" to the lexers to
> represent "**". Hope this helps!
>

The ** tests are there because ** is valid in jsonpath, and can be
used in functions like json_query() and jsonb_path_query(). So I think
we should make it explicit to users that ** is not supported by dot
access.

As long as the tests make that clear, I don’t have a strong preference
whether we throw a syntax error in the lexer or a “feature not
supported” error in the parser. If we prefer a syntax error, I’m happy
to drop patch 0008.

On Thu, Mar 27, 2025 at 3:27 PM Vik Fearing <[email protected]>
wrote:

> 2) There is no support for <JSON item method>. Since this appears to be
> constructing a jsonpath query, could that not be added to the syntax and
> allow jsonpath to throw the error if the function doesn't exist?
>

You’re right — there’s currently no support for <JSON item method>. I
intentionally didn’t include it in this patch because it would again
expand the scope of work.

Supporting <JSON item method> seems non-trivial to me, because the
current design transforms expression nodes separately for each
indirection. I previously tried a patch that simply converted the full
chain of indirections into a JSON_QUERY() call in
transformIndirection(). The problem with that approach is that in
EXPLAIN output or view definitions, we’d see the rewritten
JSON_QUERY() instead of the original dot notation — which feels like a
leaky abstraction.

To support item methods, I think we’d need to add a new kind of
indirection to represent function calls. Then, we could either: a)
explicitly add each item method token in gram.y, similar to the list
of method keywords in jsonpath_gram.y; or b) allow a generic
method-call token in gram.y, and transform it into the corresponding
JsonPathItemType that jsonpath understands.

Either way, I’m happy to work on it, but would prefer to discuss and
implement it in a separate follow-up patch.

P.S. After reading your second question, your first one makes more
sense to me — I’ve already taken the easy route of not parsing item
methods and was planning to leave that work for a follow-up patch. So
maybe it’s more consistent to just let the syntax error for ** happen,
rather than explicitly throwing a “feature not supported” error?

The only difference, I think, is that we don’t want ** in simplified
accessor syntax, whereas we do want to support item methods in the
future.

Let me know what you think!

Best,
Alex


^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
@ 2025-06-28 23:52                       ` Jelte Fennema-Nio <[email protected]>
  2025-07-09 08:10                         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2 siblings, 1 reply; 67+ messages in thread

From: Jelte Fennema-Nio @ 2025-06-28 23:52 UTC (permalink / raw)
  To: Alexandra Wang <[email protected]>; +Cc: Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>; jian he <[email protected]>

On Thu, 13 Mar 2025 at 15:02, Alexandra Wang
<[email protected]> wrote:
> I have attached the new patches.

Okay I finally found the time to look at this. I created a draft PR
for pg_duckdb[1] to see if I would run into issues. There was only one
real issue I found by doing this. The .* syntax is encoded as NULL in
refupperindexpr, but that is currently already a valid representation
of a slice operation without specifying upper or lower. The easiest
way to reproduce the problem is:

CREATE TABLE arr(a int[]);
explain (verbose) SELECT a[:] FROM arr;
                          QUERY PLAN
───────────────────────────────────────────────────────────────
 Seq Scan on public.arr  (cost=0.00..23.60 rows=1360 width=32)
   Output: a.*

That last line should instead be
  Output: a[:]

So I think we need to add a new Star node type, that represents the *
literal after parsing.

I haven't looked too closely at the code yet.





^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* Re: SQL:2023 JSON simplified accessor support
  2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2024-11-21 22:46 ` Re: SQL:2023 JSON simplified accessor support Andrew Dunstan <[email protected]>
  2024-11-26 09:12   ` Re: SQL:2023 JSON simplified accessor support Peter Eisentraut <[email protected]>
  2025-02-05 07:20     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-05 13:39       ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 15:46         ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-02-27 23:23           ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-03 19:16             ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 19:43               ` Re: SQL:2023 JSON simplified accessor support Matheus Alcantara <[email protected]>
  2025-03-03 20:22                 ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-03-04 14:05                   ` Re: SQL:2023 JSON simplified accessor support Mark Dilger <[email protected]>
  2025-03-13 14:02                     ` Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
  2025-06-28 23:52                       ` Re: SQL:2023 JSON simplified accessor support Jelte Fennema-Nio <[email protected]>
@ 2025-07-09 08:10                         ` Alexandra Wang <[email protected]>
  0 siblings, 0 replies; 67+ messages in thread

From: Alexandra Wang @ 2025-07-09 08:10 UTC (permalink / raw)
  To: Jelte Fennema-Nio <[email protected]>; +Cc: Mark Dilger <[email protected]>; Matheus Alcantara <[email protected]>; Peter Eisentraut <[email protected]>; Andrew Dunstan <[email protected]>; Nikita Glukhov <[email protected]>; pgsql-hackers; David E. Wheeler <[email protected]>; jian he <[email protected]>

Hi Jelte,

On Sat, Jun 28, 2025 at 4:52 PM Jelte Fennema-Nio <[email protected]>
wrote:

> On Thu, 13 Mar 2025 at 15:02, Alexandra Wang
> <[email protected]> wrote:
> > I have attached the new patches.
>
> Okay I finally found the time to look at this. I created a draft PR
> for pg_duckdb[1] to see if I would run into issues. There was only one
> real issue I found by doing this. The .* syntax is encoded as NULL in
> refupperindexpr, but that is currently already a valid representation
> of a slice operation without specifying upper or lower. The easiest
> way to reproduce the problem is:
>
> CREATE TABLE arr(a int[]);
> explain (verbose) SELECT a[:] FROM arr;
>                           QUERY PLAN
> ───────────────────────────────────────────────────────────────
>  Seq Scan on public.arr  (cost=0.00..23.60 rows=1360 width=32)
>    Output: a.*
>
> That last line should instead be
>   Output: a[:]
>
> So I think we need to add a new Star node type, that represents the *
> literal after parsing.
>
> I haven't looked too closely at the code yet.
>

Thank you so much for testing and reviewing, I really appreciate it! I
fixed the EXPLAIN in v12 by adding a Star node, as you suggested.
Looking forward to your thoughts when you have time for a further
review!

Best,
Alex


^ permalink  raw  reply  [nested|flat] 67+ messages in thread

* [PATCH v51 01/10] Make index_concurrently_create_copy more general
@ 2026-03-24 18:02 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 67+ messages in thread

From: Álvaro Herrera @ 2026-03-24 18:02 UTC (permalink / raw)

Add a 'boolean concurrent' option, and make it work for both cases.
Also rename it to index_create_copy.

This allows it to be reused for other purposes -- specifically, for
REPACK CONCURRENTLY.

With the CONCURRENTLY option, REPACK cannot simply swap the heap file and
rebuild its indexes. Instead, it needs to build a separate set of indexes
(including system catalog entries) *before* the actual swap, to reduce the
time AccessExclusiveLock needs to be held for.  This approach is
different from what CREATE INDEX CONCURRENTLY does.

Per a suggestion from Mihail Nikalayeu.

Author: Antonin Houska <[email protected]>
Discussion: https://postgr.es/m/41104.1754922120@localhost
---
 src/backend/catalog/index.c      | 41 ++++++++++++++++++++++----------
 src/backend/commands/indexcmds.c |  9 +++----
 src/include/catalog/index.h      |  7 +++---
 3 files changed, 36 insertions(+), 21 deletions(-)

diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 1ccfa687f05..e418d67e8e4 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1289,17 +1289,17 @@ index_create(Relation heapRelation,
 }
 
 /*
- * index_concurrently_create_copy
+ * index_create_copy
  *
- * Create concurrently an index based on the definition of the one provided by
- * caller.  The index is inserted into catalogs and needs to be built later
- * on.  This is called during concurrent reindex processing.
+ * Create an index based on the definition of the one provided by caller.  The
+ * index is inserted into catalogs. If 'concurrently' is TRUE, it needs to be
+ * built later on; otherwise it's built immediately.
  *
  * "tablespaceOid" is the tablespace to use for this index.
  */
 Oid
-index_concurrently_create_copy(Relation heapRelation, Oid oldIndexId,
-							   Oid tablespaceOid, const char *newName)
+index_create_copy(Relation heapRelation, bool concurrently,
+				  Oid oldIndexId, Oid tablespaceOid, const char *newName)
 {
 	Relation	indexRelation;
 	IndexInfo  *oldInfo,
@@ -1318,6 +1318,7 @@ index_concurrently_create_copy(Relation heapRelation, Oid oldIndexId,
 	List	   *indexColNames = NIL;
 	List	   *indexExprs = NIL;
 	List	   *indexPreds = NIL;
+	int			flags = 0;
 
 	indexRelation = index_open(oldIndexId, RowExclusiveLock);
 
@@ -1328,7 +1329,7 @@ index_concurrently_create_copy(Relation heapRelation, Oid oldIndexId,
 	 * Concurrent build of an index with exclusion constraints is not
 	 * supported.
 	 */
-	if (oldInfo->ii_ExclusionOps != NULL)
+	if (oldInfo->ii_ExclusionOps != NULL && concurrently)
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("concurrent index creation for exclusion constraints is not supported")));
@@ -1384,9 +1385,7 @@ index_concurrently_create_copy(Relation heapRelation, Oid oldIndexId,
 	}
 
 	/*
-	 * Build the index information for the new index.  Note that rebuild of
-	 * indexes with exclusion constraints is not supported, hence there is no
-	 * need to fill all the ii_Exclusion* fields.
+	 * Build the index information for the new index.
 	 */
 	newInfo = makeIndexInfo(oldInfo->ii_NumIndexAttrs,
 							oldInfo->ii_NumIndexKeyAttrs,
@@ -1395,11 +1394,24 @@ index_concurrently_create_copy(Relation heapRelation, Oid oldIndexId,
 							indexPreds,
 							oldInfo->ii_Unique,
 							oldInfo->ii_NullsNotDistinct,
-							false,	/* not ready for inserts */
-							true,
+							!concurrently,	/* isready */
+							concurrently,	/* concurrent */
 							indexRelation->rd_indam->amsummarizing,
 							oldInfo->ii_WithoutOverlaps);
 
+	/* fetch exclusion constraint info if any */
+	if (indexRelation->rd_index->indisexclusion)
+	{
+		/*
+		 * XXX Beware: we're making newInfo point to oldInfo-owned memory.  It
+		 * would be more orthodox to palloc+memcpy, but we don't need that
+		 * here at present.
+		 */
+		newInfo->ii_ExclusionOps = oldInfo->ii_ExclusionOps;
+		newInfo->ii_ExclusionProcs = oldInfo->ii_ExclusionProcs;
+		newInfo->ii_ExclusionStrats = oldInfo->ii_ExclusionStrats;
+	}
+
 	/*
 	 * Extract the list of column names and the column numbers for the new
 	 * index information.  All this information will be used for the index
@@ -1436,6 +1448,9 @@ index_concurrently_create_copy(Relation heapRelation, Oid oldIndexId,
 		stattargets[i].isnull = isnull;
 	}
 
+	if (concurrently)
+		flags = INDEX_CREATE_SKIP_BUILD | INDEX_CREATE_CONCURRENT;
+
 	/*
 	 * Now create the new index.
 	 *
@@ -1459,7 +1474,7 @@ index_concurrently_create_copy(Relation heapRelation, Oid oldIndexId,
 							  indcoloptions->values,
 							  stattargets,
 							  reloptionsDatum,
-							  INDEX_CREATE_SKIP_BUILD | INDEX_CREATE_CONCURRENT,
+							  flags,
 							  0,
 							  true, /* allow table to be a system catalog? */
 							  false,	/* is_internal? */
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 373e8234794..cba379810c7 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -3989,10 +3989,11 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 			tablespaceid = indexRel->rd_rel->reltablespace;
 
 		/* Create new index definition based on given index */
-		newIndexId = index_concurrently_create_copy(heapRel,
-													idx->indexId,
-													tablespaceid,
-													concurrentName);
+		newIndexId = index_create_copy(heapRel,
+									   true,
+									   idx->indexId,
+									   tablespaceid,
+									   concurrentName);
 
 		/*
 		 * Now open the relation of the new index, a session-level lock is
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index a38e95bc0eb..ed9e4c37d27 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -101,10 +101,9 @@ extern Oid	index_create(Relation heapRelation,
 #define	INDEX_CONSTR_CREATE_REMOVE_OLD_DEPS	(1 << 4)
 #define	INDEX_CONSTR_CREATE_WITHOUT_OVERLAPS (1 << 5)
 
-extern Oid	index_concurrently_create_copy(Relation heapRelation,
-										   Oid oldIndexId,
-										   Oid tablespaceOid,
-										   const char *newName);
+extern Oid	index_create_copy(Relation heapRelation, bool concurrently,
+							  Oid oldIndexId, Oid tablespaceOid,
+							  const char *newName);
 
 extern void index_concurrently_build(Oid heapRelationId,
 									 Oid indexRelationId);
-- 
2.47.3


--r2slln3zpmilwu22
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v51-0002-Rename-cluster.c-h-repack.c-h.patch"



^ permalink  raw  reply  [nested|flat] 67+ messages in thread


end of thread, other threads:[~2026-03-24 18:02 UTC | newest]

Thread overview: 67+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2024-11-21 20:52 Re: SQL:2023 JSON simplified accessor support Alexandra Wang <[email protected]>
2024-11-21 22:46 ` Andrew Dunstan <[email protected]>
2024-11-26 09:12   ` Peter Eisentraut <[email protected]>
2025-02-05 07:20     ` Alexandra Wang <[email protected]>
2025-02-05 13:39       ` Alexandra Wang <[email protected]>
2025-02-27 15:46         ` Alexandra Wang <[email protected]>
2025-02-27 23:23           ` Alexandra Wang <[email protected]>
2025-03-03 19:16             ` Matheus Alcantara <[email protected]>
2025-03-03 19:43               ` Matheus Alcantara <[email protected]>
2025-03-03 20:22                 ` Alexandra Wang <[email protected]>
2025-03-04 14:05                   ` Mark Dilger <[email protected]>
2025-03-04 15:34                     ` Mark Dilger <[email protected]>
2025-03-04 18:13                       ` Andrew Dunstan <[email protected]>
2025-03-13 14:02                     ` Alexandra Wang <[email protected]>
2025-03-20 14:18                       ` Peter Eisentraut <[email protected]>
2025-03-27 14:27                       ` Vik Fearing <[email protected]>
2025-04-23 16:54                         ` Nikita Malakhov <[email protected]>
2025-06-23 14:34                           ` Alexandra Wang <[email protected]>
2025-06-24 13:29                             ` jian he <[email protected]>
2025-06-25 05:56                               ` jian he <[email protected]>
2025-06-25 07:40                                 ` jian he <[email protected]>
2025-06-27 02:09                                   ` jian he <[email protected]>
2025-07-09 08:01                               ` Alexandra Wang <[email protected]>
2025-07-10 08:53                                 ` jian he <[email protected]>
2025-07-10 13:34                                   ` jian he <[email protected]>
2025-07-11 05:53                                     ` jian he <[email protected]>
2025-08-21 04:53                                   ` Alexandra Wang <[email protected]>
2025-08-22 08:19                                     ` jian he <[email protected]>
2025-08-22 19:33                                       ` Alexandra Wang <[email protected]>
2025-08-25 08:09                                         ` jian he <[email protected]>
2025-08-26 03:52                                           ` Alexandra Wang <[email protected]>
2025-08-27 04:15                                             ` jian he <[email protected]>
2025-08-27 07:26                                             ` Chao Li <[email protected]>
2025-08-29 03:29                                               ` Chao Li <[email protected]>
2025-08-30 00:53                                                 ` Alexandra Wang <[email protected]>
2025-09-02 02:43                                                   ` Chao Li <[email protected]>
2025-09-02 02:46                                               ` Chao Li <[email protected]>
2025-08-29 03:42                                             ` Chao Li <[email protected]>
2025-09-01 04:11                                               ` Alexandra Wang <[email protected]>
2025-08-29 07:22                                             ` Chao Li <[email protected]>
2025-09-02 02:59                                               ` Chao Li <[email protected]>
2025-09-02 03:32                                                 ` Chao Li <[email protected]>
2025-09-02 03:53                                                   ` David G. Johnston <[email protected]>
2025-09-02 05:27                                                     ` Chao Li <[email protected]>
2025-09-03 02:20                                                   ` Alexandra Wang <[email protected]>
2025-09-03 06:56                                                     ` Chao Li <[email protected]>
2025-09-09 23:53                                                       ` Alexandra Wang <[email protected]>
2025-09-10 04:03                                                         ` Chao Li <[email protected]>
2025-09-10 23:56                                                           ` Alexandra Wang <[email protected]>
2025-09-15 18:32                                                             ` Alexandra Wang <[email protected]>
2025-09-19 20:40                                                               ` Alexandra Wang <[email protected]>
2025-09-22 03:32                                                                 ` Chao Li <[email protected]>
2025-09-22 17:31                                                                   ` Alexandra Wang <[email protected]>
2025-09-23 05:48                                                                     ` Chao Li <[email protected]>
2025-09-24 01:05                                                                       ` Alexandra Wang <[email protected]>
2025-09-24 06:37                                                                         ` Chao Li <[email protected]>
2025-12-10 15:11                                                                         ` jian he <[email protected]>
2025-09-03 02:16                                                 ` Alexandra Wang <[email protected]>
2025-09-03 03:56                                                   ` Chao Li <[email protected]>
2025-09-09 15:14                                                     ` Alexandra Wang <[email protected]>
2025-09-10 03:52                                                       ` Chao Li <[email protected]>
2025-09-10 16:40                                                         ` Alexandra Wang <[email protected]>
2025-09-03 07:11                                                   ` Chao Li <[email protected]>
2025-06-23 14:22                         ` Alexandra Wang <[email protected]>
2025-06-28 23:52                       ` Jelte Fennema-Nio <[email protected]>
2025-07-09 08:10                         ` Alexandra Wang <[email protected]>
2026-03-24 18:02 [PATCH v51 01/10] Make index_concurrently_create_copy more general Álvaro Herrera <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox