revert-a3179ab69-doesnt-work.patch
text/x-diff
Filename: revert-a3179ab69-doesnt-work.patch
Type: text/x-diff
Part: 0
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: unified
| File | + | − |
|---|---|---|
| src/backend/optimizer/path/equivclass.c | 1 | 47 |
| src/backend/optimizer/plan/analyzejoins.c | 39 | 66 |
| src/backend/optimizer/plan/initsplan.c | 1 | 177 |
| src/backend/optimizer/util/placeholder.c | 0 | 27 |
| src/include/optimizer/paths.h | 0 | 1 |
| src/include/optimizer/placeholder.h | 0 | 1 |
| src/include/optimizer/planmain.h | 0 | 4 |
| src/test/regress/expected/join.out | 1 | 11 |
| src/test/regress/sql/join.sql | 1 | 6 |
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index 441f12f6c50..54e0b5cbdd1 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -1453,8 +1453,7 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
* For the moment we force all the Vars to be available at all join nodes
* for this eclass. Perhaps this could be improved by doing some
* pre-analysis of which members we prefer to join, but it's no worse than
- * what happened in the pre-8.3 code. (Note: rebuild_eclass_attr_needed
- * needs to match this code.)
+ * what happened in the pre-8.3 code.
*/
foreach(lc, ec->ec_members)
{
@@ -2561,51 +2560,6 @@ reconsider_full_join_clause(PlannerInfo *root, OuterJoinClauseInfo *ojcinfo)
return false; /* failed to make any deduction */
}
-/*
- * rebuild_eclass_attr_needed
- * Put back attr_needed bits for Vars/PHVs needed for join eclasses.
- *
- * This is used to rebuild attr_needed/ph_needed sets after removal of a
- * useless outer join. It should match what
- * generate_base_implied_equalities_no_const did, except that we call
- * add_vars_to_attr_needed not add_vars_to_targetlist.
- */
-void
-rebuild_eclass_attr_needed(PlannerInfo *root)
-{
- ListCell *lc;
-
- foreach(lc, root->eq_classes)
- {
- EquivalenceClass *ec = (EquivalenceClass *) lfirst(lc);
-
- /*
- * We don't expect any EC child members to exist at this point. Ensure
- * that's the case, otherwise, we might be getting asked to do
- * something this function hasn't been coded for.
- */
- Assert(ec->ec_childmembers == NULL);
-
- /* Need do anything only for a multi-member, no-const EC. */
- if (list_length(ec->ec_members) > 1 && !ec->ec_has_const)
- {
- ListCell *lc2;
-
- foreach(lc2, ec->ec_members)
- {
- EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc2);
- List *vars = pull_var_clause((Node *) cur_em->em_expr,
- PVC_RECURSE_AGGREGATES |
- PVC_RECURSE_WINDOWFUNCS |
- PVC_INCLUDE_PLACEHOLDERS);
-
- add_vars_to_attr_needed(root, vars, ec->ec_relids);
- list_free(vars);
- }
- }
- }
-}
-
/*
* find_join_domain
* Find the highest JoinDomain enclosed within the given relid set.
diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c
index 4455a046d23..11ce7e33573 100644
--- a/src/backend/optimizer/plan/analyzejoins.c
+++ b/src/backend/optimizer/plan/analyzejoins.c
@@ -28,7 +28,6 @@
#include "optimizer/optimizer.h"
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
-#include "optimizer/placeholder.h"
#include "optimizer/planmain.h"
#include "optimizer/restrictinfo.h"
#include "rewrite/rewriteManip.h"
@@ -344,6 +343,37 @@ remove_rel_from_query(PlannerInfo *root, RelOptInfo *rel,
sjinfo->ojrelid);
}
+ /*
+ * Remove references to the rel from other baserels' attr_needed arrays.
+ */
+ for (rti = 1; rti < root->simple_rel_array_size; rti++)
+ {
+ RelOptInfo *otherrel = root->simple_rel_array[rti];
+ int attroff;
+
+ /* there may be empty slots corresponding to non-baserel RTEs */
+ if (otherrel == NULL)
+ continue;
+
+ Assert(otherrel->relid == rti); /* sanity check on array */
+
+ /* no point in processing target rel itself */
+ if (otherrel == rel)
+ continue;
+
+ for (attroff = otherrel->max_attr - otherrel->min_attr;
+ attroff >= 0;
+ attroff--)
+ {
+ otherrel->attr_needed[attroff] =
+ bms_del_member(otherrel->attr_needed[attroff], relid);
+ if (sjinfo != NULL)
+ otherrel->attr_needed[attroff] =
+ bms_del_member(otherrel->attr_needed[attroff],
+ sjinfo->ojrelid);
+ }
+ }
+
/*
* Likewise remove references from SpecialJoinInfo data structures.
*
@@ -451,11 +481,13 @@ remove_rel_from_query(PlannerInfo *root, RelOptInfo *rel,
phinfo->ph_eval_at = adjust_relid_set(phinfo->ph_eval_at,
sjinfo->ojrelid, subst);
Assert(!bms_is_empty(phinfo->ph_eval_at)); /* checked previously */
- /* Reduce ph_needed to contain only "relation 0"; see below */
- if (bms_is_member(0, phinfo->ph_needed))
- phinfo->ph_needed = bms_make_singleton(0);
- else
- phinfo->ph_needed = NULL;
+
+ phinfo->ph_needed = adjust_relid_set(phinfo->ph_needed,
+ relid, subst);
+ if (sjinfo != NULL)
+ phinfo->ph_needed = adjust_relid_set(phinfo->ph_needed,
+ sjinfo->ojrelid, subst);
+ /* ph_needed might or might not become empty */
phinfo->ph_lateral = adjust_relid_set(phinfo->ph_lateral, relid, subst);
@@ -490,46 +522,6 @@ remove_rel_from_query(PlannerInfo *root, RelOptInfo *rel,
(sjinfo == NULL || bms_is_member(sjinfo->ojrelid, ec->ec_relids)))
remove_rel_from_eclass(ec, sjinfo, relid, subst);
}
-
- /*
- * Finally, we must recompute per-Var attr_needed and per-PlaceHolderVar
- * ph_needed relid sets. These have to be known accurately, else we may
- * fail to remove other now-removable outer joins. And our removal of the
- * join clause(s) for this outer join may mean that Vars that were
- * formerly needed no longer are. So we have to do this honestly by
- * repeating the construction of those relid sets. We can cheat to one
- * small extent: we can avoid re-examining the targetlist and HAVING qual
- * by preserving "relation 0" bits from the existing relid sets. This is
- * safe because we'd never remove such references.
- *
- * So, start by removing all other bits from attr_needed sets and
- * lateral_vars lists. (We already did this above for ph_needed.)
- */
- for (rti = 1; rti < root->simple_rel_array_size; rti++)
- {
- RelOptInfo *otherrel = root->simple_rel_array[rti];
- int attroff;
-
- /* there may be empty slots corresponding to non-baserel RTEs */
- if (otherrel == NULL)
- continue;
-
- Assert(otherrel->relid == rti); /* sanity check on array */
-
- for (attroff = otherrel->max_attr - otherrel->min_attr;
- attroff >= 0;
- attroff--)
- {
- if (bms_is_member(0, otherrel->attr_needed[attroff]))
- otherrel->attr_needed[attroff] = bms_make_singleton(0);
- else
- otherrel->attr_needed[attroff] = NULL;
- }
-
- if (subst > 0)
- ChangeVarNodesExtended((Node *) otherrel->lateral_vars, relid,
- subst, 0, replace_relid_callback);
- }
}
/*
@@ -626,7 +618,7 @@ remove_leftjoinrel_from_query(PlannerInfo *root, int relid,
*/
/*
- * Now remove the rel from the baserel array to prevent it from being
+ * Finally, remove the rel from the baserel array to prevent it from being
* referenced again. (We can't do this earlier because
* remove_join_clause_from_rels will touch it.)
*/
@@ -635,15 +627,6 @@ remove_leftjoinrel_from_query(PlannerInfo *root, int relid,
/* And nuke the RelOptInfo, just in case there's another access path */
pfree(rel);
-
- /*
- * Now repeat construction of attr_needed bits coming from all other
- * sources.
- */
- rebuild_placeholder_attr_needed(root);
- rebuild_joinclause_attr_needed(root);
- rebuild_eclass_attr_needed(root);
- rebuild_lateral_attr_needed(root);
}
/*
@@ -1984,16 +1967,6 @@ remove_self_join_rel(PlannerInfo *root, PlanRowMark *kmark, PlanRowMark *rmark,
/* And nuke the RelOptInfo, just in case there's another access path. */
pfree(toRemove);
-
-
- /*
- * Now repeat construction of attr_needed bits coming from all other
- * sources.
- */
- rebuild_placeholder_attr_needed(root);
- rebuild_joinclause_attr_needed(root);
- rebuild_eclass_attr_needed(root);
- rebuild_lateral_attr_needed(root);
}
/*
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 01804b085b3..0ed6feb8cdd 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -275,8 +275,6 @@ build_base_rel_tlists(PlannerInfo *root, List *final_tlist)
* have a single owning relation; we keep their attr_needed info in
* root->placeholder_list instead. Find or create the associated
* PlaceHolderInfo entry, and update its ph_needed.
- *
- * See also add_vars_to_attr_needed.
*/
void
add_vars_to_targetlist(PlannerInfo *root, List *vars,
@@ -330,62 +328,6 @@ add_vars_to_targetlist(PlannerInfo *root, List *vars,
}
}
-/*
- * add_vars_to_attr_needed
- * This does a subset of what add_vars_to_targetlist does: it just
- * updates attr_needed for Vars and ph_needed for PlaceHolderVars.
- * We assume the Vars are already in their relations' targetlists.
- *
- * This is used to rebuild attr_needed/ph_needed sets after removal
- * of a useless outer join. The removed join clause might have been
- * the only upper-level use of some other relation's Var, in which
- * case we can reduce that Var's attr_needed and thereby possibly
- * open the door to further join removals. But we can't tell that
- * without tedious reconstruction of the attr_needed data.
- *
- * Note that if a Var's attr_needed is successfully reduced to empty,
- * it will still be in the relation's targetlist even though we do
- * not really need the scan plan node to emit it. The extra plan
- * inefficiency seems tiny enough to not be worth spending planner
- * cycles to get rid of it.
- */
-void
-add_vars_to_attr_needed(PlannerInfo *root, List *vars,
- Relids where_needed)
-{
- ListCell *temp;
-
- Assert(!bms_is_empty(where_needed));
-
- foreach(temp, vars)
- {
- Node *node = (Node *) lfirst(temp);
-
- if (IsA(node, Var))
- {
- Var *var = (Var *) node;
- RelOptInfo *rel = find_base_rel(root, var->varno);
- int attno = var->varattno;
-
- if (bms_is_subset(where_needed, rel->relids))
- continue;
- Assert(attno >= rel->min_attr && attno <= rel->max_attr);
- attno -= rel->min_attr;
- rel->attr_needed[attno] = bms_add_members(rel->attr_needed[attno],
- where_needed);
- }
- else if (IsA(node, PlaceHolderVar))
- {
- PlaceHolderVar *phv = (PlaceHolderVar *) node;
- PlaceHolderInfo *phinfo = find_placeholder_info(root, phv);
-
- phinfo->ph_needed = bms_add_members(phinfo->ph_needed,
- where_needed);
- }
- else
- elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
- }
-}
/*****************************************************************************
*
@@ -788,54 +730,10 @@ extract_lateral_references(PlannerInfo *root, RelOptInfo *brel, Index rtindex)
*/
add_vars_to_targetlist(root, newvars, where_needed);
- /*
- * Remember the lateral references for rebuild_lateral_attr_needed and
- * create_lateral_join_info.
- */
+ /* Remember the lateral references for create_lateral_join_info */
brel->lateral_vars = newvars;
}
-/*
- * rebuild_lateral_attr_needed
- * Put back attr_needed bits for Vars/PHVs needed for lateral references.
- *
- * This is used to rebuild attr_needed/ph_needed sets after removal of a
- * useless outer join. It should match what find_lateral_references did,
- * except that we call add_vars_to_attr_needed not add_vars_to_targetlist.
- */
-void
-rebuild_lateral_attr_needed(PlannerInfo *root)
-{
- Index rti;
-
- /* We need do nothing if the query contains no LATERAL RTEs */
- if (!root->hasLateralRTEs)
- return;
-
- /* Examine the same baserels that find_lateral_references did */
- for (rti = 1; rti < root->simple_rel_array_size; rti++)
- {
- RelOptInfo *brel = root->simple_rel_array[rti];
- Relids where_needed;
-
- if (brel == NULL)
- continue;
- if (brel->reloptkind != RELOPT_BASEREL)
- continue;
-
- /*
- * We don't need to repeat all of extract_lateral_references, since it
- * kindly saved the extracted Vars/PHVs in lateral_vars.
- */
- if (brel->lateral_vars == NIL)
- continue;
-
- where_needed = bms_make_singleton(rti);
-
- add_vars_to_attr_needed(root, brel->lateral_vars, where_needed);
- }
-}
-
/*
* create_lateral_join_info
* Fill in the per-base-relation direct_lateral_relids, lateral_relids
@@ -2789,9 +2687,6 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
* var propagation is ensured by making ojscope include input rels from
* both sides of the join.
*
- * See also rebuild_joinclause_attr_needed, which has to partially repeat
- * this work after removal of an outer join.
- *
* Note: if the clause gets absorbed into an EquivalenceClass then this
* may be unnecessary, but for now we have to do it to cover the case
* where the EC becomes ec_broken and we end up reinserting the original
@@ -3401,11 +3296,6 @@ process_implied_equality(PlannerInfo *root,
* some of the Vars could have missed having that done because they only
* appeared in single-relation clauses originally. So do it here for
* safety.
- *
- * See also rebuild_joinclause_attr_needed, which has to partially repeat
- * this work after removal of an outer join. (Since we will put this
- * clause into the joininfo lists, that function needn't do any extra work
- * to find it.)
*/
if (bms_membership(relids) == BMS_MULTIPLE)
{
@@ -3547,72 +3437,6 @@ get_join_domain_min_rels(PlannerInfo *root, Relids domain_relids)
}
-/*
- * rebuild_joinclause_attr_needed
- * Put back attr_needed bits for Vars/PHVs needed for join clauses.
- *
- * This is used to rebuild attr_needed/ph_needed sets after removal of a
- * useless outer join. It should match what distribute_qual_to_rels did,
- * except that we call add_vars_to_attr_needed not add_vars_to_targetlist.
- */
-void
-rebuild_joinclause_attr_needed(PlannerInfo *root)
-{
- /*
- * We must examine all join clauses, but there's no value in processing
- * any join clause more than once. So it's slightly annoying that we have
- * to find them via the per-base-relation joininfo lists. Avoid duplicate
- * processing by tracking the rinfo_serial numbers of join clauses we've
- * already seen. (This doesn't work for is_clone clauses, so we must
- * waste effort on them.)
- */
- Bitmapset *seen_serials = NULL;
- Index rti;
-
- /* Scan all baserels for join clauses */
- for (rti = 1; rti < root->simple_rel_array_size; rti++)
- {
- RelOptInfo *brel = root->simple_rel_array[rti];
- ListCell *lc;
-
- if (brel == NULL)
- continue;
- if (brel->reloptkind != RELOPT_BASEREL)
- continue;
-
- foreach(lc, brel->joininfo)
- {
- RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
- Relids relids = rinfo->required_relids;
-
- if (!rinfo->is_clone) /* else serial number is not unique */
- {
- if (bms_is_member(rinfo->rinfo_serial, seen_serials))
- continue; /* saw it already */
- seen_serials = bms_add_member(seen_serials,
- rinfo->rinfo_serial);
- }
-
- if (bms_membership(relids) == BMS_MULTIPLE)
- {
- List *vars = pull_var_clause((Node *) rinfo->clause,
- PVC_RECURSE_AGGREGATES |
- PVC_RECURSE_WINDOWFUNCS |
- PVC_INCLUDE_PLACEHOLDERS);
- Relids where_needed;
-
- if (rinfo->is_clone)
- where_needed = bms_intersect(relids, root->all_baserels);
- else
- where_needed = relids;
- add_vars_to_attr_needed(root, vars, where_needed);
- list_free(vars);
- }
- }
- }
-}
-
-
/*
* match_foreign_keys_to_quals
* Match foreign-key constraints to equivalence classes and join quals
diff --git a/src/backend/optimizer/util/placeholder.c b/src/backend/optimizer/util/placeholder.c
index e1cd00a72fb..d1ba8923b26 100644
--- a/src/backend/optimizer/util/placeholder.c
+++ b/src/backend/optimizer/util/placeholder.c
@@ -314,33 +314,6 @@ fix_placeholder_input_needed_levels(PlannerInfo *root)
}
}
-/*
- * rebuild_placeholder_attr_needed
- * Put back attr_needed bits for Vars/PHVs needed in PlaceHolderVars.
- *
- * This is used to rebuild attr_needed/ph_needed sets after removal of a
- * useless outer join. It should match what
- * fix_placeholder_input_needed_levels did, except that we call
- * add_vars_to_attr_needed not add_vars_to_targetlist.
- */
-void
-rebuild_placeholder_attr_needed(PlannerInfo *root)
-{
- ListCell *lc;
-
- foreach(lc, root->placeholder_list)
- {
- PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(lc);
- List *vars = pull_var_clause((Node *) phinfo->ph_var->phexpr,
- PVC_RECURSE_AGGREGATES |
- PVC_RECURSE_WINDOWFUNCS |
- PVC_INCLUDE_PLACEHOLDERS);
-
- add_vars_to_attr_needed(root, vars, phinfo->ph_eval_at);
- list_free(vars);
- }
-}
-
/*
* add_placeholders_to_base_rels
* Add any required PlaceHolderVars to base rels' targetlists.
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 8410531f2d6..e0924e622d4 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -129,7 +129,6 @@ extern bool process_equivalence(PlannerInfo *root,
extern Expr *canonicalize_ec_expression(Expr *expr,
Oid req_type, Oid req_collation);
extern void reconsider_outer_join_clauses(PlannerInfo *root);
-extern void rebuild_eclass_attr_needed(PlannerInfo *root);
extern EquivalenceClass *get_eclass_for_sort_expr(PlannerInfo *root,
Expr *expr,
List *opfamilies,
diff --git a/src/include/optimizer/placeholder.h b/src/include/optimizer/placeholder.h
index db92d8861ba..a729e73d561 100644
--- a/src/include/optimizer/placeholder.h
+++ b/src/include/optimizer/placeholder.h
@@ -23,7 +23,6 @@ extern PlaceHolderInfo *find_placeholder_info(PlannerInfo *root,
PlaceHolderVar *phv);
extern void find_placeholders_in_jointree(PlannerInfo *root);
extern void fix_placeholder_input_needed_levels(PlannerInfo *root);
-extern void rebuild_placeholder_attr_needed(PlannerInfo *root);
extern void add_placeholders_to_base_rels(PlannerInfo *root);
extern void add_placeholders_to_joinrel(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outer_rel, RelOptInfo *inner_rel,
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 9d3debcab28..e3bf9db6b6e 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -73,11 +73,8 @@ extern void add_other_rels_to_query(PlannerInfo *root);
extern void build_base_rel_tlists(PlannerInfo *root, List *final_tlist);
extern void add_vars_to_targetlist(PlannerInfo *root, List *vars,
Relids where_needed);
-extern void add_vars_to_attr_needed(PlannerInfo *root, List *vars,
- Relids where_needed);
extern void remove_useless_groupby_columns(PlannerInfo *root);
extern void find_lateral_references(PlannerInfo *root);
-extern void rebuild_lateral_attr_needed(PlannerInfo *root);
extern void create_lateral_join_info(PlannerInfo *root);
extern List *deconstruct_jointree(PlannerInfo *root);
extern bool restriction_is_always_true(PlannerInfo *root,
@@ -101,7 +98,6 @@ extern RestrictInfo *build_implied_join_equality(PlannerInfo *root,
Expr *item2,
Relids qualscope,
Index security_level);
-extern void rebuild_joinclause_attr_needed(PlannerInfo *root);
extern void match_foreign_keys_to_quals(PlannerInfo *root);
/*
diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out
index 390aabfb34b..336def509bc 100644
--- a/src/test/regress/expected/join.out
+++ b/src/test/regress/expected/join.out
@@ -5853,13 +5853,11 @@ CREATE TEMP TABLE a (id int PRIMARY KEY, b_id int);
CREATE TEMP TABLE b (id int PRIMARY KEY, c_id int);
CREATE TEMP TABLE c (id int PRIMARY KEY);
CREATE TEMP TABLE d (a int, b int);
-CREATE TEMP TABLE e (id1 int, id2 int, PRIMARY KEY(id1, id2));
INSERT INTO a VALUES (0, 0), (1, NULL);
INSERT INTO b VALUES (0, 0), (1, NULL);
INSERT INTO c VALUES (0), (1);
INSERT INTO d VALUES (1,3), (2,2), (3,1);
-INSERT INTO e VALUES (0,0), (2,2), (3,1);
--- all these cases should be optimizable into a simple seqscan
+-- all three cases should be optimizable into a simple seqscan
explain (costs off) SELECT a.* FROM a LEFT JOIN b ON a.b_id = b.id;
QUERY PLAN
---------------
@@ -5880,14 +5878,6 @@ explain (costs off)
Seq Scan on a
(1 row)
-explain (costs off)
- SELECT a.* FROM a LEFT JOIN b ON a.id = b.id
- LEFT JOIN e ON e.id1 = a.b_id AND b.c_id = e.id2;
- QUERY PLAN
----------------
- Seq Scan on a
-(1 row)
-
-- check optimization of outer join within another special join
explain (costs off)
select id from a where id in (
diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql
index f6e7070db65..2850c20eb37 100644
--- a/src/test/regress/sql/join.sql
+++ b/src/test/regress/sql/join.sql
@@ -2086,22 +2086,17 @@ CREATE TEMP TABLE a (id int PRIMARY KEY, b_id int);
CREATE TEMP TABLE b (id int PRIMARY KEY, c_id int);
CREATE TEMP TABLE c (id int PRIMARY KEY);
CREATE TEMP TABLE d (a int, b int);
-CREATE TEMP TABLE e (id1 int, id2 int, PRIMARY KEY(id1, id2));
INSERT INTO a VALUES (0, 0), (1, NULL);
INSERT INTO b VALUES (0, 0), (1, NULL);
INSERT INTO c VALUES (0), (1);
INSERT INTO d VALUES (1,3), (2,2), (3,1);
-INSERT INTO e VALUES (0,0), (2,2), (3,1);
--- all these cases should be optimizable into a simple seqscan
+-- all three cases should be optimizable into a simple seqscan
explain (costs off) SELECT a.* FROM a LEFT JOIN b ON a.b_id = b.id;
explain (costs off) SELECT b.* FROM b LEFT JOIN c ON b.c_id = c.id;
explain (costs off)
SELECT a.* FROM a LEFT JOIN (b left join c on b.c_id = c.id)
ON (a.b_id = b.id);
-explain (costs off)
- SELECT a.* FROM a LEFT JOIN b ON a.id = b.id
- LEFT JOIN e ON e.id1 = a.b_id AND b.c_id = e.id2;
-- check optimization of outer join within another special join
explain (costs off)