diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index b7aff3775eef5b79002ce1c6314d3c64e5c4ecba..e5dd58efe33417040d3f80ca6a9c9cdb39b358af 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -48,7 +48,7 @@ static List *generate_join_implied_equalities_broken(PlannerInfo *root, 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, @@ -1000,22 +1000,18 @@ generate_join_implied_equalities(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; } @@ -1051,7 +1047,7 @@ generate_join_implied_equalities(PlannerInfo *root, nominal_join_relids, outer_relids, nominal_inner_relids, - inner_appinfo); + inner_rel); result = list_concat(result, sublist); } @@ -1244,7 +1240,7 @@ generate_join_implied_equalities_broken(PlannerInfo *root, Relids nominal_join_relids, Relids outer_relids, Relids nominal_inner_relids, - AppendRelInfo *inner_appinfo) + RelOptInfo *inner_rel) { List *result = NIL; ListCell *lc; @@ -1266,10 +1262,16 @@ generate_join_implied_equalities_broken(PlannerInfo *root, * 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; } @@ -2071,14 +2073,14 @@ generate_implied_equalities_for_column(PlannerInfo *root, { 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) { @@ -2148,10 +2150,10 @@ generate_implied_equalities_for_column(PlannerInfo *root, /* * 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, diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c index 42dcb111aeb053a6d5b00f4edd553906ff57bf65..9c22d31150a5e1f20d07e59536811d200d7055a3 100644 --- a/src/backend/optimizer/path/indxpath.c +++ b/src/backend/optimizer/path/indxpath.c @@ -2586,16 +2586,11 @@ check_partial_indexes(PlannerInfo *root, RelOptInfo *rel) * 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); diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 0410fddc546190459431e4ee58be364c07dbfa0a..1cec511e0f00afb2e525d59207d57f974046445f 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -1979,3 +1979,26 @@ adjust_inherited_tlist(List *tlist, AppendRelInfo *context) 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); +} diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index c938c2700f9d7a9dd91f396a795f6ff3e915632b..4c76f542b9b740b2a504b04ac8ec7c9612556ab0 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -713,7 +713,8 @@ build_empty_join_rel(PlannerInfo *root) * 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) @@ -737,6 +738,62 @@ 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, diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index a0bcc82b31cb28e6f99fa368dacae322a5d2064f..26b17f5f7afcfcbcaa5c9b9aae4603b37128e2f3 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -145,6 +145,8 @@ extern RelOptInfo *build_join_rel(PlannerInfo *root, 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); diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index f5fc7e8e71351a9b334b2bfb44ff22f1f27310a5..1891f4d40e26b3e4fd342947a956dfd0ac7d14cd 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -58,4 +58,7 @@ extern void expand_inherited_tables(PlannerInfo *root); 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 */ diff --git a/src/test/regress/expected/equivclass.out b/src/test/regress/expected/equivclass.out new file mode 100644 index 0000000000000000000000000000000000000000..b312292c6f337ae7c0f00eca5bd3ed0b00243026 --- /dev/null +++ b/src/test/regress/expected/equivclass.out @@ -0,0 +1,383 @@ +-- +-- 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) + diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index ab6c4e2ceed40b44bbd32527750cc8bf861c8f66..9902dbeb39c1f2bad9703d93c6b596f7d3979cbc 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -98,7 +98,7 @@ test: event_trigger # ---------- # 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, diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index 5ed2bf0ffb7bb2c157477145cb74084df572ffab..2902a05dfb6fea209a7a1a1bab341e7ca72453c2 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -126,6 +126,7 @@ test: advisory_lock test: json test: jsonb test: indirect_toast +test: equivclass test: plancache test: limit test: plpgsql diff --git a/src/test/regress/sql/equivclass.sql b/src/test/regress/sql/equivclass.sql new file mode 100644 index 0000000000000000000000000000000000000000..17fad673e9200c90357357672a6010bc55ab8764 --- /dev/null +++ b/src/test/regress/sql/equivclass.sql @@ -0,0 +1,224 @@ +-- +-- 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;