diff options
Diffstat (limited to 'src')
33 files changed, 1168 insertions, 445 deletions
diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c index 12cb18c18f7..cd77f9927e5 100644 --- a/src/backend/executor/execAmi.c +++ b/src/backend/executor/execAmi.c @@ -437,9 +437,12 @@ ExecSupportsMarkRestore(Path *pathnode) return ExecSupportsMarkRestore(((ProjectionPath *) pathnode)->subpath); else if (IsA(pathnode, MinMaxAggPath)) return false; /* childless Result */ + else if (IsA(pathnode, GroupResultPath)) + return false; /* childless Result */ else { - Assert(IsA(pathnode, ResultPath)); + /* Simple RTE_RESULT base relation */ + Assert(IsA(pathnode, Path)); return false; /* childless Result */ } diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index a806d51edcd..ecb9abd7fe9 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -2329,10 +2329,6 @@ range_table_walker(List *rtable, if (walker(rte->tablesample, context)) return true; break; - case RTE_CTE: - case RTE_NAMEDTUPLESTORE: - /* nothing to do */ - break; case RTE_SUBQUERY: if (!(flags & QTW_IGNORE_RT_SUBQUERIES)) if (walker(rte->subquery, context)) @@ -2355,6 +2351,11 @@ range_table_walker(List *rtable, if (walker(rte->values_lists, context)) return true; break; + case RTE_CTE: + case RTE_NAMEDTUPLESTORE: + case RTE_RESULT: + /* nothing to do */ + break; } if (walker(rte->securityQuals, context)) @@ -3164,10 +3165,6 @@ range_table_mutator(List *rtable, TableSampleClause *); /* we don't bother to copy eref, aliases, etc; OK? */ break; - case RTE_CTE: - case RTE_NAMEDTUPLESTORE: - /* nothing to do */ - break; case RTE_SUBQUERY: if (!(flags & QTW_IGNORE_RT_SUBQUERIES)) { @@ -3198,6 +3195,11 @@ range_table_mutator(List *rtable, case RTE_VALUES: MUTATE(newrte->values_lists, rte->values_lists, List *); break; + case RTE_CTE: + case RTE_NAMEDTUPLESTORE: + case RTE_RESULT: + /* nothing to do */ + break; } MUTATE(newrte->securityQuals, rte->securityQuals, List *); newrt = lappend(newrt, newrte); diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 0fde876c776..33f7939e058 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -1855,9 +1855,9 @@ _outMergeAppendPath(StringInfo str, const MergeAppendPath *node) } static void -_outResultPath(StringInfo str, const ResultPath *node) +_outGroupResultPath(StringInfo str, const GroupResultPath *node) { - WRITE_NODE_TYPE("RESULTPATH"); + WRITE_NODE_TYPE("GROUPRESULTPATH"); _outPathInfo(str, (const Path *) node); @@ -2213,7 +2213,6 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) WRITE_ENUM_FIELD(inhTargetKind, InheritanceKind); WRITE_BOOL_FIELD(hasJoinRTEs); WRITE_BOOL_FIELD(hasLateralRTEs); - WRITE_BOOL_FIELD(hasDeletedRTEs); WRITE_BOOL_FIELD(hasHavingQual); WRITE_BOOL_FIELD(hasPseudoConstantQuals); WRITE_BOOL_FIELD(hasRecursion); @@ -3060,6 +3059,9 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node) WRITE_NODE_FIELD(coltypmods); WRITE_NODE_FIELD(colcollations); break; + case RTE_RESULT: + /* no extra fields */ + break; default: elog(ERROR, "unrecognized RTE kind: %d", (int) node->rtekind); break; @@ -3943,8 +3945,8 @@ outNode(StringInfo str, const void *obj) case T_MergeAppendPath: _outMergeAppendPath(str, obj); break; - case T_ResultPath: - _outResultPath(str, obj); + case T_GroupResultPath: + _outGroupResultPath(str, obj); break; case T_MaterialPath: _outMaterialPath(str, obj); diff --git a/src/backend/nodes/print.c b/src/backend/nodes/print.c index 0278108771d..b9fa3f4842a 100644 --- a/src/backend/nodes/print.c +++ b/src/backend/nodes/print.c @@ -295,6 +295,10 @@ print_rt(const List *rtable) printf("%d\t%s\t[tuplestore]", i, rte->eref->aliasname); break; + case RTE_RESULT: + printf("%d\t%s\t[result]", + i, rte->eref->aliasname); + break; default: printf("%d\t%s\t[unknown rtekind]", i, rte->eref->aliasname); diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index ec6f2569ab8..43491e297b0 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -1411,6 +1411,9 @@ _readRangeTblEntry(void) READ_NODE_FIELD(coltypmods); READ_NODE_FIELD(colcollations); break; + case RTE_RESULT: + /* no extra fields */ + break; default: elog(ERROR, "unrecognized RTE kind: %d", (int) local_node->rtekind); diff --git a/src/backend/optimizer/README b/src/backend/optimizer/README index 9c852a15ef0..89ce373d5e6 100644 --- a/src/backend/optimizer/README +++ b/src/backend/optimizer/README @@ -361,7 +361,16 @@ RelOptInfo - a relation or joined relations join clauses) Path - every way to generate a RelOptInfo(sequential,index,joins) - SeqScan - represents a sequential scan plan + A plain Path node can represent several simple plans, per its pathtype: + T_SeqScan - sequential scan + T_SampleScan - tablesample scan + T_FunctionScan - function-in-FROM scan + T_TableFuncScan - table function scan + T_ValuesScan - VALUES scan + T_CteScan - CTE (WITH) scan + T_NamedTuplestoreScan - ENR scan + T_WorkTableScan - scan worktable of a recursive CTE + T_Result - childless Result plan node (used for FROM-less SELECT) IndexPath - index scan BitmapHeapPath - top of a bitmapped index scan TidPath - scan by CTID @@ -370,7 +379,7 @@ RelOptInfo - a relation or joined relations CustomPath - for custom scan providers AppendPath - append multiple subpaths together MergeAppendPath - merge multiple subpaths, preserving their common sort order - ResultPath - a childless Result plan node (used for FROM-less SELECT) + GroupResultPath - childless Result plan node (used for degenerate grouping) MaterialPath - a Material plan node UniquePath - remove duplicate rows (either by hashing or sorting) GatherPath - collect the results of parallel workers diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index e1ecfd60525..0d8a3f9592c 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -117,6 +117,8 @@ static void set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static void set_namedtuplestore_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); +static void set_result_pathlist(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte); static void set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static RelOptInfo *make_rel_from_joinlist(PlannerInfo *root, List *joinlist); @@ -437,8 +439,13 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel, set_cte_pathlist(root, rel, rte); break; case RTE_NAMEDTUPLESTORE: + /* Might as well just build the path immediately */ set_namedtuplestore_pathlist(root, rel, rte); break; + case RTE_RESULT: + /* Might as well just build the path immediately */ + set_result_pathlist(root, rel, rte); + break; default: elog(ERROR, "unexpected rtekind: %d", (int) rel->rtekind); break; @@ -510,6 +517,9 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, case RTE_NAMEDTUPLESTORE: /* tuplestore reference --- fully handled during set_rel_size */ break; + case RTE_RESULT: + /* simple Result --- fully handled during set_rel_size */ + break; default: elog(ERROR, "unexpected rtekind: %d", (int) rel->rtekind); break; @@ -712,6 +722,10 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel, * infrastructure to support that. */ return; + + case RTE_RESULT: + /* RESULT RTEs, in themselves, are no problem. */ + break; } /* @@ -2510,6 +2524,36 @@ set_namedtuplestore_pathlist(PlannerInfo *root, RelOptInfo *rel, } /* + * set_result_pathlist + * Build the (single) access path for an RTE_RESULT RTE + * + * There's no need for a separate set_result_size phase, since we + * don't support join-qual-parameterized paths for these RTEs. + */ +static void +set_result_pathlist(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte) +{ + Relids required_outer; + + /* Mark rel with estimated output rows, width, etc */ + set_result_size_estimates(root, rel); + + /* + * We don't support pushing join clauses into the quals of a Result scan, + * but it could still have required parameterization due to LATERAL refs + * in its tlist. + */ + required_outer = rel->lateral_relids; + + /* Generate appropriate path */ + add_path(rel, create_resultscan_path(root, rel, required_outer)); + + /* Select cheapest path (pretty easy in this case...) */ + set_cheapest(rel); +} + +/* * set_worktable_pathlist * Build the (single) access path for a self-reference CTE RTE * @@ -3677,9 +3721,6 @@ print_path(PlannerInfo *root, Path *path, int indent) case T_SampleScan: ptype = "SampleScan"; break; - case T_SubqueryScan: - ptype = "SubqueryScan"; - break; case T_FunctionScan: ptype = "FunctionScan"; break; @@ -3692,6 +3733,12 @@ print_path(PlannerInfo *root, Path *path, int indent) case T_CteScan: ptype = "CteScan"; break; + case T_NamedTuplestoreScan: + ptype = "NamedTuplestoreScan"; + break; + case T_Result: + ptype = "Result"; + break; case T_WorkTableScan: ptype = "WorkTableScan"; break; @@ -3716,7 +3763,7 @@ print_path(PlannerInfo *root, Path *path, int indent) ptype = "TidScan"; break; case T_SubqueryScanPath: - ptype = "SubqueryScanScan"; + ptype = "SubqueryScan"; break; case T_ForeignPath: ptype = "ForeignScan"; @@ -3742,8 +3789,8 @@ print_path(PlannerInfo *root, Path *path, int indent) case T_MergeAppendPath: ptype = "MergeAppend"; break; - case T_ResultPath: - ptype = "Result"; + case T_GroupResultPath: + ptype = "GroupResult"; break; case T_MaterialPath: ptype = "Material"; diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c index 99c5ad9b4a9..30b0e925d3c 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -1571,6 +1571,40 @@ cost_namedtuplestorescan(Path *path, PlannerInfo *root, } /* + * cost_resultscan + * Determines and returns the cost of scanning an RTE_RESULT relation. + */ +void +cost_resultscan(Path *path, PlannerInfo *root, + RelOptInfo *baserel, ParamPathInfo *param_info) +{ + Cost startup_cost = 0; + Cost run_cost = 0; + QualCost qpqual_cost; + Cost cpu_per_tuple; + + /* Should only be applied to RTE_RESULT base relations */ + Assert(baserel->relid > 0); + Assert(baserel->rtekind == RTE_RESULT); + + /* Mark the path with the correct row estimate */ + if (param_info) + path->rows = param_info->ppi_rows; + else + path->rows = baserel->rows; + + /* We charge qual cost plus cpu_tuple_cost */ + get_restriction_qual_cost(root, baserel, param_info, &qpqual_cost); + + startup_cost += qpqual_cost.startup; + cpu_per_tuple = cpu_tuple_cost + qpqual_cost.per_tuple; + run_cost += cpu_per_tuple * baserel->tuples; + + path->startup_cost = startup_cost; + path->total_cost = startup_cost + run_cost; +} + +/* * cost_recursive_union * Determines and returns the cost of performing a recursive union, * and also the estimated output size. @@ -5045,6 +5079,29 @@ set_namedtuplestore_size_estimates(PlannerInfo *root, RelOptInfo *rel) } /* + * set_result_size_estimates + * Set the size estimates for an RTE_RESULT base relation + * + * The rel's targetlist and restrictinfo list must have been constructed + * already. + * + * We set the same fields as set_baserel_size_estimates. + */ +void +set_result_size_estimates(PlannerInfo *root, RelOptInfo *rel) +{ + /* Should only be applied to RTE_RESULT base relations */ + Assert(rel->relid > 0); + Assert(planner_rt_fetch(rel->relid, root)->rtekind == RTE_RESULT); + + /* RTE_RESULT always generates a single row, natively */ + rel->tuples = 1; + + /* Now estimate number of output rows, etc */ + set_baserel_size_estimates(root, rel); +} + +/* * set_foreign_size_estimates * Set the size estimates for a base relation that is a foreign table. * diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 97d0c281325..c1aa0ba663b 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -84,7 +84,8 @@ static Plan *create_gating_plan(PlannerInfo *root, Path *path, Plan *plan, static Plan *create_join_plan(PlannerInfo *root, JoinPath *best_path); static Plan *create_append_plan(PlannerInfo *root, AppendPath *best_path); static Plan *create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path); -static Result *create_result_plan(PlannerInfo *root, ResultPath *best_path); +static Result *create_group_result_plan(PlannerInfo *root, + GroupResultPath *best_path); static ProjectSet *create_project_set_plan(PlannerInfo *root, ProjectSetPath *best_path); static Material *create_material_plan(PlannerInfo *root, MaterialPath *best_path, int flags); @@ -138,6 +139,8 @@ static CteScan *create_ctescan_plan(PlannerInfo *root, Path *best_path, List *tlist, List *scan_clauses); static NamedTuplestoreScan *create_namedtuplestorescan_plan(PlannerInfo *root, Path *best_path, List *tlist, List *scan_clauses); +static Result *create_resultscan_plan(PlannerInfo *root, Path *best_path, + List *tlist, List *scan_clauses); static WorkTableScan *create_worktablescan_plan(PlannerInfo *root, Path *best_path, List *tlist, List *scan_clauses); static ForeignScan *create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path, @@ -403,11 +406,16 @@ create_plan_recurse(PlannerInfo *root, Path *best_path, int flags) plan = (Plan *) create_minmaxagg_plan(root, (MinMaxAggPath *) best_path); } + else if (IsA(best_path, GroupResultPath)) + { + plan = (Plan *) create_group_result_plan(root, + (GroupResultPath *) best_path); + } else { - Assert(IsA(best_path, ResultPath)); - plan = (Plan *) create_result_plan(root, - (ResultPath *) best_path); + /* Simple RTE_RESULT base relation */ + Assert(IsA(best_path, Path)); + plan = create_scan_plan(root, best_path, flags); } break; case T_ProjectSet: @@ -691,6 +699,13 @@ create_scan_plan(PlannerInfo *root, Path *best_path, int flags) scan_clauses); break; + case T_Result: + plan = (Plan *) create_resultscan_plan(root, + best_path, + tlist, + scan_clauses); + break; + case T_WorkTableScan: plan = (Plan *) create_worktablescan_plan(root, best_path, @@ -922,17 +937,34 @@ create_gating_plan(PlannerInfo *root, Path *path, Plan *plan, List *gating_quals) { Plan *gplan; + Plan *splan; Assert(gating_quals); /* + * We might have a trivial Result plan already. Stacking one Result atop + * another is silly, so if that applies, just discard the input plan. + * (We're assuming its targetlist is uninteresting; it should be either + * the same as the result of build_path_tlist, or a simplified version.) + */ + splan = plan; + if (IsA(plan, Result)) + { + Result *rplan = (Result *) plan; + + if (rplan->plan.lefttree == NULL && + rplan->resconstantqual == NULL) + splan = NULL; + } + + /* * Since we need a Result node anyway, always return the path's requested * tlist; that's never a wrong choice, even if the parent node didn't ask * for CP_EXACT_TLIST. */ gplan = (Plan *) make_result(build_path_tlist(root, path), (Node *) gating_quals, - plan); + splan); /* * Notice that we don't change cost or size estimates when doing gating. @@ -1254,15 +1286,14 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path) } /* - * create_result_plan + * create_group_result_plan * Create a Result plan for 'best_path'. - * This is only used for degenerate cases, such as a query with an empty - * jointree. + * This is only used for degenerate grouping cases. * * Returns a Plan node. */ static Result * -create_result_plan(PlannerInfo *root, ResultPath *best_path) +create_group_result_plan(PlannerInfo *root, GroupResultPath *best_path) { Result *plan; List *tlist; @@ -3478,6 +3509,44 @@ create_namedtuplestorescan_plan(PlannerInfo *root, Path *best_path, } /* + * create_resultscan_plan + * Returns a Result plan for the RTE_RESULT base relation scanned by + * 'best_path' with restriction clauses 'scan_clauses' and targetlist + * 'tlist'. + */ +static Result * +create_resultscan_plan(PlannerInfo *root, Path *best_path, + List *tlist, List *scan_clauses) +{ + Result *scan_plan; + Index scan_relid = best_path->parent->relid; + RangeTblEntry *rte PG_USED_FOR_ASSERTS_ONLY; + + Assert(scan_relid > 0); + rte = planner_rt_fetch(scan_relid, root); + Assert(rte->rtekind == RTE_RESULT); + + /* 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); + + /* Replace any outer-relation variables with nestloop params */ + if (best_path->param_info) + { + scan_clauses = (List *) + replace_nestloop_params(root, (Node *) scan_clauses); + } + + scan_plan = make_result(tlist, (Node *) scan_clauses, NULL); + + copy_generic_path_info(&scan_plan->plan, best_path); + + return scan_plan; +} + +/* * create_worktablescan_plan * Returns a worktablescan plan for the base relation scanned by 'best_path' * with restriction clauses 'scan_clauses' and targetlist 'tlist'. diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index a66374094f8..1c78852a881 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -827,7 +827,7 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join, * all below it, so we should report inner_join_rels = qualscope. If * there was exactly one element, we should (and already did) report * whatever its inner_join_rels were. If there were no elements (is - * that possible?) the initialization before the loop fixed it. + * that still possible?) the initialization before the loop fixed it. */ if (list_length(f->fromlist) > 1) *inner_join_rels = *qualscope; diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index fc97a1bb507..f9f4b12db71 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -61,44 +61,6 @@ query_planner(PlannerInfo *root, List *tlist, RelOptInfo *final_rel; /* - * If the query has an empty join tree, then it's something easy like - * "SELECT 2+2;" or "INSERT ... VALUES()". Fall through quickly. - */ - if (parse->jointree->fromlist == NIL) - { - /* We need a dummy joinrel to describe the empty set of baserels */ - final_rel = build_empty_join_rel(root); - - /* - * If query allows parallelism in general, check whether the quals are - * parallel-restricted. (We need not check final_rel->reltarget - * because it's empty at this point. Anything parallel-restricted in - * the query tlist will be dealt with later.) - */ - if (root->glob->parallelModeOK) - final_rel->consider_parallel = - is_parallel_safe(root, parse->jointree->quals); - - /* The only path for it is a trivial Result path */ - add_path(final_rel, (Path *) - create_result_path(root, final_rel, - final_rel->reltarget, - (List *) parse->jointree->quals)); - - /* Select cheapest path (pretty easy in this case...) */ - set_cheapest(final_rel); - - /* - * We still are required to call qp_callback, in case it's something - * like "SELECT 2+2 ORDER BY 1". - */ - root->canon_pathkeys = NIL; - (*qp_callback) (root, qp_extra); - - return final_rel; - } - - /* * Init planner lists to empty. * * NOTE: append_rel_list was set up by subquery_planner, so do not touch @@ -125,6 +87,71 @@ query_planner(PlannerInfo *root, List *tlist, setup_simple_rel_arrays(root); /* + * In the trivial case where the jointree is a single RTE_RESULT relation, + * bypass all the rest of this function and just make a RelOptInfo and its + * one access path. This is worth optimizing because it applies for + * common cases like "SELECT expression" and "INSERT ... VALUES()". + */ + Assert(parse->jointree->fromlist != NIL); + if (list_length(parse->jointree->fromlist) == 1) + { + Node *jtnode = (Node *) linitial(parse->jointree->fromlist); + + if (IsA(jtnode, RangeTblRef)) + { + int varno = ((RangeTblRef *) jtnode)->rtindex; + RangeTblEntry *rte = root->simple_rte_array[varno]; + + Assert(rte != NULL); + if (rte->rtekind == RTE_RESULT) + { + /* Make the RelOptInfo for it directly */ + final_rel = build_simple_rel(root, varno, NULL); + + /* + * If query allows parallelism in general, check whether the + * quals are parallel-restricted. (We need not check + * final_rel->reltarget because it's empty at this point. + * Anything parallel-restricted in the query tlist will be + * dealt with later.) This is normally pretty silly, because + * a Result-only plan would never be interesting to + * parallelize. However, if force_parallel_mode is on, then + * we want to execute the Result in a parallel worker if + * possible, so we must do this. + */ + if (root->glob->parallelModeOK && + force_parallel_mode != FORCE_PARALLEL_OFF) + final_rel->consider_parallel = + is_parallel_safe(root, parse->jointree->quals); + + /* + * The only path for it is a trivial Result path. We cheat a + * bit here by using a GroupResultPath, because that way we + * can just jam the quals into it without preprocessing them. + * (But, if you hold your head at the right angle, a FROM-less + * SELECT is a kind of degenerate-grouping case, so it's not + * that much of a cheat.) + */ + add_path(final_rel, (Path *) + create_group_result_path(root, final_rel, + final_rel->reltarget, + (List *) parse->jointree->quals)); + + /* Select cheapest path (pretty easy in this case...) */ + set_cheapest(final_rel); + + /* + * We still are required to call qp_callback, in case it's + * something like "SELECT 2+2 ORDER BY 1". + */ + (*qp_callback) (root, qp_extra); + + return final_rel; + } + } + } + + /* * Populate append_rel_array with each AppendRelInfo to allow direct * lookups by child relid. */ diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 4465f002c8f..272710eed2d 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -611,6 +611,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, List *newWithCheckOptions; List *newHaving; bool hasOuterJoins; + bool hasResultRTEs; RelOptInfo *final_rel; ListCell *l; @@ -652,6 +653,12 @@ subquery_planner(PlannerGlobal *glob, Query *parse, SS_process_ctes(root); /* + * If the FROM clause is empty, replace it with a dummy RTE_RESULT RTE, so + * that we don't need so many special cases to deal with that situation. + */ + replace_empty_jointree(parse); + + /* * Look for ANY and EXISTS SubLinks in WHERE and JOIN/ON clauses, and try * to transform them into joins. Note that this step does not descend * into subqueries; if we pull up any subqueries below, their SubLinks are @@ -684,14 +691,16 @@ subquery_planner(PlannerGlobal *glob, Query *parse, /* * Detect whether any rangetable entries are RTE_JOIN kind; if not, we can - * avoid the expense of doing flatten_join_alias_vars(). Also check for - * outer joins --- if none, we can skip reduce_outer_joins(). And check - * for LATERAL RTEs, too. This must be done after we have done - * pull_up_subqueries(), of course. + * avoid the expense of doing flatten_join_alias_vars(). Likewise check + * whether any are RTE_RESULT kind; if not, we can skip + * remove_useless_result_rtes(). Also check for outer joins --- if none, + * we can skip reduce_outer_joins(). And check for LATERAL RTEs, too. + * This must be done after we have done pull_up_subqueries(), of course. */ root->hasJoinRTEs = false; root->hasLateralRTEs = false; hasOuterJoins = false; + hasResultRTEs = false; foreach(l, parse->rtable) { RangeTblEntry *rte = lfirst_node(RangeTblEntry, l); @@ -702,6 +711,10 @@ subquery_planner(PlannerGlobal *glob, Query *parse, if (IS_OUTER_JOIN(rte->jointype)) hasOuterJoins = true; } + else if (rte->rtekind == RTE_RESULT) + { + hasResultRTEs = true; + } if (rte->lateral) root->hasLateralRTEs = true; } @@ -717,10 +730,10 @@ subquery_planner(PlannerGlobal *glob, Query *parse, /* * Expand any rangetable entries that are inheritance sets into "append * relations". This can add entries to the rangetable, but they must be - * plain base relations not joins, so it's OK (and marginally more - * efficient) to do it after checking for join RTEs. We must do it after - * pulling up subqueries, else we'd fail to handle inherited tables in - * subqueries. + * plain RTE_RELATION entries, so it's OK (and marginally more efficient) + * to do it after checking for joins and other special RTEs. We must do + * this after pulling up subqueries, else we'd fail to handle inherited + * tables in subqueries. */ expand_inherited_tables(root); @@ -968,6 +981,14 @@ subquery_planner(PlannerGlobal *glob, Query *parse, reduce_outer_joins(root); /* + * If we have any RTE_RESULT relations, see if they can be deleted from + * the jointree. This step is most effectively done after we've done + * expression preprocessing and outer join reduction. + */ + if (hasResultRTEs) + remove_useless_result_rtes(root); + + /* * Do the main planning. If we have an inherited target relation, that * needs special processing, else go straight to grouping_planner. */ @@ -3894,9 +3915,9 @@ create_degenerate_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel, while (--nrows >= 0) { path = (Path *) - create_result_path(root, grouped_rel, - grouped_rel->reltarget, - (List *) parse->havingQual); + create_group_result_path(root, grouped_rel, + grouped_rel->reltarget, + (List *) parse->havingQual); paths = lappend(paths, path); } path = (Path *) @@ -3914,9 +3935,9 @@ create_degenerate_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel, { /* No grouping sets, or just one, so one output row */ path = (Path *) - create_result_path(root, grouped_rel, - grouped_rel->reltarget, - (List *) parse->havingQual); + create_group_result_path(root, grouped_rel, + grouped_rel->reltarget, + (List *) parse->havingQual); } add_path(grouped_rel, path); diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c index 64272dd6500..fd19d0acf1c 100644 --- a/src/backend/optimizer/plan/subselect.c +++ b/src/backend/optimizer/plan/subselect.c @@ -1115,12 +1115,6 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink, return NULL; /* - * The subquery must have a nonempty jointree, else we won't have a join. - */ - if (subselect->jointree->fromlist == NIL) - return NULL; - - /* * Separate out the WHERE clause. (We could theoretically also remove * top-level plain JOIN/ON clauses, but it's probably not worth the * trouble.) @@ -1149,6 +1143,11 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink, return NULL; /* + * The subquery must have a nonempty jointree, but we can make it so. + */ + replace_empty_jointree(subselect); + + /* * Prepare to pull up the sub-select into top range table. * * We rely here on the assumption that the outer query has no references diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index 77dbf4eba3d..bcbca1a0f50 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -4,12 +4,14 @@ * Planner preprocessing for subqueries and join tree manipulation. * * NOTE: the intended sequence for invoking these operations is + * replace_empty_jointree * pull_up_sublinks * inline_set_returning_functions * pull_up_subqueries * flatten_simple_union_all * do expression preprocessing (including flattening JOIN alias vars) * reduce_outer_joins + * remove_useless_result_rtes * * * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group @@ -66,14 +68,12 @@ static Node *pull_up_sublinks_qual_recurse(PlannerInfo *root, Node *node, static Node *pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode, JoinExpr *lowest_outer_join, JoinExpr *lowest_nulling_outer_join, - AppendRelInfo *containing_appendrel, - bool deletion_ok); + AppendRelInfo *containing_appendrel); static Node *pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, JoinExpr *lowest_outer_join, JoinExpr *lowest_nulling_outer_join, - AppendRelInfo *containing_appendrel, - bool deletion_ok); + AppendRelInfo *containing_appendrel); static Node *pull_up_simple_union_all(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte); static void pull_up_union_leaf_queries(Node *setOp, PlannerInfo *root, @@ -82,12 +82,10 @@ static void pull_up_union_leaf_queries(Node *setOp, PlannerInfo *root, static void make_setop_translation_list(Query *query, Index newvarno, List **translated_vars); static bool is_simple_subquery(Query *subquery, RangeTblEntry *rte, - JoinExpr *lowest_outer_join, - bool deletion_ok); + JoinExpr *lowest_outer_join); static Node *pull_up_simple_values(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte); -static bool is_simple_values(PlannerInfo *root, RangeTblEntry *rte, - bool deletion_ok); +static bool is_simple_values(PlannerInfo *root, RangeTblEntry *rte); static bool is_simple_union_all(Query *subquery); static bool is_simple_union_all_recurse(Node *setOp, Query *setOpQuery, List *colTypes); @@ -103,7 +101,6 @@ static Node *pullup_replace_vars_callback(Var *var, replace_rte_variables_context *context); static Query *pullup_replace_vars_subquery(Query *query, pullup_replace_vars_context *context); -static Node *pull_up_subqueries_cleanup(Node *jtnode); static reduce_outer_joins_state *reduce_outer_joins_pass1(Node *jtnode); static void reduce_outer_joins_pass2(Node *jtnode, reduce_outer_joins_state *state, @@ -111,14 +108,62 @@ static void reduce_outer_joins_pass2(Node *jtnode, Relids nonnullable_rels, List *nonnullable_vars, List *forced_null_vars); -static void substitute_multiple_relids(Node *node, - int varno, Relids subrelids); +static Node *remove_useless_results_recurse(PlannerInfo *root, Node *jtnode); +static int get_result_relid(PlannerInfo *root, Node *jtnode); +static void remove_result_refs(PlannerInfo *root, int varno, Node *newjtloc); +static bool find_dependent_phvs(Node *node, int varno); +static void substitute_phv_relids(Node *node, + int varno, Relids subrelids); static void fix_append_rel_relids(List *append_rel_list, int varno, Relids subrelids); static Node *find_jointree_node_for_rel(Node *jtnode, int relid); /* + * replace_empty_jointree + * If the Query's jointree is empty, replace it with a dummy RTE_RESULT + * relation. + * + * By doing this, we can avoid a bunch of corner cases that formerly existed + * for SELECTs with omitted FROM clauses. An example is that a subquery + * with empty jointree previously could not be pulled up, because that would + * have resulted in an empty relid set, making the subquery not uniquely + * identifiable for join or PlaceHolderVar processing. + * + * Unlike most other functions in this file, this function doesn't recurse; + * we rely on other processing to invoke it on sub-queries at suitable times. + */ +void +replace_empty_jointree(Query *parse) +{ + RangeTblEntry *rte; + Index rti; + RangeTblRef *rtr; + + /* Nothing to do if jointree is already nonempty */ + if (parse->jointree->fromlist != NIL) + return; + + /* We mustn't change it in the top level of a setop tree, either */ + if (parse->setOperations) + return; + + /* Create suitable RTE */ + rte = makeNode(RangeTblEntry); + rte->rtekind = RTE_RESULT; + rte->eref = makeAlias("*RESULT*", NIL); + + /* Add it to rangetable */ + parse->rtable = lappend(parse->rtable, rte); + rti = list_length(parse->rtable); + + /* And jam a reference into the jointree */ + rtr = makeNode(RangeTblRef); + rtr->rtindex = rti; + parse->jointree->fromlist = list_make1(rtr); +} + +/* * pull_up_sublinks * Attempt to pull up ANY and EXISTS SubLinks to be treated as * semijoins or anti-semijoins. @@ -611,16 +656,11 @@ pull_up_subqueries(PlannerInfo *root) { /* Top level of jointree must always be a FromExpr */ Assert(IsA(root->parse->jointree, FromExpr)); - /* Reset flag saying we need a deletion cleanup pass */ - root->hasDeletedRTEs = false; /* Recursion starts with no containing join nor appendrel */ root->parse->jointree = (FromExpr *) pull_up_subqueries_recurse(root, (Node *) root->parse->jointree, - NULL, NULL, NULL, false); - /* Apply cleanup phase if necessary */ - if (root->hasDeletedRTEs) - root->parse->jointree = (FromExpr *) - pull_up_subqueries_cleanup((Node *) root->parse->jointree); + NULL, NULL, NULL); + /* We should still have a FromExpr */ Assert(IsA(root->parse->jointree, FromExpr)); } @@ -629,8 +669,6 @@ pull_up_subqueries(PlannerInfo *root) * Recursive guts of pull_up_subqueries. * * This recursively processes the jointree and returns a modified jointree. - * Or, if it's valid to drop the current node from the jointree completely, - * it returns NULL. * * If this jointree node is within either side of an outer join, then * lowest_outer_join references the lowest such JoinExpr node; otherwise @@ -647,37 +685,27 @@ pull_up_subqueries(PlannerInfo *root) * This forces use of the PlaceHolderVar mechanism for all non-Var targetlist * items, and puts some additional restrictions on what can be pulled up. * - * deletion_ok is true if the caller can cope with us returning NULL for a - * deletable leaf node (for example, a VALUES RTE that could be pulled up). - * If it's false, we'll avoid pullup in such cases. - * * A tricky aspect of this code is that if we pull up a subquery we have * to replace Vars that reference the subquery's outputs throughout the * parent query, including quals attached to jointree nodes above the one - * we are currently processing! We handle this by being careful not to - * change the jointree structure while recursing: no nodes other than leaf - * RangeTblRef entries and entirely-empty FromExprs will be replaced or - * deleted. Also, we can't turn pullup_replace_vars loose on the whole - * jointree, because it'll return a mutated copy of the tree; we have to + * we are currently processing! We handle this by being careful to maintain + * validity of the jointree structure while recursing, in the following sense: + * whenever we recurse, all qual expressions in the tree must be reachable + * from the top level, in case the recursive call needs to modify them. + * + * Notice also that we can't turn pullup_replace_vars loose on the whole + * jointree, because it'd return a mutated copy of the tree; we have to * invoke it just on the quals, instead. This behavior is what makes it * reasonable to pass lowest_outer_join and lowest_nulling_outer_join as * pointers rather than some more-indirect way of identifying the lowest * OJs. Likewise, we don't replace append_rel_list members but only their * substructure, so the containing_appendrel reference is safe to use. - * - * Because of the rule that no jointree nodes with substructure can be - * replaced, we cannot fully handle the case of deleting nodes from the tree: - * when we delete one child of a JoinExpr, we need to replace the JoinExpr - * with a FromExpr, and that can't happen here. Instead, we set the - * root->hasDeletedRTEs flag, which tells pull_up_subqueries() that an - * additional pass over the tree is needed to clean up. */ static Node * pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode, JoinExpr *lowest_outer_join, JoinExpr *lowest_nulling_outer_join, - AppendRelInfo *containing_appendrel, - bool deletion_ok) + AppendRelInfo *containing_appendrel) { Assert(jtnode != NULL); if (IsA(jtnode, RangeTblRef)) @@ -693,15 +721,13 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode, * unless is_safe_append_member says so. */ if (rte->rtekind == RTE_SUBQUERY && - is_simple_subquery(rte->subquery, rte, - lowest_outer_join, deletion_ok) && + is_simple_subquery(rte->subquery, rte, lowest_outer_join) && (containing_appendrel == NULL || is_safe_append_member(rte->subquery))) return pull_up_simple_subquery(root, jtnode, rte, lowest_outer_join, lowest_nulling_outer_join, - containing_appendrel, - deletion_ok); + containing_appendrel); /* * Alternatively, is it a simple UNION ALL subquery? If so, flatten @@ -725,7 +751,7 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode, if (rte->rtekind == RTE_VALUES && lowest_outer_join == NULL && containing_appendrel == NULL && - is_simple_values(root, rte, deletion_ok)) + is_simple_values(root, rte)) return pull_up_simple_values(root, jtnode, rte); /* Otherwise, do nothing at this node. */ @@ -733,50 +759,16 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode, else if (IsA(jtnode, FromExpr)) { FromExpr *f = (FromExpr *) jtnode; - bool have_undeleted_child = false; ListCell *l; Assert(containing_appendrel == NULL); - - /* - * If the FromExpr has quals, it's not deletable even if its parent - * would allow deletion. - */ - if (f->quals) - deletion_ok = false; - + /* Recursively transform all the child nodes */ foreach(l, f->fromlist) { - /* - * In a non-deletable FromExpr, we can allow deletion of child - * nodes so long as at least one child remains; so it's okay - * either if any previous child survives, or if there's more to - * come. If all children are deletable in themselves, we'll force - * the last one to remain unflattened. - * - * As a separate matter, we can allow deletion of all children of - * the top-level FromExpr in a query, since that's a special case - * anyway. - */ - bool sub_deletion_ok = (deletion_ok || - have_undeleted_child || - lnext(l) != NULL || - f == root->parse->jointree); - lfirst(l) = pull_up_subqueries_recurse(root, lfirst(l), lowest_outer_join, lowest_nulling_outer_join, - NULL, - sub_deletion_ok); - if (lfirst(l) != NULL) - have_undeleted_child = true; - } - - if (deletion_ok && !have_undeleted_child) - { - /* OK to delete this FromExpr entirely */ - root->hasDeletedRTEs = true; /* probably is set already */ - return NULL; + NULL); } } else if (IsA(jtnode, JoinExpr)) @@ -788,22 +780,14 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode, switch (j->jointype) { case JOIN_INNER: - - /* - * INNER JOIN can allow deletion of either child node, but not - * both. So right child gets permission to delete only if - * left child didn't get removed. - */ j->larg = pull_up_subqueries_recurse(root, j->larg, lowest_outer_join, lowest_nulling_outer_join, - NULL, - true); + NULL); j->rarg = pull_up_subqueries_recurse(root, j->rarg, lowest_outer_join, lowest_nulling_outer_join, - NULL, - j->larg != NULL); + NULL); break; case JOIN_LEFT: case JOIN_SEMI: @@ -811,37 +795,31 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode, j->larg = pull_up_subqueries_recurse(root, j->larg, j, lowest_nulling_outer_join, - NULL, - false); + NULL); j->rarg = pull_up_subqueries_recurse(root, j->rarg, j, j, - NULL, - false); + NULL); break; case JOIN_FULL: j->larg = pull_up_subqueries_recurse(root, j->larg, j, j, - NULL, - false); + NULL); j->rarg = pull_up_subqueries_recurse(root, j->rarg, j, j, - NULL, - false); + NULL); break; case JOIN_RIGHT: j->larg = pull_up_subqueries_recurse(root, j->larg, j, j, - NULL, - false); + NULL); j->rarg = pull_up_subqueries_recurse(root, j->rarg, j, lowest_nulling_outer_join, - NULL, - false); + NULL); break; default: elog(ERROR, "unrecognized join type: %d", @@ -861,8 +839,8 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode, * * jtnode is a RangeTblRef that has been tentatively identified as a simple * subquery by pull_up_subqueries. We return the replacement jointree node, - * or NULL if the subquery can be deleted entirely, or jtnode itself if we - * determine that the subquery can't be pulled up after all. + * or jtnode itself if we determine that the subquery can't be pulled up + * after all. * * rte is the RangeTblEntry referenced by jtnode. Remaining parameters are * as for pull_up_subqueries_recurse. @@ -871,8 +849,7 @@ static Node * pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, JoinExpr *lowest_outer_join, JoinExpr *lowest_nulling_outer_join, - AppendRelInfo *containing_appendrel, - bool deletion_ok) + AppendRelInfo *containing_appendrel) { Query *parse = root->parse; int varno = ((RangeTblRef *) jtnode)->rtindex; @@ -926,6 +903,12 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, Assert(subquery->cteList == NIL); /* + * If the FROM clause is empty, replace it with a dummy RTE_RESULT RTE, so + * that we don't need so many special cases to deal with that situation. + */ + replace_empty_jointree(subquery); + + /* * Pull up any SubLinks within the subquery's quals, so that we don't * leave unoptimized SubLinks behind. */ @@ -957,8 +940,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, * easier just to keep this "if" looking the same as the one in * pull_up_subqueries_recurse. */ - if (is_simple_subquery(subquery, rte, - lowest_outer_join, deletion_ok) && + if (is_simple_subquery(subquery, rte, lowest_outer_join) && (containing_appendrel == NULL || is_safe_append_member(subquery))) { /* good to go */ @@ -1159,6 +1141,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, case RTE_JOIN: case RTE_CTE: case RTE_NAMEDTUPLESTORE: + case RTE_RESULT: /* these can't contain any lateral references */ break; } @@ -1195,7 +1178,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, Relids subrelids; subrelids = get_relids_in_jointree((Node *) subquery->jointree, false); - substitute_multiple_relids((Node *) parse, varno, subrelids); + substitute_phv_relids((Node *) parse, varno, subrelids); fix_append_rel_relids(root->append_rel_list, varno, subrelids); } @@ -1235,17 +1218,14 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, /* * Return the adjusted subquery jointree to replace the RangeTblRef entry - * in parent's jointree; or, if we're flattening a subquery with empty - * FROM list, return NULL to signal deletion of the subquery from the - * parent jointree (and set hasDeletedRTEs to ensure cleanup later). + * in parent's jointree; or, if the FromExpr is degenerate, just return + * its single member. */ - if (subquery->jointree->fromlist == NIL) - { - Assert(deletion_ok); - Assert(subquery->jointree->quals == NULL); - root->hasDeletedRTEs = true; - return NULL; - } + Assert(IsA(subquery->jointree, FromExpr)); + Assert(subquery->jointree->fromlist != NIL); + if (subquery->jointree->quals == NULL && + list_length(subquery->jointree->fromlist) == 1) + return (Node *) linitial(subquery->jointree->fromlist); return (Node *) subquery->jointree; } @@ -1381,7 +1361,7 @@ pull_up_union_leaf_queries(Node *setOp, PlannerInfo *root, int parentRTindex, rtr = makeNode(RangeTblRef); rtr->rtindex = childRTindex; (void) pull_up_subqueries_recurse(root, (Node *) rtr, - NULL, NULL, appinfo, false); + NULL, NULL, appinfo); } else if (IsA(setOp, SetOperationStmt)) { @@ -1436,12 +1416,10 @@ make_setop_translation_list(Query *query, Index newvarno, * (Note subquery is not necessarily equal to rte->subquery; it could be a * processed copy of that.) * lowest_outer_join is the lowest outer join above the subquery, or NULL. - * deletion_ok is true if it'd be okay to delete the subquery entirely. */ static bool is_simple_subquery(Query *subquery, RangeTblEntry *rte, - JoinExpr *lowest_outer_join, - bool deletion_ok) + JoinExpr *lowest_outer_join) { /* * Let's just make sure it's a valid subselect ... @@ -1491,44 +1469,6 @@ is_simple_subquery(Query *subquery, RangeTblEntry *rte, return false; /* - * Don't pull up a subquery with an empty jointree, unless it has no quals - * and deletion_ok is true and we're not underneath an outer join. - * - * query_planner() will correctly generate a Result plan for a jointree - * that's totally empty, but we can't cope with an empty FromExpr - * appearing lower down in a jointree: we identify join rels via baserelid - * sets, so we couldn't distinguish a join containing such a FromExpr from - * one without it. We can only handle such cases if the place where the - * subquery is linked is a FromExpr or inner JOIN that would still be - * nonempty after removal of the subquery, so that it's still identifiable - * via its contained baserelids. Safe contexts are signaled by - * deletion_ok. - * - * But even in a safe context, we must keep the subquery if it has any - * quals, because it's unclear where to put them in the upper query. - * - * Also, we must forbid pullup if such a subquery is underneath an outer - * join, because then we might need to wrap its output columns with - * PlaceHolderVars, and the PHVs would then have empty relid sets meaning - * we couldn't tell where to evaluate them. (This test is separate from - * the deletion_ok flag for possible future expansion: deletion_ok tells - * whether the immediate parent site in the jointree could cope, not - * whether we'd have PHV issues. It's possible this restriction could be - * fixed by letting the PHVs use the relids of the parent jointree item, - * but that complication is for another day.) - * - * Note that deletion of a subquery is also dependent on the check below - * that its targetlist contains no set-returning functions. Deletion from - * a FROM list or inner JOIN is okay only if the subquery must return - * exactly one row. - */ - if (subquery->jointree->fromlist == NIL && - (subquery->jointree->quals != NULL || - !deletion_ok || - lowest_outer_join != NULL)) - return false; - - /* * If the subquery is LATERAL, check for pullup restrictions from that. */ if (rte->lateral) @@ -1602,9 +1542,10 @@ is_simple_subquery(Query *subquery, RangeTblEntry *rte, * Pull up a single simple VALUES RTE. * * jtnode is a RangeTblRef that has been identified as a simple VALUES RTE - * by pull_up_subqueries. We always return NULL indicating that the RTE - * can be deleted entirely (all failure cases should have been detected by - * is_simple_values()). + * by pull_up_subqueries. We always return a RangeTblRef representing a + * RESULT RTE to replace it (all failure cases should have been detected by + * is_simple_values()). Actually, what we return is just jtnode, because + * we replace the VALUES RTE in the rangetable with the RESULT RTE. * * rte is the RangeTblEntry referenced by jtnode. Because of the limited * possible usage of VALUES RTEs, we do not need the remaining parameters @@ -1703,11 +1644,23 @@ pull_up_simple_values(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte) Assert(root->placeholder_list == NIL); /* - * Return NULL to signal deletion of the VALUES RTE from the parent - * jointree (and set hasDeletedRTEs to ensure cleanup later). + * Replace the VALUES RTE with a RESULT RTE. The VALUES RTE is the only + * rtable entry in the current query level, so this is easy. */ - root->hasDeletedRTEs = true; - return NULL; + Assert(list_length(parse->rtable) == 1); + + /* Create suitable RTE */ + rte = makeNode(RangeTblEntry); + rte->rtekind = RTE_RESULT; + rte->eref = makeAlias("*RESULT*", NIL); + + /* Replace rangetable */ + parse->rtable = list_make1(rte); + + /* We could manufacture a new RangeTblRef, but the one we have is fine */ + Assert(varno == 1); + + return jtnode; } /* @@ -1716,24 +1669,16 @@ pull_up_simple_values(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte) * to pull up into the parent query. * * rte is the RTE_VALUES RangeTblEntry to check. - * deletion_ok is true if it'd be okay to delete the VALUES RTE entirely. */ static bool -is_simple_values(PlannerInfo *root, RangeTblEntry *rte, bool deletion_ok) +is_simple_values(PlannerInfo *root, RangeTblEntry *rte) { Assert(rte->rtekind == RTE_VALUES); /* - * We can only pull up a VALUES RTE if deletion_ok is true. It's - * basically the same case as a sub-select with empty FROM list; see - * comments in is_simple_subquery(). - */ - if (!deletion_ok) - return false; - - /* - * Also, there must be exactly one VALUES list, else it's not semantically - * correct to delete the VALUES RTE. + * There must be exactly one VALUES list, else it's not semantically + * correct to replace the VALUES RTE with a RESULT RTE, nor would we have + * a unique set of expressions to substitute into the parent query. */ if (list_length(rte->values_lists) != 1) return false; @@ -1746,8 +1691,8 @@ is_simple_values(PlannerInfo *root, RangeTblEntry *rte, bool deletion_ok) /* * Don't pull up a VALUES that contains any set-returning or volatile - * functions. Again, the considerations here are basically identical to - * restrictions on a subquery's targetlist. + * functions. The considerations here are basically identical to the + * restrictions on a pull-able subquery's targetlist. */ if (expression_returns_set((Node *) rte->values_lists) || contain_volatile_functions((Node *) rte->values_lists)) @@ -1850,7 +1795,9 @@ is_safe_append_member(Query *subquery) /* * It's only safe to pull up the child if its jointree contains exactly * one RTE, else the AppendRelInfo data structure breaks. The one base RTE - * could be buried in several levels of FromExpr, however. + * could be buried in several levels of FromExpr, however. Also, if the + * child's jointree is completely empty, we can pull up because + * pull_up_simple_subquery will insert a single RTE_RESULT RTE instead. * * Also, the child can't have any WHERE quals because there's no place to * put them in an appendrel. (This is a bit annoying...) If we didn't @@ -1859,6 +1806,11 @@ is_safe_append_member(Query *subquery) * fix_append_rel_relids(). */ jtnode = subquery->jointree; + Assert(IsA(jtnode, FromExpr)); + /* Check the completely-empty case */ + if (jtnode->fromlist == NIL && jtnode->quals == NULL) + return true; + /* Check the more general case */ while (IsA(jtnode, FromExpr)) { if (jtnode->quals != NULL) @@ -2014,6 +1966,7 @@ replace_vars_in_jointree(Node *jtnode, case RTE_JOIN: case RTE_CTE: case RTE_NAMEDTUPLESTORE: + case RTE_RESULT: /* these shouldn't be marked LATERAL */ Assert(false); break; @@ -2290,65 +2243,6 @@ pullup_replace_vars_subquery(Query *query, NULL); } -/* - * pull_up_subqueries_cleanup - * Recursively fix up jointree after deletion of some subqueries. - * - * The jointree now contains some NULL subtrees, which we need to get rid of. - * In a FromExpr, just rebuild the child-node list with null entries deleted. - * In an inner JOIN, replace the JoinExpr node with a one-child FromExpr. - */ -static Node * -pull_up_subqueries_cleanup(Node *jtnode) -{ - Assert(jtnode != NULL); - if (IsA(jtnode, RangeTblRef)) - { - /* Nothing to do at leaf nodes. */ - } - else if (IsA(jtnode, FromExpr)) - { - FromExpr *f = (FromExpr *) jtnode; - List *newfrom = NIL; - ListCell *l; - - foreach(l, f->fromlist) - { - Node *child = (Node *) lfirst(l); - - if (child == NULL) - continue; - child = pull_up_subqueries_cleanup(child); - newfrom = lappend(newfrom, child); - } - f->fromlist = newfrom; - } - else if (IsA(jtnode, JoinExpr)) - { - JoinExpr *j = (JoinExpr *) jtnode; - - if (j->larg) - j->larg = pull_up_subqueries_cleanup(j->larg); - if (j->rarg) - j->rarg = pull_up_subqueries_cleanup(j->rarg); - if (j->larg == NULL) - { - Assert(j->jointype == JOIN_INNER); - Assert(j->rarg != NULL); - return (Node *) makeFromExpr(list_make1(j->rarg), j->quals); - } - else if (j->rarg == NULL) - { - Assert(j->jointype == JOIN_INNER); - return (Node *) makeFromExpr(list_make1(j->larg), j->quals); - } - } - else - elog(ERROR, "unrecognized node type: %d", - (int) nodeTag(jtnode)); - return jtnode; -} - /* * flatten_simple_union_all @@ -2858,9 +2752,399 @@ reduce_outer_joins_pass2(Node *jtnode, (int) nodeTag(jtnode)); } + +/* + * remove_useless_result_rtes + * Attempt to remove RTE_RESULT RTEs from the join tree. + * + * We can remove RTE_RESULT entries from the join tree using the knowledge + * that RTE_RESULT returns exactly one row and has no output columns. Hence, + * if one is inner-joined to anything else, we can delete it. Optimizations + * are also possible for some outer-join cases, as detailed below. + * + * Some of these optimizations depend on recognizing empty (constant-true) + * quals for FromExprs and JoinExprs. That makes it useful to apply this + * optimization pass after expression preprocessing, since that will have + * eliminated constant-true quals, allowing more cases to be recognized as + * optimizable. What's more, the usual reason for an RTE_RESULT to be present + * is that we pulled up a subquery or VALUES clause, thus very possibly + * replacing Vars with constants, making it more likely that a qual can be + * reduced to constant true. Also, because some optimizations depend on + * the outer-join type, it's best to have done reduce_outer_joins() first. + * + * A PlaceHolderVar referencing an RTE_RESULT RTE poses an obstacle to this + * process: we must remove the RTE_RESULT's relid from the PHV's phrels, but + * we must not reduce the phrels set to empty. If that would happen, and + * the RTE_RESULT is an immediate child of an outer join, we have to give up + * and not remove the RTE_RESULT: there is noplace else to evaluate the + * PlaceHolderVar. (That is, in such cases the RTE_RESULT *does* have output + * columns.) But if the RTE_RESULT is an immediate child of an inner join, + * we can change the PlaceHolderVar's phrels so as to evaluate it at the + * inner join instead. This is OK because we really only care that PHVs are + * evaluated above or below the correct outer joins. + * + * We used to try to do this work as part of pull_up_subqueries() where the + * potentially-optimizable cases get introduced; but it's way simpler, and + * more effective, to do it separately. + */ +void +remove_useless_result_rtes(PlannerInfo *root) +{ + ListCell *cell; + ListCell *prev; + ListCell *next; + + /* Top level of jointree must always be a FromExpr */ + Assert(IsA(root->parse->jointree, FromExpr)); + /* Recurse ... */ + root->parse->jointree = (FromExpr *) + remove_useless_results_recurse(root, (Node *) root->parse->jointree); + /* We should still have a FromExpr */ + Assert(IsA(root->parse->jointree, FromExpr)); + + /* + * Remove any PlanRowMark referencing an RTE_RESULT RTE. We obviously + * must do that for any RTE_RESULT that we just removed. But one for a + * RTE that we did not remove can be dropped anyway: since the RTE has + * only one possible output row, there is no need for EPQ to mark and + * restore that row. + * + * It's necessary, not optional, to remove the PlanRowMark for a surviving + * RTE_RESULT RTE; otherwise we'll generate a whole-row Var for the + * RTE_RESULT, which the executor has no support for. + */ + prev = NULL; + for (cell = list_head(root->rowMarks); cell; cell = next) + { + PlanRowMark *rc = (PlanRowMark *) lfirst(cell); + + next = lnext(cell); + if (rt_fetch(rc->rti, root->parse->rtable)->rtekind == RTE_RESULT) + root->rowMarks = list_delete_cell(root->rowMarks, cell, prev); + else + prev = cell; + } +} + +/* + * remove_useless_results_recurse + * Recursive guts of remove_useless_result_rtes. + * + * This recursively processes the jointree and returns a modified jointree. + */ +static Node * +remove_useless_results_recurse(PlannerInfo *root, Node *jtnode) +{ + Assert(jtnode != NULL); + if (IsA(jtnode, RangeTblRef)) + { + /* Can't immediately do anything with a RangeTblRef */ + } + else if (IsA(jtnode, FromExpr)) + { + FromExpr *f = (FromExpr *) jtnode; + Relids result_relids = NULL; + ListCell *cell; + ListCell *prev; + ListCell *next; + + /* + * We can drop RTE_RESULT rels from the fromlist so long as at least + * one child remains, since joining to a one-row table changes + * nothing. The easiest way to mechanize this rule is to modify the + * list in-place, using list_delete_cell. + */ + prev = NULL; + for (cell = list_head(f->fromlist); cell; cell = next) + { + Node *child = (Node *) lfirst(cell); + int varno; + + /* Recursively transform child ... */ + child = remove_useless_results_recurse(root, child); + /* ... and stick it back into the tree */ + lfirst(cell) = child; + next = lnext(cell); + + /* + * If it's an RTE_RESULT with at least one sibling, we can drop + * it. We don't yet know what the inner join's final relid set + * will be, so postpone cleanup of PHVs etc till after this loop. + */ + if (list_length(f->fromlist) > 1 && + (varno = get_result_relid(root, child)) != 0) + { + f->fromlist = list_delete_cell(f->fromlist, cell, prev); + result_relids = bms_add_member(result_relids, varno); + } + else + prev = cell; + } + + /* + * Clean up if we dropped any RTE_RESULT RTEs. This is a bit + * inefficient if there's more than one, but it seems better to + * optimize the support code for the single-relid case. + */ + if (result_relids) + { + int varno = -1; + + while ((varno = bms_next_member(result_relids, varno)) >= 0) + remove_result_refs(root, varno, (Node *) f); + } + + /* + * If we're not at the top of the jointree, it's valid to simplify a + * degenerate FromExpr into its single child. (At the top, we must + * keep the FromExpr since Query.jointree is required to point to a + * FromExpr.) + */ + if (f != root->parse->jointree && + f->quals == NULL && + list_length(f->fromlist) == 1) + return (Node *) linitial(f->fromlist); + } + else if (IsA(jtnode, JoinExpr)) + { + JoinExpr *j = (JoinExpr *) jtnode; + int varno; + + /* First, recurse */ + j->larg = remove_useless_results_recurse(root, j->larg); + j->rarg = remove_useless_results_recurse(root, j->rarg); + + /* Apply join-type-specific optimization rules */ + switch (j->jointype) + { + case JOIN_INNER: + + /* + * An inner join is equivalent to a FromExpr, so if either + * side was simplified to an RTE_RESULT rel, we can replace + * the join with a FromExpr with just the other side; and if + * the qual is empty (JOIN ON TRUE) then we can omit the + * FromExpr as well. + */ + if ((varno = get_result_relid(root, j->larg)) != 0) + { + remove_result_refs(root, varno, j->rarg); + if (j->quals) + jtnode = (Node *) + makeFromExpr(list_make1(j->rarg), j->quals); + else + jtnode = j->rarg; + } + else if ((varno = get_result_relid(root, j->rarg)) != 0) + { + remove_result_refs(root, varno, j->larg); + if (j->quals) + jtnode = (Node *) + makeFromExpr(list_make1(j->larg), j->quals); + else + jtnode = j->larg; + } + break; + case JOIN_LEFT: + + /* + * We can simplify this case if the RHS is an RTE_RESULT, with + * two different possibilities: + * + * If the qual is empty (JOIN ON TRUE), then the join can be + * strength-reduced to a plain inner join, since each LHS row + * necessarily has exactly one join partner. So we can always + * discard the RHS, much as in the JOIN_INNER case above. + * + * Otherwise, it's still true that each LHS row should be + * returned exactly once, and since the RHS returns no columns + * (unless there are PHVs that have to be evaluated there), we + * don't much care if it's null-extended or not. So in this + * case also, we can just ignore the qual and discard the left + * join. + */ + if ((varno = get_result_relid(root, j->rarg)) != 0 && + (j->quals == NULL || + !find_dependent_phvs((Node *) root->parse, varno))) + { + remove_result_refs(root, varno, j->larg); + jtnode = j->larg; + } + break; + case JOIN_RIGHT: + /* Mirror-image of the JOIN_LEFT case */ + if ((varno = get_result_relid(root, j->larg)) != 0 && + (j->quals == NULL || + !find_dependent_phvs((Node *) root->parse, varno))) + { + remove_result_refs(root, varno, j->rarg); + jtnode = j->rarg; + } + break; + case JOIN_SEMI: + + /* + * We may simplify this case if the RHS is an RTE_RESULT; the + * join qual becomes effectively just a filter qual for the + * LHS, since we should either return the LHS row or not. For + * simplicity we inject the filter qual into a new FromExpr. + * + * Unlike the LEFT/RIGHT cases, we just Assert that there are + * no PHVs that need to be evaluated at the semijoin's RHS, + * since the rest of the query couldn't reference any outputs + * of the semijoin's RHS. + */ + if ((varno = get_result_relid(root, j->rarg)) != 0) + { + Assert(!find_dependent_phvs((Node *) root->parse, varno)); + remove_result_refs(root, varno, j->larg); + if (j->quals) + jtnode = (Node *) + makeFromExpr(list_make1(j->larg), j->quals); + else + jtnode = j->larg; + } + break; + case JOIN_FULL: + case JOIN_ANTI: + /* We have no special smarts for these cases */ + break; + default: + elog(ERROR, "unrecognized join type: %d", + (int) j->jointype); + break; + } + } + else + elog(ERROR, "unrecognized node type: %d", + (int) nodeTag(jtnode)); + return jtnode; +} + +/* + * get_result_relid + * If jtnode is a RangeTblRef for an RTE_RESULT RTE, return its relid; + * otherwise return 0. + */ +static inline int +get_result_relid(PlannerInfo *root, Node *jtnode) +{ + int varno; + + if (!IsA(jtnode, RangeTblRef)) + return 0; + varno = ((RangeTblRef *) jtnode)->rtindex; + if (rt_fetch(varno, root->parse->rtable)->rtekind != RTE_RESULT) + return 0; + return varno; +} + +/* + * remove_result_refs + * Helper routine for dropping an unneeded RTE_RESULT RTE. + * + * This doesn't physically remove the RTE from the jointree, because that's + * more easily handled in remove_useless_results_recurse. What it does do + * is the necessary cleanup in the rest of the tree: we must adjust any PHVs + * that may reference the RTE. Be sure to call this at a point where the + * jointree is valid (no disconnected nodes). + * + * Note that we don't need to process the append_rel_list, since RTEs + * referenced directly in the jointree won't be appendrel members. + * + * varno is the RTE_RESULT's relid. + * newjtloc is the jointree location at which any PHVs referencing the + * RTE_RESULT should be evaluated instead. + */ +static void +remove_result_refs(PlannerInfo *root, int varno, Node *newjtloc) +{ + /* Fix up PlaceHolderVars as needed */ + /* If there are no PHVs anywhere, we can skip this bit */ + if (root->glob->lastPHId != 0) + { + Relids subrelids; + + subrelids = get_relids_in_jointree(newjtloc, false); + Assert(!bms_is_empty(subrelids)); + substitute_phv_relids((Node *) root->parse, varno, subrelids); + } + + /* + * We also need to remove any PlanRowMark referencing the RTE, but we + * postpone that work until we return to remove_useless_result_rtes. + */ +} + + +/* + * find_dependent_phvs - are there any PlaceHolderVars whose relids are + * exactly the given varno? + */ + +typedef struct +{ + Relids relids; + int sublevels_up; +} find_dependent_phvs_context; + +static bool +find_dependent_phvs_walker(Node *node, + find_dependent_phvs_context *context) +{ + if (node == NULL) + return false; + if (IsA(node, PlaceHolderVar)) + { + PlaceHolderVar *phv = (PlaceHolderVar *) node; + + if (phv->phlevelsup == context->sublevels_up && + bms_equal(context->relids, phv->phrels)) + return true; + /* fall through to examine children */ + } + if (IsA(node, Query)) + { + /* Recurse into subselects */ + bool result; + + context->sublevels_up++; + result = query_tree_walker((Query *) node, + find_dependent_phvs_walker, + (void *) context, 0); + context->sublevels_up--; + return result; + } + /* Shouldn't need to handle planner auxiliary nodes here */ + Assert(!IsA(node, SpecialJoinInfo)); + Assert(!IsA(node, AppendRelInfo)); + Assert(!IsA(node, PlaceHolderInfo)); + Assert(!IsA(node, MinMaxAggInfo)); + + return expression_tree_walker(node, find_dependent_phvs_walker, + (void *) context); +} + +static bool +find_dependent_phvs(Node *node, int varno) +{ + find_dependent_phvs_context context; + + context.relids = bms_make_singleton(varno); + context.sublevels_up = 0; + + /* + * Must be prepared to start with a Query or a bare expression tree. + */ + return query_or_expression_tree_walker(node, + find_dependent_phvs_walker, + (void *) &context, + 0); +} + /* - * substitute_multiple_relids - adjust node relid sets after pulling up - * a subquery + * substitute_phv_relids - adjust PlaceHolderVar relid sets after pulling up + * a subquery or removing an RTE_RESULT jointree item * * Find any PlaceHolderVar nodes in the given tree that reference the * pulled-up relid, and change them to reference the replacement relid(s). @@ -2876,11 +3160,11 @@ typedef struct int varno; int sublevels_up; Relids subrelids; -} substitute_multiple_relids_context; +} substitute_phv_relids_context; static bool -substitute_multiple_relids_walker(Node *node, - substitute_multiple_relids_context *context) +substitute_phv_relids_walker(Node *node, + substitute_phv_relids_context *context) { if (node == NULL) return false; @@ -2895,6 +3179,8 @@ substitute_multiple_relids_walker(Node *node, context->subrelids); phv->phrels = bms_del_member(phv->phrels, context->varno); + /* Assert we haven't broken the PHV */ + Assert(!bms_is_empty(phv->phrels)); } /* fall through to examine children */ } @@ -2905,7 +3191,7 @@ substitute_multiple_relids_walker(Node *node, context->sublevels_up++; result = query_tree_walker((Query *) node, - substitute_multiple_relids_walker, + substitute_phv_relids_walker, (void *) context, 0); context->sublevels_up--; return result; @@ -2916,14 +3202,14 @@ substitute_multiple_relids_walker(Node *node, Assert(!IsA(node, PlaceHolderInfo)); Assert(!IsA(node, MinMaxAggInfo)); - return expression_tree_walker(node, substitute_multiple_relids_walker, + return expression_tree_walker(node, substitute_phv_relids_walker, (void *) context); } static void -substitute_multiple_relids(Node *node, int varno, Relids subrelids) +substitute_phv_relids(Node *node, int varno, Relids subrelids) { - substitute_multiple_relids_context context; + substitute_phv_relids_context context; context.varno = varno; context.sublevels_up = 0; @@ -2933,7 +3219,7 @@ substitute_multiple_relids(Node *node, int varno, Relids subrelids) * Must be prepared to start with a Query or a bare expression tree. */ query_or_expression_tree_walker(node, - substitute_multiple_relids_walker, + substitute_phv_relids_walker, (void *) &context, 0); } @@ -2943,7 +3229,7 @@ substitute_multiple_relids(Node *node, int varno, Relids subrelids) * * When we pull up a subquery, any AppendRelInfo references to the subquery's * RT index have to be replaced by the substituted relid (and there had better - * be only one). We also need to apply substitute_multiple_relids to their + * be only one). We also need to apply substitute_phv_relids to their * translated_vars lists, since those might contain PlaceHolderVars. * * We assume we may modify the AppendRelInfo nodes in-place. @@ -2974,9 +3260,9 @@ fix_append_rel_relids(List *append_rel_list, int varno, Relids subrelids) appinfo->child_relid = subvarno; } - /* Also finish fixups for its translated vars */ - substitute_multiple_relids((Node *) appinfo->translated_vars, - varno, subrelids); + /* Also fix up any PHVs in its translated vars */ + substitute_phv_relids((Node *) appinfo->translated_vars, + varno, subrelids); } } diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 94b8fa0c3dd..ea11dbb67c3 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -1707,7 +1707,7 @@ contain_leaked_vars_walker(Node *node, void *context) * find_nonnullable_vars() is that the tested conditions really are different: * a clause like "t1.v1 IS NOT NULL OR t1.v2 IS NOT NULL" does not prove * that either v1 or v2 can't be NULL, but it does prove that the t1 row - * as a whole can't be all-NULL. + * as a whole can't be all-NULL. Also, the behavior for PHVs is different. * * top_level is true while scanning top-level AND/OR structure; here, showing * the result is either FALSE or NULL is good enough. top_level is false when @@ -1893,7 +1893,24 @@ find_nonnullable_rels_walker(Node *node, bool top_level) { PlaceHolderVar *phv = (PlaceHolderVar *) node; + /* + * If the contained expression forces any rels non-nullable, so does + * the PHV. + */ result = find_nonnullable_rels_walker((Node *) phv->phexpr, top_level); + + /* + * If the PHV's syntactic scope is exactly one rel, it will be forced + * to be evaluated at that rel, and so it will behave like a Var of + * that rel: if the rel's entire output goes to null, so will the PHV. + * (If the syntactic scope is a join, we know that the PHV will go to + * null if the whole join does; but that is AND semantics while we + * need OR semantics for find_nonnullable_rels' result, so we can't do + * anything with the knowledge.) + */ + if (phv->phlevelsup == 0 && + bms_membership(phv->phrels) == BMS_SINGLETON) + result = bms_add_members(result, phv->phrels); } return result; } diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index b2637d0e89a..0402ffe9a74 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -1430,17 +1430,17 @@ create_merge_append_path(PlannerInfo *root, } /* - * create_result_path + * create_group_result_path * Creates a path representing a Result-and-nothing-else plan. * - * This is only used for degenerate cases, such as a query with an empty - * jointree. + * This is only used for degenerate grouping cases, in which we know we + * need to produce one result row, possibly filtered by a HAVING qual. */ -ResultPath * -create_result_path(PlannerInfo *root, RelOptInfo *rel, - PathTarget *target, List *resconstantqual) +GroupResultPath * +create_group_result_path(PlannerInfo *root, RelOptInfo *rel, + PathTarget *target, List *havingqual) { - ResultPath *pathnode = makeNode(ResultPath); + GroupResultPath *pathnode = makeNode(GroupResultPath); pathnode->path.pathtype = T_Result; pathnode->path.parent = rel; @@ -1450,9 +1450,13 @@ create_result_path(PlannerInfo *root, RelOptInfo *rel, pathnode->path.parallel_safe = rel->consider_parallel; pathnode->path.parallel_workers = 0; pathnode->path.pathkeys = NIL; - pathnode->quals = resconstantqual; + pathnode->quals = havingqual; - /* Hardly worth defining a cost_result() function ... just do it */ + /* + * We can't quite use cost_resultscan() because the quals we want to + * account for are not baserestrict quals of the rel. Might as well just + * hack it here. + */ pathnode->path.rows = 1; pathnode->path.startup_cost = target->cost.startup; pathnode->path.total_cost = target->cost.startup + @@ -1462,12 +1466,12 @@ create_result_path(PlannerInfo *root, RelOptInfo *rel, * Add cost of qual, if any --- but we ignore its selectivity, since our * rowcount estimate should be 1 no matter what the qual is. */ - if (resconstantqual) + if (havingqual) { QualCost qual_cost; - cost_qual_eval(&qual_cost, resconstantqual, root); - /* resconstantqual is evaluated once at startup */ + cost_qual_eval(&qual_cost, havingqual, root); + /* havingqual is evaluated once at startup */ pathnode->path.startup_cost += qual_cost.startup + qual_cost.per_tuple; pathnode->path.total_cost += qual_cost.startup + qual_cost.per_tuple; } @@ -2021,6 +2025,32 @@ create_namedtuplestorescan_path(PlannerInfo *root, RelOptInfo *rel, } /* + * create_resultscan_path + * Creates a path corresponding to a scan of an RTE_RESULT relation, + * returning the pathnode. + */ +Path * +create_resultscan_path(PlannerInfo *root, RelOptInfo *rel, + Relids required_outer) +{ + Path *pathnode = makeNode(Path); + + pathnode->pathtype = T_Result; + pathnode->parent = rel; + pathnode->pathtarget = rel->reltarget; + pathnode->param_info = get_baserel_parampathinfo(root, rel, + required_outer); + pathnode->parallel_aware = false; + pathnode->parallel_safe = rel->consider_parallel; + pathnode->parallel_workers = 0; + pathnode->pathkeys = NIL; /* result is always unordered */ + + cost_resultscan(pathnode, root, rel, pathnode->param_info); + + return pathnode; +} + +/* * create_worktablescan_path * Creates a path corresponding to a scan of a self-reference CTE, * returning the pathnode. @@ -3560,6 +3590,11 @@ reparameterize_path(PlannerInfo *root, Path *path, spath->path.pathkeys, required_outer); } + case T_Result: + /* Supported only for RTE_RESULT scan paths */ + if (IsA(path, Path)) + return create_resultscan_path(root, rel, required_outer); + break; case T_Append: { AppendPath *apath = (AppendPath *) path; diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 261492e6b71..243344a0110 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -1628,6 +1628,7 @@ build_physical_tlist(PlannerInfo *root, RelOptInfo *rel) case RTE_VALUES: case RTE_CTE: case RTE_NAMEDTUPLESTORE: + case RTE_RESULT: /* Not all of these can have dropped cols, but share code anyway */ expandRTE(rte, varno, 0, -1, true /* include dropped */ , NULL, &colvars); diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index fe83ec45192..f04c6b76f49 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -194,7 +194,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->baserestrict_min_security = UINT_MAX; rel->joininfo = NIL; rel->has_eclass_joins = false; - rel->consider_partitionwise_join = false; /* might get changed later */ + rel->consider_partitionwise_join = false; /* might get changed later */ rel->part_scheme = NULL; rel->nparts = 0; rel->boundinfo = NULL; @@ -247,6 +247,13 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->attr_widths = (int32 *) palloc0((rel->max_attr - rel->min_attr + 1) * sizeof(int32)); break; + case RTE_RESULT: + /* RTE_RESULT has no columns, nor could it have whole-row Var */ + rel->min_attr = 0; + rel->max_attr = -1; + rel->attr_needed = NULL; + rel->attr_widths = NULL; + break; default: elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind); @@ -609,7 +616,7 @@ build_join_rel(PlannerInfo *root, joinrel->baserestrict_min_security = UINT_MAX; joinrel->joininfo = NIL; joinrel->has_eclass_joins = false; - joinrel->consider_partitionwise_join = false; /* might get changed later */ + joinrel->consider_partitionwise_join = false; /* might get changed later */ joinrel->top_parent_relids = NULL; joinrel->part_scheme = NULL; joinrel->nparts = 0; @@ -784,7 +791,7 @@ build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel, joinrel->baserestrictcost.per_tuple = 0; joinrel->joininfo = NIL; joinrel->has_eclass_joins = false; - joinrel->consider_partitionwise_join = false; /* might get changed later */ + joinrel->consider_partitionwise_join = false; /* might get changed later */ joinrel->top_parent_relids = NULL; joinrel->part_scheme = NULL; joinrel->nparts = 0; @@ -1109,36 +1116,6 @@ subbuild_joinrel_joinlist(RelOptInfo *joinrel, /* - * build_empty_join_rel - * Build a dummy join relation describing an empty set of base rels. - * - * This is used for queries with empty FROM clauses, such as "SELECT 2+2" or - * "INSERT INTO foo VALUES(...)". We don't try very hard to make the empty - * joinrel completely valid, since no real planning will be done with it --- - * we just need it to carry a simple Result path out of query_planner(). - */ -RelOptInfo * -build_empty_join_rel(PlannerInfo *root) -{ - RelOptInfo *joinrel; - - /* The dummy join relation should be the only one ... */ - Assert(root->join_rel_list == NIL); - - joinrel = makeNode(RelOptInfo); - joinrel->reloptkind = RELOPT_JOINREL; - joinrel->relids = NULL; /* empty set */ - joinrel->rows = 1; /* we produce one row for such cases */ - joinrel->rtekind = RTE_JOIN; - joinrel->reltarget = create_empty_pathtarget(); - - root->join_rel_list = lappend(root->join_rel_list, joinrel); - - return joinrel; -} - - -/* * fetch_upper_rel * Build a RelOptInfo describing some post-scan/join query processing, * or return a pre-existing one if somebody already built it. diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 8f96558b3db..1ef7c8608ad 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -2884,6 +2884,9 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc, LCS_asString(lc->strength)), parser_errposition(pstate, thisrel->location))); break; + + /* Shouldn't be possible to see RTE_RESULT here */ + default: elog(ERROR, "unrecognized RTE type: %d", (int) rte->rtekind); diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c index 09fbb588af4..3ff799f48ee 100644 --- a/src/backend/parser/parse_relation.c +++ b/src/backend/parser/parse_relation.c @@ -2519,6 +2519,9 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up, } } break; + case RTE_RESULT: + /* These expose no columns, so nothing to do */ + break; default: elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind); } @@ -2911,6 +2914,14 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum, rte->eref->aliasname))); } break; + case RTE_RESULT: + /* this probably can't happen ... */ + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column %d of relation \"%s\" does not exist", + attnum, + rte->eref->aliasname))); + break; default: elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind); } @@ -3039,6 +3050,15 @@ get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum) result = false; /* keep compiler quiet */ } break; + case RTE_RESULT: + /* this probably can't happen ... */ + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column %d of relation \"%s\" does not exist", + attnum, + rte->eref->aliasname))); + result = false; /* keep compiler quiet */ + break; default: elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind); result = false; /* keep compiler quiet */ diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index ccd396bd92b..561d8774f47 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -398,6 +398,7 @@ markTargetListOrigin(ParseState *pstate, TargetEntry *tle, case RTE_VALUES: case RTE_TABLEFUNC: case RTE_NAMEDTUPLESTORE: + case RTE_RESULT: /* not a simple relation, leave it unmarked */ break; case RTE_CTE: @@ -1531,6 +1532,7 @@ expandRecordVariable(ParseState *pstate, Var *var, int levelsup) case RTE_RELATION: case RTE_VALUES: case RTE_NAMEDTUPLESTORE: + case RTE_RESULT: /* * This case should not occur: a column of a table, values list, diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 302df16b4ae..06315677390 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -7011,6 +7011,7 @@ get_name_for_var_field(Var *var, int fieldno, case RTE_RELATION: case RTE_VALUES: case RTE_NAMEDTUPLESTORE: + case RTE_RESULT: /* * This case should not occur: a column of a table, values list, diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 10dac60cd35..4808a9e76d4 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -237,7 +237,7 @@ typedef enum NodeTag T_HashPath, T_AppendPath, T_MergeAppendPath, - T_ResultPath, + T_GroupResultPath, T_MaterialPath, T_UniquePath, T_GatherPath, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index addc2c2ec7a..4ec8a835417 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -950,7 +950,10 @@ typedef enum RTEKind RTE_TABLEFUNC, /* TableFunc(.., column list) */ RTE_VALUES, /* VALUES (<exprlist>), (<exprlist>), ... */ RTE_CTE, /* common table expr (WITH list element) */ - RTE_NAMEDTUPLESTORE /* tuplestore, e.g. for AFTER triggers */ + RTE_NAMEDTUPLESTORE, /* tuplestore, e.g. for AFTER triggers */ + RTE_RESULT /* RTE represents an empty FROM clause; such + * RTEs are added by the planner, they're not + * present during parsing or rewriting */ } RTEKind; typedef struct RangeTblEntry diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 34300613615..420ca05c30a 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -324,7 +324,6 @@ typedef struct PlannerInfo * partitioned table */ bool hasJoinRTEs; /* true if any RTEs are RTE_JOIN kind */ bool hasLateralRTEs; /* true if any RTEs are marked LATERAL */ - bool hasDeletedRTEs; /* true if any RTE was deleted from jointree */ bool hasHavingQual; /* true if havingQual was non-null */ bool hasPseudoConstantQuals; /* true if any RestrictInfo has * pseudoconstant = true */ @@ -1345,17 +1344,17 @@ typedef struct MergeAppendPath } MergeAppendPath; /* - * ResultPath represents use of a Result plan node to compute a variable-free - * targetlist with no underlying tables (a "SELECT expressions" query). - * The query could have a WHERE clause, too, represented by "quals". + * GroupResultPath represents use of a Result plan node to compute the + * output of a degenerate GROUP BY case, wherein we know we should produce + * exactly one row, which might then be filtered by a HAVING qual. * * Note that quals is a list of bare clauses, not RestrictInfos. */ -typedef struct ResultPath +typedef struct GroupResultPath { Path path; List *quals; -} ResultPath; +} GroupResultPath; /* * MaterialPath represents use of a Material plan node, i.e., caching of diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h index e7005b4a0cf..623f733d7df 100644 --- a/src/include/optimizer/cost.h +++ b/src/include/optimizer/cost.h @@ -105,6 +105,8 @@ extern void cost_ctescan(Path *path, PlannerInfo *root, RelOptInfo *baserel, ParamPathInfo *param_info); extern void cost_namedtuplestorescan(Path *path, PlannerInfo *root, RelOptInfo *baserel, ParamPathInfo *param_info); +extern void cost_resultscan(Path *path, PlannerInfo *root, + RelOptInfo *baserel, ParamPathInfo *param_info); extern void cost_recursive_union(Path *runion, Path *nrterm, Path *rterm); extern void cost_sort(Path *path, PlannerInfo *root, List *pathkeys, Cost input_cost, double tuples, int width, @@ -196,6 +198,7 @@ extern void set_cte_size_estimates(PlannerInfo *root, RelOptInfo *rel, double cte_rows); extern void set_tablefunc_size_estimates(PlannerInfo *root, RelOptInfo *rel); extern void set_namedtuplestore_size_estimates(PlannerInfo *root, RelOptInfo *rel); +extern void set_result_size_estimates(PlannerInfo *root, RelOptInfo *rel); extern void set_foreign_size_estimates(PlannerInfo *root, RelOptInfo *rel); extern PathTarget *set_pathtarget_cost_width(PlannerInfo *root, PathTarget *target); extern double compute_bitmap_pages(PlannerInfo *root, RelOptInfo *baserel, diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index bd905d3328e..aaaf3f4ff50 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -75,8 +75,10 @@ extern MergeAppendPath *create_merge_append_path(PlannerInfo *root, List *pathkeys, Relids required_outer, List *partitioned_rels); -extern ResultPath *create_result_path(PlannerInfo *root, RelOptInfo *rel, - PathTarget *target, List *resconstantqual); +extern GroupResultPath *create_group_result_path(PlannerInfo *root, + RelOptInfo *rel, + PathTarget *target, + List *havingqual); extern MaterialPath *create_material_path(RelOptInfo *rel, Path *subpath); extern UniquePath *create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath, SpecialJoinInfo *sjinfo); @@ -105,6 +107,8 @@ extern Path *create_ctescan_path(PlannerInfo *root, RelOptInfo *rel, Relids required_outer); extern Path *create_namedtuplestorescan_path(PlannerInfo *root, RelOptInfo *rel, Relids required_outer); +extern Path *create_resultscan_path(PlannerInfo *root, RelOptInfo *rel, + Relids required_outer); extern Path *create_worktablescan_path(PlannerInfo *root, RelOptInfo *rel, Relids required_outer); extern ForeignPath *create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel, @@ -275,7 +279,6 @@ extern Relids min_join_parameterization(PlannerInfo *root, Relids joinrelids, RelOptInfo *outer_rel, RelOptInfo *inner_rel); -extern RelOptInfo *build_empty_join_rel(PlannerInfo *root); extern RelOptInfo *fetch_upper_rel(PlannerInfo *root, UpperRelationKind kind, Relids relids); extern Relids find_childrel_parents(PlannerInfo *root, RelOptInfo *rel); diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index 62d45dd1426..a03a024ce93 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -21,11 +21,13 @@ /* * prototypes for prepjointree.c */ +extern void replace_empty_jointree(Query *parse); extern void pull_up_sublinks(PlannerInfo *root); extern void inline_set_returning_functions(PlannerInfo *root); extern void pull_up_subqueries(PlannerInfo *root); extern void flatten_simple_union_all(PlannerInfo *root); extern void reduce_outer_joins(PlannerInfo *root); +extern void remove_useless_result_rtes(PlannerInfo *root); extern Relids get_relids_in_jointree(Node *jtnode, bool include_joins); extern Relids get_relids_for_join(PlannerInfo *root, int joinrelid); diff --git a/src/test/isolation/expected/eval-plan-qual.out b/src/test/isolation/expected/eval-plan-qual.out index 49b3fb34469..bbbb62ef4b1 100644 --- a/src/test/isolation/expected/eval-plan-qual.out +++ b/src/test/isolation/expected/eval-plan-qual.out @@ -239,9 +239,9 @@ id value starting permutation: wrjt selectjoinforupdate c2 c1 step wrjt: UPDATE jointest SET data = 42 WHERE id = 7; step selectjoinforupdate: - set enable_nestloop to 0; - set enable_hashjoin to 0; - set enable_seqscan to 0; + set local enable_nestloop to 0; + set local enable_hashjoin to 0; + set local enable_seqscan to 0; explain (costs off) select * from jointest a join jointest b on a.id=b.id for update; select * from jointest a join jointest b on a.id=b.id for update; @@ -269,6 +269,45 @@ id data id data 10 0 10 0 step c1: COMMIT; +starting permutation: wrjt selectresultforupdate c2 c1 +step wrjt: UPDATE jointest SET data = 42 WHERE id = 7; +step selectresultforupdate: + select * from (select 1 as x) ss1 join (select 7 as y) ss2 on true + left join table_a a on a.id = x, jointest jt + where jt.id = y; + explain (verbose, costs off) + select * from (select 1 as x) ss1 join (select 7 as y) ss2 on true + left join table_a a on a.id = x, jointest jt + where jt.id = y for update of jt, ss1, ss2; + select * from (select 1 as x) ss1 join (select 7 as y) ss2 on true + left join table_a a on a.id = x, jointest jt + where jt.id = y for update of jt, ss1, ss2; + <waiting ...> +step c2: COMMIT; +step selectresultforupdate: <... completed> +x y id value id data + +1 7 1 tableAValue 7 0 +QUERY PLAN + +LockRows + Output: 1, 7, a.id, a.value, jt.id, jt.data, jt.ctid, a.ctid + -> Nested Loop Left Join + Output: 1, 7, a.id, a.value, jt.id, jt.data, jt.ctid, a.ctid + -> Nested Loop + Output: jt.id, jt.data, jt.ctid + -> Seq Scan on public.jointest jt + Output: jt.id, jt.data, jt.ctid + Filter: (jt.id = 7) + -> Result + -> Seq Scan on public.table_a a + Output: a.id, a.value, a.ctid + Filter: (a.id = 1) +x y id value id data + +1 7 1 tableAValue 7 42 +step c1: COMMIT; + starting permutation: wrtwcte multireadwcte c1 c2 step wrtwcte: UPDATE table_a SET value = 'tableAValue2' WHERE id = 1; step multireadwcte: diff --git a/src/test/isolation/specs/eval-plan-qual.spec b/src/test/isolation/specs/eval-plan-qual.spec index 367922de751..2e1b5095e8e 100644 --- a/src/test/isolation/specs/eval-plan-qual.spec +++ b/src/test/isolation/specs/eval-plan-qual.spec @@ -102,14 +102,29 @@ step "updateforcip" { # these tests exercise mark/restore during EPQ recheck, cf bug #15032 step "selectjoinforupdate" { - set enable_nestloop to 0; - set enable_hashjoin to 0; - set enable_seqscan to 0; + set local enable_nestloop to 0; + set local enable_hashjoin to 0; + set local enable_seqscan to 0; explain (costs off) select * from jointest a join jointest b on a.id=b.id for update; select * from jointest a join jointest b on a.id=b.id for update; } +# these tests exercise Result plan nodes participating in EPQ + +step "selectresultforupdate" { + select * from (select 1 as x) ss1 join (select 7 as y) ss2 on true + left join table_a a on a.id = x, jointest jt + where jt.id = y; + explain (verbose, costs off) + select * from (select 1 as x) ss1 join (select 7 as y) ss2 on true + left join table_a a on a.id = x, jointest jt + where jt.id = y for update of jt, ss1, ss2; + select * from (select 1 as x) ss1 join (select 7 as y) ss2 on true + left join table_a a on a.id = x, jointest jt + where jt.id = y for update of jt, ss1, ss2; +} + session "s2" setup { BEGIN ISOLATION LEVEL READ COMMITTED; } @@ -190,4 +205,5 @@ permutation "updateforcip" "updateforcip2" "c1" "c2" "read_a" permutation "updateforcip" "updateforcip3" "c1" "c2" "read_a" permutation "wrtwcte" "readwcte" "c1" "c2" permutation "wrjt" "selectjoinforupdate" "c2" "c1" +permutation "wrjt" "selectresultforupdate" "c2" "c1" permutation "wrtwcte" "multireadwcte" "c1" "c2" diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out index 1f5378080d3..2829878ed2a 100644 --- a/src/test/regress/expected/join.out +++ b/src/test/regress/expected/join.out @@ -31,6 +31,10 @@ INSERT INTO J2_TBL VALUES (5, -5); INSERT INTO J2_TBL VALUES (0, NULL); INSERT INTO J2_TBL VALUES (NULL, NULL); INSERT INTO J2_TBL VALUES (NULL, 0); +-- useful in some tests below +create temp table onerow(); +insert into onerow default values; +analyze onerow; -- -- CORRELATION NAMES -- Make sure that table/column aliases are supported @@ -2227,20 +2231,17 @@ explain (costs off) select * from int8_tbl i1 left join (int8_tbl i2 join (select 123 as x) ss on i2.q1 = x) on i1.q2 = i2.q2 order by 1, 2; - QUERY PLAN -------------------------------------------------- + QUERY PLAN +------------------------------------------- Sort Sort Key: i1.q1, i1.q2 -> Hash Left Join Hash Cond: (i1.q2 = i2.q2) -> Seq Scan on int8_tbl i1 -> Hash - -> Hash Join - Hash Cond: (i2.q1 = (123)) - -> Seq Scan on int8_tbl i2 - -> Hash - -> Result -(11 rows) + -> Seq Scan on int8_tbl i2 + Filter: (q1 = 123) +(8 rows) select * from int8_tbl i1 left join (int8_tbl i2 join (select 123 as x) ss on i2.q1 = x) on i1.q2 = i2.q2 @@ -3133,8 +3134,8 @@ select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from tenk1 t1 inner join int4_tbl i1 left join (select v1.x2, v2.y1, 11 AS d1 - from (values(1,0)) v1(x1,x2) - left join (values(3,1)) v2(y1,y2) + from (select 1,0 from onerow) v1(x1,x2) + left join (select 3,1 from onerow) v2(y1,y2) on v1.x1 = v2.y2) subq1 on (i1.f1 = subq1.x2) on (t1.unique2 = subq1.d1) @@ -3144,27 +3145,26 @@ where t1.unique2 < 42 and t1.stringu1 > t2.stringu2; QUERY PLAN ----------------------------------------------------------------------- Nested Loop - Join Filter: (t1.stringu1 > t2.stringu2) -> Nested Loop - Join Filter: ((0) = i1.f1) + Join Filter: (t1.stringu1 > t2.stringu2) -> Nested Loop -> Nested Loop - Join Filter: ((1) = (1)) - -> Result - -> Result + -> Seq Scan on onerow + -> Seq Scan on onerow onerow_1 -> Index Scan using tenk1_unique2 on tenk1 t1 Index Cond: ((unique2 = (11)) AND (unique2 < 42)) - -> Seq Scan on int4_tbl i1 - -> Index Scan using tenk1_unique1 on tenk1 t2 - Index Cond: (unique1 = (3)) -(14 rows) + -> Index Scan using tenk1_unique1 on tenk1 t2 + Index Cond: (unique1 = (3)) + -> Seq Scan on int4_tbl i1 + Filter: (f1 = 0) +(13 rows) select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from tenk1 t1 inner join int4_tbl i1 left join (select v1.x2, v2.y1, 11 AS d1 - from (values(1,0)) v1(x1,x2) - left join (values(3,1)) v2(y1,y2) + from (select 1,0 from onerow) v1(x1,x2) + left join (select 3,1 from onerow) v2(y1,y2) on v1.x1 = v2.y2) subq1 on (i1.f1 = subq1.x2) on (t1.unique2 = subq1.d1) @@ -3196,6 +3196,50 @@ where t1.unique1 < i4.f1; ---- (0 rows) +-- this variant is foldable by the remove-useless-RESULT-RTEs code +explain (costs off) +select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from + tenk1 t1 + inner join int4_tbl i1 + left join (select v1.x2, v2.y1, 11 AS d1 + from (values(1,0)) v1(x1,x2) + left join (values(3,1)) v2(y1,y2) + on v1.x1 = v2.y2) subq1 + on (i1.f1 = subq1.x2) + on (t1.unique2 = subq1.d1) + left join tenk1 t2 + on (subq1.y1 = t2.unique1) +where t1.unique2 < 42 and t1.stringu1 > t2.stringu2; + QUERY PLAN +----------------------------------------------------------------- + Nested Loop + Join Filter: (t1.stringu1 > t2.stringu2) + -> Nested Loop + -> Seq Scan on int4_tbl i1 + Filter: (f1 = 0) + -> Index Scan using tenk1_unique2 on tenk1 t1 + Index Cond: ((unique2 = (11)) AND (unique2 < 42)) + -> Index Scan using tenk1_unique1 on tenk1 t2 + Index Cond: (unique1 = (3)) +(9 rows) + +select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from + tenk1 t1 + inner join int4_tbl i1 + left join (select v1.x2, v2.y1, 11 AS d1 + from (values(1,0)) v1(x1,x2) + left join (values(3,1)) v2(y1,y2) + on v1.x1 = v2.y2) subq1 + on (i1.f1 = subq1.x2) + on (t1.unique2 = subq1.d1) + left join tenk1 t2 + on (subq1.y1 = t2.unique1) +where t1.unique2 < 42 and t1.stringu1 > t2.stringu2; + unique2 | stringu1 | unique1 | stringu2 +---------+----------+---------+---------- + 11 | WFAAAA | 3 | LKIAAA +(1 row) + -- -- test extraction of restriction OR clauses from join OR clause -- (we used to only do this for indexable clauses) @@ -3596,7 +3640,7 @@ select t1.* from -> Hash Right Join Output: i8.q2 Hash Cond: ((NULL::integer) = i8b1.q2) - -> Hash Left Join + -> Hash Join Output: i8.q2, (NULL::integer) Hash Cond: (i8.q1 = i8b2.q1) -> Seq Scan on public.int8_tbl i8 @@ -4018,10 +4062,10 @@ select * from QUERY PLAN --------------------------------------- Nested Loop Left Join - Join Filter: ((1) = COALESCE((1))) -> Result -> Hash Full Join Hash Cond: (a1.unique1 = (1)) + Filter: (1 = COALESCE((1))) -> Seq Scan on tenk1 a1 -> Hash -> Result @@ -4951,13 +4995,10 @@ select v.* from -4567890123456789 | (20 rows) -create temp table dual(); -insert into dual default values; -analyze dual; select v.* from (int8_tbl x left join (select q1,(select coalesce(q2,0)) q2 from int8_tbl) y on x.q2 = y.q1) left join int4_tbl z on z.f1 = x.q2, - lateral (select x.q1,y.q1 from dual union all select x.q2,y.q2 from dual) v(vx,vy); + lateral (select x.q1,y.q1 from onerow union all select x.q2,y.q2 from onerow) v(vx,vy); vx | vy -------------------+------------------- 4567890123456789 | 123 diff --git a/src/test/regress/expected/subselect.out b/src/test/regress/expected/subselect.out index 588d0695892..a54b4a5a7c8 100644 --- a/src/test/regress/expected/subselect.out +++ b/src/test/regress/expected/subselect.out @@ -999,7 +999,7 @@ select * from QUERY PLAN ---------------------------------------------------------- Subquery Scan on ss - Output: x, u + Output: ss.x, ss.u Filter: tattle(ss.x, 8) -> ProjectSet Output: 9, unnest('{1,2,3,11,12,13}'::integer[]) @@ -1061,7 +1061,7 @@ select * from QUERY PLAN ---------------------------------------------------------- Subquery Scan on ss - Output: x, u + Output: ss.x, ss.u Filter: tattle(ss.x, ss.u) -> ProjectSet Output: 9, unnest('{1,2,3,11,12,13}'::integer[]) diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql index 334a4dce2d0..11dc4c7a544 100644 --- a/src/test/regress/sql/join.sql +++ b/src/test/regress/sql/join.sql @@ -37,6 +37,12 @@ INSERT INTO J2_TBL VALUES (0, NULL); INSERT INTO J2_TBL VALUES (NULL, NULL); INSERT INTO J2_TBL VALUES (NULL, 0); +-- useful in some tests below +create temp table onerow(); +insert into onerow default values; +analyze onerow; + + -- -- CORRELATION NAMES -- Make sure that table/column aliases are supported @@ -940,8 +946,8 @@ select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from tenk1 t1 inner join int4_tbl i1 left join (select v1.x2, v2.y1, 11 AS d1 - from (values(1,0)) v1(x1,x2) - left join (values(3,1)) v2(y1,y2) + from (select 1,0 from onerow) v1(x1,x2) + left join (select 3,1 from onerow) v2(y1,y2) on v1.x1 = v2.y2) subq1 on (i1.f1 = subq1.x2) on (t1.unique2 = subq1.d1) @@ -953,8 +959,8 @@ select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from tenk1 t1 inner join int4_tbl i1 left join (select v1.x2, v2.y1, 11 AS d1 - from (values(1,0)) v1(x1,x2) - left join (values(3,1)) v2(y1,y2) + from (select 1,0 from onerow) v1(x1,x2) + left join (select 3,1 from onerow) v2(y1,y2) on v1.x1 = v2.y2) subq1 on (i1.f1 = subq1.x2) on (t1.unique2 = subq1.d1) @@ -980,6 +986,35 @@ select ss1.d1 from on t1.tenthous = ss1.d1 where t1.unique1 < i4.f1; +-- this variant is foldable by the remove-useless-RESULT-RTEs code + +explain (costs off) +select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from + tenk1 t1 + inner join int4_tbl i1 + left join (select v1.x2, v2.y1, 11 AS d1 + from (values(1,0)) v1(x1,x2) + left join (values(3,1)) v2(y1,y2) + on v1.x1 = v2.y2) subq1 + on (i1.f1 = subq1.x2) + on (t1.unique2 = subq1.d1) + left join tenk1 t2 + on (subq1.y1 = t2.unique1) +where t1.unique2 < 42 and t1.stringu1 > t2.stringu2; + +select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from + tenk1 t1 + inner join int4_tbl i1 + left join (select v1.x2, v2.y1, 11 AS d1 + from (values(1,0)) v1(x1,x2) + left join (values(3,1)) v2(y1,y2) + on v1.x1 = v2.y2) subq1 + on (i1.f1 = subq1.x2) + on (t1.unique2 = subq1.d1) + left join tenk1 t2 + on (subq1.y1 = t2.unique1) +where t1.unique2 < 42 and t1.stringu1 > t2.stringu2; + -- -- test extraction of restriction OR clauses from join OR clause -- (we used to only do this for indexable clauses) @@ -1661,13 +1696,10 @@ select v.* from (int8_tbl x left join (select q1,(select coalesce(q2,0)) q2 from int8_tbl) y on x.q2 = y.q1) left join int4_tbl z on z.f1 = x.q2, lateral (select x.q1,y.q1 union all select x.q2,y.q2) v(vx,vy); -create temp table dual(); -insert into dual default values; -analyze dual; select v.* from (int8_tbl x left join (select q1,(select coalesce(q2,0)) q2 from int8_tbl) y on x.q2 = y.q1) left join int4_tbl z on z.f1 = x.q2, - lateral (select x.q1,y.q1 from dual union all select x.q2,y.q2 from dual) v(vx,vy); + lateral (select x.q1,y.q1 from onerow union all select x.q2,y.q2 from onerow) v(vx,vy); explain (verbose, costs off) select * from |