Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Rework code to determine partition pruning procedure
authorAlvaro Herrera <alvherre@alvh.no-ip.org>
Thu, 19 Apr 2018 14:22:31 +0000 (11:22 -0300)
committerAlvaro Herrera <alvherre@alvh.no-ip.org>
Thu, 19 Apr 2018 15:01:37 +0000 (12:01 -0300)
Amit Langote reported that partition prune was unable to work with
arrays, enums, etc, which led him to research the appropriate way to
match query clauses to partition keys: instead of searching for an exact
match of the expression's type, it is better to rely on the fact that
the expression qual has already been resolved to a specific operator,
and that the partition key is linked to a specific operator family.
With that info, it's possible to figure out the strategy and comparison
function to use for the pruning clause in a manner that works reliably
for pseudo-types also.

Include new test cases that demonstrate pruning where pseudotypes are
involved.

Author: Amit Langote, Álvaro Herrera
Discussion: https://postgr.es/m/2b02f1e9-9812-9c41-972d-517bdc0f815d@lab.ntt.co.jp

src/backend/partitioning/partprune.c
src/test/regress/expected/partition_prune.out
src/test/regress/sql/partition_prune.sql

index 7666c6c412dba5a70173ed8a8d7ff146bf6a4d9d..e1b5825f00c49bf75d330c259f31996b735d88c6 100644 (file)
@@ -946,13 +946,7 @@ gen_prune_step_op(GeneratePruningStepsContext *context,
     * InvalidStrategy to signal get_matching_list_bounds to do the right
     * thing.
     */
-   if (op_is_ne)
-   {
-       Assert(opstrategy == BTEqualStrategyNumber);
-       opstep->opstrategy = InvalidStrategy;
-   }
-   else
-       opstep->opstrategy = opstrategy;
+   opstep->opstrategy = op_is_ne ? InvalidStrategy : opstrategy;
    Assert(list_length(exprs) == list_length(cmpfns));
    opstep->exprs = exprs;
    opstep->cmpfns = cmpfns;
@@ -1426,10 +1420,12 @@ match_clause_to_partition_key(RelOptInfo *rel,
        OpExpr     *opclause = (OpExpr *) clause;
        Expr       *leftop,
                   *rightop;
-       Oid         commutator = InvalidOid,
+       Oid         op_lefttype,
+                   op_righttype,
+                   commutator = InvalidOid,
                    negator = InvalidOid;
        Oid         cmpfn;
-       Oid         exprtype;
+       int         op_strategy;
        bool        is_opne_listp = false;
        PartClauseInfo *partclause;
 
@@ -1483,33 +1479,38 @@ match_clause_to_partition_key(RelOptInfo *rel,
            return PARTCLAUSE_UNSUPPORTED;
 
        /*
-        * Normally we only bother with operators that are listed as being
-        * part of the partitioning operator family.  But we make an exception
-        * in one case -- operators named '<>' are not listed in any operator
-        * family whatsoever, in which case, we try to perform partition
-        * pruning with it only if list partitioning is in use.
+        * Determine the input types of the operator we're considering.
+        *
+        * Normally we only care about operators that are listed as being part
+        * of the partitioning operator family.  But there is one exception:
+        * the not-equals operators are not listed in any operator family
+        * whatsoever, but their negators (equality) are.  We can use one of
+        * those if we find it, but only for list partitioning.
         */
-       if (!op_in_opfamily(opclause->opno, partopfamily))
+       if (op_in_opfamily(opclause->opno, partopfamily))
        {
+           Oid     oper;
+
+           oper = OidIsValid(commutator) ? commutator : opclause->opno;
+           get_op_opfamily_properties(oper, partopfamily, false,
+                                      &op_strategy, &op_lefttype,
+                                      &op_righttype);
+       }
+       else
+       {
+           /* Not good unless list partitioning */
            if (part_scheme->strategy != PARTITION_STRATEGY_LIST)
                return PARTCLAUSE_UNSUPPORTED;
 
-           /*
-            * To confirm if the operator is really '<>', check if its negator
-            * is a btree equality operator.
-            */
+           /* See if the negator is equality */
            negator = get_negator(opclause->opno);
            if (OidIsValid(negator) && op_in_opfamily(negator, partopfamily))
            {
-               Oid         lefttype;
-               Oid         righttype;
-               int         strategy;
-
                get_op_opfamily_properties(negator, partopfamily, false,
-                                          &strategy, &lefttype, &righttype);
-
-               if (strategy == BTEqualStrategyNumber)
-                   is_opne_listp = true;
+                                          &op_strategy, &op_lefttype,
+                                          &op_righttype);
+               if (op_strategy == BTEqualStrategyNumber)
+                   is_opne_listp = true;   /* bingo */
            }
 
            /* Operator isn't really what we were hoping it'd be. */
@@ -1517,24 +1518,41 @@ match_clause_to_partition_key(RelOptInfo *rel,
                return PARTCLAUSE_UNSUPPORTED;
        }
 
-       /* Check if we're going to need a cross-type comparison function. */
-       exprtype = exprType((Node *) expr);
-       if (exprtype != part_scheme->partopcintype[partkeyidx])
+       /*
+        * Now find the procedure to use, based on the types.  If the clause's
+        * other argument is of the same type as the partitioning opclass's
+        * declared input type, we can use the procedure cached in
+        * PartitionKey.  If not, search for a cross-type one in the same
+        * opfamily; if one doesn't exist, give up on pruning for this clause.
+        */
+       if (op_righttype == part_scheme->partopcintype[partkeyidx])
+           cmpfn = part_scheme->partsupfunc[partkeyidx].fn_oid;
+       else
        {
            switch (part_scheme->strategy)
            {
+               /*
+                * For range and list partitioning, we need the ordering
+                * procedure with lefttype being the partition key's type, and
+                * righttype the clause's operator's right type.
+                */
                case PARTITION_STRATEGY_LIST:
                case PARTITION_STRATEGY_RANGE:
                    cmpfn =
                        get_opfamily_proc(part_scheme->partopfamily[partkeyidx],
                                          part_scheme->partopcintype[partkeyidx],
-                                         exprtype, BTORDER_PROC);
+                                         op_righttype, BTORDER_PROC);
                    break;
 
+               /*
+                * For hash partitioning, we need the hashing procedure for
+                * the clause's type.
+                */
                case PARTITION_STRATEGY_HASH:
                    cmpfn =
                        get_opfamily_proc(part_scheme->partopfamily[partkeyidx],
-                                         exprtype, exprtype, HASHEXTENDED_PROC);
+                                         op_righttype, op_righttype,
+                                         HASHEXTENDED_PROC);
                    break;
 
                default:
@@ -1547,34 +1565,26 @@ match_clause_to_partition_key(RelOptInfo *rel,
            if (!OidIsValid(cmpfn))
                return PARTCLAUSE_UNSUPPORTED;
        }
-       else
-           cmpfn = part_scheme->partsupfunc[partkeyidx].fn_oid;
 
+       /*
+        * Build the clause, passing the negator or commutator if applicable.
+        */
        partclause = (PartClauseInfo *) palloc(sizeof(PartClauseInfo));
        partclause->keyno = partkeyidx;
-
-       /* For <> operator clauses, pass on the negator. */
-       partclause->op_is_ne = false;
-       partclause->op_strategy = InvalidStrategy;
-
        if (is_opne_listp)
        {
            Assert(OidIsValid(negator));
            partclause->opno = negator;
            partclause->op_is_ne = true;
-
-           /*
-            * We already know the strategy in this case, so may as well set
-            * it rather than having to look it up later.
-            */
-           partclause->op_strategy = BTEqualStrategyNumber;
+           partclause->op_strategy = InvalidStrategy;
        }
-       /* And if commuted before matching, pass on the commutator */
-       else if (OidIsValid(commutator))
-           partclause->opno = commutator;
        else
-           partclause->opno = opclause->opno;
-
+       {
+           partclause->opno = OidIsValid(commutator) ?
+               commutator : opclause->opno;
+           partclause->op_is_ne = false;
+           partclause->op_strategy = op_strategy;
+       }
        partclause->expr = expr;
        partclause->cmpfn = cmpfn;
 
index 844cfb3e425d4a9f37561cb6f75dadd9d72b7aa1..9c65ee001de47e7596c46d576b74faade30d7304 100644 (file)
@@ -2599,3 +2599,141 @@ select * from boolp where a = (select value from boolvalues where not value);
 
 drop table boolp;
 reset enable_indexonlyscan;
+--
+-- check that pruning works properly when the partition key is of a
+-- pseudotype
+--
+-- array type list partition key
+create table pp_arrpart (a int[]) partition by list (a);
+create table pp_arrpart1 partition of pp_arrpart for values in ('{1}');
+create table pp_arrpart2 partition of pp_arrpart for values in ('{2, 3}', '{4, 5}');
+explain (costs off) select * from pp_arrpart where a = '{1}';
+               QUERY PLAN               
+----------------------------------------
+ Append
+   ->  Seq Scan on pp_arrpart1
+         Filter: (a = '{1}'::integer[])
+(3 rows)
+
+explain (costs off) select * from pp_arrpart where a = '{1, 2}';
+        QUERY PLAN        
+--------------------------
+ Result
+   One-Time Filter: false
+(2 rows)
+
+explain (costs off) select * from pp_arrpart where a in ('{4, 5}', '{1}');
+                              QUERY PLAN                              
+----------------------------------------------------------------------
+ Append
+   ->  Seq Scan on pp_arrpart1
+         Filter: ((a = '{4,5}'::integer[]) OR (a = '{1}'::integer[]))
+   ->  Seq Scan on pp_arrpart2
+         Filter: ((a = '{4,5}'::integer[]) OR (a = '{1}'::integer[]))
+(5 rows)
+
+drop table pp_arrpart;
+-- array type hash partition key
+create table pph_arrpart (a int[]) partition by hash (a);
+create table pph_arrpart1 partition of pph_arrpart for values with (modulus 2, remainder 0);
+create table pph_arrpart2 partition of pph_arrpart for values with (modulus 2, remainder 1);
+insert into pph_arrpart values ('{1}'), ('{1, 2}'), ('{4, 5}');
+select tableoid::regclass, * from pph_arrpart order by 1;
+   tableoid   |   a   
+--------------+-------
+ pph_arrpart1 | {1,2}
+ pph_arrpart1 | {4,5}
+ pph_arrpart2 | {1}
+(3 rows)
+
+explain (costs off) select * from pph_arrpart where a = '{1}';
+               QUERY PLAN               
+----------------------------------------
+ Append
+   ->  Seq Scan on pph_arrpart2
+         Filter: (a = '{1}'::integer[])
+(3 rows)
+
+explain (costs off) select * from pph_arrpart where a = '{1, 2}';
+                QUERY PLAN                
+------------------------------------------
+ Append
+   ->  Seq Scan on pph_arrpart1
+         Filter: (a = '{1,2}'::integer[])
+(3 rows)
+
+explain (costs off) select * from pph_arrpart where a in ('{4, 5}', '{1}');
+                              QUERY PLAN                              
+----------------------------------------------------------------------
+ Append
+   ->  Seq Scan on pph_arrpart1
+         Filter: ((a = '{4,5}'::integer[]) OR (a = '{1}'::integer[]))
+   ->  Seq Scan on pph_arrpart2
+         Filter: ((a = '{4,5}'::integer[]) OR (a = '{1}'::integer[]))
+(5 rows)
+
+drop table pph_arrpart;
+-- enum type list partition key
+create type pp_colors as enum ('green', 'blue', 'black');
+create table pp_enumpart (a pp_colors) partition by list (a);
+create table pp_enumpart_green partition of pp_enumpart for values in ('green');
+create table pp_enumpart_blue partition of pp_enumpart for values in ('blue');
+explain (costs off) select * from pp_enumpart where a = 'blue';
+               QUERY PLAN                
+-----------------------------------------
+ Append
+   ->  Seq Scan on pp_enumpart_blue
+         Filter: (a = 'blue'::pp_colors)
+(3 rows)
+
+explain (costs off) select * from pp_enumpart where a = 'black';
+        QUERY PLAN        
+--------------------------
+ Result
+   One-Time Filter: false
+(2 rows)
+
+drop table pp_enumpart;
+drop type pp_colors;
+-- record type as partition key
+create type pp_rectype as (a int, b int);
+create table pp_recpart (a pp_rectype) partition by list (a);
+create table pp_recpart_11 partition of pp_recpart for values in ('(1,1)');
+create table pp_recpart_23 partition of pp_recpart for values in ('(2,3)');
+explain (costs off) select * from pp_recpart where a = '(1,1)'::pp_rectype;
+                QUERY PLAN                 
+-------------------------------------------
+ Append
+   ->  Seq Scan on pp_recpart_11
+         Filter: (a = '(1,1)'::pp_rectype)
+(3 rows)
+
+explain (costs off) select * from pp_recpart where a = '(1,2)'::pp_rectype;
+        QUERY PLAN        
+--------------------------
+ Result
+   One-Time Filter: false
+(2 rows)
+
+drop table pp_recpart;
+drop type pp_rectype;
+-- range type partition key
+create table pp_intrangepart (a int4range) partition by list (a);
+create table pp_intrangepart12 partition of pp_intrangepart for values in ('[1,2]');
+create table pp_intrangepart2inf partition of pp_intrangepart for values in ('[2,)');
+explain (costs off) select * from pp_intrangepart where a = '[1,2]'::int4range;
+                QUERY PLAN                
+------------------------------------------
+ Append
+   ->  Seq Scan on pp_intrangepart12
+         Filter: (a = '[1,3)'::int4range)
+(3 rows)
+
+explain (costs off) select * from pp_intrangepart where a = '(1,2)'::int4range;
+        QUERY PLAN        
+--------------------------
+ Result
+   One-Time Filter: false
+(2 rows)
+
+drop table pp_intrangepart;
index e808d1a439f30ecdb8047139d122042643f62d70..b38b39c71e02b7dcde89832f0e2ec4b989c82e37 100644 (file)
@@ -645,3 +645,56 @@ select * from boolp where a = (select value from boolvalues where not value);
 drop table boolp;
 
 reset enable_indexonlyscan;
+
+--
+-- check that pruning works properly when the partition key is of a
+-- pseudotype
+--
+
+-- array type list partition key
+create table pp_arrpart (a int[]) partition by list (a);
+create table pp_arrpart1 partition of pp_arrpart for values in ('{1}');
+create table pp_arrpart2 partition of pp_arrpart for values in ('{2, 3}', '{4, 5}');
+explain (costs off) select * from pp_arrpart where a = '{1}';
+explain (costs off) select * from pp_arrpart where a = '{1, 2}';
+explain (costs off) select * from pp_arrpart where a in ('{4, 5}', '{1}');
+drop table pp_arrpart;
+
+-- array type hash partition key
+create table pph_arrpart (a int[]) partition by hash (a);
+create table pph_arrpart1 partition of pph_arrpart for values with (modulus 2, remainder 0);
+create table pph_arrpart2 partition of pph_arrpart for values with (modulus 2, remainder 1);
+insert into pph_arrpart values ('{1}'), ('{1, 2}'), ('{4, 5}');
+select tableoid::regclass, * from pph_arrpart order by 1;
+explain (costs off) select * from pph_arrpart where a = '{1}';
+explain (costs off) select * from pph_arrpart where a = '{1, 2}';
+explain (costs off) select * from pph_arrpart where a in ('{4, 5}', '{1}');
+drop table pph_arrpart;
+
+-- enum type list partition key
+create type pp_colors as enum ('green', 'blue', 'black');
+create table pp_enumpart (a pp_colors) partition by list (a);
+create table pp_enumpart_green partition of pp_enumpart for values in ('green');
+create table pp_enumpart_blue partition of pp_enumpart for values in ('blue');
+explain (costs off) select * from pp_enumpart where a = 'blue';
+explain (costs off) select * from pp_enumpart where a = 'black';
+drop table pp_enumpart;
+drop type pp_colors;
+
+-- record type as partition key
+create type pp_rectype as (a int, b int);
+create table pp_recpart (a pp_rectype) partition by list (a);
+create table pp_recpart_11 partition of pp_recpart for values in ('(1,1)');
+create table pp_recpart_23 partition of pp_recpart for values in ('(2,3)');
+explain (costs off) select * from pp_recpart where a = '(1,1)'::pp_rectype;
+explain (costs off) select * from pp_recpart where a = '(1,2)'::pp_rectype;
+drop table pp_recpart;
+drop type pp_rectype;
+
+-- range type partition key
+create table pp_intrangepart (a int4range) partition by list (a);
+create table pp_intrangepart12 partition of pp_intrangepart for values in ('[1,2]');
+create table pp_intrangepart2inf partition of pp_intrangepart for values in ('[2,)');
+explain (costs off) select * from pp_intrangepart where a = '[1,2]'::int4range;
+explain (costs off) select * from pp_intrangepart where a = '(1,2)'::int4range;
+drop table pp_intrangepart;