]> granicus.if.org Git - postgresql/commitdiff
Better handle pseudotypes as partition keys
authorAlvaro Herrera <alvherre@alvh.no-ip.org>
Tue, 10 Jul 2018 19:07:28 +0000 (15:07 -0400)
committerAlvaro Herrera <alvherre@alvh.no-ip.org>
Tue, 10 Jul 2018 19:07:28 +0000 (15:07 -0400)
We fail to handle polymorphic types properly when they are used as
partition keys: we were unnecessarily adding a RelabelType node on top,
which confuses code examining the nodes.  In particular, this makes
predtest.c-based partition pruning not to work, and ruleutils.c to emit
expressions that are uglier than needed.  Fix it by not adding RelabelType
when not needed.

In master/11 the new pruning code is separate so it doesn't suffer from
this problem, since we already fixed it (in essentially the same way) in
e5dcbb88a15d, which also added a few tests; back-patch those tests to
pg10 also.  But since UPDATE/DELETE still uses predtest.c in pg11, this
change improves partitioning for those cases too.  Add tests for this.
The ruleutils.c behavior change is relevant in pg11/master too.

Co-authored-by: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
Co-authored-by: Álvaro Herrera <alvherre@alvh.no-ip.org>
Reviewed-by: Álvaro Herrera <alvherre@alvh.no-ip.org>
Reviewed-by: Robert Haas <robertmhaas@gmail.com>
Discussion: https://postgr.es/m/54745d13-7ed4-54ac-97d8-ea1eec95ae25@lab.ntt.co.jp

src/backend/catalog/partition.c
src/test/regress/expected/create_table.out
src/test/regress/expected/inherit.out
src/test/regress/sql/inherit.sql

index 17704f36b9501c4c4b2476775298dc2df6815f63..627b09f01e9386cc2b1c5b05e1b1c5e701d36c20 100644 (file)
@@ -1167,7 +1167,10 @@ RelationGetPartitionDispatchInfo(Relation rel,
  * get_partition_operator
  *
  * Return oid of the operator of given strategy for a given partition key
- * column.
+ * column.  It is assumed that the partitioning key is of the same type as the
+ * chosen partitioning opclass, or at least binary-compatible.  In the latter
+ * case, *need_relabel is set to true if the opclass is not of a polymorphic
+ * type, otherwise false.
  */
 static Oid
 get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
@@ -1176,40 +1179,26 @@ get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
        Oid                     operoid;
 
        /*
-        * First check if there exists an operator of the given strategy, with
-        * this column's type as both its lefttype and righttype, in the
-        * partitioning operator family specified for the column.
+        * Get the operator in the partitioning opfamily using the opclass'
+        * declared input type as both left- and righttype.
         */
        operoid = get_opfamily_member(key->partopfamily[col],
-                                                                 key->parttypid[col],
-                                                                 key->parttypid[col],
+                                                                 key->partopcintype[col],
+                                                                 key->partopcintype[col],
                                                                  strategy);
+       if (!OidIsValid(operoid))
+               elog(ERROR, "missing operator %d(%u,%u) in partition opfamily %u",
+                        strategy, key->partopcintype[col], key->partopcintype[col],
+                        key->partopfamily[col]);
 
        /*
-        * If one doesn't exist, we must resort to using an operator in the same
-        * operator family but with the operator class declared input type.  It is
-        * OK to do so, because the column's type is known to be binary-coercible
-        * with the operator class input type (otherwise, the operator class in
-        * question would not have been accepted as the partitioning operator
-        * class).  We must however inform the caller to wrap the non-Const
-        * expression with a RelabelType node to denote the implicit coercion. It
-        * ensures that the resulting expression structurally matches similarly
-        * processed expressions within the optimizer.
+        * If the partition key column is not of the same type as the operator
+        * class and not polymorphic, tell caller to wrap the non-Const expression
+        * in a RelabelType.  This matches what parse_coerce.c does.
         */
-       if (!OidIsValid(operoid))
-       {
-               operoid = get_opfamily_member(key->partopfamily[col],
-                                                                         key->partopcintype[col],
-                                                                         key->partopcintype[col],
-                                                                         strategy);
-               if (!OidIsValid(operoid))
-                       elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
-                                strategy, key->partopcintype[col], key->partopcintype[col],
-                                key->partopfamily[col]);
-               *need_relabel = true;
-       }
-       else
-               *need_relabel = false;
+       *need_relabel = (key->parttypid[col] != key->partopcintype[col] &&
+                                        key->partopcintype[col] != RECORDOID &&
+                                        !IsPolymorphicType(key->partopcintype[col]));
 
        return operoid;
 }
index ab1d4ecaee653a13bdf0dec105d88de8c337b7b4..0026aa11dd46ff499f22c30a64146b9f91a7216a 100644 (file)
@@ -808,7 +808,7 @@ CREATE TABLE arrlp12 PARTITION OF arrlp FOR VALUES IN ('{1}', '{2}');
 --------+-----------+-----------+----------+---------+----------+--------------+-------------
  a      | integer[] |           |          |         | extended |              | 
 Partition of: arrlp FOR VALUES IN ('{1}', '{2}')
-Partition constraint: ((a IS NOT NULL) AND (((a)::anyarray OPERATOR(pg_catalog.=) '{1}'::integer[]) OR ((a)::anyarray OPERATOR(pg_catalog.=) '{2}'::integer[])))
+Partition constraint: ((a IS NOT NULL) AND ((a = '{1}'::integer[]) OR (a = '{2}'::integer[])))
 
 DROP TABLE arrlp;
 -- partition on boolean column
index 2efae8038ff169759ec5699c249f6a1f89bb86c4..714fe02429a6facf4f8c67dc91f86c7887fb0b86 100644 (file)
@@ -1973,3 +1973,119 @@ select min(a), max(a) from parted_minmax where b = '12345';
 (1 row)
 
 drop table parted_minmax;
+--
+-- 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)
+
+explain (costs off) update pp_arrpart set a = a where a = '{1}';
+               QUERY PLAN               
+----------------------------------------
+ Update on pp_arrpart
+   Update on pp_arrpart1
+   ->  Seq Scan on pp_arrpart1
+         Filter: (a = '{1}'::integer[])
+(4 rows)
+
+explain (costs off) delete from pp_arrpart where a = '{1}';
+               QUERY PLAN               
+----------------------------------------
+ Delete on pp_arrpart
+   Delete on pp_arrpart1
+   ->  Seq Scan on pp_arrpart1
+         Filter: (a = '{1}'::integer[])
+(4 rows)
+
+drop table pp_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 55770f5681dbafc6c76ee95731971035902d9467..46b73f14991a93634825a4f66ec06efec4ecbfbb 100644 (file)
@@ -683,3 +683,48 @@ insert into parted_minmax values (1,'12345');
 explain (costs off) select min(a), max(a) from parted_minmax where b = '12345';
 select min(a), max(a) from parted_minmax where b = '12345';
 drop table parted_minmax;
+
+
+--
+-- 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}');
+explain (costs off) update pp_arrpart set a = a where a = '{1}';
+explain (costs off) delete from pp_arrpart where a = '{1}';
+drop table pp_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;