(15 rows)
-- Create an index, and then attempt to force a nested loop with inner index
--- scan so that we can see parameter-related information. Also, let's try
--- actually running the query, but try to suppress potentially variable output.
+-- scan so that we can see parameter-related information.
CREATE INDEX ON vegetables (id);
ANALYZE vegetables;
SET enable_hashjoin = false;
SET enable_material = false;
SET enable_mergejoin = false;
SET enable_seqscan = false;
+-- Let's try actually running the query, but try to suppress potentially
+-- variable output.
SELECT explain_filter($$
EXPLAIN (BUFFERS OFF, COSTS OFF, SUMMARY OFF, TIMING OFF, ANALYZE, DEBUG)
SELECT * FROM vegetables v1, vegetables v2 WHERE v1.id = v2.id;
Parse Location: 0 to end
(47 rows)
+-- Test the RANGE_TABLE option with a case that involves an outer join.
+SELECT explain_filter($$
+EXPLAIN (RANGE_TABLE, COSTS OFF)
+SELECT * FROM daucus d LEFT JOIN brassica b ON d.id = b.id;
+$$);
+ explain_filter
+---------------------------------------------------------
+ Nested Loop Left Join
+ Outer Join RTIs: 3
+ -> Index Scan using daucus_id_idx on daucus d
+ Scan RTI: 1
+ -> Index Scan using brassica_id_idx on brassica b
+ Index Cond: (id = d.id)
+ Scan RTI: 2
+ RTI 1 (relation, in-from-clause):
+ Alias: d ()
+ Eref: d (id, name, genus)
+ Relation: daucus
+ Relation Kind: relation
+ Relation Lock Mode: AccessShareLock
+ Permission Info Index: 1
+ RTI 2 (relation, in-from-clause):
+ Alias: b ()
+ Eref: b (id, name, genus)
+ Relation: brassica
+ Relation Kind: relation
+ Relation Lock Mode: AccessShareLock
+ Permission Info Index: 2
+ RTI 3 (join, in-from-clause):
+ Eref: unnamed_join (id, name, genus, id, name, genus)
+ Join Type: Left
+ Unprunable RTIs: 1 2
+(25 rows)
+
+-- Restore default settings.
RESET enable_hashjoin;
RESET enable_material;
RESET enable_mergejoin;
((Result *) plan)->relids,
es);
break;
+ case T_MergeJoin:
+ case T_NestLoop:
+ case T_HashJoin:
+
+ /*
+ * 'ojrelids' is only meaningful for non-inner joins, but if
+ * it somehow ends up set for an inner join, print it anyway.
+ */
+ if (((Join *) plan)->jointype != JOIN_INNER ||
+ ((Join *) plan)->ojrelids != NULL)
+ overexplain_bitmapset("Outer Join RTIs",
+ ((Join *) plan)->ojrelids,
+ es);
+ break;
default:
break;
}
$$);
-- Create an index, and then attempt to force a nested loop with inner index
--- scan so that we can see parameter-related information. Also, let's try
--- actually running the query, but try to suppress potentially variable output.
+-- scan so that we can see parameter-related information.
CREATE INDEX ON vegetables (id);
ANALYZE vegetables;
SET enable_hashjoin = false;
SET enable_material = false;
SET enable_mergejoin = false;
SET enable_seqscan = false;
+
+-- Let's try actually running the query, but try to suppress potentially
+-- variable output.
SELECT explain_filter($$
EXPLAIN (BUFFERS OFF, COSTS OFF, SUMMARY OFF, TIMING OFF, ANALYZE, DEBUG)
SELECT * FROM vegetables v1, vegetables v2 WHERE v1.id = v2.id;
$$);
+
+-- Test the RANGE_TABLE option with a case that involves an outer join.
+SELECT explain_filter($$
+EXPLAIN (RANGE_TABLE, COSTS OFF)
+SELECT * FROM daucus d LEFT JOIN brassica b ON d.id = b.id;
+$$);
+
+-- Restore default settings.
RESET enable_hashjoin;
RESET enable_material;
RESET enable_mergejoin;
static NestLoop *make_nestloop(List *tlist,
List *joinclauses, List *otherclauses, List *nestParams,
Plan *lefttree, Plan *righttree,
- JoinType jointype, bool inner_unique);
+ JoinType jointype,
+ Relids ojrelids,
+ bool inner_unique);
static HashJoin *make_hashjoin(List *tlist,
List *joinclauses, List *otherclauses,
List *hashclauses,
List *hashoperators, List *hashcollations,
List *hashkeys,
Plan *lefttree, Plan *righttree,
- JoinType jointype, bool inner_unique);
+ JoinType jointype,
+ Relids ojrelids,
+ bool inner_unique);
static Hash *make_hash(Plan *lefttree,
List *hashkeys,
Oid skewTable,
bool *mergereversals,
bool *mergenullsfirst,
Plan *lefttree, Plan *righttree,
- JoinType jointype, bool inner_unique,
+ JoinType jointype,
+ Relids ojrelids,
+ bool inner_unique,
bool skip_mark_restore);
static Sort *make_sort(Plan *lefttree, int numCols,
AttrNumber *sortColIdx, Oid *sortOperators,
Plan *outer_plan;
Plan *inner_plan;
Relids outerrelids;
+ Relids ojrelids;
List *tlist = build_path_tlist(root, &best_path->jpath.path);
List *joinrestrictclauses = best_path->jpath.joinrestrictinfo;
List *joinclauses;
replace_nestloop_params(root, (Node *) otherclauses);
}
+ /* Identify any outer joins computed at this level */
+ ojrelids = bms_difference(best_path->jpath.path.parent->relids,
+ bms_union(best_path->jpath.outerjoinpath->parent->relids,
+ best_path->jpath.innerjoinpath->parent->relids));
+
/*
* Identify any nestloop parameters that should be supplied by this join
* node, and remove them from root->curOuterParams.
outer_plan,
inner_plan,
best_path->jpath.jointype,
+ ojrelids,
best_path->jpath.inner_unique);
copy_generic_path_info(&join_plan->join.plan, &best_path->jpath.path);
MergeJoin *join_plan;
Plan *outer_plan;
Plan *inner_plan;
+ Relids ojrelids;
List *tlist = build_path_tlist(root, &best_path->jpath.path);
List *joinclauses;
List *otherclauses;
mergeclauses = get_switched_clauses(best_path->path_mergeclauses,
best_path->jpath.outerjoinpath->parent->relids);
+ /* Identify any outer joins computed at this level */
+ ojrelids = bms_difference(best_path->jpath.path.parent->relids,
+ bms_union(outer_path->parent->relids,
+ inner_path->parent->relids));
+
/*
* Create explicit sort nodes for the outer and inner paths if necessary.
*/
outer_plan,
inner_plan,
best_path->jpath.jointype,
+ ojrelids,
best_path->jpath.inner_unique,
best_path->skip_mark_restore);
Hash *hash_plan;
Plan *outer_plan;
Plan *inner_plan;
+ Relids ojrelids;
List *tlist = build_path_tlist(root, &best_path->jpath.path);
List *joinclauses;
List *otherclauses;
hash_plan->rows_total = best_path->inner_rows_total;
}
+ /* Identify any outer joins computed at this level */
+ ojrelids = bms_difference(best_path->jpath.path.parent->relids,
+ bms_union(best_path->jpath.outerjoinpath->parent->relids,
+ best_path->jpath.innerjoinpath->parent->relids));
+
join_plan = make_hashjoin(tlist,
joinclauses,
otherclauses,
outer_plan,
(Plan *) hash_plan,
best_path->jpath.jointype,
+ ojrelids,
best_path->jpath.inner_unique);
copy_generic_path_info(&join_plan->join.plan, &best_path->jpath.path);
Plan *lefttree,
Plan *righttree,
JoinType jointype,
+ Relids ojrelids,
bool inner_unique)
{
NestLoop *node = makeNode(NestLoop);
node->join.jointype = jointype;
node->join.inner_unique = inner_unique;
node->join.joinqual = joinclauses;
+ node->join.ojrelids = ojrelids;
node->nestParams = nestParams;
return node;
Plan *lefttree,
Plan *righttree,
JoinType jointype,
+ Relids ojrelids,
bool inner_unique)
{
HashJoin *node = makeNode(HashJoin);
node->join.jointype = jointype;
node->join.inner_unique = inner_unique;
node->join.joinqual = joinclauses;
+ node->join.ojrelids = ojrelids;
return node;
}
Plan *lefttree,
Plan *righttree,
JoinType jointype,
+ Relids ojrelids,
bool inner_unique,
bool skip_mark_restore)
{
node->join.jointype = jointype;
node->join.inner_unique = inner_unique;
node->join.joinqual = joinclauses;
+ node->join.ojrelids = ojrelids;
return node;
}
/*
* Now we need to fix up the targetlist and qpqual, which are logically
- * above the join. This means that, if it's not an inner join, any Vars
- * and PHVs appearing here should have nullingrels that include the
- * effects of the outer join, ie they will have nullingrels equal to the
- * input Vars' nullingrels plus the bit added by the outer join. We don't
- * currently have enough info available here to identify what that should
- * be, so we just tell fix_join_expr to accept superset nullingrels
- * matches instead of exact ones.
+ * above the join. This means that, if it's an outer join with non-empty
+ * ojrelids, any Vars and PHVs appearing here should have nullingrels that
+ * include the effects of the outer join, ie they will have nullingrels
+ * equal to the input Vars' nullingrels plus the bit added by the outer
+ * join. We don't currently have enough info available here to identify
+ * what that should be, so we just tell fix_join_expr to accept superset
+ * nullingrels matches instead of exact ones.
*/
join->plan.targetlist = fix_join_expr(root,
join->plan.targetlist,
inner_itlist,
(Index) 0,
rtoffset,
- (join->jointype == JOIN_INNER ? NRM_EQUAL : NRM_SUPERSET),
+ (bms_is_empty(join->ojrelids) ? NRM_EQUAL : NRM_SUPERSET),
NUM_EXEC_TLIST((Plan *) join));
join->plan.qual = fix_join_expr(root,
join->plan.qual,
inner_itlist,
(Index) 0,
rtoffset,
- (join->jointype == JOIN_INNER ? NRM_EQUAL : NRM_SUPERSET),
+ (bms_is_empty(join->ojrelids) ? NRM_EQUAL : NRM_SUPERSET),
NUM_EXEC_QUAL((Plan *) join));
pfree(outer_itlist);
* inner_unique each outer tuple can match to no more than one inner tuple
* joinqual: qual conditions that came from JOIN/ON or JOIN/USING
* (plan.qual contains conditions that came from WHERE)
+ * ojrelids: outer joins completed at this level
*
* When jointype is INNER, joinqual and plan.qual are semantically
* interchangeable. For OUTER jointypes, the two are *not* interchangeable;
bool inner_unique;
/* JOIN quals (in addition to plan.qual) */
List *joinqual;
+ Bitmapset *ojrelids;
} Join;
/* ----------------