Relids nominal_join_relids,
Relids outer_relids,
Relids nominal_inner_relids,
- AppendRelInfo *inner_appinfo);
+ RelOptInfo *inner_rel);
static Oid select_equality_operator(EquivalenceClass *ec,
Oid lefttype, Oid righttype);
static RestrictInfo *create_join_clause(PlannerInfo *root,
Relids inner_relids = inner_rel->relids;
Relids nominal_inner_relids;
Relids nominal_join_relids;
- AppendRelInfo *inner_appinfo;
ListCell *lc;
/* If inner rel is a child, extra setup work is needed */
if (inner_rel->reloptkind == RELOPT_OTHER_MEMBER_REL)
{
- /* Lookup parent->child translation data */
- inner_appinfo = find_childrel_appendrelinfo(root, inner_rel);
- /* Construct relids for the parent rel */
- nominal_inner_relids = bms_make_singleton(inner_appinfo->parent_relid);
+ /* Fetch relid set for the topmost parent rel */
+ nominal_inner_relids = find_childrel_top_parent(root, inner_rel)->relids;
/* ECs will be marked with the parent's relid, not the child's */
nominal_join_relids = bms_union(outer_relids, nominal_inner_relids);
}
else
{
- inner_appinfo = NULL;
nominal_inner_relids = inner_relids;
nominal_join_relids = join_relids;
}
nominal_join_relids,
outer_relids,
nominal_inner_relids,
- inner_appinfo);
+ inner_rel);
result = list_concat(result, sublist);
}
Relids nominal_join_relids,
Relids outer_relids,
Relids nominal_inner_relids,
- AppendRelInfo *inner_appinfo)
+ RelOptInfo *inner_rel)
{
List *result = NIL;
ListCell *lc;
* RestrictInfos that are not listed in ec_derives, but there shouldn't be
* any duplication, and it's a sufficiently narrow corner case that we
* shouldn't sweat too much over it anyway.
+ *
+ * Since inner_rel might be an indirect descendant of the baserel
+ * mentioned in the ec_sources clauses, we have to be prepared to apply
+ * multiple levels of Var translation.
*/
- if (inner_appinfo)
- result = (List *) adjust_appendrel_attrs(root, (Node *) result,
- inner_appinfo);
+ if (inner_rel->reloptkind == RELOPT_OTHER_MEMBER_REL &&
+ result != NIL)
+ result = (List *) adjust_appendrel_attrs_multilevel(root,
+ (Node *) result,
+ inner_rel);
return result;
}
{
List *result = NIL;
bool is_child_rel = (rel->reloptkind == RELOPT_OTHER_MEMBER_REL);
- Index parent_relid;
+ Relids parent_relids;
ListCell *lc1;
- /* If it's a child rel, we'll need to know what its parent is */
+ /* If it's a child rel, we'll need to know what its parent(s) are */
if (is_child_rel)
- parent_relid = find_childrel_appendrelinfo(root, rel)->parent_relid;
+ parent_relids = find_childrel_parents(root, rel);
else
- parent_relid = 0; /* not used, but keep compiler quiet */
+ parent_relids = NULL; /* not used, but keep compiler quiet */
foreach(lc1, root->eq_classes)
{
/*
* Also, if this is a child rel, avoid generating a useless join
- * to its parent rel.
+ * to its parent rel(s).
*/
if (is_child_rel &&
- bms_is_member(parent_relid, other_em->em_relids))
+ bms_overlap(parent_relids, other_em->em_relids))
continue;
eq_op = select_equality_operator(cur_ec,
* Add on any equivalence-derivable join clauses. Computing the correct
* relid sets for generate_join_implied_equalities is slightly tricky
* because the rel could be a child rel rather than a true baserel, and in
- * that case we must remove its parent's relid from all_baserels.
+ * that case we must remove its parents' relid(s) from all_baserels.
*/
if (rel->reloptkind == RELOPT_OTHER_MEMBER_REL)
- {
- /* Lookup parent->child translation data */
- AppendRelInfo *appinfo = find_childrel_appendrelinfo(root, rel);
-
otherrels = bms_difference(root->all_baserels,
- bms_make_singleton(appinfo->parent_relid));
- }
+ find_childrel_parents(root, rel));
else
otherrels = bms_difference(root->all_baserels, rel->relids);
return new_tlist;
}
+
+/*
+ * adjust_appendrel_attrs_multilevel
+ * Apply Var translations from a toplevel appendrel parent down to a child.
+ *
+ * In some cases we need to translate expressions referencing a baserel
+ * to reference an appendrel child that's multiple levels removed from it.
+ */
+Node *
+adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node,
+ RelOptInfo *child_rel)
+{
+ AppendRelInfo *appinfo = find_childrel_appendrelinfo(root, child_rel);
+ RelOptInfo *parent_rel = find_base_rel(root, appinfo->parent_relid);
+
+ /* If parent is also a child, first recurse to apply its translations */
+ if (parent_rel->reloptkind == RELOPT_OTHER_MEMBER_REL)
+ node = adjust_appendrel_attrs_multilevel(root, node, parent_rel);
+ else
+ Assert(parent_rel->reloptkind == RELOPT_BASEREL);
+ /* Now translate for this child */
+ return adjust_appendrel_attrs(root, node, appinfo);
+}
* Get the AppendRelInfo associated with an appendrel child rel.
*
* This search could be eliminated by storing a link in child RelOptInfos,
- * but for now it doesn't seem performance-critical.
+ * but for now it doesn't seem performance-critical. (Also, it might be
+ * difficult to maintain such a link during mutation of the append_rel_list.)
*/
AppendRelInfo *
find_childrel_appendrelinfo(PlannerInfo *root, RelOptInfo *rel)
}
+/*
+ * find_childrel_top_parent
+ * Fetch the topmost appendrel parent rel of an appendrel child rel.
+ *
+ * Since appendrels can be nested, a child could have multiple levels of
+ * appendrel ancestors. This function locates the topmost ancestor,
+ * which will be a regular baserel not an otherrel.
+ */
+RelOptInfo *
+find_childrel_top_parent(PlannerInfo *root, RelOptInfo *rel)
+{
+ do
+ {
+ AppendRelInfo *appinfo = find_childrel_appendrelinfo(root, rel);
+ Index prelid = appinfo->parent_relid;
+
+ /* traverse up to the parent rel, loop if it's also a child rel */
+ rel = find_base_rel(root, prelid);
+ } while (rel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+
+ Assert(rel->reloptkind == RELOPT_BASEREL);
+
+ return rel;
+}
+
+
+/*
+ * find_childrel_parents
+ * Compute the set of parent relids of an appendrel child rel.
+ *
+ * Since appendrels can be nested, a child could have multiple levels of
+ * appendrel ancestors. This function computes a Relids set of all the
+ * parent relation IDs.
+ */
+Relids
+find_childrel_parents(PlannerInfo *root, RelOptInfo *rel)
+{
+ Relids result = NULL;
+
+ do
+ {
+ AppendRelInfo *appinfo = find_childrel_appendrelinfo(root, rel);
+ Index prelid = appinfo->parent_relid;
+
+ result = bms_add_member(result, prelid);
+
+ /* traverse up to the parent rel, loop if it's also a child rel */
+ rel = find_base_rel(root, prelid);
+ } while (rel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+
+ Assert(rel->reloptkind == RELOPT_BASEREL);
+
+ return result;
+}
+
+
/*
* get_baserel_parampathinfo
* Get the ParamPathInfo for a parameterized path for a base relation,
extern RelOptInfo *build_empty_join_rel(PlannerInfo *root);
extern AppendRelInfo *find_childrel_appendrelinfo(PlannerInfo *root,
RelOptInfo *rel);
+extern RelOptInfo *find_childrel_top_parent(PlannerInfo *root, RelOptInfo *rel);
+extern Relids find_childrel_parents(PlannerInfo *root, RelOptInfo *rel);
extern ParamPathInfo *get_baserel_parampathinfo(PlannerInfo *root,
RelOptInfo *baserel,
Relids required_outer);
extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node,
AppendRelInfo *appinfo);
+extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node,
+ RelOptInfo *child_rel);
+
#endif /* PREP_H */
--- /dev/null
+--
+-- Tests for the planner's "equivalence class" mechanism
+--
+-- One thing that's not tested well during normal querying is the logic
+-- for handling "broken" ECs. This is because an EC can only become broken
+-- if its underlying btree operator family doesn't include a complete set
+-- of cross-type equality operators. There are not (and should not be)
+-- any such families built into Postgres; so we have to hack things up
+-- to create one. We do this by making two alias types that are really
+-- int8 (so we need no new C code) and adding only some operators for them
+-- into the standard integer_ops opfamily.
+create type int8alias1;
+create function int8alias1in(cstring) returns int8alias1
+ strict immutable language internal as 'int8in';
+NOTICE: return type int8alias1 is only a shell
+create function int8alias1out(int8alias1) returns cstring
+ strict immutable language internal as 'int8out';
+NOTICE: argument type int8alias1 is only a shell
+create type int8alias1 (
+ input = int8alias1in,
+ output = int8alias1out,
+ like = int8
+);
+create type int8alias2;
+create function int8alias2in(cstring) returns int8alias2
+ strict immutable language internal as 'int8in';
+NOTICE: return type int8alias2 is only a shell
+create function int8alias2out(int8alias2) returns cstring
+ strict immutable language internal as 'int8out';
+NOTICE: argument type int8alias2 is only a shell
+create type int8alias2 (
+ input = int8alias2in,
+ output = int8alias2out,
+ like = int8
+);
+create cast (int8 as int8alias1) without function;
+create cast (int8 as int8alias2) without function;
+create cast (int8alias1 as int8) without function;
+create cast (int8alias2 as int8) without function;
+create function int8alias1eq(int8alias1, int8alias1) returns bool
+ strict immutable language internal as 'int8eq';
+create operator = (
+ procedure = int8alias1eq,
+ leftarg = int8alias1, rightarg = int8alias1,
+ commutator = =,
+ restrict = eqsel, join = eqjoinsel,
+ merges
+);
+alter operator family integer_ops using btree add
+ operator 3 = (int8alias1, int8alias1);
+create function int8alias2eq(int8alias2, int8alias2) returns bool
+ strict immutable language internal as 'int8eq';
+create operator = (
+ procedure = int8alias2eq,
+ leftarg = int8alias2, rightarg = int8alias2,
+ commutator = =,
+ restrict = eqsel, join = eqjoinsel,
+ merges
+);
+alter operator family integer_ops using btree add
+ operator 3 = (int8alias2, int8alias2);
+create function int8alias1eq(int8, int8alias1) returns bool
+ strict immutable language internal as 'int8eq';
+create operator = (
+ procedure = int8alias1eq,
+ leftarg = int8, rightarg = int8alias1,
+ restrict = eqsel, join = eqjoinsel,
+ merges
+);
+alter operator family integer_ops using btree add
+ operator 3 = (int8, int8alias1);
+create function int8alias1eq(int8alias1, int8alias2) returns bool
+ strict immutable language internal as 'int8eq';
+create operator = (
+ procedure = int8alias1eq,
+ leftarg = int8alias1, rightarg = int8alias2,
+ restrict = eqsel, join = eqjoinsel,
+ merges
+);
+alter operator family integer_ops using btree add
+ operator 3 = (int8alias1, int8alias2);
+create function int8alias1lt(int8alias1, int8alias1) returns bool
+ strict immutable language internal as 'int8lt';
+create operator < (
+ procedure = int8alias1lt,
+ leftarg = int8alias1, rightarg = int8alias1
+);
+alter operator family integer_ops using btree add
+ operator 1 < (int8alias1, int8alias1);
+create function int8alias1cmp(int8, int8alias1) returns int
+ strict immutable language internal as 'btint8cmp';
+alter operator family integer_ops using btree add
+ function 1 int8alias1cmp (int8, int8alias1);
+create table ec0 (ff int8 primary key, f1 int8, f2 int8);
+create table ec1 (ff int8 primary key, f1 int8alias1, f2 int8alias2);
+create table ec2 (xf int8 primary key, x1 int8alias1, x2 int8alias2);
+-- for the moment we only want to look at nestloop plans
+set enable_hashjoin = off;
+set enable_mergejoin = off;
+--
+-- Note that for cases where there's a missing operator, we don't care so
+-- much whether the plan is ideal as that we don't fail or generate an
+-- outright incorrect plan.
+--
+explain (costs off)
+ select * from ec0 where ff = f1 and f1 = '42'::int8;
+ QUERY PLAN
+----------------------------------
+ Index Scan using ec0_pkey on ec0
+ Index Cond: (ff = 42::bigint)
+ Filter: (f1 = 42::bigint)
+(3 rows)
+
+explain (costs off)
+ select * from ec0 where ff = f1 and f1 = '42'::int8alias1;
+ QUERY PLAN
+---------------------------------------
+ Index Scan using ec0_pkey on ec0
+ Index Cond: (ff = '42'::int8alias1)
+ Filter: (f1 = '42'::int8alias1)
+(3 rows)
+
+explain (costs off)
+ select * from ec1 where ff = f1 and f1 = '42'::int8alias1;
+ QUERY PLAN
+---------------------------------------
+ Index Scan using ec1_pkey on ec1
+ Index Cond: (ff = '42'::int8alias1)
+ Filter: (f1 = '42'::int8alias1)
+(3 rows)
+
+explain (costs off)
+ select * from ec1 where ff = f1 and f1 = '42'::int8alias2;
+ QUERY PLAN
+---------------------------------------------------
+ Seq Scan on ec1
+ Filter: ((ff = f1) AND (f1 = '42'::int8alias2))
+(2 rows)
+
+explain (costs off)
+ select * from ec1, ec2 where ff = x1 and ff = '42'::int8;
+ QUERY PLAN
+---------------------------------------------------------------
+ Nested Loop
+ Join Filter: (ec1.ff = ec2.x1)
+ -> Index Scan using ec1_pkey on ec1
+ Index Cond: ((ff = 42::bigint) AND (ff = 42::bigint))
+ -> Seq Scan on ec2
+(5 rows)
+
+explain (costs off)
+ select * from ec1, ec2 where ff = x1 and ff = '42'::int8alias1;
+ QUERY PLAN
+---------------------------------------------
+ Nested Loop
+ -> Index Scan using ec1_pkey on ec1
+ Index Cond: (ff = '42'::int8alias1)
+ -> Seq Scan on ec2
+ Filter: (x1 = '42'::int8alias1)
+(5 rows)
+
+explain (costs off)
+ select * from ec1, ec2 where ff = x1 and '42'::int8 = x1;
+ QUERY PLAN
+----------------------------------------
+ Nested Loop
+ Join Filter: (ec1.ff = ec2.x1)
+ -> Index Scan using ec1_pkey on ec1
+ Index Cond: (ff = 42::bigint)
+ -> Seq Scan on ec2
+ Filter: (42::bigint = x1)
+(6 rows)
+
+explain (costs off)
+ select * from ec1, ec2 where ff = x1 and x1 = '42'::int8alias1;
+ QUERY PLAN
+---------------------------------------------
+ Nested Loop
+ -> Index Scan using ec1_pkey on ec1
+ Index Cond: (ff = '42'::int8alias1)
+ -> Seq Scan on ec2
+ Filter: (x1 = '42'::int8alias1)
+(5 rows)
+
+explain (costs off)
+ select * from ec1, ec2 where ff = x1 and x1 = '42'::int8alias2;
+ QUERY PLAN
+-----------------------------------------
+ Nested Loop
+ -> Seq Scan on ec2
+ Filter: (x1 = '42'::int8alias2)
+ -> Index Scan using ec1_pkey on ec1
+ Index Cond: (ff = ec2.x1)
+(5 rows)
+
+create unique index ec1_expr1 on ec1((ff + 1));
+create unique index ec1_expr2 on ec1((ff + 2 + 1));
+create unique index ec1_expr3 on ec1((ff + 3 + 1));
+create unique index ec1_expr4 on ec1((ff + 4));
+explain (costs off)
+ select * from ec1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss1
+ where ss1.x = ec1.f1 and ec1.ff = 42::int8;
+ QUERY PLAN
+-----------------------------------------------------
+ Nested Loop
+ -> Index Scan using ec1_pkey on ec1
+ Index Cond: (ff = 42::bigint)
+ -> Append
+ -> Index Scan using ec1_expr2 on ec1 ec1_1
+ Index Cond: (((ff + 2) + 1) = ec1.f1)
+ -> Index Scan using ec1_expr3 on ec1 ec1_2
+ Index Cond: (((ff + 3) + 1) = ec1.f1)
+ -> Index Scan using ec1_expr4 on ec1 ec1_3
+ Index Cond: ((ff + 4) = ec1.f1)
+(10 rows)
+
+explain (costs off)
+ select * from ec1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss1
+ where ss1.x = ec1.f1 and ec1.ff = 42::int8 and ec1.ff = ec1.f1;
+ QUERY PLAN
+---------------------------------------------------------------
+ Nested Loop
+ Join Filter: ((((ec1_1.ff + 2) + 1)) = ec1.f1)
+ -> Index Scan using ec1_pkey on ec1
+ Index Cond: ((ff = 42::bigint) AND (ff = 42::bigint))
+ Filter: (ff = f1)
+ -> Append
+ -> Index Scan using ec1_expr2 on ec1 ec1_1
+ Index Cond: (((ff + 2) + 1) = 42::bigint)
+ -> Index Scan using ec1_expr3 on ec1 ec1_2
+ Index Cond: (((ff + 3) + 1) = 42::bigint)
+ -> Index Scan using ec1_expr4 on ec1 ec1_3
+ Index Cond: ((ff + 4) = 42::bigint)
+(12 rows)
+
+explain (costs off)
+ select * from ec1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss2
+ where ss1.x = ec1.f1 and ss1.x = ss2.x and ec1.ff = 42::int8;
+ QUERY PLAN
+---------------------------------------------------------------------
+ Nested Loop
+ -> Nested Loop
+ -> Index Scan using ec1_pkey on ec1
+ Index Cond: (ff = 42::bigint)
+ -> Append
+ -> Index Scan using ec1_expr2 on ec1 ec1_1
+ Index Cond: (((ff + 2) + 1) = ec1.f1)
+ -> Index Scan using ec1_expr3 on ec1 ec1_2
+ Index Cond: (((ff + 3) + 1) = ec1.f1)
+ -> Index Scan using ec1_expr4 on ec1 ec1_3
+ Index Cond: ((ff + 4) = ec1.f1)
+ -> Append
+ -> Index Scan using ec1_expr2 on ec1 ec1_4
+ Index Cond: (((ff + 2) + 1) = (((ec1_1.ff + 2) + 1)))
+ -> Index Scan using ec1_expr3 on ec1 ec1_5
+ Index Cond: (((ff + 3) + 1) = (((ec1_1.ff + 2) + 1)))
+ -> Index Scan using ec1_expr4 on ec1 ec1_6
+ Index Cond: ((ff + 4) = (((ec1_1.ff + 2) + 1)))
+(18 rows)
+
+-- let's try that as a mergejoin
+set enable_mergejoin = on;
+set enable_nestloop = off;
+explain (costs off)
+ select * from ec1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss2
+ where ss1.x = ec1.f1 and ss1.x = ss2.x and ec1.ff = 42::int8;
+ QUERY PLAN
+-----------------------------------------------------------------
+ Merge Join
+ Merge Cond: ((((ec1_4.ff + 2) + 1)) = (((ec1_1.ff + 2) + 1)))
+ -> Merge Append
+ Sort Key: (((ec1_4.ff + 2) + 1))
+ -> Index Scan using ec1_expr2 on ec1 ec1_4
+ -> Index Scan using ec1_expr3 on ec1 ec1_5
+ -> Index Scan using ec1_expr4 on ec1 ec1_6
+ -> Materialize
+ -> Merge Join
+ Merge Cond: ((((ec1_1.ff + 2) + 1)) = ec1.f1)
+ -> Merge Append
+ Sort Key: (((ec1_1.ff + 2) + 1))
+ -> Index Scan using ec1_expr2 on ec1 ec1_1
+ -> Index Scan using ec1_expr3 on ec1 ec1_2
+ -> Index Scan using ec1_expr4 on ec1 ec1_3
+ -> Materialize
+ -> Sort
+ Sort Key: ec1.f1
+ -> Index Scan using ec1_pkey on ec1
+ Index Cond: (ff = 42::bigint)
+(20 rows)
+
+-- check partially indexed scan
+set enable_nestloop = on;
+set enable_mergejoin = off;
+drop index ec1_expr3;
+explain (costs off)
+ select * from ec1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss1
+ where ss1.x = ec1.f1 and ec1.ff = 42::int8;
+ QUERY PLAN
+-----------------------------------------------------
+ Nested Loop
+ -> Index Scan using ec1_pkey on ec1
+ Index Cond: (ff = 42::bigint)
+ -> Append
+ -> Index Scan using ec1_expr2 on ec1 ec1_1
+ Index Cond: (((ff + 2) + 1) = ec1.f1)
+ -> Seq Scan on ec1 ec1_2
+ Filter: (((ff + 3) + 1) = ec1.f1)
+ -> Index Scan using ec1_expr4 on ec1 ec1_3
+ Index Cond: ((ff + 4) = ec1.f1)
+(10 rows)
+
+-- let's try that as a mergejoin
+set enable_mergejoin = on;
+set enable_nestloop = off;
+explain (costs off)
+ select * from ec1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss1
+ where ss1.x = ec1.f1 and ec1.ff = 42::int8;
+ QUERY PLAN
+-----------------------------------------------------
+ Merge Join
+ Merge Cond: ((((ec1_1.ff + 2) + 1)) = ec1.f1)
+ -> Merge Append
+ Sort Key: (((ec1_1.ff + 2) + 1))
+ -> Index Scan using ec1_expr2 on ec1 ec1_1
+ -> Sort
+ Sort Key: (((ec1_2.ff + 3) + 1))
+ -> Seq Scan on ec1 ec1_2
+ -> Index Scan using ec1_expr4 on ec1 ec1_3
+ -> Materialize
+ -> Sort
+ Sort Key: ec1.f1
+ -> Index Scan using ec1_pkey on ec1
+ Index Cond: (ff = 42::bigint)
+(14 rows)
+
# ----------
# Another group of parallel tests
# ----------
-test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock json jsonb indirect_toast
+test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock json jsonb indirect_toast equivclass
# ----------
# Another group of parallel tests
# NB: temp.sql does a reconnect which transiently uses 2 connections,
test: json
test: jsonb
test: indirect_toast
+test: equivclass
test: plancache
test: limit
test: plpgsql
--- /dev/null
+--
+-- Tests for the planner's "equivalence class" mechanism
+--
+
+-- One thing that's not tested well during normal querying is the logic
+-- for handling "broken" ECs. This is because an EC can only become broken
+-- if its underlying btree operator family doesn't include a complete set
+-- of cross-type equality operators. There are not (and should not be)
+-- any such families built into Postgres; so we have to hack things up
+-- to create one. We do this by making two alias types that are really
+-- int8 (so we need no new C code) and adding only some operators for them
+-- into the standard integer_ops opfamily.
+
+create type int8alias1;
+create function int8alias1in(cstring) returns int8alias1
+ strict immutable language internal as 'int8in';
+create function int8alias1out(int8alias1) returns cstring
+ strict immutable language internal as 'int8out';
+create type int8alias1 (
+ input = int8alias1in,
+ output = int8alias1out,
+ like = int8
+);
+
+create type int8alias2;
+create function int8alias2in(cstring) returns int8alias2
+ strict immutable language internal as 'int8in';
+create function int8alias2out(int8alias2) returns cstring
+ strict immutable language internal as 'int8out';
+create type int8alias2 (
+ input = int8alias2in,
+ output = int8alias2out,
+ like = int8
+);
+
+create cast (int8 as int8alias1) without function;
+create cast (int8 as int8alias2) without function;
+create cast (int8alias1 as int8) without function;
+create cast (int8alias2 as int8) without function;
+
+create function int8alias1eq(int8alias1, int8alias1) returns bool
+ strict immutable language internal as 'int8eq';
+create operator = (
+ procedure = int8alias1eq,
+ leftarg = int8alias1, rightarg = int8alias1,
+ commutator = =,
+ restrict = eqsel, join = eqjoinsel,
+ merges
+);
+alter operator family integer_ops using btree add
+ operator 3 = (int8alias1, int8alias1);
+
+create function int8alias2eq(int8alias2, int8alias2) returns bool
+ strict immutable language internal as 'int8eq';
+create operator = (
+ procedure = int8alias2eq,
+ leftarg = int8alias2, rightarg = int8alias2,
+ commutator = =,
+ restrict = eqsel, join = eqjoinsel,
+ merges
+);
+alter operator family integer_ops using btree add
+ operator 3 = (int8alias2, int8alias2);
+
+create function int8alias1eq(int8, int8alias1) returns bool
+ strict immutable language internal as 'int8eq';
+create operator = (
+ procedure = int8alias1eq,
+ leftarg = int8, rightarg = int8alias1,
+ restrict = eqsel, join = eqjoinsel,
+ merges
+);
+alter operator family integer_ops using btree add
+ operator 3 = (int8, int8alias1);
+
+create function int8alias1eq(int8alias1, int8alias2) returns bool
+ strict immutable language internal as 'int8eq';
+create operator = (
+ procedure = int8alias1eq,
+ leftarg = int8alias1, rightarg = int8alias2,
+ restrict = eqsel, join = eqjoinsel,
+ merges
+);
+alter operator family integer_ops using btree add
+ operator 3 = (int8alias1, int8alias2);
+
+create function int8alias1lt(int8alias1, int8alias1) returns bool
+ strict immutable language internal as 'int8lt';
+create operator < (
+ procedure = int8alias1lt,
+ leftarg = int8alias1, rightarg = int8alias1
+);
+alter operator family integer_ops using btree add
+ operator 1 < (int8alias1, int8alias1);
+
+create function int8alias1cmp(int8, int8alias1) returns int
+ strict immutable language internal as 'btint8cmp';
+alter operator family integer_ops using btree add
+ function 1 int8alias1cmp (int8, int8alias1);
+
+create table ec0 (ff int8 primary key, f1 int8, f2 int8);
+create table ec1 (ff int8 primary key, f1 int8alias1, f2 int8alias2);
+create table ec2 (xf int8 primary key, x1 int8alias1, x2 int8alias2);
+
+-- for the moment we only want to look at nestloop plans
+set enable_hashjoin = off;
+set enable_mergejoin = off;
+
+--
+-- Note that for cases where there's a missing operator, we don't care so
+-- much whether the plan is ideal as that we don't fail or generate an
+-- outright incorrect plan.
+--
+
+explain (costs off)
+ select * from ec0 where ff = f1 and f1 = '42'::int8;
+explain (costs off)
+ select * from ec0 where ff = f1 and f1 = '42'::int8alias1;
+explain (costs off)
+ select * from ec1 where ff = f1 and f1 = '42'::int8alias1;
+explain (costs off)
+ select * from ec1 where ff = f1 and f1 = '42'::int8alias2;
+
+explain (costs off)
+ select * from ec1, ec2 where ff = x1 and ff = '42'::int8;
+explain (costs off)
+ select * from ec1, ec2 where ff = x1 and ff = '42'::int8alias1;
+explain (costs off)
+ select * from ec1, ec2 where ff = x1 and '42'::int8 = x1;
+explain (costs off)
+ select * from ec1, ec2 where ff = x1 and x1 = '42'::int8alias1;
+explain (costs off)
+ select * from ec1, ec2 where ff = x1 and x1 = '42'::int8alias2;
+
+create unique index ec1_expr1 on ec1((ff + 1));
+create unique index ec1_expr2 on ec1((ff + 2 + 1));
+create unique index ec1_expr3 on ec1((ff + 3 + 1));
+create unique index ec1_expr4 on ec1((ff + 4));
+
+explain (costs off)
+ select * from ec1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss1
+ where ss1.x = ec1.f1 and ec1.ff = 42::int8;
+
+explain (costs off)
+ select * from ec1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss1
+ where ss1.x = ec1.f1 and ec1.ff = 42::int8 and ec1.ff = ec1.f1;
+
+explain (costs off)
+ select * from ec1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss2
+ where ss1.x = ec1.f1 and ss1.x = ss2.x and ec1.ff = 42::int8;
+
+-- let's try that as a mergejoin
+set enable_mergejoin = on;
+set enable_nestloop = off;
+
+explain (costs off)
+ select * from ec1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss2
+ where ss1.x = ec1.f1 and ss1.x = ss2.x and ec1.ff = 42::int8;
+
+-- check partially indexed scan
+set enable_nestloop = on;
+set enable_mergejoin = off;
+
+drop index ec1_expr3;
+
+explain (costs off)
+ select * from ec1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss1
+ where ss1.x = ec1.f1 and ec1.ff = 42::int8;
+
+-- let's try that as a mergejoin
+set enable_mergejoin = on;
+set enable_nestloop = off;
+
+explain (costs off)
+ select * from ec1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss1
+ where ss1.x = ec1.f1 and ec1.ff = 42::int8;