From b8a785c71d4fca8aa5660b6be91b5637c00f8f6f Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Thu, 6 Apr 2017 22:36:05 +0300
Subject: [PATCH v1 4/8] Add jsonpath array constructors

---
 doc/src/sgml/func.sgml                       |  7 ++++
 src/backend/utils/adt/jsonpath.c             | 30 ++++++++++++---
 src/backend/utils/adt/jsonpath_exec.c        | 19 ++++++++++
 src/backend/utils/adt/jsonpath_gram.y        |  2 +
 src/include/utils/jsonpath.h                 |  1 +
 src/test/regress/expected/jsonb_jsonpath.out | 57 ++++++++++++++++++++++++++++
 src/test/regress/expected/jsonpath.out       | 12 ++++++
 src/test/regress/sql/jsonb_jsonpath.sql      | 11 ++++++
 src/test/regress/sql/jsonpath.sql            |  3 ++
 9 files changed, 136 insertions(+), 6 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9defd1a..7347e71 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -12632,6 +12632,13 @@ table2-mapping
         <entry><literal>pg $[*], 4, 5</literal></entry>
         <entry><literal>1, 2, 3, 4, 5</literal></entry>
        </row>
+       <row>
+        <entry>Array constructor</entry>
+        <entry>Construct a JSON array by enumeration of its elements enclosed in brackets</entry>
+        <entry><literal>[1, 2, 3]</literal></entry>
+        <entry><literal>pg [$[*], 4, 5]</literal></entry>
+        <entry><literal>[1, 2, 3, 4, 5]</literal></entry>
+       </row>
       </tbody>
      </tgroup>
     </table>
diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index 0fb4257..16031ba 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -368,13 +368,19 @@ flattenJsonPathParseItem(JsonPathEncodingContext *cxt, JsonPathParseItem *item,
 		case jpiMinus:
 		case jpiExists:
 		case jpiDatetime:
+		case jpiArray:
 			{
 				int32		arg = reserveSpaceForItemPointer(buf);
 
-				chld = !item->value.arg ? pos :
-					flattenJsonPathParseItem(cxt, item->value.arg,
-											 nestingLevel + argNestingLevel,
-											 insideArraySubscript);
+				if (item->type == jpiArray)
+					checkJsonPathExtensionsEnabled(cxt, item->type);
+
+				if (!item->value.arg)
+					break;
+
+				chld = flattenJsonPathParseItem(cxt, item->value.arg,
+												nestingLevel + argNestingLevel,
+												insideArraySubscript);
 				*(int32 *) (buf->data + arg) = chld - pos;
 			}
 			break;
@@ -780,6 +786,15 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
 			if (printBracketes || jspHasNext(v))
 				appendStringInfoChar(buf, ')');
 			break;
+		case jpiArray:
+			appendStringInfoChar(buf, '[');
+			if (v->content.arg)
+			{
+				jspGetArg(v, &elem);
+				printJsonPathItem(buf, &elem, false, false);
+			}
+			appendStringInfoChar(buf, ']');
+			break;
 		default:
 			elog(ERROR, "unrecognized jsonpath item type: %d", v->type);
 	}
@@ -979,6 +994,7 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
 		case jpiMinus:
 		case jpiFilter:
 		case jpiDatetime:
+		case jpiArray:
 			read_int32(v->content.arg, base, pos);
 			break;
 		case jpiIndexArray:
@@ -1009,7 +1025,8 @@ jspGetArg(JsonPathItem *v, JsonPathItem *a)
 		   v->type == jpiExists ||
 		   v->type == jpiPlus ||
 		   v->type == jpiMinus ||
-		   v->type == jpiDatetime);
+		   v->type == jpiDatetime ||
+		   v->type == jpiArray);
 
 	jspInitByBuffer(a, v->base, v->content.arg);
 }
@@ -1060,7 +1077,8 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a)
 			   v->type == jpiDatetime ||
 			   v->type == jpiKeyValue ||
 			   v->type == jpiStartsWith ||
-			   v->type == jpiSequence);
+			   v->type == jpiSequence ||
+			   v->type == jpiArray);
 
 		if (a)
 			jspInitByBuffer(a, v->base, v->nextPos);
diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
index 1e31d3c..1dee30a 100644
--- a/src/backend/utils/adt/jsonpath_exec.c
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -1175,6 +1175,25 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
 				break;
 			}
 
+		case jpiArray:
+			{
+				JsonValueList list = {0};
+				JsonbValue *arr;
+
+				if (jsp->content.arg)
+				{
+					jspGetArg(jsp, &elem);
+					res = executeItem(cxt, &elem, jb, &list);
+
+					if (jperIsError(res))
+						break;
+				}
+
+				arr = wrapItemsInArray(&list);
+				res = executeNextItem(cxt, jsp, NULL, arr, found, false);
+				break;
+			}
+
 		default:
 			elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type);
 	}
diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y
index 1de6f5a..3507c1f 100644
--- a/src/backend/utils/adt/jsonpath_gram.y
+++ b/src/backend/utils/adt/jsonpath_gram.y
@@ -217,6 +217,8 @@ path_primary:
 	| '@'							{ $$ = makeItemType(jpiCurrent); }
 	| LAST_P						{ $$ = makeItemType(jpiLast); }
 	| '(' expr_seq ')'				{ $$ = $2; }
+	| '[' ']'						{ $$ = makeItemUnary(jpiArray, NULL); }
+	| '[' expr_or_seq ']'			{ $$ = makeItemUnary(jpiArray, $2); }
 	;
 
 accessor_expr:
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index 4cbdce3..1b2b964 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -88,6 +88,7 @@ typedef enum JsonPathItemType
 	jpiStartsWith,				/* STARTS WITH predicate */
 	jpiLikeRegex,				/* LIKE_REGEX predicate */
 	jpiSequence,				/* sequence constructor: 'expr, ...' */
+	jpiArray,					/* array constructor: '[expr, ...]' */
 } JsonPathItemType;
 
 /* XQuery regex mode flags for LIKE_REGEX predicate */
diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out
index c5252ad..cd56876 100644
--- a/src/test/regress/expected/jsonb_jsonpath.out
+++ b/src/test/regress/expected/jsonb_jsonpath.out
@@ -2199,6 +2199,12 @@ SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*] ? (@.a > 10)');
 ------------------
 (0 rows)
 
+SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', 'pg [$[*].a]');
+ jsonb_path_query 
+------------------
+ [1, 2]
+(1 row)
+
 SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}, {}]', 'strict $[*].a');
 ERROR:  JSON object does not contain key "a"
 SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a');
@@ -2231,6 +2237,12 @@ SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].
  []
 (1 row)
 
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', 'pg [$[*].a]');
+ jsonb_path_query_array 
+------------------------
+ [[1, 2]]
+(1 row)
+
 SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}, {}]', 'strict $[*].a');
 ERROR:  JSON object does not contain key "a"
 SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}, {}]', 'strict $[*].a', silent => true);
@@ -2578,3 +2590,48 @@ select jsonb_path_query('[1,2,3,4,5]', 'pg $[(0, $[*], 5) ? (@ == 3)]');
 
 select jsonb_path_query('[1,2,3,4,5]', 'pg $[(0, $[*], 3) ? (@ == 3)]');
 ERROR:  jsonpath array subscript is not a single numeric value
+-- extension: array constructors
+select jsonb_path_query('[1, 2, 3]', 'pg []');
+ jsonb_path_query 
+------------------
+ []
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', 'pg [1, 2, $[*], 4, 5]');
+   jsonb_path_query    
+-----------------------
+ [1, 2, 1, 2, 3, 4, 5]
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', 'pg [1, 2, $[*], 4, 5][*]');
+ jsonb_path_query 
+------------------
+ 1
+ 2
+ 1
+ 2
+ 3
+ 4
+ 5
+(7 rows)
+
+select jsonb_path_query('[1, 2, 3]', 'pg [(1, (2, $[*])), (4, 5)]');
+   jsonb_path_query    
+-----------------------
+ [1, 2, 1, 2, 3, 4, 5]
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', 'pg [[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]');
+       jsonb_path_query        
+-------------------------------
+ [[1, 2], [1, 2, 3, 4], 5, []]
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', 'pg strict [1, 2, $[*].a, 4, 5]');
+ERROR:  jsonpath member accessor can only be applied to an object
+select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', 'pg [$[*][*] ? (@ > 3)]');
+ jsonb_path_query 
+------------------
+ [4, 5, 6, 7]
+(1 row)
+
diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out
index f6a132c..e212deb 100644
--- a/src/test/regress/expected/jsonpath.out
+++ b/src/test/regress/expected/jsonpath.out
@@ -604,6 +604,18 @@ select 'pg $[(1, (2, $.a)), 3, (4, 5)]'::jsonpath;
  pg $[(1, (2, $."a")),3,(4, 5)]
 (1 row)
 
+select 'pg []'::jsonpath;
+ jsonpath 
+----------
+ pg []
+(1 row)
+
+select 'pg [[1, 2], ([(3, 4, 5), 6], []), $.a[*]]'::jsonpath;
+                  jsonpath                   
+---------------------------------------------
+ pg [[1, 2], ([(3, 4, 5), 6], []), $."a"[*]]
+(1 row)
+
 select '$ ? (@.a < 1)'::jsonpath;
    jsonpath    
 ---------------
diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql
index 0619ade..be5c7c8 100644
--- a/src/test/regress/sql/jsonb_jsonpath.sql
+++ b/src/test/regress/sql/jsonb_jsonpath.sql
@@ -525,6 +525,7 @@ set time zone default;
 
 SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*]');
 SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*] ? (@.a > 10)');
+SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', 'pg [$[*].a]');
 
 SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}, {}]', 'strict $[*].a');
 SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a');
@@ -532,6 +533,7 @@ SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ == 1)');
 SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ > 10)');
 SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', vars => '{"min": 1, "max": 4}');
 SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', vars => '{"min": 3, "max": 4}');
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', 'pg [$[*].a]');
 
 SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}, {}]', 'strict $[*].a');
 SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}, {}]', 'strict $[*].a', silent => true);
@@ -587,3 +589,12 @@ select jsonb_path_query('[1,2,3,4,5]', 'pg -(10, 20, $[1 to 3], 30)');
 select jsonb_path_query('[1,2,3,4,5]', 'pg lax (10, 20.5, $[1 to 3], "30").double()');
 select jsonb_path_query('[1,2,3,4,5]', 'pg $[(0, $[*], 5) ? (@ == 3)]');
 select jsonb_path_query('[1,2,3,4,5]', 'pg $[(0, $[*], 3) ? (@ == 3)]');
+
+-- extension: array constructors
+select jsonb_path_query('[1, 2, 3]', 'pg []');
+select jsonb_path_query('[1, 2, 3]', 'pg [1, 2, $[*], 4, 5]');
+select jsonb_path_query('[1, 2, 3]', 'pg [1, 2, $[*], 4, 5][*]');
+select jsonb_path_query('[1, 2, 3]', 'pg [(1, (2, $[*])), (4, 5)]');
+select jsonb_path_query('[1, 2, 3]', 'pg [[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]');
+select jsonb_path_query('[1, 2, 3]', 'pg strict [1, 2, $[*].a, 4, 5]');
+select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', 'pg [$[*][*] ? (@ > 3)]');
diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql
index 2a53168..23ae376 100644
--- a/src/test/regress/sql/jsonpath.sql
+++ b/src/test/regress/sql/jsonpath.sql
@@ -113,6 +113,9 @@ select 'pg (1, 2, $.a) == 5'::jsonpath;
 select 'pg $[(1, 2, $.a) to (3, 4)]'::jsonpath;
 select 'pg $[(1, (2, $.a)), 3, (4, 5)]'::jsonpath;
 
+select 'pg []'::jsonpath;
+select 'pg [[1, 2], ([(3, 4, 5), 6], []), $.a[*]]'::jsonpath;
+
 select '$ ? (@.a < 1)'::jsonpath;
 select '$ ? (@.a < -1)'::jsonpath;
 select '$ ? (@.a < +1)'::jsonpath;
-- 
2.7.4

