parametrized_function_scan.patch
text/x-patch
Filename: parametrized_function_scan.patch
Type: text/x-patch
Part: 0
Message:
WIP: parameterized function scan
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: unified
| File | + | − |
|---|---|---|
| src/backend/optimizer/path/joinpath.c | 48 | 3 |
| src/backend/optimizer/plan/createplan.c | 16 | 0 |
| src/backend/optimizer/util/relnode.c | 15 | 0 |
| src/backend/parser/parse_clause.c | 0 | 19 |
| src/include/nodes/relation.h | 7 | 0 |
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 446319d..f30ae4e 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -85,6 +85,44 @@ add_paths_to_joinrel(PlannerInfo *root,
SemiAntiJoinFactors semifactors;
Relids param_source_rels = NULL;
ListCell *lc;
+ bool nest_loop_only = false;
+
+ /*
+ * Should the outer path be parametrized by the inner, there's no method
+ * to evaluate such a join.
+ */
+ if (bms_overlap(outerrel->func_arg_relids, innerrel->relids)) {
+ return;
+ }
+
+ /*
+ * Only nest-loop join is considered viable method for lateral function.
+ *
+ * Non-empty 'innerrel->func_arg_relids' isn't a sufficient condition for
+ * NL join with parametrized function on the inner side:
+ *
+ * If there's no overlap, the function arguments have already been used
+ * to construct the innerrel (no reason to use them again) or they are supplied
+ * from higher level (and thus constant for the current join) .
+ */
+ if (bms_overlap(innerrel->func_arg_relids, outerrel->relids)) {
+ /*
+ * Merge join is the only method to evaluate full join, but merge join is a bad option
+ * for lateral functions.
+ */
+ if (jointype == JOIN_FULL) {
+ return;
+ }
+
+ /*
+ * TODO
+ * Probably not relevant. Verify.
+ */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI) {
+ return;
+ }
+ nest_loop_only = true;
+ }
/*
* Find potential mergejoin clauses. We can skip this if we are not
@@ -92,7 +130,7 @@ add_paths_to_joinrel(PlannerInfo *root,
* way of implementing a full outer join, so override enable_mergejoin if
* it's a full join.
*/
- if (enable_mergejoin || jointype == JOIN_FULL)
+ if ((enable_mergejoin || jointype == JOIN_FULL) && !nest_loop_only)
mergeclause_list = select_mergejoin_clauses(root,
joinrel,
outerrel,
@@ -151,7 +189,7 @@ add_paths_to_joinrel(PlannerInfo *root,
* 1. Consider mergejoin paths where both relations must be explicitly
* sorted. Skip this if we can't mergejoin.
*/
- if (mergejoin_allowed)
+ if (mergejoin_allowed && !nest_loop_only)
sort_inner_and_outer(root, joinrel, outerrel, innerrel,
restrictlist, mergeclause_list, jointype,
sjinfo, param_source_rels);
@@ -192,10 +230,13 @@ add_paths_to_joinrel(PlannerInfo *root,
* before being joined. As above, disregard enable_hashjoin for full
* joins, because there may be no other alternative.
*/
- if (enable_hashjoin || jointype == JOIN_FULL)
+ if ((enable_hashjoin || jointype == JOIN_FULL) && !nest_loop_only)
hash_inner_and_outer(root, joinrel, outerrel, innerrel,
restrictlist, jointype,
sjinfo, &semifactors, param_source_rels);
+
+ /* Prepare for check of function parametrization at the next higher level. */
+ joinrel->func_arg_relids = bms_union(outerrel->func_arg_relids, innerrel->func_arg_relids);
}
/*
@@ -654,6 +695,7 @@ match_unsorted_outer(PlannerInfo *root,
Path *inner_cheapest_total = innerrel->cheapest_total_path;
Path *matpath = NULL;
ListCell *lc1;
+ bool nest_loop_only = bms_overlap(innerrel->func_arg_relids, outerrel->relids);
/*
* Nestloop only supports inner, left, semi, and anti joins. Also, if we
@@ -814,6 +856,9 @@ match_unsorted_outer(PlannerInfo *root,
if (save_jointype == JOIN_UNIQUE_OUTER)
continue;
+ if (nest_loop_only)
+ continue;
+
/* Look for useful mergeclauses (if any) */
mergeclauses = find_mergeclauses_for_pathkeys(root,
outerpath->pathkeys,
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index c34b9b8..5552fa8 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -583,8 +583,10 @@ create_join_plan(PlannerInfo *root, JoinPath *best_path)
/* For a nestloop, include outer relids in curOuterRels for inner side */
if (best_path->path.pathtype == T_NestLoop)
+ {
root->curOuterRels = bms_union(root->curOuterRels,
best_path->outerjoinpath->parent->relids);
+ }
inner_plan = create_plan_recurse(root, best_path->innerjoinpath);
@@ -1663,18 +1665,32 @@ create_functionscan_plan(PlannerInfo *root, Path *best_path,
FunctionScan *scan_plan;
Index scan_relid = best_path->parent->relid;
RangeTblEntry *rte;
+ FuncExpr *funcExpr;
/* it should be a function base rel... */
Assert(scan_relid > 0);
rte = planner_rt_fetch(scan_relid, root);
Assert(rte->rtekind == RTE_FUNCTION);
+ /*
+ * Ensure that vars in argument expressions get substituted in case the function participates in a join.
+ *
+ * (Function can only be used as inner relation of nest-loop join.)
+ */
+ funcExpr = (FuncExpr *) rte->funcexpr;
+ if (funcExpr->args != NULL) {
+ funcExpr->args = (List *)
+ replace_nestloop_params(root, (Node *) funcExpr->args);
+ }
+
/* Sort clauses into best execution order */
scan_clauses = order_qual_clauses(root, scan_clauses);
/* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
scan_clauses = extract_actual_clauses(scan_clauses, false);
+
+
scan_plan = make_functionscan(tlist, scan_clauses, scan_relid,
rte->funcexpr,
rte->eref->colnames,
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index bfdd9ff..427e7e9 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -20,6 +20,7 @@
#include "optimizer/placeholder.h"
#include "optimizer/plancat.h"
#include "optimizer/restrictinfo.h"
+#include "optimizer/var.h"
#include "utils/hsearch.h"
@@ -97,6 +98,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
rel = makeNode(RelOptInfo);
rel->reloptkind = reloptkind;
rel->relids = bms_make_singleton(relid);
+ rel->func_arg_relids = NULL;
rel->rows = 0;
rel->width = 0;
rel->reltargetlist = NIL;
@@ -154,6 +156,19 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
break;
}
+ /*
+ * If function arguments reference other relations, we need to keep track
+ * in order to check for any join it's going to participate in whether parametrization
+ * is possible.
+ */
+ if (rte->rtekind == RTE_FUNCTION) {
+ FuncExpr *funcExpr = (FuncExpr *) rte->funcexpr;
+
+ if (funcExpr->args != NULL) {
+ rel->func_arg_relids = pull_varnos((Node *) funcExpr->args);
+ }
+ }
+
/* Save the finished struct in the query's simple_rel_array */
root->simple_rel_array[relid] = rel;
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 97ab9d5..6178b17 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -557,25 +557,6 @@ transformRangeFunction(ParseState *pstate, RangeFunction *r)
assign_expr_collations(pstate, funcexpr);
/*
- * The function parameters cannot make use of any variables from other
- * FROM items. (Compare to transformRangeSubselect(); the coding is
- * different though because we didn't parse as a sub-select with its own
- * level of namespace.)
- *
- * XXX this will need further work to support SQL99's LATERAL() feature,
- * wherein such references would indeed be legal.
- */
- if (pstate->p_relnamespace || pstate->p_varnamespace)
- {
- if (contain_vars_of_level(funcexpr, 0))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
- errmsg("function expression in FROM cannot refer to other relations of same query level"),
- parser_errposition(pstate,
- locate_var_of_level(funcexpr, 0))));
- }
-
- /*
* Disallow aggregate functions in the expression. (No reason to postpone
* this check until parseCheckAggregates.)
*/
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index e1d5fc0..c3f3394 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -292,6 +292,10 @@ typedef struct PlannerInfo
*
* relids - Set of base-relation identifiers; it is a base relation
* if there is just one, a join relation if more than one
+ * func_arg_relids - union of relations that all functions within the current relation
+ * reference via function arglists. Unlike clause parameters, this set is only
+ * determined by the functions contained in 'relids'. That's why it's not stored
+ * in 'ppilist' for each specific path.
* rows - estimated number of tuples in the relation after restriction
* clauses have been applied (ie, output rows of a plan for it)
* width - avg. number of bytes per tuple in the relation after the
@@ -394,6 +398,9 @@ typedef struct RelOptInfo
/* all relations included in this RelOptInfo */
Relids relids; /* set of base relids (rangetable indexes) */
+ /* union of relids used as parameters by all RTE functions that this relation contains */
+ Relids func_arg_relids;
+
/* size estimates generated by planner */
double rows; /* estimated number of result tuples */
int width; /* estimated avg width of result tuples */