From 644e165748bf5c33765ddb2eae04f6cc5de41a6c Mon Sep 17 00:00:00 2001
From: Alexandra Wang <alexandra.wang.oss@gmail.com>
Date: Tue, 8 Jul 2025 22:18:07 -0700
Subject: [PATCH v23 3/3] 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;

For backward compatibility, when a field name matches an existing
function that takes jsonb as its argument (e.g., jsonb_typeof,
jsonb_array_length), the function call takes precedence. This matches
the behavior of hstore dot notation.

Implementation details:

This patch adds two new fields to SubscriptingRef:

- refprivate: Container-type-specific private data. For jsonb dot
  notation, this stores the JsonPath expression used during execution.

- refskipsubscripteval: When true, the executor will not evaluate
  refupperindexpr and reflowerindexpr. The container type's execution
callbacks handle subscript processing instead.

During query transformation, if dot-notation is present, a JSONPath
expression is constructed to represent the access chain, and the
refskipsubscripteval flag is set to true.

Then during execution, if a JSONPath expression, extracted from
refprivate, 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 <glukhov.n.a@gmail.com>
Co-authored-by: Alexandra Wang <alexandra.wang.oss@gmail.com>
Reviewed-by: Andrew Dunstan <andrew@dunslane.net>
Reviewed-by: Matheus Alcantara <matheusssilv97@gmail.com>
Reviewed-by: Mark Dilger <mark.dilger@enterprisedb.com>
Reviewed-by: Jian He <jian.universality@gmail.com>
Reviewed-by: Vik Fearing <vik@postgresfriends.org>
Reviewed-by: Nikita Malakhov <hukutoc@gmail.com>
Reviewed-by: Peter Eisentraut <peter@eisentraut.org>
Reviewed-by: Chao Li <li.evan.chao@gmail.com>
Tested-by: Jelte Fennema-Nio <postgres@jeltef.nl>
---
 doc/src/sgml/json.sgml                        | 301 ++++++++++++
 src/backend/catalog/sql_features.txt          |   4 +-
 src/backend/executor/execExpr.c               |  76 +--
 src/backend/nodes/nodeFuncs.c                 |  14 +
 src/backend/utils/adt/jsonbsubs.c             | 368 +++++++++++++--
 src/backend/utils/adt/ruleutils.c             |  43 +-
 src/include/nodes/primnodes.h                 |  66 ++-
 .../ecpg/test/expected/sql-sqljson.c          | 122 ++++-
 .../ecpg/test/expected/sql-sqljson.stderr     | 100 ++++
 .../ecpg/test/expected/sql-sqljson.stdout     |   7 +
 src/interfaces/ecpg/test/sql/sqljson.pgc      |  43 +-
 src/test/regress/expected/jsonb.out           | 437 +++++++++++++++++-
 src/test/regress/sql/jsonb.sql                | 115 +++++
 src/tools/pgindent/typedefs.list              |   1 +
 14 files changed, 1592 insertions(+), 105 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 3a8ad201607..c484abd6a37 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 088eca24021..3dd908d0173 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -3315,50 +3315,56 @@ ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref,
 								   state->steps_len - 1);
 	}
 
-	/* Evaluate upper subscripts */
-	i = 0;
-	foreach(lc, sbsref->refupperindexpr)
+	/* Evaluate upper subscripts, unless the container type handles it */
+	if (!sbsref->refskipsubscripteval)
 	{
-		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)
+	if (!sbsref->refskipsubscripteval)
 	{
-		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 d29664ca5d4..b30528051b5 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;
@@ -2128,6 +2140,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:
@@ -3007,6 +3020,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 85a8d6a0f5d..382d9a7fe9f 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -15,50 +15,333 @@
 #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 "parser/parse_func.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;
 
+/*
+ * 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:
+ * - refprivate: the generated JsonPath
+ * - refskipsubscripteval: set to true when refprivate is used
+ * - 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->refprivate = 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->refprivate = (Node *) makeConst(JSONPATHOID, -1, InvalidOid, -1, jsp, false, false);
+		sbsref->refskipsubscripteval = true;
+	}
+
+	return pathlen;
+}
 
 /*
  * 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;
 	ListCell   *idx;
 
+	/* Determine the result type of the subscripting operation; always jsonb */
+	sbsref->refrestype = JSONBOID;
+	sbsref->reftypmod = -1;
+
+	/*
+	 * For dot notation (first element is a String), check if a function with
+	 * that name exists. If so, let the parser try function call instead. This
+	 * preserves backward compatibility with existing single-argument
+	 * functions like jsonb_typeof(), jsonb_array_length(), etc.
+	 */
+	if (IsA(linitial(indirection), String))
+	{
+		char	   *fieldname = strVal(linitial(indirection));
+		Oid			argtypes[1] = {sbsref->refcontainertype};
+		Oid			funcoid;
+
+		funcoid = LookupFuncName(list_make1(makeString(pstrdup(fieldname))),
+								 1, argtypes, true);
+		if (OidIsValid(funcoid))
+			return 0;
+	}
+
+	if (jsonb_check_jsonpath_needed(indirection))
+	{
+		int			pathlen;
+
+		pathlen = jsonb_subscript_make_jsonpath(pstate, indirection, sbsref);
+		if (sbsref->refprivate)
+			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)
 	{
-		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;
 
@@ -70,23 +353,23 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 
 		if (ai->uidx)
 		{
-			Oid			subExprType = InvalidOid,
-						targetType = UNKNOWNOID;
+			Oid			subExprType;
+			Oid			targetType = InvalidOid;
 
 			subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
 			subExprType = exprType(subExpr);
 
 			if (subExprType != UNKNOWNOID)
 			{
-				Oid			targets[2] = {INT4OID, TEXTOID};
+				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.
+				 * possibly extended with jsonpath in the future.
 				 */
-				for (int i = 0; i < 2; i++)
+				for (int i = 0; i < lengthof(targets); i++)
 				{
 					if (can_coerce_type(1, &subExprType, &targets[i], COERCION_IMPLICIT))
 					{
@@ -94,7 +377,7 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 						 * One type has already succeeded, it means there are
 						 * two coercion targets possible, failure.
 						 */
-						if (targetType != UNKNOWNOID)
+						if (OidIsValid(targetType))
 							ereport(ERROR,
 									(errcode(ERRCODE_DATATYPE_MISMATCH),
 									 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
@@ -108,7 +391,7 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 				/*
 				 * No suitable types were found, failure.
 				 */
-				if (targetType == UNKNOWNOID)
+				if (!OidIsValid(targetType))
 					ereport(ERROR,
 							(errcode(ERRCODE_DATATYPE_MISMATCH),
 							 errmsg("subscript type %s is not supported", format_type_be(subExprType)),
@@ -119,7 +402,7 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 				targetType = TEXTOID;
 
 			/*
-			 * We known from can_coerce_type that coercion will succeed, so
+			 * We know 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.
@@ -130,11 +413,6 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 								  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))));
 		}
 		else
 		{
@@ -142,7 +420,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"),
@@ -156,9 +434,7 @@ 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);
 }
 
 /*
@@ -210,7 +486,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));
@@ -238,17 +514,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);
+	}
 }
 
 /*
@@ -356,7 +647,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 */
@@ -365,6 +656,9 @@ jsonb_exec_setup(const SubscriptingRef *sbsref,
 	workspace->expectArray = false;
 	ptr = ((char *) workspace) + MAXALIGN(sizeof(JsonbSubWorkspace));
 
+	if (sbsref->refprivate)
+		workspace->jsonpath = DatumGetJsonPathP(castNode(Const, sbsref->refprivate)->constvalue);
+
 	/*
 	 * This coding assumes sizeof(Datum) >= sizeof(Oid), else we might
 	 * misalign the indexOid pointer
@@ -402,8 +696,8 @@ Datum
 jsonb_subscript_handler(PG_FUNCTION_ARGS)
 {
 	static const SubscriptRoutines sbsroutines = {
-		.transform = jsonb_subscript_transform,
-		.transform_partial = NULL,
+		.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/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 033b625f3fc..581b5523833 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9342,10 +9342,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->refprivate;
+
 				if (need_parens)
 					appendStringInfoChar(buf, '(');
 				get_rule_expr((Node *) sbsref->refexpr, context, showimplicit);
@@ -13038,17 +13041,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 5211cadc258..870580b460b 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -721,18 +721,42 @@ 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 refprivate 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 refprivate 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-type-specific private data for subscripting. This field
+	 * allows subscripting implementations to store custom information needed
+	 * during execution, such as a JsonPath for jsonb dot notation. When set,
+	 * the container type's exec_setup callback is responsible for
+	 * interpreting this data. NULL for standard subscripting.
+	 */
+	Node	   *refprivate;
+
+	/*
+	 * If true, the executor will not evaluate refupperindexpr and
+	 * reflowerindexpr. The container type's execution callbacks are then
+	 * fully responsible for subscript handling. This is set by container
+	 * types that handle subscripting in a custom way (e.g., jsonb dot
+	 * notation uses a JsonPath stored in refprivate instead of evaluating
+	 * individual subscript expressions).
+	 */
+	bool		refskipsubscripteval;
 } SubscriptingRef;
 
 /*
@@ -2385,4 +2409,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..96dcc94455d 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.c
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.c
@@ -174,7 +174,7 @@ if (sqlca.sqlcode < 0) sqlprint();}
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 33 "sqljson.pgc"
 
-  // error
+  /* error */
 
   { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json_object ( 1 : 1 , '2' : null , 1 : '2' absent on null without unique keys )", ECPGt_EOIT, 
 	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
@@ -224,7 +224,7 @@ if (sqlca.sqlcode < 0) sqlprint();}
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 48 "sqljson.pgc"
 
-  // error
+  /* error */
 
   { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( '   1   ' :: jsonb )", ECPGt_EOIT, 
 	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
@@ -242,7 +242,7 @@ if (sqlca.sqlcode < 0) sqlprint();}
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 54 "sqljson.pgc"
 
-  // error
+  /* error */
 
   { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( '{\"a\": 1, \"a\": 2}' )", ECPGt_EOIT, 
 	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
@@ -262,7 +262,7 @@ if (sqlca.sqlcode < 0) sqlprint();}
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 60 "sqljson.pgc"
 
-  // error
+  /* error */
 
   { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json_scalar ( null )", ECPGt_EOIT, 
 	ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), 
@@ -390,7 +390,7 @@ if (sqlca.sqlcode < 0) sqlprint();}
 if (sqlca.sqlcode < 0) sqlprint();}
 #line 99 "sqljson.pgc"
 
-  // error
+  /* error */
 
   { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "with val ( js ) as ( values ( '{ \"a\": 1, \"b\": [{ \"a\": 1, \"b\": 0, \"a\": 2 }] }' ) ) select js is json \"IS JSON\" , js is not json \"IS NOT JSON\" , js is json value \"IS VALUE\" , js is json object \"IS OBJECT\" , js is json array \"IS ARRAY\" , js is json scalar \"IS SCALAR\" , js is json without unique keys \"WITHOUT UNIQUE\" , js is json with unique keys \"WITH UNIQUE\" from val", ECPGt_EOIT, 
 	ECPGt_bool,&(is_json[0]),(long)1,(long)1,sizeof(bool), 
@@ -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..f4dbbb18e92 100644
--- a/src/interfaces/ecpg/test/sql/sqljson.pgc
+++ b/src/interfaces/ecpg/test/sql/sqljson.pgc
@@ -31,7 +31,7 @@ EXEC SQL END DECLARE SECTION;
   printf("Found json=%s\n", json);
 
   EXEC SQL SELECT JSON_OBJECT(1: 1, '1': NULL WITH UNIQUE) INTO :json;
-  // error
+  /* error */
 
   EXEC SQL SELECT JSON_OBJECT(1: 1, '2': NULL, 1: '2' ABSENT ON NULL WITHOUT UNIQUE KEYS) INTO :json;
   printf("Found json=%s\n", json);
@@ -46,19 +46,19 @@ EXEC SQL END DECLARE SECTION;
   printf("Found json=%s\n", json);
 
   EXEC SQL SELECT JSON('{ "a" : 1 } ' FORMAT JSON ENCODING UTF8) INTO :json;
-  // error
+  /* error */
 
   EXEC SQL SELECT JSON('   1   '::jsonb) INTO :json;
   printf("Found json=%s\n", json);
 
   EXEC SQL SELECT JSON('   1   '::json WITH UNIQUE KEYS) INTO json;
-  // error
+  /* error */
 
   EXEC SQL SELECT JSON('{"a": 1, "a": 2}') INTO :json;
   printf("Found json=%s\n", json);
 
   EXEC SQL SELECT JSON('{"a": 1, "a": 2}' WITH UNIQUE KEYS) INTO :json;
-  // error
+  /* error */
 
   EXEC SQL SELECT JSON_SCALAR(NULL) INTO :json;
   printf("Found json=%s\n", json);
@@ -97,7 +97,7 @@ EXEC SQL END DECLARE SECTION;
   printf("Found json=%s\n", json);
 
   EXEC SQL SELECT JSON_SERIALIZE('{ "a" : 1 } ' RETURNING jsonb);
-  // error
+  /* error */
 
   EXEC SQL WITH val (js) AS (VALUES ('{ "a": 1, "b": [{ "a": 1, "b": 0, "a": 2 }] }'))
 	  SELECT
@@ -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 93535fd7dee..ec6bb116c94 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5021,6 +5021,12 @@ select ('123'::jsonb)['a'];
  
 (1 row)
 
+select ('123'::jsonb).a;
+ a 
+---
+ 
+(1 row)
+
 select ('123'::jsonb)[0];
  jsonb 
 -------
@@ -5033,12 +5039,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 
 -------
@@ -5051,6 +5069,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 
 -------
@@ -5063,6 +5087,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 
 -------
@@ -5075,6 +5105,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];
@@ -5104,6 +5140,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 
 -------
@@ -5116,54 +5158,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
@@ -5922,9 +6036,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;
@@ -5946,8 +6060,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 21db0db81d6..4e2f44c2813 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1310,30 +1310,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'];
@@ -1614,3 +1633,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 14dec2d49c1..dd41643947e 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -819,6 +819,7 @@ FdwRoutine
 FetchDirection
 FetchDirectionKeywords
 FetchStmt
+FieldAccessorExpr
 FieldSelect
 FieldStore
 File
-- 
2.50.1 (Apple Git-155)

