From: Nikita Glukhov 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 pg $[*], 4, 5 1, 2, 3, 4, 5 + + Array constructor + Construct a JSON array by enumeration of its elements enclosed in brackets + [1, 2, 3] + pg [$[*], 4, 5] + [1, 2, 3, 4, 5] + 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 --------------574DCE16CAD28E71E2083DCD Content-Type: text/x-patch; name="v1-0005-Add-jsonpath-object-constructors.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="v1-0005-Add-jsonpath-object-constructors.patch"