From 2d32ec307203bdc07013c57d9f0cd89adcbd4b35 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 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 <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: Mark Dilger <mark.dilger@enterprisedb.com>
Tested-by: Jian He <jian.universality@gmail.com>
---
 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)

