diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index c79374265c..172106f404 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -201,7 +201,8 @@ static PruneStepResult *perform_pruning_combine_step(PartitionPruneContext *cont static PartClauseMatchStatus match_boolean_partition_clause(Oid partopfamily, Expr *clause, Expr *partkey, - Expr **outconst); + Expr **outconst, + bool *noteq); static void partkey_datum_from_expr(PartitionPruneContext *context, Expr *expr, int stateidx, Datum *value, bool *isnull); @@ -1802,12 +1803,13 @@ match_clause_to_partition_key(GeneratePruningStepsContext *context, Oid partopfamily = part_scheme->partopfamily[partkeyidx], partcoll = part_scheme->partcollation[partkeyidx]; Expr *expr; + bool noteq; /* * Recognize specially shaped clauses that match a Boolean partition key. */ boolmatchstatus = match_boolean_partition_clause(partopfamily, clause, - partkey, &expr); + partkey, &expr, ¬eq); if (boolmatchstatus == PARTCLAUSE_MATCH_CLAUSE) { @@ -1817,7 +1819,7 @@ match_clause_to_partition_key(GeneratePruningStepsContext *context, partclause->keyno = partkeyidx; /* Do pruning with the Boolean equality operator. */ partclause->opno = BooleanEqualOperator; - partclause->op_is_ne = false; + partclause->op_is_ne = noteq; partclause->expr = expr; /* We know that expr is of Boolean type. */ partclause->cmpfn = part_scheme->partsupfunc[partkeyidx].fn_oid; @@ -3581,20 +3583,22 @@ perform_pruning_combine_step(PartitionPruneContext *context, * match_boolean_partition_clause * * If we're able to match the clause to the partition key as specially-shaped - * boolean clause, set *outconst to a Const containing a true or false value - * and return PARTCLAUSE_MATCH_CLAUSE. Returns PARTCLAUSE_UNSUPPORTED if the - * clause is not a boolean clause or if the boolean clause is unsuitable for - * partition pruning. Returns PARTCLAUSE_NOMATCH if it's a bool quals but - * just does not match this partition key. *outconst is set to NULL in the - * latter two cases. + * boolean clause, set *outconst to a Const containing a true or false value, + * set *noteq according to if the clause was in the "not" form, i.e. "is not + * true" or "is not false", and return PARTCLAUSE_MATCH_CLAUSE. Returns + * PARTCLAUSE_UNSUPPORTED if the clause is not a boolean clause or if the + * boolean clause is unsuitable for partition pruning. Returns + * PARTCLAUSE_NOMATCH if it's a bool quals but just does not match this + * partition key. *outconst is set to NULL in the latter two cases. */ static PartClauseMatchStatus match_boolean_partition_clause(Oid partopfamily, Expr *clause, Expr *partkey, - Expr **outconst) + Expr **outconst, bool *noteq) { Expr *leftop; *outconst = NULL; + *noteq = false; if (!IsBooleanOpfamily(partopfamily)) return PARTCLAUSE_UNSUPPORTED; @@ -3613,11 +3617,25 @@ match_boolean_partition_clause(Oid partopfamily, Expr *clause, Expr *partkey, leftop = ((RelabelType *) leftop)->arg; if (equal(leftop, partkey)) - *outconst = (btest->booltesttype == IS_TRUE || - btest->booltesttype == IS_NOT_FALSE) - ? (Expr *) makeBoolConst(true, false) - : (Expr *) makeBoolConst(false, false); - + { + switch (btest->booltesttype) + { + case IS_NOT_TRUE: + *noteq = true; + /* fall through */ + case IS_TRUE: + *outconst = (Expr *) makeBoolConst(true, false); + break; + case IS_NOT_FALSE: + *noteq = true; + /* fall through */ + case IS_FALSE: + *outconst = (Expr *) makeBoolConst(false, false); + break; + default: + return PARTCLAUSE_UNSUPPORTED; + } + } if (*outconst) return PARTCLAUSE_MATCH_CLAUSE; } @@ -3632,11 +3650,9 @@ match_boolean_partition_clause(Oid partopfamily, Expr *clause, Expr *partkey, /* Compare to the partition key, and make up a clause ... */ if (equal(leftop, partkey)) - *outconst = is_not_clause ? - (Expr *) makeBoolConst(false, false) : - (Expr *) makeBoolConst(true, false); + *outconst = (Expr *) makeBoolConst(!is_not_clause, false); else if (equal(negate_clause((Node *) leftop), partkey)) - *outconst = (Expr *) makeBoolConst(false, false); + *outconst = (Expr *) makeBoolConst(is_not_clause, false); if (*outconst) return PARTCLAUSE_MATCH_CLAUSE; diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out index 7555764c77..f7564c3afc 100644 --- a/src/test/regress/expected/partition_prune.out +++ b/src/test/regress/expected/partition_prune.out @@ -1038,6 +1038,7 @@ create table boolpart (a bool) partition by list (a); create table boolpart_default partition of boolpart default; create table boolpart_t partition of boolpart for values in ('true'); create table boolpart_f partition of boolpart for values in ('false'); +insert into boolpart values (true), (false), (null); explain (costs off) select * from boolpart where a in (true, false); QUERY PLAN ------------------------------------------------ @@ -1070,20 +1071,25 @@ explain (costs off) select * from boolpart where a is true or a is not true; Filter: ((a IS TRUE) OR (a IS NOT TRUE)) -> Seq Scan on boolpart_t boolpart_2 Filter: ((a IS TRUE) OR (a IS NOT TRUE)) -(5 rows) + -> Seq Scan on boolpart_default boolpart_3 + Filter: ((a IS TRUE) OR (a IS NOT TRUE)) +(7 rows) explain (costs off) select * from boolpart where a is not true; - QUERY PLAN ---------------------------------- - Seq Scan on boolpart_f boolpart - Filter: (a IS NOT TRUE) -(2 rows) + QUERY PLAN +----------------------------------------------- + Append + -> Seq Scan on boolpart_f boolpart_1 + Filter: (a IS NOT TRUE) + -> Seq Scan on boolpart_default boolpart_2 + Filter: (a IS NOT TRUE) +(5 rows) explain (costs off) select * from boolpart where a is not true and a is not false; - QUERY PLAN --------------------------- - Result - One-Time Filter: false + QUERY PLAN +-------------------------------------------------- + Seq Scan on boolpart_default boolpart + Filter: ((a IS NOT TRUE) AND (a IS NOT FALSE)) (2 rows) explain (costs off) select * from boolpart where a is unknown; @@ -1110,6 +1116,205 @@ explain (costs off) select * from boolpart where a is not unknown; Filter: (a IS NOT UNKNOWN) (7 rows) +select * from boolpart where a in (true, false); + a +--- + f + t +(2 rows) + +select * from boolpart where a = false; + a +--- + f +(1 row) + +select * from boolpart where not a = false; + a +--- + t +(1 row) + +select * from boolpart where a is true or a is not true; + a +--- + f + t + +(3 rows) + +select * from boolpart where a is not true; + a +--- + f + +(2 rows) + +select * from boolpart where a is not true and a is not false; + a +--- + +(1 row) + +select * from boolpart where a is unknown; + a +--- + +(1 row) + +select * from boolpart where a is not unknown; + a +--- + f + t +(2 rows) + +-- inverse boolean partitioning - a seemingly unlikely design, but we've got +-- code for it, so we'd better test it. +create table iboolpart (a bool) partition by list ((not a)); +create table iboolpart_default partition of iboolpart default; +create table iboolpart_f partition of iboolpart for values in ('true'); +create table iboolpart_t partition of iboolpart for values in ('false'); +insert into iboolpart values (true), (false), (null); +explain (costs off) select * from iboolpart where a in (true, false); + QUERY PLAN +------------------------------------------------- + Append + -> Seq Scan on iboolpart_t iboolpart_1 + Filter: (a = ANY ('{t,f}'::boolean[])) + -> Seq Scan on iboolpart_f iboolpart_2 + Filter: (a = ANY ('{t,f}'::boolean[])) + -> Seq Scan on iboolpart_default iboolpart_3 + Filter: (a = ANY ('{t,f}'::boolean[])) +(7 rows) + +explain (costs off) select * from iboolpart where a = false; + QUERY PLAN +----------------------------------- + Seq Scan on iboolpart_f iboolpart + Filter: (NOT a) +(2 rows) + +explain (costs off) select * from iboolpart where not a = false; + QUERY PLAN +----------------------------------- + Seq Scan on iboolpart_t iboolpart + Filter: a +(2 rows) + +explain (costs off) select * from iboolpart where a is true or a is not true; + QUERY PLAN +-------------------------------------------------- + Append + -> Seq Scan on iboolpart_t iboolpart_1 + Filter: ((a IS TRUE) OR (a IS NOT TRUE)) + -> Seq Scan on iboolpart_f iboolpart_2 + Filter: ((a IS TRUE) OR (a IS NOT TRUE)) + -> Seq Scan on iboolpart_default iboolpart_3 + Filter: ((a IS TRUE) OR (a IS NOT TRUE)) +(7 rows) + +explain (costs off) select * from iboolpart where a is not true; + QUERY PLAN +------------------------------------------------- + Append + -> Seq Scan on iboolpart_t iboolpart_1 + Filter: (a IS NOT TRUE) + -> Seq Scan on iboolpart_f iboolpart_2 + Filter: (a IS NOT TRUE) + -> Seq Scan on iboolpart_default iboolpart_3 + Filter: (a IS NOT TRUE) +(7 rows) + +explain (costs off) select * from iboolpart where a is not true and a is not false; + QUERY PLAN +-------------------------------------------------------- + Append + -> Seq Scan on iboolpart_t iboolpart_1 + Filter: ((a IS NOT TRUE) AND (a IS NOT FALSE)) + -> Seq Scan on iboolpart_f iboolpart_2 + Filter: ((a IS NOT TRUE) AND (a IS NOT FALSE)) + -> Seq Scan on iboolpart_default iboolpart_3 + Filter: ((a IS NOT TRUE) AND (a IS NOT FALSE)) +(7 rows) + +explain (costs off) select * from iboolpart where a is unknown; + QUERY PLAN +------------------------------------------------- + Append + -> Seq Scan on iboolpart_t iboolpart_1 + Filter: (a IS UNKNOWN) + -> Seq Scan on iboolpart_f iboolpart_2 + Filter: (a IS UNKNOWN) + -> Seq Scan on iboolpart_default iboolpart_3 + Filter: (a IS UNKNOWN) +(7 rows) + +explain (costs off) select * from iboolpart where a is not unknown; + QUERY PLAN +------------------------------------------------- + Append + -> Seq Scan on iboolpart_t iboolpart_1 + Filter: (a IS NOT UNKNOWN) + -> Seq Scan on iboolpart_f iboolpart_2 + Filter: (a IS NOT UNKNOWN) + -> Seq Scan on iboolpart_default iboolpart_3 + Filter: (a IS NOT UNKNOWN) +(7 rows) + +select * from iboolpart where a in (true, false); + a +--- + t + f +(2 rows) + +select * from iboolpart where a = false; + a +--- + f +(1 row) + +select * from iboolpart where not a = false; + a +--- + t +(1 row) + +select * from iboolpart where a is true or a is not true; + a +--- + t + f + +(3 rows) + +select * from iboolpart where a is not true; + a +--- + f + +(2 rows) + +select * from iboolpart where a is not true and a is not false; + a +--- + +(1 row) + +select * from iboolpart where a is unknown; + a +--- + +(1 row) + +select * from iboolpart where a is not unknown; + a +--- + t + f +(2 rows) + create table boolrangep (a bool, b bool, c int) partition by range (a,b,c); create table boolrangep_tf partition of boolrangep for values from ('true', 'false', 0) to ('true', 'false', 100); create table boolrangep_ft partition of boolrangep for values from ('false', 'true', 0) to ('false', 'true', 100); @@ -1530,7 +1735,7 @@ explain (costs off) select * from rparted_by_int2 where a > 100000000000000; Filter: (a > '100000000000000'::bigint) (2 rows) -drop table lp, coll_pruning, rlp, mc3p, mc2p, boolpart, boolrangep, rp, coll_pruning_multi, like_op_noprune, lparted_by_int2, rparted_by_int2; +drop table lp, coll_pruning, rlp, mc3p, mc2p, boolpart, iboolpart, boolrangep, rp, coll_pruning_multi, like_op_noprune, lparted_by_int2, rparted_by_int2; -- -- Test Partition pruning for HASH partitioning -- diff --git a/src/test/regress/sql/partition_prune.sql b/src/test/regress/sql/partition_prune.sql index d70bd8610c..3d18514889 100644 --- a/src/test/regress/sql/partition_prune.sql +++ b/src/test/regress/sql/partition_prune.sql @@ -158,6 +158,7 @@ create table boolpart (a bool) partition by list (a); create table boolpart_default partition of boolpart default; create table boolpart_t partition of boolpart for values in ('true'); create table boolpart_f partition of boolpart for values in ('false'); +insert into boolpart values (true), (false), (null); explain (costs off) select * from boolpart where a in (true, false); explain (costs off) select * from boolpart where a = false; @@ -168,6 +169,41 @@ explain (costs off) select * from boolpart where a is not true and a is not fals explain (costs off) select * from boolpart where a is unknown; explain (costs off) select * from boolpart where a is not unknown; +select * from boolpart where a in (true, false); +select * from boolpart where a = false; +select * from boolpart where not a = false; +select * from boolpart where a is true or a is not true; +select * from boolpart where a is not true; +select * from boolpart where a is not true and a is not false; +select * from boolpart where a is unknown; +select * from boolpart where a is not unknown; + +-- inverse boolean partitioning - a seemingly unlikely design, but we've got +-- code for it, so we'd better test it. +create table iboolpart (a bool) partition by list ((not a)); +create table iboolpart_default partition of iboolpart default; +create table iboolpart_f partition of iboolpart for values in ('true'); +create table iboolpart_t partition of iboolpart for values in ('false'); +insert into iboolpart values (true), (false), (null); + +explain (costs off) select * from iboolpart where a in (true, false); +explain (costs off) select * from iboolpart where a = false; +explain (costs off) select * from iboolpart where not a = false; +explain (costs off) select * from iboolpart where a is true or a is not true; +explain (costs off) select * from iboolpart where a is not true; +explain (costs off) select * from iboolpart where a is not true and a is not false; +explain (costs off) select * from iboolpart where a is unknown; +explain (costs off) select * from iboolpart where a is not unknown; + +select * from iboolpart where a in (true, false); +select * from iboolpart where a = false; +select * from iboolpart where not a = false; +select * from iboolpart where a is true or a is not true; +select * from iboolpart where a is not true; +select * from iboolpart where a is not true and a is not false; +select * from iboolpart where a is unknown; +select * from iboolpart where a is not unknown; + create table boolrangep (a bool, b bool, c int) partition by range (a,b,c); create table boolrangep_tf partition of boolrangep for values from ('true', 'false', 0) to ('true', 'false', 100); create table boolrangep_ft partition of boolrangep for values from ('false', 'true', 0) to ('false', 'true', 100); @@ -294,7 +330,7 @@ create table rparted_by_int2_maxvalue partition of rparted_by_int2 for values fr -- all partitions but rparted_by_int2_maxvalue pruned explain (costs off) select * from rparted_by_int2 where a > 100000000000000; -drop table lp, coll_pruning, rlp, mc3p, mc2p, boolpart, boolrangep, rp, coll_pruning_multi, like_op_noprune, lparted_by_int2, rparted_by_int2; +drop table lp, coll_pruning, rlp, mc3p, mc2p, boolpart, iboolpart, boolrangep, rp, coll_pruning_multi, like_op_noprune, lparted_by_int2, rparted_by_int2; -- -- Test Partition pruning for HASH partitioning