diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c index 29b2be602c2..f5926ab89df 100644 --- a/contrib/postgres_fdw/postgres_fdw.c +++ b/contrib/postgres_fdw/postgres_fdw.c @@ -6522,10 +6522,8 @@ foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel, expr, true, false, - false, root->qual_security_level, grouped_rel->relids, - NULL, NULL); if (is_foreign_expr(root, grouped_rel, expr)) fpinfo->remote_conds = lappend(fpinfo->remote_conds, rinfo); diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 26b294d5d07..ae0f9bdc8a5 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -2745,7 +2745,6 @@ set_function_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) if (var) pathkeys = build_expression_pathkey(root, (Expr *) var, - NULL, /* below outer joins */ Int8LessOperator, rel->relids, false); diff --git a/src/backend/optimizer/path/clausesel.c b/src/backend/optimizer/path/clausesel.c index 61db6ad951b..435438a1735 100644 --- a/src/backend/optimizer/path/clausesel.c +++ b/src/backend/optimizer/path/clausesel.c @@ -715,12 +715,6 @@ clause_selectivity_ext(PlannerInfo *root, return (Selectivity) 1.0; } - /* - * If the clause is marked redundant, always return 1.0. - */ - if (rinfo->norm_selec > 1) - return (Selectivity) 1.0; - /* * If possible, cache the result of the selectivity calculation for * the clause. We can cache if varRelid is zero or the clause diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c index 7d957a47a49..7918bb6f0db 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -4787,7 +4787,6 @@ compute_semi_anti_join_factors(PlannerInfo *root, norm_sjinfo.commute_below = NULL; /* we don't bother trying to make the remaining fields valid */ norm_sjinfo.lhs_strict = false; - norm_sjinfo.delay_upper_joins = false; norm_sjinfo.semi_can_btree = false; norm_sjinfo.semi_can_hash = false; norm_sjinfo.semi_operators = NIL; @@ -4956,7 +4955,6 @@ approx_tuple_count(PlannerInfo *root, JoinPath *path, List *quals) sjinfo.commute_below = NULL; /* we don't bother trying to make the remaining fields valid */ sjinfo.lhs_strict = false; - sjinfo.delay_upper_joins = false; sjinfo.semi_can_btree = false; sjinfo.semi_can_hash = false; sjinfo.semi_operators = NIL; diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index 490953f2ff5..007229d26cc 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -34,7 +34,7 @@ static EquivalenceMember *add_eq_member(EquivalenceClass *ec, - Expr *expr, Relids relids, Relids nullable_relids, + Expr *expr, Relids relids, EquivalenceMember *parent, Oid datatype); static bool is_exprlist_member(Expr *node, List *exprs); @@ -131,9 +131,7 @@ process_equivalence(PlannerInfo *root, Expr *item1; Expr *item2; Relids item1_relids, - item2_relids, - item1_nullable_relids, - item2_nullable_relids; + item2_relids; List *opfamilies; EquivalenceClass *ec1, *ec2; @@ -202,12 +200,10 @@ process_equivalence(PlannerInfo *root, make_restrictinfo(root, (Expr *) ntest, restrictinfo->is_pushed_down, - restrictinfo->outerjoin_delayed, restrictinfo->pseudoconstant, restrictinfo->security_level, NULL, - restrictinfo->outer_relids, - restrictinfo->nullable_relids); + restrictinfo->outer_relids); } return false; } @@ -225,12 +221,6 @@ process_equivalence(PlannerInfo *root, return false; /* RHS is non-strict but not constant */ } - /* Calculate nullable-relid sets for each side of the clause */ - item1_nullable_relids = bms_intersect(item1_relids, - restrictinfo->nullable_relids); - item2_nullable_relids = bms_intersect(item2_relids, - restrictinfo->nullable_relids); - /* * We use the declared input types of the operator, not exprType() of the * inputs, as the nominal datatypes for opfamily lookup. This presumes @@ -400,7 +390,7 @@ process_equivalence(PlannerInfo *root, else if (ec1) { /* Case 3: add item2 to ec1 */ - em2 = add_eq_member(ec1, item2, item2_relids, item2_nullable_relids, + em2 = add_eq_member(ec1, item2, item2_relids, NULL, item2_type); ec1->ec_sources = lappend(ec1->ec_sources, restrictinfo); ec1->ec_below_outer_join |= below_outer_join; @@ -418,7 +408,7 @@ process_equivalence(PlannerInfo *root, else if (ec2) { /* Case 3: add item1 to ec2 */ - em1 = add_eq_member(ec2, item1, item1_relids, item1_nullable_relids, + em1 = add_eq_member(ec2, item1, item1_relids, NULL, item1_type); ec2->ec_sources = lappend(ec2->ec_sources, restrictinfo); ec2->ec_below_outer_join |= below_outer_join; @@ -452,9 +442,9 @@ process_equivalence(PlannerInfo *root, ec->ec_min_security = restrictinfo->security_level; ec->ec_max_security = restrictinfo->security_level; ec->ec_merged = NULL; - em1 = add_eq_member(ec, item1, item1_relids, item1_nullable_relids, + em1 = add_eq_member(ec, item1, item1_relids, NULL, item1_type); - em2 = add_eq_member(ec, item2, item2_relids, item2_nullable_relids, + em2 = add_eq_member(ec, item2, item2_relids, NULL, item2_type); root->eq_classes = lappend(root->eq_classes, ec); @@ -545,13 +535,12 @@ canonicalize_ec_expression(Expr *expr, Oid req_type, Oid req_collation) */ static EquivalenceMember * add_eq_member(EquivalenceClass *ec, Expr *expr, Relids relids, - Relids nullable_relids, EquivalenceMember *parent, Oid datatype) + EquivalenceMember *parent, Oid datatype) { EquivalenceMember *em = makeNode(EquivalenceMember); em->em_expr = expr; em->em_relids = relids; - em->em_nullable_relids = nullable_relids; em->em_is_const = false; em->em_is_child = (parent != NULL); em->em_datatype = datatype; @@ -588,13 +577,6 @@ add_eq_member(EquivalenceClass *ec, Expr *expr, Relids relids, * equivalence class it is a member of; if none, optionally build a new * single-member EquivalenceClass for it. * - * expr is the expression, and nullable_relids is the set of base relids - * that are potentially nullable below it. We actually only care about - * the set of such relids that are used in the expression; but for caller - * convenience, we perform that intersection step here. The caller need - * only be sure that nullable_relids doesn't omit any nullable rels that - * might appear in the expr. - * * sortref is the SortGroupRef of the originating SortGroupClause, if any, * or zero if not. (It should never be zero if the expression is volatile!) * @@ -623,7 +605,6 @@ add_eq_member(EquivalenceClass *ec, Expr *expr, Relids relids, EquivalenceClass * get_eclass_for_sort_expr(PlannerInfo *root, Expr *expr, - Relids nullable_relids, List *opfamilies, Oid opcintype, Oid collation, @@ -719,13 +700,12 @@ get_eclass_for_sort_expr(PlannerInfo *root, elog(ERROR, "volatile EquivalenceClass has no sortref"); /* - * Get the precise set of nullable relids appearing in the expression. + * Get the precise set of relids appearing in the expression. */ expr_relids = pull_varnos(root, (Node *) expr); - nullable_relids = bms_intersect(nullable_relids, expr_relids); newem = add_eq_member(newec, copyObject(expr), expr_relids, - nullable_relids, NULL, opcintype); + NULL, opcintype); /* * add_eq_member doesn't check for volatile functions, set-returning @@ -1163,11 +1143,8 @@ generate_base_implied_equalities_const(PlannerInfo *root, { RestrictInfo *restrictinfo = (RestrictInfo *) linitial(ec->ec_sources); - if (bms_membership(restrictinfo->required_relids) != BMS_MULTIPLE) - { - distribute_restrictinfo_to_rels(root, restrictinfo); - return; - } + distribute_restrictinfo_to_rels(root, restrictinfo); + return; } /* @@ -1211,8 +1188,6 @@ generate_base_implied_equalities_const(PlannerInfo *root, rinfo = process_implied_equality(root, eq_op, ec->ec_collation, cur_em->em_expr, const_em->em_expr, bms_copy(ec->ec_relids), - bms_union(cur_em->em_nullable_relids, - const_em->em_nullable_relids), ec->ec_min_security, ec->ec_below_outer_join, cur_em->em_is_const); @@ -1285,8 +1260,6 @@ generate_base_implied_equalities_no_const(PlannerInfo *root, rinfo = process_implied_equality(root, eq_op, ec->ec_collation, prev_em->em_expr, cur_em->em_expr, bms_copy(ec->ec_relids), - bms_union(prev_em->em_nullable_relids, - cur_em->em_nullable_relids), ec->ec_min_security, ec->ec_below_outer_join, false); @@ -1889,8 +1862,6 @@ create_join_clause(PlannerInfo *root, rightem->em_expr, bms_union(leftem->em_relids, rightem->em_relids), - bms_union(leftem->em_nullable_relids, - rightem->em_nullable_relids), ec->ec_min_security); /* If it's a child clause, copy the parent's rinfo_serial */ @@ -1979,23 +1950,11 @@ create_join_clause(PlannerInfo *root, * If we don't find any match for a set-aside outer join clause, we must * throw it back into the regular joinclause processing by passing it to * distribute_restrictinfo_to_rels(). If we do generate a derived clause, - * however, the outer-join clause is redundant. We still throw it back, - * because otherwise the join will be seen as a clauseless join and avoided - * during join order searching; but we mark it as redundant to keep from - * messing up the joinrel's size estimate. (This behavior means that the - * API for this routine is uselessly complex: we could have just put all - * the clauses into the regular processing initially. We keep it because - * someday we might want to do something else, such as inserting "dummy" - * joinclauses instead of real ones.) - * - * Outer join clauses that are marked outerjoin_delayed are special: this - * condition means that one or both VARs might go to null due to a lower - * outer join. We can still push a constant through the clause, but only - * if its operator is strict; and we *have to* throw the clause back into - * regular joinclause processing. By keeping the strict join clause, - * we ensure that any null-extended rows that are mistakenly generated due - * to suppressing rows not matching the constant will be rejected at the - * upper outer join. (This doesn't work for full-join clauses.) + * however, the outer-join clause is redundant. We must still put some + * clause into the regular processing, because otherwise the join will be + * seen as a clauseless join and avoided during join order searching. + * We handle this by generating a constant-TRUE clause that is marked with + * required_relids that make it a join between the correct relations. */ void reconsider_outer_join_clauses(PlannerInfo *root) @@ -2021,10 +1980,14 @@ reconsider_outer_join_clauses(PlannerInfo *root) /* remove it from the list */ root->left_join_clauses = foreach_delete_current(root->left_join_clauses, cell); - /* we throw it back anyway (see notes above) */ - /* but the thrown-back clause has no extra selectivity */ - rinfo->norm_selec = 2.0; - rinfo->outer_selec = 1.0; + /* throw back a dummy replacement clause (see notes above) */ + rinfo = make_restrictinfo(root, + (Expr *) makeBoolConst(true, false), + true, /* is_pushed_down */ + false, /* pseudoconstant */ + 0, /* security_level */ + rinfo->required_relids, + rinfo->outer_relids); distribute_restrictinfo_to_rels(root, rinfo); } } @@ -2042,10 +2005,14 @@ reconsider_outer_join_clauses(PlannerInfo *root) /* remove it from the list */ root->right_join_clauses = foreach_delete_current(root->right_join_clauses, cell); - /* we throw it back anyway (see notes above) */ - /* but the thrown-back clause has no extra selectivity */ - rinfo->norm_selec = 2.0; - rinfo->outer_selec = 1.0; + /* throw back a dummy replacement clause (see notes above) */ + rinfo = make_restrictinfo(root, + (Expr *) makeBoolConst(true, false), + true, /* is_pushed_down */ + false, /* pseudoconstant */ + 0, /* security_level */ + rinfo->required_relids, + rinfo->outer_relids); distribute_restrictinfo_to_rels(root, rinfo); } } @@ -2063,10 +2030,14 @@ reconsider_outer_join_clauses(PlannerInfo *root) /* remove it from the list */ root->full_join_clauses = foreach_delete_current(root->full_join_clauses, cell); - /* we throw it back anyway (see notes above) */ - /* but the thrown-back clause has no extra selectivity */ - rinfo->norm_selec = 2.0; - rinfo->outer_selec = 1.0; + /* throw back a dummy replacement clause (see notes above) */ + rinfo = make_restrictinfo(root, + (Expr *) makeBoolConst(true, false), + true, /* is_pushed_down */ + false, /* pseudoconstant */ + 0, /* security_level */ + rinfo->required_relids, + rinfo->outer_relids); distribute_restrictinfo_to_rels(root, rinfo); } } @@ -2110,18 +2081,13 @@ reconsider_outer_join_clause(PlannerInfo *root, OuterJoinClauseInfo *ojcinfo, left_type, right_type, inner_datatype; - Relids inner_relids, - inner_nullable_relids; + Relids inner_relids; ListCell *lc1; Assert(is_opclause(rinfo->clause)); opno = ((OpExpr *) rinfo->clause)->opno; collation = ((OpExpr *) rinfo->clause)->inputcollid; - /* If clause is outerjoin_delayed, operator must be strict */ - if (rinfo->outerjoin_delayed && !op_strict(opno)) - return false; - /* Extract needed info from the clause */ op_input_types(opno, &left_type, &right_type); if (outer_on_left) @@ -2138,8 +2104,6 @@ reconsider_outer_join_clause(PlannerInfo *root, OuterJoinClauseInfo *ojcinfo, inner_datatype = left_type; inner_relids = rinfo->left_relids; } - inner_nullable_relids = bms_intersect(inner_relids, - rinfo->nullable_relids); /* Scan EquivalenceClasses for a match to outervar */ foreach(lc1, root->eq_classes) @@ -2200,7 +2164,6 @@ reconsider_outer_join_clause(PlannerInfo *root, OuterJoinClauseInfo *ojcinfo, innervar, cur_em->em_expr, bms_copy(inner_relids), - bms_copy(inner_nullable_relids), cur_ec->ec_min_security); if (process_equivalence(root, &newrinfo, true)) match = true; @@ -2238,15 +2201,9 @@ reconsider_full_join_clause(PlannerInfo *root, OuterJoinClauseInfo *ojcinfo) left_type, right_type; Relids left_relids, - right_relids, - left_nullable_relids, - right_nullable_relids; + right_relids; ListCell *lc1; - /* Can't use an outerjoin_delayed clause here */ - if (rinfo->outerjoin_delayed) - return false; - /* Extract needed info from the clause */ Assert(is_opclause(rinfo->clause)); opno = ((OpExpr *) rinfo->clause)->opno; @@ -2256,10 +2213,6 @@ reconsider_full_join_clause(PlannerInfo *root, OuterJoinClauseInfo *ojcinfo) rightvar = (Expr *) get_rightop(rinfo->clause); left_relids = rinfo->left_relids; right_relids = rinfo->right_relids; - left_nullable_relids = bms_intersect(left_relids, - rinfo->nullable_relids); - right_nullable_relids = bms_intersect(right_relids, - rinfo->nullable_relids); foreach(lc1, root->eq_classes) { @@ -2361,7 +2314,6 @@ reconsider_full_join_clause(PlannerInfo *root, OuterJoinClauseInfo *ojcinfo) leftvar, cur_em->em_expr, bms_copy(left_relids), - bms_copy(left_nullable_relids), cur_ec->ec_min_security); if (process_equivalence(root, &newrinfo, true)) matchleft = true; @@ -2377,7 +2329,6 @@ reconsider_full_join_clause(PlannerInfo *root, OuterJoinClauseInfo *ojcinfo) rightvar, cur_em->em_expr, bms_copy(right_relids), - bms_copy(right_nullable_relids), cur_ec->ec_min_security); if (process_equivalence(root, &newrinfo, true)) matchright = true; @@ -2675,7 +2626,6 @@ add_child_rel_equivalences(PlannerInfo *root, /* OK, generate transformed child version */ Expr *child_expr; Relids new_relids; - Relids new_nullable_relids; if (parent_rel->reloptkind == RELOPT_BASEREL) { @@ -2705,21 +2655,7 @@ add_child_rel_equivalences(PlannerInfo *root, top_parent_relids); new_relids = bms_add_members(new_relids, child_relids); - /* - * And likewise for nullable_relids. Note this code assumes - * parent and child relids are singletons. - */ - new_nullable_relids = cur_em->em_nullable_relids; - if (bms_overlap(new_nullable_relids, top_parent_relids)) - { - new_nullable_relids = bms_difference(new_nullable_relids, - top_parent_relids); - new_nullable_relids = bms_add_members(new_nullable_relids, - child_relids); - } - - (void) add_eq_member(cur_ec, child_expr, - new_relids, new_nullable_relids, + (void) add_eq_member(cur_ec, child_expr, new_relids, cur_em, cur_em->em_datatype); /* Record this EC index for the child rel */ @@ -2816,7 +2752,6 @@ add_child_join_rel_equivalences(PlannerInfo *root, /* Yes, generate transformed child version */ Expr *child_expr; Relids new_relids; - Relids new_nullable_relids; if (parent_joinrel->reloptkind == RELOPT_JOINREL) { @@ -2847,20 +2782,7 @@ add_child_join_rel_equivalences(PlannerInfo *root, top_parent_relids); new_relids = bms_add_members(new_relids, child_relids); - /* - * For nullable_relids, we must selectively replace parent - * nullable relids with child ones. - */ - new_nullable_relids = cur_em->em_nullable_relids; - if (bms_overlap(new_nullable_relids, top_parent_relids)) - new_nullable_relids = - adjust_child_relids_multilevel(root, - new_nullable_relids, - child_joinrel, - child_joinrel->top_parent); - - (void) add_eq_member(cur_ec, child_expr, - new_relids, new_nullable_relids, + (void) add_eq_member(cur_ec, child_expr, new_relids, cur_em, cur_em->em_datatype); } } diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 56dd1073c5d..d7cb11c851f 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -743,7 +743,6 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2) sjinfo->commute_below = NULL; /* we don't bother trying to make the remaining fields valid */ sjinfo->lhs_strict = false; - sjinfo->delay_upper_joins = false; sjinfo->semi_can_btree = false; sjinfo->semi_can_hash = false; sjinfo->semi_operators = NIL; diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c index d2e241c983c..c4e7f97f687 100644 --- a/src/backend/optimizer/path/pathkeys.c +++ b/src/backend/optimizer/path/pathkeys.c @@ -180,9 +180,6 @@ pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys) * Given an expression and sort-order information, create a PathKey. * The result is always a "canonical" PathKey, but it might be redundant. * - * expr is the expression, and nullable_relids is the set of base relids - * that are potentially nullable below it. - * * If the PathKey is being generated from a SortGroupClause, sortref should be * the SortGroupClause's SortGroupRef; otherwise zero. * @@ -198,7 +195,6 @@ pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys) static PathKey * make_pathkey_from_sortinfo(PlannerInfo *root, Expr *expr, - Relids nullable_relids, Oid opfamily, Oid opcintype, Oid collation, @@ -234,7 +230,7 @@ make_pathkey_from_sortinfo(PlannerInfo *root, equality_op); /* Now find or (optionally) create a matching EquivalenceClass */ - eclass = get_eclass_for_sort_expr(root, expr, nullable_relids, + eclass = get_eclass_for_sort_expr(root, expr, opfamilies, opcintype, collation, sortref, rel, create_it); @@ -257,7 +253,6 @@ make_pathkey_from_sortinfo(PlannerInfo *root, static PathKey * make_pathkey_from_sortop(PlannerInfo *root, Expr *expr, - Relids nullable_relids, Oid ordering_op, bool nulls_first, Index sortref, @@ -279,7 +274,6 @@ make_pathkey_from_sortop(PlannerInfo *root, return make_pathkey_from_sortinfo(root, expr, - nullable_relids, opfamily, opcintype, collation, @@ -584,12 +578,10 @@ build_index_pathkeys(PlannerInfo *root, } /* - * OK, try to make a canonical pathkey for this sort key. Note we're - * underneath any outer joins, so nullable_relids should be NULL. + * OK, try to make a canonical pathkey for this sort key. */ cpathkey = make_pathkey_from_sortinfo(root, indexkey, - NULL, index->sortopfamily[i], index->opcintype[i], index->indexcollations[i], @@ -743,14 +735,12 @@ build_partition_pathkeys(PlannerInfo *root, RelOptInfo *partrel, /* * Try to make a canonical pathkey for this partkey. * - * We're considering a baserel scan, so nullable_relids should be - * NULL. Also, we assume the PartitionDesc lists any NULL partition - * last, so we treat the scan like a NULLS LAST index: we have - * nulls_first for backwards scan only. + * We assume the PartitionDesc lists any NULL partition last, so we + * treat the scan like a NULLS LAST index: we have nulls_first for + * backwards scan only. */ cpathkey = make_pathkey_from_sortinfo(root, keyCol, - NULL, partscheme->partopfamily[i], partscheme->partopcintype[i], partscheme->partcollation[i], @@ -799,7 +789,7 @@ build_partition_pathkeys(PlannerInfo *root, RelOptInfo *partrel, * Build a pathkeys list that describes an ordering by a single expression * using the given sort operator. * - * expr, nullable_relids, and rel are as for make_pathkey_from_sortinfo. + * expr and rel are as for make_pathkey_from_sortinfo. * We induce the other arguments assuming default sort order for the operator. * * Similarly to make_pathkey_from_sortinfo, the result is NIL if create_it @@ -808,7 +798,6 @@ build_partition_pathkeys(PlannerInfo *root, RelOptInfo *partrel, List * build_expression_pathkey(PlannerInfo *root, Expr *expr, - Relids nullable_relids, Oid opno, Relids rel, bool create_it) @@ -827,7 +816,6 @@ build_expression_pathkey(PlannerInfo *root, cpathkey = make_pathkey_from_sortinfo(root, expr, - nullable_relids, opfamily, opcintype, exprCollation((Node *) expr), @@ -908,14 +896,11 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel, * expression is *not* volatile in the outer query: it's just * a Var referencing whatever the subquery emitted. (IOW, the * outer query isn't going to re-execute the volatile - * expression itself.) So this is okay. Likewise, it's - * correct to pass nullable_relids = NULL, because we're - * underneath any outer joins appearing in the outer query. + * expression itself.) So this is okay. */ outer_ec = get_eclass_for_sort_expr(root, (Expr *) outer_var, - NULL, sub_eclass->ec_opfamilies, sub_member->em_datatype, sub_eclass->ec_collation, @@ -997,7 +982,6 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel, /* See if we have a matching EC for the TLE */ outer_ec = get_eclass_for_sort_expr(root, (Expr *) outer_var, - NULL, sub_eclass->ec_opfamilies, sub_expr_type, sub_expr_coll, @@ -1138,13 +1122,6 @@ build_join_pathkeys(PlannerInfo *root, * The resulting PathKeys are always in canonical form. (Actually, there * is no longer any code anywhere that creates non-canonical PathKeys.) * - * We assume that root->nullable_baserels is the set of base relids that could - * have gone to NULL below the SortGroupClause expressions. This is okay if - * the expressions came from the query's top level (ORDER BY, DISTINCT, etc) - * and if this function is only invoked after deconstruct_jointree. In the - * future we might have to make callers pass in the appropriate - * nullable-relids set, but for now it seems unnecessary. - * * 'sortclauses' is a list of SortGroupClause nodes * 'tlist' is the targetlist to find the referenced tlist entries in */ @@ -1210,7 +1187,6 @@ make_pathkeys_for_sortclauses_extended(PlannerInfo *root, } pathkey = make_pathkey_from_sortop(root, sortkey, - root->nullable_baserels, sortcl->sortop, sortcl->nulls_first, sortcl->tleSortGroupRef, @@ -1268,7 +1244,6 @@ initialize_mergeclause_eclasses(PlannerInfo *root, RestrictInfo *restrictinfo) restrictinfo->left_ec = get_eclass_for_sort_expr(root, (Expr *) get_leftop(clause), - restrictinfo->nullable_relids, restrictinfo->mergeopfamilies, lefttype, ((OpExpr *) clause)->inputcollid, @@ -1278,7 +1253,6 @@ initialize_mergeclause_eclasses(PlannerInfo *root, RestrictInfo *restrictinfo) restrictinfo->right_ec = get_eclass_for_sort_expr(root, (Expr *) get_rightop(clause), - restrictinfo->nullable_relids, restrictinfo->mergeopfamilies, righttype, ((OpExpr *) clause)->inputcollid, diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c index fbb652e7b05..42da62c1c6b 100644 --- a/src/backend/optimizer/plan/analyzejoins.c +++ b/src/backend/optimizer/plan/analyzejoins.c @@ -170,11 +170,10 @@ join_is_removable(PlannerInfo *root, SpecialJoinInfo *sjinfo) int attroff; /* - * Must be a non-delaying left join to a single baserel, else we aren't - * going to be able to do anything with it. + * Must be a left join to a single baserel, else we aren't going to be + * able to do anything with it. */ - if (sjinfo->jointype != JOIN_LEFT || - sjinfo->delay_upper_joins) + if (sjinfo->jointype != JOIN_LEFT) return false; if (!bms_get_singleton_member(sjinfo->min_righthand, &innerrelid)) @@ -570,13 +569,10 @@ reduce_unique_semijoins(PlannerInfo *root) List *restrictlist; /* - * Must be a non-delaying semijoin to a single baserel, else we aren't - * going to be able to do anything with it. (It's probably not - * possible for delay_upper_joins to be set on a semijoin, but we - * might as well check.) + * Must be a semijoin to a single baserel, else we aren't going to be + * able to do anything with it. */ - if (sjinfo->jointype != JOIN_SEMI || - sjinfo->delay_upper_joins) + if (sjinfo->jointype != JOIN_SEMI) continue; if (!bms_get_singleton_member(sjinfo->min_righthand, &innerrelid)) diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index 7db82915493..0f4163bffd6 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -94,6 +94,8 @@ static void deconstruct_distribute(PlannerInfo *root, JoinTreeItem *jtitem, static void process_security_barrier_quals(PlannerInfo *root, int rti, Relids qualscope, bool below_outer_join); +static void mark_rels_nulled_by_join(PlannerInfo *root, Index ojrelid, + Relids lower_rels); static SpecialJoinInfo *make_outerjoininfo(PlannerInfo *root, Relids left_rels, Relids right_rels, Relids inner_join_rels, @@ -128,10 +130,6 @@ static void distribute_qual_to_rels(PlannerInfo *root, Node *clause, bool is_clone, List **postponed_qual_list, List **postponed_oj_qual_list); -static bool check_outerjoin_delay(PlannerInfo *root, Relids *relids_p, - Relids *nullable_relids_p, bool is_pushed_down); -static bool check_equivalence_delay(PlannerInfo *root, - RestrictInfo *restrictinfo); static bool check_redundant_nullability_qual(PlannerInfo *root, Node *clause); static void check_mergejoinable(RestrictInfo *restrictinfo); static void check_hashjoinable(RestrictInfo *restrictinfo); @@ -737,15 +735,6 @@ create_lateral_join_info(PlannerInfo *root) * A sub-joinlist represents a subproblem to be planned separately. Currently * sub-joinlists arise only from FULL OUTER JOIN or when collapsing of * subproblems is stopped by join_collapse_limit or from_collapse_limit. - * - * NOTE: when dealing with inner joins, it is appropriate to let a qual clause - * be evaluated at the lowest level where all the variables it mentions are - * available. However, we cannot push a qual down into the nullable side(s) - * of an outer join since the qual might eliminate matching rows and cause a - * NULL row to be incorrectly emitted by the join. Therefore, we artificially - * OR the minimum-relids of such an outer join into the required_relids of - * clauses appearing above it. This forces those clauses to be delayed until - * application of the outer join (or maybe even higher in the join tree). */ List * deconstruct_jointree(PlannerInfo *root) @@ -757,9 +746,8 @@ deconstruct_jointree(PlannerInfo *root) /* * After this point, no more PlaceHolderInfos may be made, because - * make_outerjoininfo and update_placeholder_eval_levels require all - * active placeholders to be present in root->placeholder_list while we - * crawl up the join tree. + * make_outerjoininfo requires all active placeholders to be present in + * root->placeholder_list while we crawl up the join tree. */ root->placeholdersFrozen = true; @@ -770,7 +758,6 @@ deconstruct_jointree(PlannerInfo *root) /* These are filled as we scan the jointree */ root->all_baserels = NULL; root->outer_join_rels = NULL; - root->nullable_baserels = NULL; /* Perform the initial scan of the jointree */ result = deconstruct_recurse(root, (Node *) root->parse->jointree, @@ -798,31 +785,12 @@ deconstruct_jointree(PlannerInfo *root) */ if (root->join_info_list) { - /* - * XXX hack: when we call distribute_qual_to_rels to process one of - * these clauses, neither the owning SpecialJoinInfo nor any later - * ones can appear in root->join_info_list, else the wrong things will - * happen. Fake it out by emptying join_info_list and rebuilding it - * as we go. This works because join_info_list is only appended to - * during deconstruct_distribute, so we know we are examining - * SpecialJoinInfos bottom-up, just like the first time. We can get - * rid of this hack later, after fixing things so that - * distribute_qual_to_rels doesn't have that requirement about - * join_info_list. - */ - root->join_info_list = NIL; - foreach(lc, item_list) { JoinTreeItem *jtitem = (JoinTreeItem *) lfirst(lc); if (jtitem->oj_joinclauses != NIL) deconstruct_distribute_oj_quals(root, item_list, jtitem); - - /* XXX Rest of hack: rebuild join_info_list as we go */ - if (jtitem->sjinfo) - root->join_info_list = lappend(root->join_info_list, - jtitem->sjinfo); } } @@ -926,7 +894,6 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, else if (IsA(jtnode, JoinExpr)) { JoinExpr *j = (JoinExpr *) jtnode; - Relids nullable_rels; JoinTreeItem *left_item, *right_item; List *leftjoinlist, @@ -952,8 +919,6 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, jtitem->right_rels = right_item->qualscope; /* Inner join adds no restrictions for quals */ jtitem->nonnullable_rels = NULL; - /* and it doesn't force anything to null, either */ - nullable_rels = NULL; break; case JOIN_LEFT: case JOIN_ANTI: @@ -976,13 +941,14 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, j->rtindex); root->outer_join_rels = bms_add_member(root->outer_join_rels, j->rtindex); + mark_rels_nulled_by_join(root, j->rtindex, + right_item->qualscope); } jtitem->inner_join_rels = bms_union(left_item->inner_join_rels, right_item->inner_join_rels); jtitem->left_rels = left_item->qualscope; jtitem->right_rels = right_item->qualscope; jtitem->nonnullable_rels = left_item->qualscope; - nullable_rels = right_item->qualscope; break; case JOIN_SEMI: /* Recurse */ @@ -1005,13 +971,6 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, jtitem->right_rels = right_item->qualscope; /* Semi join adds no restrictions for quals */ jtitem->nonnullable_rels = NULL; - - /* - * Theoretically, a semijoin would null the RHS; but since the - * RHS can't be accessed above the join, this is immaterial - * and we needn't account for it. - */ - nullable_rels = NULL; break; case JOIN_FULL: /* Recurse */ @@ -1031,27 +990,25 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, j->rtindex); root->outer_join_rels = bms_add_member(root->outer_join_rels, j->rtindex); + mark_rels_nulled_by_join(root, j->rtindex, + left_item->qualscope); + mark_rels_nulled_by_join(root, j->rtindex, + right_item->qualscope); jtitem->inner_join_rels = bms_union(left_item->inner_join_rels, right_item->inner_join_rels); jtitem->left_rels = left_item->qualscope; jtitem->right_rels = right_item->qualscope; /* each side is both outer and inner */ jtitem->nonnullable_rels = jtitem->qualscope; - nullable_rels = jtitem->qualscope; break; default: /* JOIN_RIGHT was eliminated during reduce_outer_joins() */ elog(ERROR, "unrecognized join type: %d", (int) j->jointype); leftjoinlist = rightjoinlist = NIL; /* keep compiler quiet */ - nullable_rels = NULL; break; } - /* Report all rels that will be nulled anywhere in the jointree */ - root->nullable_baserels = bms_add_members(root->nullable_baserels, - nullable_rels); - /* * Compute the output joinlist. We fold subproblems together except * at a FULL JOIN or where join_collapse_limit would be exceeded. @@ -1276,11 +1233,7 @@ deconstruct_distribute(PlannerInfo *root, JoinTreeItem *jtitem, /* And add the SpecialJoinInfo to join_info_list */ if (sjinfo) - { root->join_info_list = lappend(root->join_info_list, sjinfo); - /* Each time we do that, recheck placeholder eval levels */ - update_placeholder_eval_levels(root, sjinfo); - } } else { @@ -1345,6 +1298,33 @@ process_security_barrier_quals(PlannerInfo *root, Assert(security_level <= root->qual_security_level); } +/* + * mark_rels_nulled_by_join + * Fill RelOptInfo.nulling_relids of baserels nulled by this outer join + * + * Inputs: + * ojrelid: RT index of the join RTE (must not be 0) + * lower_rels: the base+OJ Relids syntactically below nullable side of join + */ +static void +mark_rels_nulled_by_join(PlannerInfo *root, Index ojrelid, + Relids lower_rels) +{ + int relid = -1; + + while ((relid = bms_next_member(lower_rels, relid)) > 0) + { + RelOptInfo *rel = root->simple_rel_array[relid]; + + if (rel == NULL) /* must be an outer join */ + { + Assert(bms_is_member(relid, root->outer_join_rels)); + continue; + } + rel->nulling_relids = bms_add_member(rel->nulling_relids, ojrelid); + } +} + /* * make_outerjoininfo * Build a SpecialJoinInfo for the current outer join @@ -1422,8 +1402,6 @@ make_outerjoininfo(PlannerInfo *root, sjinfo->commute_above_l = NULL; sjinfo->commute_above_r = NULL; sjinfo->commute_below = NULL; - /* this always starts out false */ - sjinfo->delay_upper_joins = false; compute_semijoin_info(root, sjinfo, clause); @@ -1578,17 +1556,6 @@ make_outerjoininfo(PlannerInfo *root, * Also, we must preserve ordering anyway if we have unsafe PHVs, or * if either this join or the lower OJ is a semijoin or antijoin. * - * Here, we have to consider that "our join condition" includes any - * clauses that syntactically appeared above the lower OJ and below - * ours; those are equivalent to degenerate clauses in our OJ and must - * be treated as such. Such clauses obviously can't reference our - * LHS, and they must be non-strict for the lower OJ's RHS (else - * reduce_outer_joins would have reduced the lower OJ to a plain - * join). Hence the other ways in which we handle clauses within our - * join condition are not affected by them. The net effect is - * therefore sufficiently represented by the delay_upper_joins flag - * saved for us by check_outerjoin_delay. - * * When we don't need to preserve ordering, check to see if outer join * identity 3 applies, and if so, remove the lower OJ's ojrelid from * our min_righthand so that commutation is allowed. @@ -1602,7 +1569,7 @@ make_outerjoininfo(PlannerInfo *root, jointype == JOIN_ANTI || otherinfo->jointype == JOIN_SEMI || otherinfo->jointype == JOIN_ANTI || - !otherinfo->lhs_strict || otherinfo->delay_upper_joins) + !otherinfo->lhs_strict) { /* Preserve ordering */ min_righthand = bms_add_members(min_righthand, @@ -2152,8 +2119,8 @@ distribute_quals_to_rels(PlannerInfo *root, List *clauses, * level, which will be ojscope not necessarily qualscope. * * At the time this is called, root->join_info_list must contain entries for - * all and only those special joins that are syntactically below this qual; - * in particular, the passed-in SpecialJoinInfo isn't yet in that list. + * at least those special joins that are syntactically below this qual. + * (We now need that only for detection of redundant IS NULL quals.) */ static void distribute_qual_to_rels(PlannerInfo *root, Node *clause, @@ -2171,11 +2138,9 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, { Relids relids; bool is_pushed_down; - bool outerjoin_delayed; bool pseudoconstant = false; bool maybe_equivalence; bool maybe_outer_join; - Relids nullable_relids; RestrictInfo *restrictinfo; /* @@ -2326,21 +2291,12 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, maybe_equivalence = false; maybe_outer_join = true; - /* Check to see if must be delayed by lower outer join */ - outerjoin_delayed = check_outerjoin_delay(root, - &relids, - &nullable_relids, - false); - /* * Now force the qual to be evaluated exactly at the level of joining * corresponding to the outer join. We cannot let it get pushed down * into the nonnullable side, since then we'd produce no output rows, * rather than the intended single null-extended row, for any * nonnullable-side rows failing the qual. - * - * (Do this step after calling check_outerjoin_delay, because that - * trashes relids.) */ Assert(ojscope); relids = ojscope; @@ -2354,34 +2310,16 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, */ is_pushed_down = true; - /* Check to see if must be delayed by lower outer join */ - outerjoin_delayed = check_outerjoin_delay(root, - &relids, - &nullable_relids, - true); + /* + * It's possible that this is an IS NULL clause that's redundant with + * a lower antijoin; if so we can just discard it. We need not test + * in any of the other cases, because this will only be possible for + * pushed-down clauses. + */ + if (check_redundant_nullability_qual(root, clause)) + return; - if (outerjoin_delayed) - { - /* Should still be a subset of current scope ... */ - Assert(root->hasLateralRTEs || bms_is_subset(relids, qualscope)); - Assert(ojscope == NULL || bms_is_subset(relids, ojscope)); - - /* - * Because application of the qual will be delayed by outer join, - * we mustn't assume its vars are equal everywhere. - */ - maybe_equivalence = false; - - /* - * It's possible that this is an IS NULL clause that's redundant - * with a lower antijoin; if so we can just discard it. We need - * not test in any of the other cases, because this will only be - * possible for pushed-down, delayed clauses. - */ - if (check_redundant_nullability_qual(root, clause)) - return; - } - else if (!allow_equivalence) + if (!allow_equivalence) { /* Caller says it mustn't become an equivalence class */ maybe_equivalence = false; @@ -2389,8 +2327,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, else { /* - * Qual is not delayed by any lower outer-join restriction, so we - * can consider feeding it to the equivalence machinery. However, + * Consider feeding qual to the equivalence machinery. However, * if it's itself within an outer-join clause, treat it as though * it appeared below that outer join (note that we can only get * here when the clause references only nullable-side rels). @@ -2413,12 +2350,10 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, restrictinfo = make_restrictinfo(root, (Expr *) clause, is_pushed_down, - outerjoin_delayed, pseudoconstant, security_level, relids, - outerjoin_nonnullable, - nullable_relids); + outerjoin_nonnullable); /* Apply appropriate clone marking, too */ restrictinfo->has_clone = has_clone; @@ -2466,6 +2401,8 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, check_mergejoinable(restrictinfo); /* + * XXX rewrite: + * * If it is a true equivalence clause, send it to the EquivalenceClass * machinery. We do *not* attach it directly to any restriction or join * lists. The EC code will propagate it to the appropriate places later. @@ -2501,8 +2438,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, { if (maybe_equivalence) { - if (check_equivalence_delay(root, restrictinfo) && - process_equivalence(root, &restrictinfo, below_outer_join)) + if (process_equivalence(root, &restrictinfo, below_outer_join)) return; /* EC rejected it, so set left_ec/right_ec the hard way ... */ if (restrictinfo->mergeopfamilies) /* EC might have changed this */ @@ -2567,165 +2503,6 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, distribute_restrictinfo_to_rels(root, restrictinfo); } -/* - * check_outerjoin_delay - * Detect whether a qual referencing the given relids must be delayed - * in application due to the presence of a lower outer join, and/or - * may force extra delay of higher-level outer joins. - * - * If the qual must be delayed, add relids to *relids_p to reflect the lowest - * safe level for evaluating the qual, and return true. Any extra delay for - * higher-level joins is reflected by setting delay_upper_joins to true in - * SpecialJoinInfo structs. We also compute nullable_relids, the set of - * referenced relids that are nullable by lower outer joins (note that this - * can be nonempty even for a non-delayed qual). - * - * For an is_pushed_down qual, we can evaluate the qual as soon as (1) we have - * all the rels it mentions, and (2) we are at or above any outer joins that - * can null any of these rels and are below the syntactic location of the - * given qual. We must enforce (2) because pushing down such a clause below - * the OJ might cause the OJ to emit null-extended rows that should not have - * been formed, or that should have been rejected by the clause. (This is - * only an issue for non-strict quals, since if we can prove a qual mentioning - * only nullable rels is strict, we'd have reduced the outer join to an inner - * join in reduce_outer_joins().) - * - * To enforce (2), scan the join_info_list and merge the required-relid sets of - * any such OJs into the clause's own reference list. At the time we are - * called, the join_info_list contains only outer joins below this qual. We - * have to repeat the scan until no new relids get added; this ensures that - * the qual is suitably delayed regardless of the order in which OJs get - * executed. As an example, if we have one OJ with LHS=A, RHS=B, and one with - * LHS=B, RHS=C, it is implied that these can be done in either order; if the - * B/C join is done first then the join to A can null C, so a qual actually - * mentioning only C cannot be applied below the join to A. - * - * For a non-pushed-down qual, this isn't going to determine where we place the - * qual, but we need to determine outerjoin_delayed and nullable_relids anyway - * for use later in the planning process. - * - * Lastly, a pushed-down qual that references the nullable side of any current - * join_info_list member and has to be evaluated above that OJ (because its - * required relids overlap the LHS too) causes that OJ's delay_upper_joins - * flag to be set true. This will prevent any higher-level OJs from - * being interchanged with that OJ, which would result in not having any - * correct place to evaluate the qual. (The case we care about here is a - * sub-select WHERE clause within the RHS of some outer join. The WHERE - * clause must effectively be treated as a degenerate clause of that outer - * join's condition. Rather than trying to match such clauses with joins - * directly, we set delay_upper_joins here, and when the upper outer join - * is processed by make_outerjoininfo, it will refrain from allowing the - * two OJs to commute.) - */ -static bool -check_outerjoin_delay(PlannerInfo *root, - Relids *relids_p, /* in/out parameter */ - Relids *nullable_relids_p, /* output parameter */ - bool is_pushed_down) -{ - Relids relids; - Relids nullable_relids; - bool outerjoin_delayed; - bool found_some; - - /* fast path if no special joins */ - if (root->join_info_list == NIL) - { - *nullable_relids_p = NULL; - return false; - } - - /* must copy relids because we need the original value at the end */ - relids = bms_copy(*relids_p); - nullable_relids = NULL; - outerjoin_delayed = false; - do - { - ListCell *l; - - found_some = false; - foreach(l, root->join_info_list) - { - SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) lfirst(l); - - /* do we reference any nullable rels of this OJ? */ - if (bms_overlap(relids, sjinfo->min_righthand) || - (sjinfo->jointype == JOIN_FULL && - bms_overlap(relids, sjinfo->min_lefthand))) - { - /* yes; have we included all its rels in relids? */ - if (!bms_is_subset(sjinfo->min_lefthand, relids) || - !bms_is_subset(sjinfo->min_righthand, relids)) - { - /* no, so add them in */ - relids = bms_add_members(relids, sjinfo->min_lefthand); - relids = bms_add_members(relids, sjinfo->min_righthand); - outerjoin_delayed = true; - /* we'll need another iteration */ - found_some = true; - } - /* track all the nullable rels of relevant OJs */ - nullable_relids = bms_add_members(nullable_relids, - sjinfo->min_righthand); - if (sjinfo->jointype == JOIN_FULL) - nullable_relids = bms_add_members(nullable_relids, - sjinfo->min_lefthand); - /* set delay_upper_joins if needed */ - if (is_pushed_down && sjinfo->jointype != JOIN_FULL && - bms_overlap(relids, sjinfo->min_lefthand)) - sjinfo->delay_upper_joins = true; - } - } - } while (found_some); - - /* identify just the actually-referenced nullable rels */ - nullable_relids = bms_int_members(nullable_relids, *relids_p); - - /* replace *relids_p, and return nullable_relids */ - bms_free(*relids_p); - *relids_p = relids; - *nullable_relids_p = nullable_relids; - return outerjoin_delayed; -} - -/* - * check_equivalence_delay - * Detect whether a potential equivalence clause is rendered unsafe - * by outer-join-delay considerations. Return true if it's safe. - * - * The initial tests in distribute_qual_to_rels will consider a mergejoinable - * clause to be a potential equivalence clause if it is not outerjoin_delayed. - * But since the point of equivalence processing is that we will recombine the - * two sides of the clause with others, we have to check that each side - * satisfies the not-outerjoin_delayed condition on its own; otherwise it might - * not be safe to evaluate everywhere we could place a derived equivalence - * condition. - */ -static bool -check_equivalence_delay(PlannerInfo *root, - RestrictInfo *restrictinfo) -{ - Relids relids; - Relids nullable_relids; - - /* fast path if no special joins */ - if (root->join_info_list == NIL) - return true; - - /* must copy restrictinfo's relids to avoid changing it */ - relids = bms_copy(restrictinfo->left_relids); - /* check left side does not need delay */ - if (check_outerjoin_delay(root, &relids, &nullable_relids, true)) - return false; - - /* and similarly for the right side */ - relids = bms_copy(restrictinfo->right_relids); - if (check_outerjoin_delay(root, &relids, &nullable_relids, true)) - return false; - - return true; -} - /* * check_redundant_nullability_qual * Check to see if the qual is an IS NULL qual that is redundant with @@ -2740,25 +2517,33 @@ static bool check_redundant_nullability_qual(PlannerInfo *root, Node *clause) { Var *forced_null_var; - Index forced_null_rel; ListCell *lc; /* Check for IS NULL, and identify the Var forced to NULL */ forced_null_var = find_forced_null_var(clause); if (forced_null_var == NULL) return false; - forced_null_rel = forced_null_var->varno; /* * If the Var comes from the nullable side of a lower antijoin, the IS - * NULL condition is necessarily true. + * NULL condition is necessarily true. If it's not nulled by anything, + * there is no point in searching the join_info_list. Otherwise, we need + * to find out whether the nulling rel is an antijoin. */ + if (forced_null_var->varnullingrels == NULL) + return false; + foreach(lc, root->join_info_list) { SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) lfirst(lc); - if (sjinfo->jointype == JOIN_ANTI && - bms_is_member(forced_null_rel, sjinfo->syn_righthand)) + /* + * This test will not succeed if sjinfo->ojrelid is zero, which is + * possible for an antijoin that was converted from a semijoin; but in + * such a case the Var couldn't have come from its nullable side. + */ + if (sjinfo->jointype == JOIN_ANTI && sjinfo->ojrelid != 0 && + bms_is_member(sjinfo->ojrelid, forced_null_var->varnullingrels)) return true; } @@ -2846,11 +2631,6 @@ distribute_restrictinfo_to_rels(PlannerInfo *root, * variable-free. Otherwise the qual is applied at the lowest join level * that provides all its variables. * - * "nullable_relids" is the set of relids used in the expressions that are - * potentially nullable below the expressions. (This has to be supplied by - * caller because this function is used after deconstruct_jointree, so we - * don't have knowledge of where the clause items came from.) - * * "security_level" is the security level to assign to the new restrictinfo. * * "both_const" indicates whether both items are known pseudo-constant; @@ -2876,7 +2656,6 @@ process_implied_equality(PlannerInfo *root, Expr *item1, Expr *item2, Relids qualscope, - Relids nullable_relids, Index security_level, bool below_outer_join, bool both_const) @@ -2956,12 +2735,10 @@ process_implied_equality(PlannerInfo *root, restrictinfo = make_restrictinfo(root, (Expr *) clause, true, /* is_pushed_down */ - false, /* outerjoin_delayed */ pseudoconstant, security_level, relids, - NULL, /* outer_relids */ - nullable_relids); + NULL); /* outer_relids */ /* * If it's a join clause, add vars used in the clause to targetlists of @@ -3026,7 +2803,6 @@ build_implied_join_equality(PlannerInfo *root, Expr *item1, Expr *item2, Relids qualscope, - Relids nullable_relids, Index security_level) { RestrictInfo *restrictinfo; @@ -3050,12 +2826,10 @@ build_implied_join_equality(PlannerInfo *root, restrictinfo = make_restrictinfo(root, clause, true, /* is_pushed_down */ - false, /* outerjoin_delayed */ false, /* pseudoconstant */ security_level, /* security_level */ qualscope, /* required_relids */ - NULL, /* outer_relids */ - nullable_relids); /* nullable_relids */ + NULL); /* outer_relids */ /* Set mergejoinability/hashjoinability flags */ check_mergejoinable(restrictinfo); @@ -3123,8 +2897,7 @@ match_foreign_keys_to_quals(PlannerInfo *root) * Note: for simple inner joins, any match should be in an eclass. * "Loose" quals that syntactically match an FK equality must have * been rejected for EC status because they are outer-join quals or - * similar. We can still consider them to match the FK if they are - * not outerjoin_delayed. + * similar. We can still consider them to match the FK. */ for (colno = 0; colno < fkinfo->nkeys; colno++) { @@ -3159,10 +2932,6 @@ match_foreign_keys_to_quals(PlannerInfo *root) Var *leftvar; Var *rightvar; - /* Ignore outerjoin-delayed clauses */ - if (rinfo->outerjoin_delayed) - continue; - /* Only binary OpExprs are useful for consideration */ if (!IsA(clause, OpExpr) || list_length(clause->args) != 2) diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 320caebd874..8674ad674d9 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -1044,10 +1044,11 @@ subquery_planner(PlannerGlobal *glob, Query *parse, /* * 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. + * the jointree. We also rely on this processing to flatten single-child + * FromExprs underneath outer joins. This step is most effectively done + * after we've done expression preprocessing and outer join reduction. */ - if (hasResultRTEs) + if (hasResultRTEs || hasOuterJoins) remove_useless_result_rtes(root); /* diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index 9c96a558fc3..eacfb66b31a 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -130,6 +130,7 @@ static void reduce_outer_joins_pass2(Node *jtnode, static void report_reduced_full_join(reduce_outer_joins_pass2_state *state2, int rtindex, Relids relids); static Node *remove_useless_results_recurse(PlannerInfo *root, Node *jtnode, + Node **parent_quals, Relids *dropped_outer_joins); static int get_result_relid(PlannerInfo *root, Node *jtnode); static void remove_result_refs(PlannerInfo *root, int varno, Node *newjtloc); @@ -3085,12 +3086,31 @@ report_reduced_full_join(reduce_outer_joins_pass2_state *state2, /* * remove_useless_result_rtes * Attempt to remove RTE_RESULT RTEs from the join tree. + * Also, elide single-child FromExprs where possible. * * 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. * + * This pass also replaces single-child FromExprs with their child node + * where possible. It's appropriate to do that here and not earlier because + * RTE_RESULT removal might reduce a multiple-child FromExpr to have only one + * child. We can remove such a FromExpr if its quals are empty, or if it's + * semantically valid to merge the quals into those of the parent node. + * While removing unnecessary join tree nodes has some micro-efficiency value, + * the real reason to do this is to eliminate cases where the nullable side of + * an outer join node is a FromExpr whose single child is another outer join. + * To correctly determine whether the two outer joins can commute, + * deconstruct_jointree() must treat any quals of such a FromExpr as being + * degenerate quals of the upper outer join. The best way to do that is to + * make them actually *be* quals of the upper join, by dropping the FromExpr + * and hoisting the quals up into the upper join's quals. (Note that there is + * no hazard when the intermediate FromExpr has multiple children, since then + * it represents an inner join that cannot commute with the upper outer join.) + * As long as we have to do that, we might as well elide such FromExprs + * everywhere. + * * 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 @@ -3131,6 +3151,7 @@ remove_useless_result_rtes(PlannerInfo *root) root->parse->jointree = (FromExpr *) remove_useless_results_recurse(root, (Node *) root->parse->jointree, + NULL, &dropped_outer_joins); /* We should still have a FromExpr */ Assert(IsA(root->parse->jointree, FromExpr)); @@ -3184,9 +3205,14 @@ remove_useless_result_rtes(PlannerInfo *root) * This recursively processes the jointree and returns a modified jointree. * In addition, the RT indexes of any removed outer-join nodes are added to * *dropped_outer_joins. + * + * jtnode is the current jointree node. If it could be valid to merge + * its quals into those of the parent node, parent_quals should point to + * the parent's quals list; otherwise, pass NULL for parent_quals. */ static Node * remove_useless_results_recurse(PlannerInfo *root, Node *jtnode, + Node **parent_quals, Relids *dropped_outer_joins) { Assert(jtnode != NULL); @@ -3214,8 +3240,9 @@ remove_useless_results_recurse(PlannerInfo *root, Node *jtnode, Node *child = (Node *) lfirst(cell); int varno; - /* Recursively transform child ... */ + /* Recursively transform child, allowing it to push up quals ... */ child = remove_useless_results_recurse(root, child, + &f->quals, dropped_outer_joins); /* ... and stick it back into the tree */ lfirst(cell) = child; @@ -3249,25 +3276,54 @@ remove_useless_results_recurse(PlannerInfo *root, Node *jtnode, } /* - * 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 the FromExpr now has only one child, see if we can elide it. + * This is always valid if there are no quals, except at the top of + * the jointree (since Query.jointree is required to point to a + * FromExpr). Otherwise, we can do it if we can push the quals up to + * the parent node. + * + * Note: while it would not be terribly hard to generalize this + * transformation to merge multi-child FromExprs into their parent + * FromExpr, that risks making the parent join too expensive to plan. + * We leave it to later processing to decide heuristically whether + * that's a good idea. Pulling up a single child is always OK, + * however. */ - if (f != root->parse->jointree && - f->quals == NULL && - list_length(f->fromlist) == 1) + if (list_length(f->fromlist) == 1 && + f != root->parse->jointree && + (f->quals == NULL || parent_quals != NULL)) + { + /* + * Merge any quals up to parent. They should be in implicit-AND + * format by now, so we just need to concatenate lists. Put the + * child quals at the front, on the grounds that they should + * nominally be evaluated earlier. + */ + if (f->quals != NULL) + *parent_quals = (Node *) + list_concat(castNode(List, f->quals), + castNode(List, *parent_quals)); return (Node *) linitial(f->fromlist); + } } else if (IsA(jtnode, JoinExpr)) { JoinExpr *j = (JoinExpr *) jtnode; int varno; - /* First, recurse */ + /* + * First, recurse. We can accept pushed-up FromExpr quals from either + * child if the jointype is INNER, and we can accept them from the RHS + * child if the jointype is LEFT. + */ j->larg = remove_useless_results_recurse(root, j->larg, + (j->jointype == JOIN_INNER) ? + &j->quals : NULL, dropped_outer_joins); j->rarg = remove_useless_results_recurse(root, j->rarg, + (j->jointype == JOIN_INNER || + j->jointype == JOIN_LEFT) ? + &j->quals : NULL, dropped_outer_joins); /* Apply join-type-specific optimization rules */ @@ -3278,9 +3334,9 @@ remove_useless_results_recurse(PlannerInfo *root, Node *jtnode, /* * 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. + * the join with a FromExpr with just the other side. + * Furthermore, we can elide that FromExpr according to the + * same rules as above. * * Just as in the FromExpr case, we can't simplify if the * other input rel references any PHVs that are marked as to @@ -3295,20 +3351,34 @@ remove_useless_results_recurse(PlannerInfo *root, Node *jtnode, !find_dependent_phvs_in_jointree(root, j->rarg, varno)) { remove_result_refs(root, varno, j->rarg); - if (j->quals) + if (j->quals != NULL && parent_quals == NULL) jtnode = (Node *) makeFromExpr(list_make1(j->rarg), j->quals); else + { + /* Merge any quals up to parent */ + if (j->quals != NULL) + *parent_quals = (Node *) + list_concat(castNode(List, j->quals), + castNode(List, *parent_quals)); jtnode = j->rarg; + } } else if ((varno = get_result_relid(root, j->rarg)) != 0) { remove_result_refs(root, varno, j->larg); - if (j->quals) + if (j->quals != NULL && parent_quals == NULL) jtnode = (Node *) makeFromExpr(list_make1(j->larg), j->quals); else + { + /* Merge any quals up to parent */ + if (j->quals != NULL) + *parent_quals = (Node *) + list_concat(castNode(List, j->quals), + castNode(List, *parent_quals)); jtnode = j->larg; + } } break; case JOIN_LEFT: @@ -3346,8 +3416,9 @@ remove_useless_results_recurse(PlannerInfo *root, Node *jtnode, /* * 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. + * LHS, since we should either return the LHS row or not. The + * filter clause must go into a new FromExpr if we can't push + * it up to the parent. * * There is a fine point about PHVs that are supposed to be * evaluated at the RHS. Such PHVs could only appear in the @@ -3365,11 +3436,18 @@ remove_useless_results_recurse(PlannerInfo *root, Node *jtnode, { Assert(j->rtindex == 0); remove_result_refs(root, varno, j->larg); - if (j->quals) + if (j->quals != NULL && parent_quals == NULL) jtnode = (Node *) makeFromExpr(list_make1(j->larg), j->quals); else + { + /* Merge any quals up to parent */ + if (j->quals != NULL) + *parent_quals = (Node *) + list_concat(castNode(List, j->quals), + castNode(List, *parent_quals)); jtnode = j->larg; + } } break; case JOIN_FULL: diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c index d449b5c2746..9d377385f1f 100644 --- a/src/backend/optimizer/util/appendinfo.c +++ b/src/backend/optimizer/util/appendinfo.c @@ -467,9 +467,6 @@ adjust_appendrel_attrs_mutator(Node *node, newinfo->outer_relids = adjust_child_relids(oldinfo->outer_relids, context->nappinfos, context->appinfos); - newinfo->nullable_relids = adjust_child_relids(oldinfo->nullable_relids, - context->nappinfos, - context->appinfos); newinfo->left_relids = adjust_child_relids(oldinfo->left_relids, context->nappinfos, context->appinfos); diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index bf3ea26cf4b..bae9688e461 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -894,10 +894,9 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel, make_restrictinfo(root, (Expr *) onecq, rinfo->is_pushed_down, - rinfo->outerjoin_delayed, pseudoconstant, rinfo->security_level, - NULL, NULL, NULL)); + NULL, NULL)); /* track minimum security level among child quals */ cq_min_security = Min(cq_min_security, rinfo->security_level); } @@ -930,9 +929,9 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel, /* not likely that we'd see constants here, so no check */ childquals = lappend(childquals, make_restrictinfo(root, qual, - true, false, false, + true, false, security_level, - NULL, NULL, NULL)); + NULL, NULL)); cq_min_security = Min(cq_min_security, security_level); } security_level++; diff --git a/src/backend/optimizer/util/orclauses.c b/src/backend/optimizer/util/orclauses.c index abc994dbf2e..85ecdfc14f2 100644 --- a/src/backend/optimizer/util/orclauses.c +++ b/src/backend/optimizer/util/orclauses.c @@ -98,18 +98,13 @@ extract_restriction_or_clauses(PlannerInfo *root) * joinclause that is considered safe to move to this rel by the * parameterized-path machinery, even though what we are going to do * with it is not exactly a parameterized path. - * - * However, it seems best to ignore clauses that have been marked - * redundant (by setting norm_selec > 1). That likely can't happen - * for OR clauses, but let's be safe. */ foreach(lc, rel->joininfo) { RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); if (restriction_is_or_clause(rinfo) && - join_clause_is_movable_to(rinfo, rel) && - rinfo->norm_selec <= 1) + join_clause_is_movable_to(rinfo, rel)) { /* Try to extract a qual for this rel only */ Expr *orclause = extract_or_clause(rinfo, rel); @@ -272,10 +267,8 @@ consider_new_or_clause(PlannerInfo *root, RelOptInfo *rel, orclause, true, false, - false, join_or_rinfo->security_level, NULL, - NULL, NULL); /* @@ -344,7 +337,6 @@ consider_new_or_clause(PlannerInfo *root, RelOptInfo *rel, sjinfo.commute_below = NULL; /* we don't bother trying to make the remaining fields valid */ sjinfo.lhs_strict = false; - sjinfo.delay_upper_joins = false; sjinfo.semi_can_btree = false; sjinfo.semi_can_hash = false; sjinfo.semi_operators = NIL; @@ -356,7 +348,7 @@ consider_new_or_clause(PlannerInfo *root, RelOptInfo *rel, /* And hack cached selectivity so join size remains the same */ join_or_rinfo->norm_selec = orig_selec / or_selec; - /* ensure result stays in sane range, in particular not "redundant" */ + /* ensure result stays in sane range */ if (join_or_rinfo->norm_selec > 1) join_or_rinfo->norm_selec = 1; /* as explained above, we don't touch outer_selec */ diff --git a/src/backend/optimizer/util/placeholder.c b/src/backend/optimizer/util/placeholder.c index af10dbd124f..9c6cb5eba7d 100644 --- a/src/backend/optimizer/util/placeholder.c +++ b/src/backend/optimizer/util/placeholder.c @@ -134,7 +134,6 @@ find_placeholder_info(PlannerInfo *root, PlaceHolderVar *phv) phinfo->ph_eval_at = bms_copy(phv->phrels); Assert(!bms_is_empty(phinfo->ph_eval_at)); } - /* ph_eval_at may change later, see update_placeholder_eval_levels */ phinfo->ph_needed = NULL; /* initially it's unused */ /* for the moment, estimate width using just the datatype info */ phinfo->ph_width = get_typavgwidth(exprType((Node *) phv->phexpr), @@ -284,102 +283,6 @@ find_placeholders_in_expr(PlannerInfo *root, Node *expr) list_free(vars); } -/* - * update_placeholder_eval_levels - * Adjust the target evaluation levels for placeholders - * - * The initial eval_at level set by find_placeholder_info was the set of - * rels used in the placeholder's expression (or the whole subselect below - * the placeholder's syntactic location, if the expr is variable-free). - * If the query contains any outer joins that can null any of those rels, - * we must delay evaluation to above those joins. - * - * We repeat this operation each time we add another outer join to - * root->join_info_list. It's somewhat annoying to have to do that, but - * since we don't have very much information on the placeholders' locations, - * it's hard to avoid. Each placeholder's eval_at level must be correct - * by the time it starts to figure in outer-join delay decisions for higher - * outer joins. - * - * In future we might want to put additional policy/heuristics here to - * try to determine an optimal evaluation level. The current rules will - * result in evaluation at the lowest possible level. However, pushing a - * placeholder eval up the tree is likely to further constrain evaluation - * order for outer joins, so it could easily be counterproductive; and we - * don't have enough information at this point to make an intelligent choice. - */ -void -update_placeholder_eval_levels(PlannerInfo *root, SpecialJoinInfo *new_sjinfo) -{ - ListCell *lc1; - - foreach(lc1, root->placeholder_list) - { - PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(lc1); - Relids syn_level = phinfo->ph_var->phrels; - Relids eval_at; - bool found_some; - ListCell *lc2; - - /* - * We don't need to do any work on this placeholder unless the - * newly-added outer join is syntactically beneath its location. - */ - if (!bms_is_subset(new_sjinfo->syn_lefthand, syn_level) || - !bms_is_subset(new_sjinfo->syn_righthand, syn_level)) - continue; - - /* - * Check for delays due to lower outer joins. This is the same logic - * as in check_outerjoin_delay in initsplan.c, except that we don't - * have anything to do with the delay_upper_joins flags; delay of - * upper outer joins will be handled later, based on the eval_at - * values we compute now. - */ - eval_at = phinfo->ph_eval_at; - - do - { - found_some = false; - foreach(lc2, root->join_info_list) - { - SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) lfirst(lc2); - - /* disregard joins not within the PHV's sub-select */ - if (!bms_is_subset(sjinfo->syn_lefthand, syn_level) || - !bms_is_subset(sjinfo->syn_righthand, syn_level)) - continue; - - /* do we reference any nullable rels of this OJ? */ - if (bms_overlap(eval_at, sjinfo->min_righthand) || - (sjinfo->jointype == JOIN_FULL && - bms_overlap(eval_at, sjinfo->min_lefthand))) - { - /* yes; have we included all its rels in eval_at? */ - if (!bms_is_subset(sjinfo->min_lefthand, eval_at) || - !bms_is_subset(sjinfo->min_righthand, eval_at)) - { - /* no, so add them in */ - eval_at = bms_add_members(eval_at, - sjinfo->min_lefthand); - eval_at = bms_add_members(eval_at, - sjinfo->min_righthand); - if (sjinfo->ojrelid) - eval_at = bms_add_member(eval_at, sjinfo->ojrelid); - /* we'll need another iteration */ - found_some = true; - } - } - } - } while (found_some); - - /* Can't move the PHV's eval_at level to above its syntactic level */ - Assert(bms_is_subset(eval_at, syn_level)); - - phinfo->ph_eval_at = eval_at; - } -} - /* * fix_placeholder_input_needed_levels * Adjust the "needed at" levels for placeholder inputs diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index ebfb4ddd121..ad84cc43e11 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -283,6 +283,12 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->top_parent = parent->top_parent ? parent->top_parent : parent; rel->top_parent_relids = rel->top_parent->relids; + /* + * A child rel is below the same outer joins as its parent. (We + * presume this info was already calculated for the parent.) + */ + rel->nulling_relids = parent->nulling_relids; + /* * Also propagate lateral-reference information from appendrel parent * rels to their child rels. We intentionally give each child rel the @@ -306,6 +312,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->parent = NULL; rel->top_parent = NULL; rel->top_parent_relids = NULL; + rel->nulling_relids = NULL; rel->direct_lateral_relids = NULL; rel->lateral_relids = NULL; rel->lateral_referencers = NULL; @@ -685,6 +692,7 @@ build_join_rel(PlannerInfo *root, joinrel->max_attr = 0; joinrel->attr_needed = NULL; joinrel->attr_widths = NULL; + joinrel->nulling_relids = NULL; joinrel->lateral_vars = NIL; joinrel->lateral_referencers = NULL; joinrel->indexlist = NIL; @@ -874,6 +882,7 @@ build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel, joinrel->max_attr = 0; joinrel->attr_needed = NULL; joinrel->attr_widths = NULL; + joinrel->nulling_relids = NULL; joinrel->lateral_vars = NIL; joinrel->lateral_referencers = NULL; joinrel->indexlist = NIL; @@ -1646,18 +1655,9 @@ get_joinrel_parampathinfo(PlannerInfo *root, RelOptInfo *joinrel, { RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - /* - * In principle, join_clause_is_movable_into() should accept anything - * returned by generate_join_implied_equalities(); but because its - * analysis is only approximate, sometimes it doesn't. So we - * currently cannot use this Assert; instead just assume it's okay to - * apply the joinclause at this level. - */ -#ifdef NOT_USED Assert(join_clause_is_movable_into(rinfo, joinrel->relids, join_and_req)); -#endif if (join_clause_is_movable_into(rinfo, outer_path->parent->relids, outer_and_req)) @@ -1720,12 +1720,9 @@ get_joinrel_parampathinfo(PlannerInfo *root, RelOptInfo *joinrel, { RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - /* As above, can't quite assert this here */ -#ifdef NOT_USED Assert(join_clause_is_movable_into(rinfo, outer_path->parent->relids, real_outer_and_req)); -#endif if (!join_clause_is_movable_into(rinfo, outer_path->parent->relids, outer_and_req)) diff --git a/src/backend/optimizer/util/restrictinfo.c b/src/backend/optimizer/util/restrictinfo.c index 1350f011a62..c44bd2f8157 100644 --- a/src/backend/optimizer/util/restrictinfo.c +++ b/src/backend/optimizer/util/restrictinfo.c @@ -25,21 +25,17 @@ static RestrictInfo *make_restrictinfo_internal(PlannerInfo *root, Expr *clause, Expr *orclause, bool is_pushed_down, - bool outerjoin_delayed, bool pseudoconstant, Index security_level, Relids required_relids, - Relids outer_relids, - Relids nullable_relids); + Relids outer_relids); static Expr *make_sub_restrictinfos(PlannerInfo *root, Expr *clause, bool is_pushed_down, - bool outerjoin_delayed, bool pseudoconstant, Index security_level, Relids required_relids, - Relids outer_relids, - Relids nullable_relids); + Relids outer_relids); /* @@ -47,9 +43,9 @@ static Expr *make_sub_restrictinfos(PlannerInfo *root, * * Build a RestrictInfo node containing the given subexpression. * - * The is_pushed_down, outerjoin_delayed, and pseudoconstant flags for the + * The is_pushed_down and pseudoconstant flags for the * RestrictInfo must be supplied by the caller, as well as the correct values - * for security_level, outer_relids, and nullable_relids. + * for security_level and outer_relids. * required_relids can be NULL, in which case it defaults to the actual clause * contents (i.e., clause_relids). * @@ -65,12 +61,10 @@ RestrictInfo * make_restrictinfo(PlannerInfo *root, Expr *clause, bool is_pushed_down, - bool outerjoin_delayed, bool pseudoconstant, Index security_level, Relids required_relids, - Relids outer_relids, - Relids nullable_relids) + Relids outer_relids) { /* * If it's an OR clause, build a modified copy with RestrictInfos inserted @@ -80,12 +74,10 @@ make_restrictinfo(PlannerInfo *root, return (RestrictInfo *) make_sub_restrictinfos(root, clause, is_pushed_down, - outerjoin_delayed, pseudoconstant, security_level, required_relids, - outer_relids, - nullable_relids); + outer_relids); /* Shouldn't be an AND clause, else AND/OR flattening messed up */ Assert(!is_andclause(clause)); @@ -94,12 +86,10 @@ make_restrictinfo(PlannerInfo *root, clause, NULL, is_pushed_down, - outerjoin_delayed, pseudoconstant, security_level, required_relids, - outer_relids, - nullable_relids); + outer_relids); } /* @@ -112,12 +102,10 @@ make_restrictinfo_internal(PlannerInfo *root, Expr *clause, Expr *orclause, bool is_pushed_down, - bool outerjoin_delayed, bool pseudoconstant, Index security_level, Relids required_relids, - Relids outer_relids, - Relids nullable_relids) + Relids outer_relids) { RestrictInfo *restrictinfo = makeNode(RestrictInfo); Relids baserels; @@ -125,14 +113,12 @@ make_restrictinfo_internal(PlannerInfo *root, restrictinfo->clause = clause; restrictinfo->orclause = orclause; restrictinfo->is_pushed_down = is_pushed_down; - restrictinfo->outerjoin_delayed = outerjoin_delayed; restrictinfo->pseudoconstant = pseudoconstant; restrictinfo->has_clone = false; /* may get set by caller */ restrictinfo->is_clone = false; /* may get set by caller */ restrictinfo->can_join = false; /* may get set below */ restrictinfo->security_level = security_level; restrictinfo->outer_relids = outer_relids; - restrictinfo->nullable_relids = nullable_relids; /* * If it's potentially delayable by lower-level security quals, figure out @@ -258,9 +244,9 @@ make_restrictinfo_internal(PlannerInfo *root, * implicit-AND lists at top level of RestrictInfo lists. Only ORs and * simple clauses are valid RestrictInfos. * - * The same is_pushed_down, outerjoin_delayed, and pseudoconstant flag + * The same is_pushed_down and pseudoconstant flag * values can be applied to all RestrictInfo nodes in the result. Likewise - * for security_level, outer_relids, and nullable_relids. + * for security_level and outer_relids. * * The given required_relids are attached to our top-level output, * but any OR-clause constituents are allowed to default to just the @@ -270,12 +256,10 @@ static Expr * make_sub_restrictinfos(PlannerInfo *root, Expr *clause, bool is_pushed_down, - bool outerjoin_delayed, bool pseudoconstant, Index security_level, Relids required_relids, - Relids outer_relids, - Relids nullable_relids) + Relids outer_relids) { if (is_orclause(clause)) { @@ -287,22 +271,18 @@ make_sub_restrictinfos(PlannerInfo *root, make_sub_restrictinfos(root, lfirst(temp), is_pushed_down, - outerjoin_delayed, pseudoconstant, security_level, NULL, - outer_relids, - nullable_relids)); + outer_relids)); return (Expr *) make_restrictinfo_internal(root, clause, make_orclause(orlist), is_pushed_down, - outerjoin_delayed, pseudoconstant, security_level, required_relids, - outer_relids, - nullable_relids); + outer_relids); } else if (is_andclause(clause)) { @@ -314,12 +294,10 @@ make_sub_restrictinfos(PlannerInfo *root, make_sub_restrictinfos(root, lfirst(temp), is_pushed_down, - outerjoin_delayed, pseudoconstant, security_level, required_relids, - outer_relids, - nullable_relids)); + outer_relids)); return make_andclause(andlist); } else @@ -327,12 +305,10 @@ make_sub_restrictinfos(PlannerInfo *root, clause, NULL, is_pushed_down, - outerjoin_delayed, pseudoconstant, security_level, required_relids, - outer_relids, - nullable_relids); + outer_relids); } /* @@ -436,6 +412,21 @@ restriction_is_securely_promotable(RestrictInfo *restrictinfo, return false; } +/* + * Detect whether a RestrictInfo's clause is constant TRUE (note that it's + * surely of type boolean). No such WHERE clause could survive qual + * canonicalization, but equivclass.c may generate such RestrictInfos for + * reasons discussed therein. We should drop them again when creating + * the finished plan, which is handled by the next few functions. + */ +static inline bool +rinfo_is_constant_true(RestrictInfo *rinfo) +{ + return IsA(rinfo->clause, Const) && + !((Const *) rinfo->clause)->constisnull && + DatumGetBool(((Const *) rinfo->clause)->constvalue); +} + /* * get_actual_clauses * @@ -455,6 +446,7 @@ get_actual_clauses(List *restrictinfo_list) RestrictInfo *rinfo = lfirst_node(RestrictInfo, l); Assert(!rinfo->pseudoconstant); + Assert(!rinfo_is_constant_true(rinfo)); result = lappend(result, rinfo->clause); } @@ -466,6 +458,7 @@ get_actual_clauses(List *restrictinfo_list) * * Extract bare clauses from 'restrictinfo_list', returning either the * regular ones or the pseudoconstant ones per 'pseudoconstant'. + * Constant-TRUE clauses are dropped in any case. */ List * extract_actual_clauses(List *restrictinfo_list, @@ -478,7 +471,8 @@ extract_actual_clauses(List *restrictinfo_list, { RestrictInfo *rinfo = lfirst_node(RestrictInfo, l); - if (rinfo->pseudoconstant == pseudoconstant) + if (rinfo->pseudoconstant == pseudoconstant && + !rinfo_is_constant_true(rinfo)) result = lappend(result, rinfo->clause); } return result; @@ -489,7 +483,7 @@ extract_actual_clauses(List *restrictinfo_list, * * Extract bare clauses from 'restrictinfo_list', separating those that * semantically match the join level from those that were pushed down. - * Pseudoconstant clauses are excluded from the results. + * Pseudoconstant and constant-TRUE clauses are excluded from the results. * * This is only used at outer joins, since for plain joins we don't care * about pushed-down-ness. @@ -511,13 +505,15 @@ extract_actual_join_clauses(List *restrictinfo_list, if (RINFO_IS_PUSHED_DOWN(rinfo, joinrelids)) { - if (!rinfo->pseudoconstant) + if (!rinfo->pseudoconstant && + !rinfo_is_constant_true(rinfo)) *otherquals = lappend(*otherquals, rinfo->clause); } else { /* joinquals shouldn't have been marked pseudoconstant */ Assert(!rinfo->pseudoconstant); + Assert(!rinfo_is_constant_true(rinfo)); *joinquals = lappend(*joinquals, rinfo->clause); } } @@ -618,8 +614,17 @@ join_clause_is_movable_to(RestrictInfo *rinfo, RelOptInfo *baserel) if (bms_is_member(baserel->relid, rinfo->outer_relids)) return false; - /* Target rel must not be nullable below the clause */ - if (bms_is_member(baserel->relid, rinfo->nullable_relids)) + /* + * Target rel's Vars must not be nulled by any outer join. We can check + * this without groveling through the individual Vars by seeing whether + * clause_relids (which includes all such Vars' varnullingrels) includes + * any outer join that can null the target rel. You might object that + * this could reject the clause on the basis of an OJ relid that came from + * some other rel's Var. However, that would still mean that the clause + * came from above that outer join and shouldn't be pushed down; so there + * should be no false positives. + */ + if (bms_overlap(rinfo->clause_relids, baserel->nulling_relids)) return false; /* Clause must not use any rels with LATERAL references to this rel */ @@ -651,18 +656,15 @@ join_clause_is_movable_to(RestrictInfo *rinfo, RelOptInfo *baserel) * relation plus the outer rels. We also check that it does reference at * least one current Var, ensuring that the clause will be pushed down to * a unique place in a parameterized join tree. And we check that we're - * not pushing the clause into its outer-join outer side, nor down into - * a lower outer join's inner side. + * not pushing the clause into its outer-join outer side. * - * The check about pushing a clause down into a lower outer join's inner side - * is only approximate; it sometimes returns "false" when actually it would - * be safe to use the clause here because we're still above the outer join - * in question. This is okay as long as the answers at different join levels - * are consistent: it just means we might sometimes fail to push a clause as - * far down as it could safely be pushed. It's unclear whether it would be - * worthwhile to do this more precisely. (But if it's ever fixed to be - * exactly accurate, there's an Assert in get_joinrel_parampathinfo() that - * should be re-enabled.) + * We used to need to check that we're not pushing the clause into a lower + * outer join's inner side. However, now that clause_relids includes + * references to potentially-nulling outer joins, the other tests handle that + * concern. If the clause references any Var coming from the inside of a + * lower outer join, its clause_relids will mention that outer join, causing + * the evaluability check to fail; while if it references no such Vars, the + * references-a-target-rel check will fail. * * There's no check here equivalent to join_clause_is_movable_to's test on * lateral_referencers. We assume the caller wouldn't be inquiring unless @@ -704,14 +706,5 @@ join_clause_is_movable_into(RestrictInfo *rinfo, if (bms_overlap(currentrelids, rinfo->outer_relids)) return false; - /* - * Target rel(s) must not be nullable below the clause. This is - * approximate, in the safe direction, because the current join might be - * above the join where the nulling would happen, in which case the clause - * would work correctly here. But we don't have enough info to be sure. - */ - if (bms_overlap(currentrelids, rinfo->nullable_relids)) - return false; - return true; } diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index 630a0440895..f843659a18f 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -268,14 +268,6 @@ struct PlannerInfo */ Relids all_query_rels; - /* - * nullable_baserels is a Relids set of base relids that are nullable by - * some outer join in the jointree; these are rels that are potentially - * nullable below the WHERE clause, SELECT targetlist, etc. This is - * computed in deconstruct_jointree. - */ - Relids nullable_baserels; - /* * join_rel_list is a list of all join-relation RelOptInfos we have * considered in this planning run. For small problems we just scan the @@ -694,6 +686,7 @@ typedef struct PartitionSchemeData *PartitionScheme; * the attribute is needed as part of final targetlist * attr_widths - cache space for per-attribute width estimates; * zero means not computed yet + * nulling_relids - relids of outer joins that can null this rel * lateral_vars - lateral cross-references of rel, if any (list of * Vars and PlaceHolderVars) * lateral_referencers - relids of rels that reference this one laterally @@ -927,6 +920,8 @@ typedef struct RelOptInfo Relids *attr_needed pg_node_attr(read_write_ignore); /* array indexed [min_attr .. max_attr] */ int32 *attr_widths pg_node_attr(read_write_ignore); + /* relids of outer joins that can null this baserel */ + Relids nulling_relids; /* LATERAL Vars and PHVs referenced by rel */ List *lateral_vars; /* rels that reference this baserel laterally */ @@ -1389,7 +1384,6 @@ typedef struct EquivalenceMember Expr *em_expr; /* the expression represented */ Relids em_relids; /* all relids appearing in em_expr */ - Relids em_nullable_relids; /* nullable by lower outer joins */ bool em_is_const; /* expression is pseudoconstant? */ bool em_is_child; /* derived version for a child relation? */ Oid em_datatype; /* the "nominal type" used by the opfamily */ @@ -2404,26 +2398,12 @@ typedef struct LimitPath * conditions. Possibly we should rename it to reflect that meaning? But * see also the comments for RINFO_IS_PUSHED_DOWN, below.) * - * RestrictInfo nodes also contain an outerjoin_delayed flag, which is true - * if the clause's applicability must be delayed due to any outer joins - * appearing below it (ie, it has to be postponed to some join level higher - * than the set of relations it actually references). - * * There is also an outer_relids field, which is NULL except for outer join * clauses; for those, it is the set of relids on the outer side of the * clause's outer join. (These are rels that the clause cannot be applied to * in parameterized scans, since pushing it into the join's outer side would * lead to wrong answers.) * - * There is also a nullable_relids field, which is the set of rels the clause - * references that can be forced null by some outer join below the clause. - * - * outerjoin_delayed = true is subtly different from nullable_relids != NULL: - * a clause might reference some nullable rels and yet not be - * outerjoin_delayed because it also references all the other rels of the - * outer join(s). A clause that is not outerjoin_delayed can be enforced - * anywhere it is computable. - * * To handle security-barrier conditions efficiently, we mark RestrictInfo * nodes with a security_level field, in which higher values identify clauses * coming from less-trusted sources. The exact semantics are that a clause @@ -2497,9 +2477,6 @@ typedef struct RestrictInfo /* true if clause was pushed down in level */ bool is_pushed_down; - /* true if delayed by lower outer join */ - bool outerjoin_delayed; - /* see comment above */ bool can_join pg_node_attr(equal_ignore); @@ -2531,9 +2508,6 @@ typedef struct RestrictInfo /* If an outer-join clause, the outer-side relations, else NULL: */ Relids outer_relids; - /* The relids used in the clause that are nullable by lower outer joins: */ - Relids nullable_relids; - /* * Relids in the left/right side of the clause. These fields are set for * any binary opclause. @@ -2579,10 +2553,7 @@ typedef struct RestrictInfo /* eval cost of clause; -1 if not yet set */ QualCost eval_cost pg_node_attr(equal_ignore); - /* - * selectivity for "normal" (JOIN_INNER) semantics; -1 if not yet set; >1 - * means a redundant clause - */ + /* selectivity for "normal" (JOIN_INNER) semantics; -1 if not yet set */ Selectivity norm_selec pg_node_attr(equal_ignore); /* selectivity for outer join semantics; -1 if not yet set */ Selectivity outer_selec pg_node_attr(equal_ignore); @@ -2788,12 +2759,6 @@ typedef struct PlaceHolderVar * upper-level outer joins even if it appears in their RHS). We don't bother * to set lhs_strict for FULL JOINs, however. * - * delay_upper_joins is set true if we detect a pushed-down clause that has - * to be evaluated after this join is formed (because it references the RHS). - * Any outer joins that have such a clause and this join in their RHS cannot - * commute with this join, because that would leave noplace to check the - * pushed-down clause. (We don't track this for FULL JOINs, either.) - * * For a semijoin, we also extract the join operators and their RHS arguments * and set semi_operators, semi_rhs_exprs, semi_can_btree, and semi_can_hash. * This is done in support of possibly unique-ifying the RHS, so we don't @@ -2808,8 +2773,8 @@ typedef struct PlaceHolderVar * not allowed within join_info_list. We also create transient * SpecialJoinInfos with jointype == JOIN_INNER for outer joins, since for * cost estimation purposes it is sometimes useful to know the join size under - * plain innerjoin semantics. Note that lhs_strict, delay_upper_joins, and - * of course the semi_xxx fields are not set meaningfully within such structs. + * plain innerjoin semantics. Note that lhs_strict and the semi_xxx fields + * are not set meaningfully within such structs. */ #ifndef HAVE_SPECIALJOININFO_TYPEDEF typedef struct SpecialJoinInfo SpecialJoinInfo; @@ -2831,7 +2796,6 @@ struct SpecialJoinInfo Relids commute_above_r; /* commuting OJs above this one, if RHS */ Relids commute_below; /* commuting OJs below this one */ bool lhs_strict; /* joinclause is strict for some LHS rel */ - bool delay_upper_joins; /* can't commute with upper RHS */ /* Remaining fields are set only for JOIN_SEMI jointype: */ bool semi_can_btree; /* true if semi_operators are all btree */ bool semi_can_hash; /* true if semi_operators are all hash */ diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index 9b38627efd8..1b02a1dc087 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -128,7 +128,6 @@ extern Expr *canonicalize_ec_expression(Expr *expr, extern void reconsider_outer_join_clauses(PlannerInfo *root); extern EquivalenceClass *get_eclass_for_sort_expr(PlannerInfo *root, Expr *expr, - Relids nullable_relids, List *opfamilies, Oid opcintype, Oid collation, @@ -216,7 +215,7 @@ extern List *build_index_pathkeys(PlannerInfo *root, IndexOptInfo *index, extern List *build_partition_pathkeys(PlannerInfo *root, RelOptInfo *partrel, ScanDirection scandir, bool *partialkeys); extern List *build_expression_pathkey(PlannerInfo *root, Expr *expr, - Relids nullable_relids, Oid opno, + Oid opno, Relids rel, bool create_it); extern List *convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel, List *subquery_pathkeys, diff --git a/src/include/optimizer/placeholder.h b/src/include/optimizer/placeholder.h index 31e1578e822..acb9cf9f05c 100644 --- a/src/include/optimizer/placeholder.h +++ b/src/include/optimizer/placeholder.h @@ -22,8 +22,6 @@ extern PlaceHolderVar *make_placeholder_expr(PlannerInfo *root, Expr *expr, extern PlaceHolderInfo *find_placeholder_info(PlannerInfo *root, PlaceHolderVar *phv); extern void find_placeholders_in_jointree(PlannerInfo *root); -extern void update_placeholder_eval_levels(PlannerInfo *root, - SpecialJoinInfo *new_sjinfo); extern void fix_placeholder_input_needed_levels(PlannerInfo *root); extern void add_placeholders_to_base_rels(PlannerInfo *root); extern void add_placeholders_to_joinrel(PlannerInfo *root, RelOptInfo *joinrel, diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h index 95ecefdadec..3e6e60f5498 100644 --- a/src/include/optimizer/planmain.h +++ b/src/include/optimizer/planmain.h @@ -83,7 +83,6 @@ extern RestrictInfo *process_implied_equality(PlannerInfo *root, Expr *item1, Expr *item2, Relids qualscope, - Relids nullable_relids, Index security_level, bool below_outer_join, bool both_const); @@ -93,7 +92,6 @@ extern RestrictInfo *build_implied_join_equality(PlannerInfo *root, Expr *item1, Expr *item2, Relids qualscope, - Relids nullable_relids, Index security_level); extern void match_foreign_keys_to_quals(PlannerInfo *root); diff --git a/src/include/optimizer/restrictinfo.h b/src/include/optimizer/restrictinfo.h index c79bb420e4e..c9e30776c52 100644 --- a/src/include/optimizer/restrictinfo.h +++ b/src/include/optimizer/restrictinfo.h @@ -19,17 +19,15 @@ /* Convenience macro for the common case of a valid-everywhere qual */ #define make_simple_restrictinfo(root, clause) \ - make_restrictinfo(root, clause, true, false, false, 0, NULL, NULL, NULL) + make_restrictinfo(root, clause, true, false, 0, NULL, NULL) extern RestrictInfo *make_restrictinfo(PlannerInfo *root, Expr *clause, bool is_pushed_down, - bool outerjoin_delayed, bool pseudoconstant, Index security_level, Relids required_relids, - Relids outer_relids, - Relids nullable_relids); + Relids outer_relids); extern RestrictInfo *commute_restrictinfo(RestrictInfo *rinfo, Oid comm_op); extern bool restriction_is_or_clause(RestrictInfo *restrictinfo); extern bool restriction_is_securely_promotable(RestrictInfo *restrictinfo, diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out index 51c9df3d582..87e12768042 100644 --- a/src/test/regress/expected/join.out +++ b/src/test/regress/expected/join.out @@ -2308,8 +2308,8 @@ order by 1, 2; (5 rows) -- --- regression test: check a case where join_clause_is_movable_into() gives --- an imprecise result, causing an assertion failure +-- regression test: check a case where join_clause_is_movable_into() +-- used to give an imprecise result, causing an assertion failure -- select count(*) from @@ -4020,10 +4020,10 @@ explain (costs off) select q1, unique2, thousand, hundred from int8_tbl a left join tenk1 b on q1 = unique2 where coalesce(thousand,123) = q1 and q1 = coalesce(hundred,123); - QUERY PLAN --------------------------------------------------------------------------------------- + QUERY PLAN +---------------------------------------------------------------------------------------------------------- Nested Loop Left Join - Filter: ((COALESCE(b.thousand, 123) = a.q1) AND (a.q1 = COALESCE(b.hundred, 123))) + Filter: ((COALESCE(b.thousand, 123) = COALESCE(b.hundred, 123)) AND (a.q1 = COALESCE(b.hundred, 123))) -> Seq Scan on int8_tbl a -> Index Scan using tenk1_unique2 on tenk1 b Index Cond: (unique2 = a.q1) @@ -4064,8 +4064,8 @@ explain (costs off) select a.unique1, b.unique1, c.unique1, coalesce(b.twothousand, a.twothousand) from tenk1 a left join tenk1 b on b.thousand = a.unique1 left join tenk1 c on c.unique2 = coalesce(b.twothousand, a.twothousand) where a.unique2 < 10 and coalesce(b.twothousand, a.twothousand) = 44; - QUERY PLAN ---------------------------------------------------------------------------------------------- + QUERY PLAN +--------------------------------------------------------------- Nested Loop Left Join -> Nested Loop Left Join Filter: (COALESCE(b.twothousand, a.twothousand) = 44) @@ -4076,7 +4076,7 @@ select a.unique1, b.unique1, c.unique1, coalesce(b.twothousand, a.twothousand) -> Bitmap Index Scan on tenk1_thous_tenthous Index Cond: (thousand = a.unique1) -> Index Scan using tenk1_unique2 on tenk1 c - Index Cond: ((unique2 = COALESCE(b.twothousand, a.twothousand)) AND (unique2 = 44)) + Index Cond: (unique2 = 44) (11 rows) select a.unique1, b.unique1, c.unique1, coalesce(b.twothousand, a.twothousand) @@ -4561,7 +4561,6 @@ where tt1.f1 = ss1.c0; Output: tt4.f1 -> Nested Loop Left Join Output: tt4.f1 - Join Filter: (tt3.f1 = tt4.f1) -> Seq Scan on public.text_tbl tt3 Output: tt3.f1 Filter: (tt3.f1 = 'foo'::text) @@ -4579,7 +4578,7 @@ where tt1.f1 = ss1.c0; Output: (tt4.f1) -> Seq Scan on public.text_tbl tt5 Output: tt4.f1 -(33 rows) +(32 rows) select 1 from text_tbl as tt1 @@ -4686,24 +4685,22 @@ explain (costs off) QUERY PLAN ------------------------------------------------- Nested Loop Left Join - Join Filter: (a.f1 = b.unique2) -> Seq Scan on int4_tbl a Filter: (f1 = 0) -> Index Scan using tenk1_unique2 on tenk1 b Index Cond: (unique2 = 0) -(6 rows) +(5 rows) explain (costs off) select * from tenk1 a full join tenk1 b using(unique2) where unique2 = 42; QUERY PLAN ------------------------------------------------- Merge Full Join - Merge Cond: (a.unique2 = b.unique2) -> Index Scan using tenk1_unique2 on tenk1 a Index Cond: (unique2 = 42) -> Index Scan using tenk1_unique2 on tenk1 b Index Cond: (unique2 = 42) -(6 rows) +(5 rows) -- -- test that quals attached to an outer join have correct semantics, @@ -4791,12 +4788,11 @@ select a.unique1, b.unique2 QUERY PLAN ---------------------------------------------------- Nested Loop Left Join - Join Filter: (a.unique1 = b.unique2) -> Index Only Scan using onek_unique1 on onek a Index Cond: (unique1 = 42) -> Index Only Scan using onek_unique2 on onek b Index Cond: (unique2 = 42) -(6 rows) +(5 rows) select a.unique1, b.unique2 from onek a full join onek b on a.unique1 = b.unique2 @@ -4813,12 +4809,11 @@ select a.unique1, b.unique2 QUERY PLAN ---------------------------------------------------- Nested Loop Left Join - Join Filter: (a.unique1 = b.unique2) -> Index Only Scan using onek_unique2 on onek b Index Cond: (unique2 = 43) -> Index Only Scan using onek_unique1 on onek a Index Cond: (unique1 = 43) -(6 rows) +(5 rows) select a.unique1, b.unique2 from onek a full join onek b on a.unique1 = b.unique2 @@ -6202,12 +6197,13 @@ select * from int8_tbl i8 left join lateral -------------------------------------- Nested Loop Left Join Output: i8.q1, i8.q2, f1, (i8.q2) + Join Filter: false -> Seq Scan on public.int8_tbl i8 Output: i8.q1, i8.q2 -> Result Output: f1, i8.q2 One-Time Filter: false -(7 rows) +(8 rows) explain (verbose, costs off) select * from int8_tbl i8 left join lateral diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql index 7035e4a4c42..2bb24dbfcdc 100644 --- a/src/test/regress/sql/join.sql +++ b/src/test/regress/sql/join.sql @@ -411,8 +411,8 @@ select * from int8_tbl i1 left join (int8_tbl i2 join order by 1, 2; -- --- regression test: check a case where join_clause_is_movable_into() gives --- an imprecise result, causing an assertion failure +-- regression test: check a case where join_clause_is_movable_into() +-- used to give an imprecise result, causing an assertion failure -- select count(*) from