1
0
mirror of https://github.com/postgres/postgres.git synced 2025-08-31 17:02:12 +03:00

Allow an alias to be attached to a JOIN ... USING

This allows something like

    SELECT ... FROM t1 JOIN t2 USING (a, b, c) AS x

where x has the columns a, b, c and unlike a regular alias it does not
hide the range variables of the tables being joined t1 and t2.

Per SQL:2016 feature F404 "Range variable for common column names".

Reviewed-by: Vik Fearing <vik.fearing@2ndquadrant.com>
Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
Discussion: https://www.postgresql.org/message-id/flat/454638cf-d563-ab76-a585-2564428062af@2ndquadrant.com
This commit is contained in:
Peter Eisentraut
2021-03-31 17:09:24 +02:00
parent 27e1f14563
commit 055fee7eb4
22 changed files with 315 additions and 29 deletions

View File

@@ -1744,6 +1744,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
NIL,
NIL,
NULL,
NULL,
false);
sv_namespace = pstate->p_namespace;

View File

@@ -509,7 +509,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> sub_type opt_materialized
%type <value> NumericOnly
%type <list> NumericOnly_list
%type <alias> alias_clause opt_alias_clause
%type <alias> alias_clause opt_alias_clause opt_alias_clause_for_join_using
%type <list> func_alias_clause
%type <sortby> sortby
%type <ielem> index_elem index_elem_options
@@ -12144,6 +12144,7 @@ joined_table:
n->larg = $1;
n->rarg = $4;
n->usingClause = NIL;
n->join_using_alias = NULL;
n->quals = NULL;
$$ = n;
}
@@ -12155,9 +12156,16 @@ joined_table:
n->larg = $1;
n->rarg = $4;
if ($5 != NULL && IsA($5, List))
n->usingClause = (List *) $5; /* USING clause */
{
/* USING clause */
n->usingClause = linitial_node(List, castNode(List, $5));
n->join_using_alias = lsecond_node(Alias, castNode(List, $5));
}
else
n->quals = $5; /* ON clause */
{
/* ON clause */
n->quals = $5;
}
$$ = n;
}
| table_ref JOIN table_ref join_qual
@@ -12169,9 +12177,16 @@ joined_table:
n->larg = $1;
n->rarg = $3;
if ($4 != NULL && IsA($4, List))
n->usingClause = (List *) $4; /* USING clause */
{
/* USING clause */
n->usingClause = linitial_node(List, castNode(List, $4));
n->join_using_alias = lsecond_node(Alias, castNode(List, $4));
}
else
n->quals = $4; /* ON clause */
{
/* ON clause */
n->quals = $4;
}
$$ = n;
}
| table_ref NATURAL join_type JOIN table_ref
@@ -12182,6 +12197,7 @@ joined_table:
n->larg = $1;
n->rarg = $5;
n->usingClause = NIL; /* figure out which columns later... */
n->join_using_alias = NULL;
n->quals = NULL; /* fill later */
$$ = n;
}
@@ -12194,6 +12210,7 @@ joined_table:
n->larg = $1;
n->rarg = $4;
n->usingClause = NIL; /* figure out which columns later... */
n->join_using_alias = NULL;
n->quals = NULL; /* fill later */
$$ = n;
}
@@ -12228,6 +12245,22 @@ opt_alias_clause: alias_clause { $$ = $1; }
| /*EMPTY*/ { $$ = NULL; }
;
/*
* The alias clause after JOIN ... USING only accepts the AS ColId spelling,
* per SQL standard. (The grammar could parse the other variants, but they
* don't seem to be useful, and it might lead to parser problems in the
* future.)
*/
opt_alias_clause_for_join_using:
AS ColId
{
$$ = makeNode(Alias);
$$->aliasname = $2;
/* the column name list will be inserted later */
}
| /*EMPTY*/ { $$ = NULL; }
;
/*
* func_alias_clause can include both an Alias and a coldeflist, so we make it
* return a 2-element list that gets disassembled by calling production.
@@ -12272,15 +12305,24 @@ opt_outer: OUTER_P
/* JOIN qualification clauses
* Possibilities are:
* USING ( column list ) allows only unqualified column names,
* USING ( column list ) [ AS alias ]
* allows only unqualified column names,
* which must match between tables.
* ON expr allows more general qualifications.
*
* We return USING as a List node, while an ON-expr will not be a List.
* We return USING as a two-element List (the first item being a sub-List
* of the common column names, and the second either an Alias item or NULL).
* An ON-expr will not be a List, so it can be told apart that way.
*/
join_qual: USING '(' name_list ')' { $$ = (Node *) $3; }
| ON a_expr { $$ = $2; }
join_qual: USING '(' name_list ')' opt_alias_clause_for_join_using
{
$$ = (Node *) list_make2($3, $5);
}
| ON a_expr
{
$$ = $2;
}
;

View File

@@ -1265,6 +1265,13 @@ transformFromClauseItem(ParseState *pstate, Node *n,
j->usingClause = rlist;
}
/*
* If a USING clause alias was specified, save the USING columns as
* its column list.
*/
if (j->join_using_alias)
j->join_using_alias->colnames = j->usingClause;
/*
* Now transform the join qualifications, if any.
*/
@@ -1460,6 +1467,7 @@ transformFromClauseItem(ParseState *pstate, Node *n,
res_colvars,
l_colnos,
r_colnos,
j->join_using_alias,
j->alias,
true);
@@ -1493,6 +1501,30 @@ transformFromClauseItem(ParseState *pstate, Node *n,
pstate->p_joinexprs = lappend(pstate->p_joinexprs, j);
Assert(list_length(pstate->p_joinexprs) == j->rtindex);
/*
* If the join has a USING alias, build a ParseNamespaceItem for that
* and add it to the list of nsitems in the join's input.
*/
if (j->join_using_alias)
{
ParseNamespaceItem *jnsitem;
jnsitem = (ParseNamespaceItem *) palloc(sizeof(ParseNamespaceItem));
jnsitem->p_names = j->join_using_alias;
jnsitem->p_rte = nsitem->p_rte;
jnsitem->p_rtindex = nsitem->p_rtindex;
/* no need to copy the first N columns, just use res_nscolumns */
jnsitem->p_nscolumns = res_nscolumns;
/* set default visibility flags; might get changed later */
jnsitem->p_rel_visible = true;
jnsitem->p_cols_visible = true;
jnsitem->p_lateral_only = false;
jnsitem->p_lateral_ok = true;
/* Per SQL, we must check for alias conflicts */
checkNameSpaceConflicts(pstate, list_make1(jnsitem), my_namespace);
my_namespace = lappend(my_namespace, jnsitem);
}
/*
* Prepare returned namespace list. If the JOIN has an alias then it
* hides the contained RTEs completely; otherwise, the contained RTEs

View File

@@ -2512,26 +2512,61 @@ static Node *
transformWholeRowRef(ParseState *pstate, ParseNamespaceItem *nsitem,
int sublevels_up, int location)
{
Var *result;
/*
* Build the appropriate referencing node. Note that if the RTE is a
* function returning scalar, we create just a plain reference to the
* function value, not a composite containing a single column. This is
* pretty inconsistent at first sight, but it's what we've done
* historically. One argument for it is that "rel" and "rel.*" mean the
* same thing for composite relations, so why not for scalar functions...
* Build the appropriate referencing node. Normally this can be a
* whole-row Var, but if the nsitem is a JOIN USING alias then it contains
* only a subset of the columns of the underlying join RTE, so that will
* not work. Instead we immediately expand the reference into a RowExpr.
* Since the JOIN USING's common columns are fully determined at this
* point, there seems no harm in expanding it now rather than during
* planning.
*
* Note that if the RTE is a function returning scalar, we create just a
* plain reference to the function value, not a composite containing a
* single column. This is pretty inconsistent at first sight, but it's
* what we've done historically. One argument for it is that "rel" and
* "rel.*" mean the same thing for composite relations, so why not for
* scalar functions...
*/
result = makeWholeRowVar(nsitem->p_rte, nsitem->p_rtindex,
sublevels_up, true);
if (nsitem->p_names == nsitem->p_rte->eref)
{
Var *result;
/* location is not filled in by makeWholeRowVar */
result->location = location;
result = makeWholeRowVar(nsitem->p_rte, nsitem->p_rtindex,
sublevels_up, true);
/* mark relation as requiring whole-row SELECT access */
markVarForSelectPriv(pstate, result);
/* location is not filled in by makeWholeRowVar */
result->location = location;
return (Node *) result;
/* mark relation as requiring whole-row SELECT access */
markVarForSelectPriv(pstate, result);
return (Node *) result;
}
else
{
RowExpr *rowexpr;
List *fields;
/*
* We want only as many columns as are listed in p_names->colnames,
* and we should use those names not whatever possibly-aliased names
* are in the RTE. We needn't worry about marking the RTE for SELECT
* access, as the common columns are surely so marked already.
*/
expandRTE(nsitem->p_rte, nsitem->p_rtindex,
sublevels_up, location, false,
NULL, &fields);
rowexpr = makeNode(RowExpr);
rowexpr->args = list_truncate(fields,
list_length(nsitem->p_names->colnames));
rowexpr->row_typeid = RECORDOID;
rowexpr->row_format = COERCE_IMPLICIT_CAST;
rowexpr->colnames = copyObject(nsitem->p_names->colnames);
rowexpr->location = location;
return (Node *) rowexpr;
}
}
/*

View File

@@ -753,6 +753,12 @@ scanNSItemForColumn(ParseState *pstate, ParseNamespaceItem *nsitem,
* else return InvalidAttrNumber.
* If the name proves ambiguous within this RTE, raise error.
*
* Actually, we only search the names listed in "eref". This can be either
* rte->eref, in which case we are indeed searching all the column names,
* or for a join it can be rte->join_using_alias, in which case we are only
* considering the common column names (which are the first N columns of the
* join, so everything works).
*
* pstate and location are passed only for error-reporting purposes.
*
* Side effect: if fuzzystate is non-NULL, check non-system columns
@@ -2134,6 +2140,7 @@ addRangeTableEntryForJoin(ParseState *pstate,
List *aliasvars,
List *leftcols,
List *rightcols,
Alias *join_using_alias,
Alias *alias,
bool inFromCl)
{
@@ -2162,6 +2169,7 @@ addRangeTableEntryForJoin(ParseState *pstate,
rte->joinaliasvars = aliasvars;
rte->joinleftcols = leftcols;
rte->joinrightcols = rightcols;
rte->join_using_alias = join_using_alias;
rte->alias = alias;
eref = alias ? copyObject(alias) : makeAlias("unnamed_join", NIL);