aboutsummaryrefslogtreecommitdiff
path: root/src/backend/optimizer
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/optimizer
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/optimizer')
-rw-r--r--src/backend/optimizer/path/allpaths.c51
-rw-r--r--src/backend/optimizer/path/costsize.c22
-rw-r--r--src/backend/optimizer/path/pathkeys.c51
-rw-r--r--src/backend/optimizer/plan/createplan.c35
-rw-r--r--src/backend/optimizer/plan/initsplan.c2
-rw-r--r--src/backend/optimizer/plan/planner.c4
-rw-r--r--src/backend/optimizer/plan/setrefs.c9
-rw-r--r--src/backend/optimizer/plan/subselect.c34
-rw-r--r--src/backend/optimizer/prep/prepjointree.c9
-rw-r--r--src/backend/optimizer/util/clauses.c16
-rw-r--r--src/backend/optimizer/util/pathnode.c4
11 files changed, 181 insertions, 56 deletions
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index bfd3809a007..96fe50f0b27 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -18,6 +18,7 @@
#include <math.h>
#include "catalog/pg_class.h"
+#include "catalog/pg_operator.h"
#include "foreign/fdwapi.h"
#include "nodes/nodeFuncs.h"
#ifdef OPTIMIZER_DEBUG
@@ -1258,6 +1259,7 @@ static void
set_function_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
{
Relids required_outer;
+ List *pathkeys = NIL;
/*
* We don't support pushing join clauses into the quals of a function
@@ -1266,8 +1268,55 @@ set_function_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
required_outer = rel->lateral_relids;
+ /*
+ * The result is considered unordered unless ORDINALITY was used, in which
+ * case it is ordered by the ordinal column (the last one). See if we
+ * care, by checking for uses of that Var in equivalence classes.
+ */
+ if (rte->funcordinality)
+ {
+ AttrNumber ordattno = rel->max_attr;
+ Var *var = NULL;
+ ListCell *lc;
+
+ /*
+ * Is there a Var for it in reltargetlist? If not, the query did not
+ * reference the ordinality column, or at least not in any way that
+ * would be interesting for sorting.
+ */
+ foreach(lc, rel->reltargetlist)
+ {
+ Var *node = (Var *) lfirst(lc);
+
+ /* checking varno/varlevelsup is just paranoia */
+ if (IsA(node, Var) &&
+ node->varattno == ordattno &&
+ node->varno == rel->relid &&
+ node->varlevelsup == 0)
+ {
+ var = node;
+ break;
+ }
+ }
+
+ /*
+ * Try to build pathkeys for this Var with int8 sorting. We tell
+ * build_expression_pathkey not to build any new equivalence class; if
+ * the Var isn't already mentioned in some EC, it means that nothing
+ * cares about the ordering.
+ */
+ if (var)
+ pathkeys = build_expression_pathkey(root,
+ (Expr *) var,
+ NULL, /* below outer joins */
+ Int8LessOperator,
+ rel->relids,
+ false);
+ }
+
/* Generate appropriate path */
- add_path(rel, create_functionscan_path(root, rel, required_outer));
+ add_path(rel, create_functionscan_path(root, rel,
+ pathkeys, required_outer));
/* Select cheapest path (pretty easy in this case...) */
set_cheapest(rel);
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index e7f8cec0fed..50f08521bfe 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -1076,9 +1076,9 @@ cost_functionscan(Path *path, PlannerInfo *root,
path->rows = baserel->rows;
/*
- * Estimate costs of executing the function expression.
+ * Estimate costs of executing the function expression(s).
*
- * Currently, nodeFunctionscan.c always executes the function to
+ * Currently, nodeFunctionscan.c always executes the functions to
* completion before returning any rows, and caches the results in a
* tuplestore. So the function eval cost is all startup cost, and per-row
* costs are minimal.
@@ -1088,7 +1088,7 @@ cost_functionscan(Path *path, PlannerInfo *root,
* estimates for functions tend to be, there's not a lot of point in that
* refinement right now.
*/
- cost_qual_eval_node(&exprcost, rte->funcexpr, root);
+ cost_qual_eval_node(&exprcost, (Node *) rte->functions, root);
startup_cost += exprcost.startup + exprcost.per_tuple;
@@ -3845,14 +3845,26 @@ void
set_function_size_estimates(PlannerInfo *root, RelOptInfo *rel)
{
RangeTblEntry *rte;
+ ListCell *lc;
/* Should only be applied to base relations that are functions */
Assert(rel->relid > 0);
rte = planner_rt_fetch(rel->relid, root);
Assert(rte->rtekind == RTE_FUNCTION);
- /* Estimate number of rows the function itself will return */
- rel->tuples = expression_returns_set_rows(rte->funcexpr);
+ /*
+ * Estimate number of rows the functions will return. The rowcount of the
+ * node is that of the largest function result.
+ */
+ rel->tuples = 0;
+ foreach(lc, rte->functions)
+ {
+ RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
+ double ntup = expression_returns_set_rows(rtfunc->funcexpr);
+
+ if (ntup > rel->tuples)
+ rel->tuples = ntup;
+ }
/* Now estimate number of output rows, etc */
set_baserel_size_estimates(root, rel);
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index 032b2cdc133..9c8ede658f4 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -502,6 +502,57 @@ build_index_pathkeys(PlannerInfo *root,
}
/*
+ * build_expression_pathkey
+ * Build a pathkeys list that describes an ordering by a single expression
+ * using the given sort operator.
+ *
+ * expr, nullable_relids, and rel are as for make_pathkey_from_sortinfo.
+ * We induce the other arguments assuming default sort order for the operator.
+ *
+ * Similarly to make_pathkey_from_sortinfo, the result is NIL if create_it
+ * is false and the expression isn't already in some EquivalenceClass.
+ */
+List *
+build_expression_pathkey(PlannerInfo *root,
+ Expr *expr,
+ Relids nullable_relids,
+ Oid opno,
+ Relids rel,
+ bool create_it)
+{
+ List *pathkeys;
+ Oid opfamily,
+ opcintype;
+ int16 strategy;
+ PathKey *cpathkey;
+
+ /* Find the operator in pg_amop --- failure shouldn't happen */
+ if (!get_ordering_op_properties(opno,
+ &opfamily, &opcintype, &strategy))
+ elog(ERROR, "operator %u is not a valid ordering operator",
+ opno);
+
+ cpathkey = make_pathkey_from_sortinfo(root,
+ expr,
+ nullable_relids,
+ opfamily,
+ opcintype,
+ exprCollation((Node *) expr),
+ (strategy == BTGreaterStrategyNumber),
+ (strategy == BTGreaterStrategyNumber),
+ 0,
+ rel,
+ create_it);
+
+ if (cpathkey)
+ pathkeys = list_make1(cpathkey);
+ else
+ pathkeys = NIL;
+
+ return pathkeys;
+}
+
+/*
* convert_subquery_pathkeys
* Build a pathkeys list that describes the ordering of a subquery's
* result, in the terms of the outer query. This is essentially a
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 5947e5b136f..f2c122d2959 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -115,9 +115,7 @@ static BitmapHeapScan *make_bitmap_heapscan(List *qptlist,
static TidScan *make_tidscan(List *qptlist, List *qpqual, Index scanrelid,
List *tidquals);
static FunctionScan *make_functionscan(List *qptlist, List *qpqual,
- Index scanrelid, Node *funcexpr, bool ordinality,
- List *funccolnames, List *funccoltypes, List *funccoltypmods,
- List *funccolcollations);
+ Index scanrelid, List *functions, bool funcordinality);
static ValuesScan *make_valuesscan(List *qptlist, List *qpqual,
Index scanrelid, List *values_lists);
static CteScan *make_ctescan(List *qptlist, List *qpqual,
@@ -1709,13 +1707,13 @@ create_functionscan_plan(PlannerInfo *root, Path *best_path,
FunctionScan *scan_plan;
Index scan_relid = best_path->parent->relid;
RangeTblEntry *rte;
- Node *funcexpr;
+ List *functions;
/* it should be a function base rel... */
Assert(scan_relid > 0);
rte = planner_rt_fetch(scan_relid, root);
Assert(rte->rtekind == RTE_FUNCTION);
- funcexpr = rte->funcexpr;
+ functions = rte->functions;
/* Sort clauses into best execution order */
scan_clauses = order_qual_clauses(root, scan_clauses);
@@ -1728,17 +1726,12 @@ create_functionscan_plan(PlannerInfo *root, Path *best_path,
{
scan_clauses = (List *)
replace_nestloop_params(root, (Node *) scan_clauses);
- /* The func expression itself could contain nestloop params, too */
- funcexpr = replace_nestloop_params(root, funcexpr);
+ /* The function expressions could contain nestloop params, too */
+ functions = (List *) replace_nestloop_params(root, (Node *) functions);
}
scan_plan = make_functionscan(tlist, scan_clauses, scan_relid,
- funcexpr,
- rte->funcordinality,
- rte->eref->colnames,
- rte->funccoltypes,
- rte->funccoltypmods,
- rte->funccolcollations);
+ functions, rte->funcordinality);
copy_path_costsize(&scan_plan->scan.plan, best_path);
@@ -3388,12 +3381,8 @@ static FunctionScan *
make_functionscan(List *qptlist,
List *qpqual,
Index scanrelid,
- Node *funcexpr,
- bool ordinality,
- List *funccolnames,
- List *funccoltypes,
- List *funccoltypmods,
- List *funccolcollations)
+ List *functions,
+ bool funcordinality)
{
FunctionScan *node = makeNode(FunctionScan);
Plan *plan = &node->scan.plan;
@@ -3404,12 +3393,8 @@ make_functionscan(List *qptlist,
plan->lefttree = NULL;
plan->righttree = NULL;
node->scan.scanrelid = scanrelid;
- node->funcexpr = funcexpr;
- node->funcordinality = ordinality;
- node->funccolnames = funccolnames;
- node->funccoltypes = funccoltypes;
- node->funccoltypmods = funccoltypmods;
- node->funccolcollations = funccolcollations;
+ node->functions = functions;
+ node->funcordinality = funcordinality;
return node;
}
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 04a399ee13c..59606643925 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -307,7 +307,7 @@ extract_lateral_references(PlannerInfo *root, RelOptInfo *brel, Index rtindex)
if (rte->rtekind == RTE_SUBQUERY)
vars = pull_vars_of_level((Node *) rte->subquery, 1);
else if (rte->rtekind == RTE_FUNCTION)
- vars = pull_vars_of_level(rte->funcexpr, 0);
+ vars = pull_vars_of_level((Node *) rte->functions, 0);
else if (rte->rtekind == RTE_VALUES)
vars = pull_vars_of_level((Node *) rte->values_lists, 0);
else
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index d8aa35dee79..66707944a04 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -485,9 +485,9 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
}
else if (rte->rtekind == RTE_FUNCTION)
{
- /* Preprocess the function expression fully */
+ /* Preprocess the function expression(s) fully */
kind = rte->lateral ? EXPRKIND_RTFUNC_LATERAL : EXPRKIND_RTFUNC;
- rte->funcexpr = preprocess_expression(root, rte->funcexpr, kind);
+ rte->functions = (List *) preprocess_expression(root, (Node *) rte->functions, kind);
}
else if (rte->rtekind == RTE_VALUES)
{
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index b78d72701b3..5c9f3d64ce7 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -381,10 +381,7 @@ add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
/* zap unneeded sub-structure */
newrte->subquery = NULL;
newrte->joinaliasvars = NIL;
- newrte->funcexpr = NULL;
- newrte->funccoltypes = NIL;
- newrte->funccoltypmods = NIL;
- newrte->funccolcollations = NIL;
+ newrte->functions = NIL;
newrte->values_lists = NIL;
newrte->values_collations = NIL;
newrte->ctecoltypes = NIL;
@@ -525,8 +522,8 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
fix_scan_list(root, splan->scan.plan.targetlist, rtoffset);
splan->scan.plan.qual =
fix_scan_list(root, splan->scan.plan.qual, rtoffset);
- splan->funcexpr =
- fix_scan_expr(root, splan->funcexpr, rtoffset);
+ splan->functions =
+ fix_scan_list(root, splan->functions, rtoffset);
}
break;
case T_ValuesScan:
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 0df70c4443b..d8cabbd5bfe 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -2135,9 +2135,37 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
break;
case T_FunctionScan:
- finalize_primnode(((FunctionScan *) plan)->funcexpr,
- &context);
- context.paramids = bms_add_members(context.paramids, scan_params);
+ {
+ FunctionScan *fscan = (FunctionScan *) plan;
+ ListCell *lc;
+
+ /*
+ * Call finalize_primnode independently on each function
+ * expression, so that we can record which params are
+ * referenced in each, in order to decide which need
+ * re-evaluating during rescan.
+ */
+ foreach(lc, fscan->functions)
+ {
+ RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
+ finalize_primnode_context funccontext;
+
+ funccontext = context;
+ funccontext.paramids = NULL;
+
+ finalize_primnode(rtfunc->funcexpr, &funccontext);
+
+ /* remember results for execution */
+ rtfunc->funcparams = funccontext.paramids;
+
+ /* add the function's params to the overall set */
+ context.paramids = bms_add_members(context.paramids,
+ funccontext.paramids);
+ }
+
+ context.paramids = bms_add_members(context.paramids,
+ scan_params);
+ }
break;
case T_ValuesScan:
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index c742cc9542b..485ac31bd37 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -580,10 +580,7 @@ inline_set_returning_functions(PlannerInfo *root)
/* Successful expansion, replace the rtable entry */
rte->rtekind = RTE_SUBQUERY;
rte->subquery = funcquery;
- rte->funcexpr = NULL;
- rte->funccoltypes = NIL;
- rte->funccoltypmods = NIL;
- rte->funccolcollations = NIL;
+ rte->functions = NIL;
}
}
}
@@ -1623,8 +1620,8 @@ replace_vars_in_jointree(Node *jtnode,
context);
break;
case RTE_FUNCTION:
- rte->funcexpr =
- pullup_replace_vars(rte->funcexpr,
+ rte->functions = (List *)
+ pullup_replace_vars((Node *) rte->functions,
context);
break;
case RTE_VALUES:
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 7ce8a9d8180..a7fdd52c294 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -4509,6 +4509,7 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
Query *
inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
{
+ RangeTblFunction *rtfunc;
FuncExpr *fexpr;
Oid func_oid;
HeapTuple func_tuple;
@@ -4537,14 +4538,18 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
*/
check_stack_depth();
- /* Fail if the caller wanted ORDINALITY - we don't implement that here. */
+ /* Fail if the RTE has ORDINALITY - we don't implement that here. */
if (rte->funcordinality)
return NULL;
- /* Fail if FROM item isn't a simple FuncExpr */
- fexpr = (FuncExpr *) rte->funcexpr;
- if (fexpr == NULL || !IsA(fexpr, FuncExpr))
+ /* Fail if RTE isn't a single, simple FuncExpr */
+ if (list_length(rte->functions) != 1)
return NULL;
+ rtfunc = (RangeTblFunction *) linitial(rte->functions);
+
+ if (!IsA(rtfunc->funcexpr, FuncExpr))
+ return NULL;
+ fexpr = (FuncExpr *) rtfunc->funcexpr;
func_oid = fexpr->funcid;
@@ -4734,7 +4739,8 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
*/
if (fexpr->funcresulttype == RECORDOID &&
get_func_result_type(func_oid, NULL, NULL) == TYPEFUNC_RECORD &&
- !tlist_matches_coltypelist(querytree->targetList, rte->funccoltypes))
+ !tlist_matches_coltypelist(querytree->targetList,
+ rtfunc->funccoltypes))
goto fail;
/*
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 64b17051913..a7169efd856 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -1623,7 +1623,7 @@ create_subqueryscan_path(PlannerInfo *root, RelOptInfo *rel,
*/
Path *
create_functionscan_path(PlannerInfo *root, RelOptInfo *rel,
- Relids required_outer)
+ List *pathkeys, Relids required_outer)
{
Path *pathnode = makeNode(Path);
@@ -1631,7 +1631,7 @@ create_functionscan_path(PlannerInfo *root, RelOptInfo *rel,
pathnode->parent = rel;
pathnode->param_info = get_baserel_parampathinfo(root, rel,
required_outer);
- pathnode->pathkeys = NIL; /* for now, assume unordered result */
+ pathnode->pathkeys = pathkeys;
cost_functionscan(pathnode, root, rel, pathnode->param_info);