aboutsummaryrefslogtreecommitdiff
path: root/src/backend/parser/parse_clause.c
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2013-11-21 19:37:02 -0500
committerTom Lane <tgl@sss.pgh.pa.us>2013-11-21 19:37:20 -0500
commit784e762e886e6f72f548da86a27cd2ead87dbd1c (patch)
tree9c21fc1545c96a655ec4591e1ba3c8d99cdfccf8 /src/backend/parser/parse_clause.c
parent38f432898131270e5b64245786cb67f322538bae (diff)
downloadpostgresql-784e762e886e6f72f548da86a27cd2ead87dbd1c.tar.gz
postgresql-784e762e886e6f72f548da86a27cd2ead87dbd1c.zip
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
Diffstat (limited to 'src/backend/parser/parse_clause.c')
-rw-r--r--src/backend/parser/parse_clause.c192
1 files changed, 156 insertions, 36 deletions
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 7a1261d0fd2..8b4c0ae0d3b 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -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;
}