From b376b023aed5b4769cb29daa7f55e6eb22b90afd Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Sat, 1 Apr 2023 23:15:26 +0300 Subject: [PATCH v7 5/5] Allow processing of .* by generic subscripting --- src/backend/parser/gram.y | 2 + src/backend/parser/parse_expr.c | 34 +++-- src/backend/parser/parse_target.c | 60 ++++++--- src/backend/utils/adt/ruleutils.c | 6 +- src/include/parser/parse_expr.h | 3 + src/test/regress/expected/jsonb.out | 195 +++++++++++++++++++--------- 6 files changed, 206 insertions(+), 94 deletions(-) diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index d7f9c00c409..5f69ac661bd 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -18959,6 +18959,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) @@ -18969,6 +18970,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 afe953fdbea..b0c38529872 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); @@ -453,12 +453,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)); @@ -487,7 +482,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", @@ -513,6 +522,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 5e126145ea5..965e2f06e7b 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; } } @@ -1348,22 +1360,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); } /* diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index af5417d0859..10006b749dc 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -12919,7 +12919,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 14123929475..9057186bdf3 100644 --- a/src/test/regress/expected/jsonb.out +++ b/src/test/regress/expected/jsonb.out @@ -5891,19 +5891,39 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8; 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).* FROM test_jsonb_dot_notation; -ERROR: type jsonb is not composite + jb +------------------------------------------------------------------------------------------------------------------------------ + [[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; -ERROR: type jsonb is not composite + jb +------------------------------------------------------------------------------------------------------------------------------ + [[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; -ERROR: type jsonb is not composite + jb +------------------------------------------------------------------------------------------------------------------------------ + [[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; -ERROR: type jsonb is not composite + jb +------------------------------------------------------------------------------------------------------------------------------ + [[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; -ERROR: type jsonb is not composite + jb +------------------------------------------------------------------------------------------------------------------------------ + [[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; a ------------------------------------------------------------------------- @@ -5935,75 +5955,120 @@ SELECT (jb).a.b FROM test_jsonb_dot_notation; (1 row) SELECT (jb).a.* FROM test_jsonb_dot_notation; -ERROR: type jsonb is not composite + a +------------------------------------------- + ["c", "d", "f", {"y": "yyy", "z": "zzz"}] +(1 row) + SELECT (jb).a.*.b FROM test_jsonb_dot_notation; -ERROR: improper use of "*" at or near "FROM" -LINE 1: SELECT (jb).a.*.b FROM test_jsonb_dot_notation; - ^ + b +--- + +(1 row) + SELECT (jb).a.*.x FROM test_jsonb_dot_notation; -ERROR: improper use of "*" at or near "FROM" -LINE 1: SELECT (jb).a.*.x FROM test_jsonb_dot_notation; - ^ + x +--- + +(1 row) + SELECT (jb).a.*.y FROM test_jsonb_dot_notation; -ERROR: improper use of "*" at or near "FROM" -LINE 1: SELECT (jb).a.*.y FROM test_jsonb_dot_notation; - ^ + y +------- + "yyy" +(1 row) + SELECT (jb).a.*.* FROM test_jsonb_dot_notation; -ERROR: improper use of "*" at or near "FROM" -LINE 1: SELECT (jb).a.*.* FROM test_jsonb_dot_notation; - ^ + a +---------------- + ["yyy", "zzz"] +(1 row) + SELECT (jb).*.x FROM test_jsonb_dot_notation; -ERROR: improper use of "*" at or near "FROM" -LINE 1: 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; -ERROR: improper use of "*" at or near "FROM" -LINE 1: 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; -ERROR: row expansion via "*" is not supported here -LINE 1: SELECT ((jb).*).x FROM test_jsonb_dot_notation t; - ^ + x +--- + +(1 row) + SELECT ((jb).*).x FROM test_jsonb_dot_notation t; -ERROR: row expansion via "*" is not supported here -LINE 1: SELECT ((jb).*).x FROM test_jsonb_dot_notation t; - ^ + x +--- + +(1 row) + SELECT ((jb).*)[:].x FROM test_jsonb_dot_notation t; -ERROR: row expansion via "*" is not supported here -LINE 1: 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; -ERROR: improper use of "*" at or near "FROM" -LINE 1: 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; -ERROR: improper use of "*" at or near "FROM" -LINE 1: SELECT (jb).*.x.* FROM test_jsonb_dot_notation; - ^ + x +------------------------------ + ["yyy", "zzz", "YYY", "ZZZ"] +(1 row) + SELECT (jb).*.x.y FROM test_jsonb_dot_notation; -ERROR: improper use of "*" at or near "FROM" -LINE 1: SELECT (jb).*.x.y FROM test_jsonb_dot_notation; - ^ + y +---------------- + ["yyy", "YYY"] +(1 row) + SELECT (jb).*.x.z FROM test_jsonb_dot_notation; -ERROR: improper use of "*" at or near "FROM" -LINE 1: SELECT (jb).*.x.z FROM test_jsonb_dot_notation; - ^ + z +---------------- + ["zzz", "ZZZ"] +(1 row) + SELECT (jb).*.*.y FROM test_jsonb_dot_notation; -ERROR: improper use of "*" at or near "FROM" -LINE 1: SELECT (jb).*.*.y FROM test_jsonb_dot_notation; - ^ + y +---------------- + ["yyy", "YYY"] +(1 row) + SELECT (jb).*.*.* FROM test_jsonb_dot_notation; -ERROR: improper use of "*" at or near "FROM" -LINE 1: SELECT (jb).*.*.* FROM test_jsonb_dot_notation; - ^ + jb +------------------------------ + ["yyy", "zzz", "YYY", "ZZZ"] +(1 row) + SELECT (jb).*.*.*.* FROM test_jsonb_dot_notation; -ERROR: improper use of "*" at or near "FROM" -LINE 1: SELECT (jb).*.*.*.* FROM test_jsonb_dot_notation; - ^ + jb +---- + +(1 row) + SELECT (jb).a.b.c.* FROM test_jsonb_dot_notation; -ERROR: type jsonb is not composite + c +--- + +(1 row) + 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) + EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a FROM test_jsonb_dot_notation; QUERY PLAN -------------------------------------------- @@ -6019,10 +6084,16 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation; (2 rows) EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*['b'] FROM test_jsonb_dot_notation; -ERROR: improper use of "*" at or near "FROM" -LINE 1: EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*['b'] FROM test_... - ^ + QUERY PLAN +-------------------------------------------- + Seq Scan on public.test_jsonb_dot_notation + Output: jb.a.*['b'::text] +(2 rows) + EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:2]['b'].b FROM test_jsonb_dot_notation; -ERROR: improper use of "*" at or near "FROM" -LINE 1: ... (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:2]['b'].b FROM test_... - ^ + QUERY PLAN +-------------------------------------------- + Seq Scan on public.test_jsonb_dot_notation + Output: jb.a.*[1:2][:'b'::text].b +(2 rows) + -- 2.39.5 (Apple Git-154)