mirror of
https://github.com/postgres/postgres.git
synced 2025-07-15 19:21:59 +03:00
Support multi-argument UNNEST(), and TABLE() syntax for multiple functions.
This patch adds the ability to write TABLE( function1(), function2(), ...) as a single FROM-clause entry. The result is the concatenation of the first row from each function, followed by the second row from each function, etc; with NULLs inserted if any function produces fewer rows than others. This is believed to be a much more useful behavior than what Postgres currently does with multiple SRFs in a SELECT list. This syntax also provides a reasonable way to combine use of column definition lists with WITH ORDINALITY: put the column definition list inside TABLE(), where it's clear that it doesn't control the ordinality column as well. Also implement SQL-compliant multiple-argument UNNEST(), by turning UNNEST(a,b,c) into TABLE(unnest(a), unnest(b), unnest(c)). The SQL standard specifies TABLE() with only a single function, not multiple functions, and it seems to require an implicit UNNEST() which is not what this patch does. There may be something wrong with that reading of the spec, though, because if it's right then the spec's TABLE() is just a pointless alternative spelling of UNNEST(). After further review of that, we might choose to adopt a different syntax for what this patch does, but in any case this functionality seems clearly worthwhile. Andrew Gierth, reviewed by Zoltán Böszörményi and Heikki Linnakangas, and significantly revised by me
This commit is contained in:
@ -406,6 +406,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
|
||||
a_expr b_expr c_expr AexprConst indirection_el
|
||||
columnref in_expr having_clause func_table array_expr
|
||||
ExclusionWhereClause
|
||||
%type <list> func_table_item func_table_list opt_col_def_list
|
||||
%type <boolean> opt_ordinality
|
||||
%type <list> ExclusionConstraintList ExclusionConstraintElem
|
||||
%type <list> func_arg_list
|
||||
%type <node> func_arg_expr
|
||||
@ -613,6 +615,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
|
||||
*/
|
||||
%token NULLS_FIRST NULLS_LAST WITH_ORDINALITY WITH_TIME
|
||||
|
||||
|
||||
/* Precedence: lowest to highest */
|
||||
%nonassoc SET /* see relation_expr_opt_alias */
|
||||
%left UNION EXCEPT
|
||||
@ -1926,10 +1929,11 @@ alter_table_cmd:
|
||||
n->subtype = AT_AlterColumnType;
|
||||
n->name = $3;
|
||||
n->def = (Node *) def;
|
||||
/* We only use these three fields of the ColumnDef node */
|
||||
/* We only use these fields of the ColumnDef node */
|
||||
def->typeName = $6;
|
||||
def->collClause = (CollateClause *) $7;
|
||||
def->raw_default = $8;
|
||||
def->location = @3;
|
||||
$$ = (Node *)n;
|
||||
}
|
||||
/* ALTER FOREIGN TABLE <name> ALTER [COLUMN] <colname> OPTIONS */
|
||||
@ -2354,10 +2358,11 @@ alter_type_cmd:
|
||||
n->name = $3;
|
||||
n->def = (Node *) def;
|
||||
n->behavior = $8;
|
||||
/* We only use these three fields of the ColumnDef node */
|
||||
/* We only use these fields of the ColumnDef node */
|
||||
def->typeName = $6;
|
||||
def->collClause = (CollateClause *) $7;
|
||||
def->raw_default = NULL;
|
||||
def->location = @3;
|
||||
$$ = (Node *)n;
|
||||
}
|
||||
;
|
||||
@ -2782,6 +2787,7 @@ columnDef: ColId Typename create_generic_options ColQualList
|
||||
n->fdwoptions = $3;
|
||||
SplitColQualList($4, &n->constraints, &n->collClause,
|
||||
yyscanner);
|
||||
n->location = @1;
|
||||
$$ = (Node *)n;
|
||||
}
|
||||
;
|
||||
@ -2801,6 +2807,7 @@ columnOptions: ColId WITH OPTIONS ColQualList
|
||||
n->collOid = InvalidOid;
|
||||
SplitColQualList($4, &n->constraints, &n->collClause,
|
||||
yyscanner);
|
||||
n->location = @1;
|
||||
$$ = (Node *)n;
|
||||
}
|
||||
;
|
||||
@ -9648,44 +9655,19 @@ table_ref: relation_expr opt_alias_clause
|
||||
}
|
||||
| func_table func_alias_clause
|
||||
{
|
||||
RangeFunction *n = makeNode(RangeFunction);
|
||||
n->lateral = false;
|
||||
n->ordinality = false;
|
||||
n->funccallnode = $1;
|
||||
RangeFunction *n = (RangeFunction *) $1;
|
||||
n->alias = linitial($2);
|
||||
n->coldeflist = lsecond($2);
|
||||
$$ = (Node *) n;
|
||||
}
|
||||
| func_table WITH_ORDINALITY func_alias_clause
|
||||
{
|
||||
RangeFunction *n = makeNode(RangeFunction);
|
||||
n->lateral = false;
|
||||
n->ordinality = true;
|
||||
n->funccallnode = $1;
|
||||
n->alias = linitial($3);
|
||||
n->coldeflist = lsecond($3);
|
||||
$$ = (Node *) n;
|
||||
}
|
||||
| LATERAL_P func_table func_alias_clause
|
||||
{
|
||||
RangeFunction *n = makeNode(RangeFunction);
|
||||
RangeFunction *n = (RangeFunction *) $2;
|
||||
n->lateral = true;
|
||||
n->ordinality = false;
|
||||
n->funccallnode = $2;
|
||||
n->alias = linitial($3);
|
||||
n->coldeflist = lsecond($3);
|
||||
$$ = (Node *) n;
|
||||
}
|
||||
| LATERAL_P func_table WITH_ORDINALITY func_alias_clause
|
||||
{
|
||||
RangeFunction *n = makeNode(RangeFunction);
|
||||
n->lateral = true;
|
||||
n->ordinality = true;
|
||||
n->funccallnode = $2;
|
||||
n->alias = linitial($4);
|
||||
n->coldeflist = lsecond($4);
|
||||
$$ = (Node *) n;
|
||||
}
|
||||
| select_with_parens opt_alias_clause
|
||||
{
|
||||
RangeSubselect *n = makeNode(RangeSubselect);
|
||||
@ -9996,7 +9978,54 @@ relation_expr_opt_alias: relation_expr %prec UMINUS
|
||||
}
|
||||
;
|
||||
|
||||
func_table: func_expr_windowless { $$ = $1; }
|
||||
/*
|
||||
* func_table represents a function invocation in a FROM list. It can be
|
||||
* a plain function call, like "foo(...)", or a TABLE expression with
|
||||
* one or more function calls, "TABLE (foo(...), bar(...))",
|
||||
* optionally with WITH ORDINALITY attached.
|
||||
* In the TABLE syntax, a column definition list can be given for each
|
||||
* function, for example:
|
||||
* TABLE (foo() AS (foo_res_a text, foo_res_b text),
|
||||
* bar() AS (bar_res_a text, bar_res_b text))
|
||||
* It's also possible to attach a column definition list to the RangeFunction
|
||||
* as a whole, but that's handled by the table_ref production.
|
||||
*/
|
||||
func_table: func_expr_windowless opt_ordinality
|
||||
{
|
||||
RangeFunction *n = makeNode(RangeFunction);
|
||||
n->lateral = false;
|
||||
n->ordinality = $2;
|
||||
n->is_table = false;
|
||||
n->functions = list_make1(list_make2($1, NIL));
|
||||
/* alias and coldeflist are set by table_ref production */
|
||||
$$ = (Node *) n;
|
||||
}
|
||||
| TABLE '(' func_table_list ')' opt_ordinality
|
||||
{
|
||||
RangeFunction *n = makeNode(RangeFunction);
|
||||
n->lateral = false;
|
||||
n->ordinality = $5;
|
||||
n->is_table = true;
|
||||
n->functions = $3;
|
||||
/* alias and coldeflist are set by table_ref production */
|
||||
$$ = (Node *) n;
|
||||
}
|
||||
;
|
||||
|
||||
func_table_item: func_expr_windowless opt_col_def_list
|
||||
{ $$ = list_make2($1, $2); }
|
||||
;
|
||||
|
||||
func_table_list: func_table_item { $$ = list_make1($1); }
|
||||
| func_table_list ',' func_table_item { $$ = lappend($1, $3); }
|
||||
;
|
||||
|
||||
opt_col_def_list: AS '(' TableFuncElementList ')' { $$ = $3; }
|
||||
| /*EMPTY*/ { $$ = NIL; }
|
||||
;
|
||||
|
||||
opt_ordinality: WITH_ORDINALITY { $$ = true; }
|
||||
| /*EMPTY*/ { $$ = false; }
|
||||
;
|
||||
|
||||
|
||||
@ -10051,6 +10080,7 @@ TableFuncElement: ColId Typename opt_collate_clause
|
||||
n->collClause = (CollateClause *) $3;
|
||||
n->collOid = InvalidOid;
|
||||
n->constraints = NIL;
|
||||
n->location = @1;
|
||||
$$ = (Node *)n;
|
||||
}
|
||||
;
|
||||
@ -11172,11 +11202,11 @@ func_application: func_name '(' ')'
|
||||
|
||||
|
||||
/*
|
||||
* func_expr and its cousin func_expr_windowless is split out from c_expr just
|
||||
* func_expr and its cousin func_expr_windowless are split out from c_expr just
|
||||
* so that we have classifications for "everything that is a function call or
|
||||
* looks like one". This isn't very important, but it saves us having to document
|
||||
* which variants are legal in the backwards-compatible functional-index syntax
|
||||
* for CREATE INDEX.
|
||||
* looks like one". This isn't very important, but it saves us having to
|
||||
* document which variants are legal in places like "FROM function()" or the
|
||||
* backwards-compatible functional-index syntax for CREATE INDEX.
|
||||
* (Note that many of the special SQL functions wouldn't actually make any
|
||||
* sense as functional index entries, but we ignore that consideration here.)
|
||||
*/
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "optimizer/tlist.h"
|
||||
#include "parser/analyze.h"
|
||||
#include "parser/parsetree.h"
|
||||
#include "parser/parser.h"
|
||||
#include "parser/parse_clause.h"
|
||||
#include "parser/parse_coerce.h"
|
||||
#include "parser/parse_collate.h"
|
||||
@ -515,24 +516,18 @@ transformRangeSubselect(ParseState *pstate, RangeSubselect *r)
|
||||
static RangeTblEntry *
|
||||
transformRangeFunction(ParseState *pstate, RangeFunction *r)
|
||||
{
|
||||
Node *funcexpr;
|
||||
char *funcname;
|
||||
List *funcexprs = NIL;
|
||||
List *funcnames = NIL;
|
||||
List *coldeflists = NIL;
|
||||
bool is_lateral;
|
||||
RangeTblEntry *rte;
|
||||
|
||||
/*
|
||||
* Get function name for possible use as alias. We use the same
|
||||
* transformation rules as for a SELECT output expression. For a FuncCall
|
||||
* node, the result will be the function name, but it is possible for the
|
||||
* grammar to hand back other node types.
|
||||
*/
|
||||
funcname = FigureColname(r->funccallnode);
|
||||
ListCell *lc;
|
||||
|
||||
/*
|
||||
* We make lateral_only names of this level visible, whether or not the
|
||||
* function is explicitly marked LATERAL. This is needed for SQL spec
|
||||
* compliance in the case of UNNEST(), and seems useful on convenience
|
||||
* grounds for all functions in FROM.
|
||||
* RangeFunction is explicitly marked LATERAL. This is needed for SQL
|
||||
* spec compliance in the case of UNNEST(), and seems useful on
|
||||
* convenience grounds for all functions in FROM.
|
||||
*
|
||||
* (LATERAL can't nest within a single pstate level, so we don't need
|
||||
* save/restore logic here.)
|
||||
@ -541,46 +536,171 @@ transformRangeFunction(ParseState *pstate, RangeFunction *r)
|
||||
pstate->p_lateral_active = true;
|
||||
|
||||
/*
|
||||
* Transform the raw expression.
|
||||
* Transform the raw expressions.
|
||||
*
|
||||
* While transforming, also save function names for possible use as alias
|
||||
* and column names. We use the same transformation rules as for a SELECT
|
||||
* output expression. For a FuncCall node, the result will be the
|
||||
* function name, but it is possible for the grammar to hand back other
|
||||
* node types.
|
||||
*
|
||||
* We have to get this info now, because FigureColname only works on raw
|
||||
* parsetrees. Actually deciding what to do with the names is left up to
|
||||
* addRangeTableEntryForFunction.
|
||||
*
|
||||
* Likewise, collect column definition lists if there were any. But
|
||||
* complain if we find one here and the RangeFunction has one too.
|
||||
*/
|
||||
funcexpr = transformExpr(pstate, r->funccallnode, EXPR_KIND_FROM_FUNCTION);
|
||||
foreach(lc, r->functions)
|
||||
{
|
||||
List *pair = (List *) lfirst(lc);
|
||||
Node *fexpr;
|
||||
List *coldeflist;
|
||||
|
||||
/* Disassemble the function-call/column-def-list pairs */
|
||||
Assert(list_length(pair) == 2);
|
||||
fexpr = (Node *) linitial(pair);
|
||||
coldeflist = (List *) lsecond(pair);
|
||||
|
||||
/*
|
||||
* If we find a function call unnest() with more than one argument and
|
||||
* no special decoration, transform it into separate unnest() calls on
|
||||
* each argument. This is a kluge, for sure, but it's less nasty than
|
||||
* other ways of implementing the SQL-standard UNNEST() syntax.
|
||||
*
|
||||
* If there is any decoration (including a coldeflist), we don't
|
||||
* transform, which probably means a no-such-function error later. We
|
||||
* could alternatively throw an error right now, but that doesn't seem
|
||||
* tremendously helpful. If someone is using any such decoration,
|
||||
* then they're not using the SQL-standard syntax, and they're more
|
||||
* likely expecting an un-tweaked function call.
|
||||
*
|
||||
* Note: the transformation changes a non-schema-qualified unnest()
|
||||
* function name into schema-qualified pg_catalog.unnest(). This
|
||||
* choice is also a bit debatable, but it seems reasonable to force
|
||||
* use of built-in unnest() when we make this transformation.
|
||||
*/
|
||||
if (IsA(fexpr, FuncCall))
|
||||
{
|
||||
FuncCall *fc = (FuncCall *) fexpr;
|
||||
|
||||
if (list_length(fc->funcname) == 1 &&
|
||||
strcmp(strVal(linitial(fc->funcname)), "unnest") == 0 &&
|
||||
list_length(fc->args) > 1 &&
|
||||
fc->agg_order == NIL &&
|
||||
fc->agg_filter == NULL &&
|
||||
!fc->agg_star &&
|
||||
!fc->agg_distinct &&
|
||||
!fc->func_variadic &&
|
||||
fc->over == NULL &&
|
||||
coldeflist == NIL)
|
||||
{
|
||||
ListCell *lc;
|
||||
|
||||
foreach(lc, fc->args)
|
||||
{
|
||||
Node *arg = (Node *) lfirst(lc);
|
||||
FuncCall *newfc;
|
||||
|
||||
newfc = makeFuncCall(SystemFuncName("unnest"),
|
||||
list_make1(arg),
|
||||
fc->location);
|
||||
|
||||
funcexprs = lappend(funcexprs,
|
||||
transformExpr(pstate, (Node *) newfc,
|
||||
EXPR_KIND_FROM_FUNCTION));
|
||||
|
||||
funcnames = lappend(funcnames,
|
||||
FigureColname((Node *) newfc));
|
||||
|
||||
/* coldeflist is empty, so no error is possible */
|
||||
|
||||
coldeflists = lappend(coldeflists, coldeflist);
|
||||
}
|
||||
continue; /* done with this function item */
|
||||
}
|
||||
}
|
||||
|
||||
/* normal case ... */
|
||||
funcexprs = lappend(funcexprs,
|
||||
transformExpr(pstate, fexpr,
|
||||
EXPR_KIND_FROM_FUNCTION));
|
||||
|
||||
funcnames = lappend(funcnames,
|
||||
FigureColname(fexpr));
|
||||
|
||||
if (coldeflist && r->coldeflist)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("multiple column definition lists are not allowed for the same function"),
|
||||
parser_errposition(pstate,
|
||||
exprLocation((Node *) r->coldeflist))));
|
||||
|
||||
coldeflists = lappend(coldeflists, coldeflist);
|
||||
}
|
||||
|
||||
pstate->p_lateral_active = false;
|
||||
|
||||
/*
|
||||
* We must assign collations now so that we can fill funccolcollations.
|
||||
* We must assign collations now so that the RTE exposes correct collation
|
||||
* info for Vars created from it.
|
||||
*/
|
||||
assign_expr_collations(pstate, funcexpr);
|
||||
assign_list_collations(pstate, funcexprs);
|
||||
|
||||
/*
|
||||
* Install the top-level coldeflist if there was one (we already checked
|
||||
* that there was no conflicting per-function coldeflist).
|
||||
*
|
||||
* We only allow this when there's a single function (even after UNNEST
|
||||
* expansion) and no WITH ORDINALITY. The reason for the latter
|
||||
* restriction is that it's not real clear whether the ordinality column
|
||||
* should be in the coldeflist, and users are too likely to make mistakes
|
||||
* in one direction or the other. Putting the coldeflist inside TABLE()
|
||||
* is much clearer in this case.
|
||||
*/
|
||||
if (r->coldeflist)
|
||||
{
|
||||
if (list_length(funcexprs) != 1)
|
||||
{
|
||||
if (r->is_table)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("TABLE() with multiple functions cannot have a column definition list"),
|
||||
errhint("Put a separate column definition list for each function inside TABLE()."),
|
||||
parser_errposition(pstate,
|
||||
exprLocation((Node *) r->coldeflist))));
|
||||
else
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("UNNEST() with multiple arguments cannot have a column definition list"),
|
||||
errhint("Use separate UNNEST() calls inside TABLE(), and attach a column definition list to each one."),
|
||||
parser_errposition(pstate,
|
||||
exprLocation((Node *) r->coldeflist))));
|
||||
}
|
||||
if (r->ordinality)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("WITH ORDINALITY cannot be used with a column definition list"),
|
||||
errhint("Put the column definition list inside TABLE()."),
|
||||
parser_errposition(pstate,
|
||||
exprLocation((Node *) r->coldeflist))));
|
||||
|
||||
coldeflists = list_make1(r->coldeflist);
|
||||
}
|
||||
|
||||
/*
|
||||
* Mark the RTE as LATERAL if the user said LATERAL explicitly, or if
|
||||
* there are any lateral cross-references in it.
|
||||
*/
|
||||
is_lateral = r->lateral || contain_vars_of_level(funcexpr, 0);
|
||||
is_lateral = r->lateral || contain_vars_of_level((Node *) funcexprs, 0);
|
||||
|
||||
/*
|
||||
* OK, build an RTE for the function.
|
||||
*/
|
||||
rte = addRangeTableEntryForFunction(pstate, funcname, funcexpr,
|
||||
rte = addRangeTableEntryForFunction(pstate,
|
||||
funcnames, funcexprs, coldeflists,
|
||||
r, is_lateral, true);
|
||||
|
||||
/*
|
||||
* If a coldeflist was supplied, ensure it defines a legal set of names
|
||||
* (no duplicates) and datatypes (no pseudo-types, for instance).
|
||||
* addRangeTableEntryForFunction looked up the type names but didn't check
|
||||
* them further than that.
|
||||
*/
|
||||
if (r->coldeflist)
|
||||
{
|
||||
TupleDesc tupdesc;
|
||||
|
||||
tupdesc = BuildDescFromLists(rte->eref->colnames,
|
||||
rte->funccoltypes,
|
||||
rte->funccoltypmods,
|
||||
rte->funccolcollations);
|
||||
CheckAttributeNamesTypes(tupdesc, RELKIND_COMPOSITE_TYPE, false);
|
||||
}
|
||||
|
||||
return rte;
|
||||
}
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -472,7 +472,7 @@ GetColumnDefCollation(ParseState *pstate, ColumnDef *coldef, Oid typeOid)
|
||||
{
|
||||
Oid result;
|
||||
Oid typcollation = get_typcollation(typeOid);
|
||||
int location = -1;
|
||||
int location = coldef->location;
|
||||
|
||||
if (coldef->collClause)
|
||||
{
|
||||
|
@ -754,6 +754,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
|
||||
def->collClause = NULL;
|
||||
def->collOid = attribute->attcollation;
|
||||
def->constraints = NIL;
|
||||
def->location = -1;
|
||||
|
||||
/*
|
||||
* Add to column list
|
||||
@ -969,6 +970,7 @@ transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
|
||||
n->collClause = NULL;
|
||||
n->collOid = attr->attcollation;
|
||||
n->constraints = NIL;
|
||||
n->location = -1;
|
||||
cxt->columns = lappend(cxt->columns, n);
|
||||
}
|
||||
DecrTupleDescRefCount(tupdesc);
|
||||
|
Reference in New Issue
Block a user