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