Fix list partition constraints for partition keys of array type.
authorRobert Haas <rhaas@postgresql.org>
Wed, 31 Jan 2018 20:43:11 +0000 (15:43 -0500)
committerRobert Haas <rhaas@postgresql.org>
Wed, 31 Jan 2018 20:43:11 +0000 (15:43 -0500)
The old code generated always generated a constraint of the form
col = ANY(ARRAY[val1, val2, ...]), but that's invalid when col is an
array type.  Instead, generate col = val when there's only one value,
col = val1 OR col = val2 OR ... when there are multiple values and
col is of array type, and the old form when there are multiple values
and col is not of an array type.

As a side benefit, this makes constraint exclusion able to prune
a list partition declared to accept a single Boolean value, which
didn't work before.

Amit Langote, reviewed by Etsuro Fujita

Discussion: http://postgr.es/m/97267195-e235-89d1-a41a-c110198dfce9@lab.ntt.co.jp

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

index e69bbc0345b32cb5195d76525850972c28328833..45945511f078eddb9b279a7883b88c235bf9e623 100644 (file)
@@ -1625,18 +1625,60 @@ make_partition_op_expr(PartitionKey key, int keynum,
    {
        case PARTITION_STRATEGY_LIST:
            {
-               ScalarArrayOpExpr *saopexpr;
-
-               /* Build leftop = ANY (rightop) */
-               saopexpr = makeNode(ScalarArrayOpExpr);
-               saopexpr->opno = operoid;
-               saopexpr->opfuncid = get_opcode(operoid);
-               saopexpr->useOr = true;
-               saopexpr->inputcollid = key->partcollation[keynum];
-               saopexpr->args = list_make2(arg1, arg2);
-               saopexpr->location = -1;
-
-               result = (Expr *) saopexpr;
+               List       *elems = (List *) arg2;
+               int         nelems = list_length(elems);
+
+               Assert(nelems >= 1);
+               Assert(keynum == 0);
+
+               if (nelems > 1 &&
+                   !type_is_array(key->parttypid[keynum]))
+               {
+                   ArrayExpr  *arrexpr;
+                   ScalarArrayOpExpr *saopexpr;
+
+                   /* Construct an ArrayExpr for the right-hand inputs */
+                   arrexpr = makeNode(ArrayExpr);
+                   arrexpr->array_typeid =
+                                   get_array_type(key->parttypid[keynum]);
+                   arrexpr->array_collid = key->parttypcoll[keynum];
+                   arrexpr->element_typeid = key->parttypid[keynum];
+                   arrexpr->elements = elems;
+                   arrexpr->multidims = false;
+                   arrexpr->location = -1;
+
+                   /* Build leftop = ANY (rightop) */
+                   saopexpr = makeNode(ScalarArrayOpExpr);
+                   saopexpr->opno = operoid;
+                   saopexpr->opfuncid = get_opcode(operoid);
+                   saopexpr->useOr = true;
+                   saopexpr->inputcollid = key->partcollation[keynum];
+                   saopexpr->args = list_make2(arg1, arrexpr);
+                   saopexpr->location = -1;
+
+                   result = (Expr *) saopexpr;
+               }
+               else
+               {
+                   List       *elemops = NIL;
+                   ListCell   *lc;
+
+                   foreach (lc, elems)
+                   {
+                       Expr   *elem = lfirst(lc),
+                              *elemop;
+
+                       elemop = make_opclause(operoid,
+                                              BOOLOID,
+                                              false,
+                                              arg1, elem,
+                                              InvalidOid,
+                                              key->partcollation[keynum]);
+                       elemops = lappend(elemops, elemop);
+                   }
+
+                   result = nelems > 1 ? makeBoolExpr(OR_EXPR, elemops, -1) : linitial(elemops);
+               }
                break;
            }
 
@@ -1758,11 +1800,10 @@ get_qual_for_list(Relation parent, PartitionBoundSpec *spec)
    PartitionKey key = RelationGetPartitionKey(parent);
    List       *result;
    Expr       *keyCol;
-   ArrayExpr  *arr;
    Expr       *opexpr;
    NullTest   *nulltest;
    ListCell   *cell;
-   List       *arrelems = NIL;
+   List       *elems = NIL;
    bool        list_has_null = false;
 
    /*
@@ -1828,7 +1869,7 @@ get_qual_for_list(Relation parent, PartitionBoundSpec *spec)
                            false,  /* isnull */
                            key->parttypbyval[0]);
 
-           arrelems = lappend(arrelems, val);
+           elems = lappend(elems, val);
        }
    }
    else
@@ -1843,30 +1884,25 @@ get_qual_for_list(Relation parent, PartitionBoundSpec *spec)
            if (val->constisnull)
                list_has_null = true;
            else
-               arrelems = lappend(arrelems, copyObject(val));
+               elems = lappend(elems, copyObject(val));
        }
    }
 
-   if (arrelems)
+   if (elems)
    {
-       /* Construct an ArrayExpr for the non-null partition values */
-       arr = makeNode(ArrayExpr);
-       arr->array_typeid = !type_is_array(key->parttypid[0])
-           ? get_array_type(key->parttypid[0])
-           : key->parttypid[0];
-       arr->array_collid = key->parttypcoll[0];
-       arr->element_typeid = key->parttypid[0];
-       arr->elements = arrelems;
-       arr->multidims = false;
-       arr->location = -1;
-
-       /* Generate the main expression, i.e., keyCol = ANY (arr) */
+       /*
+        * Generate the operator expression from the non-null partition
+        * values.
+        */
        opexpr = make_partition_op_expr(key, 0, BTEqualStrategyNumber,
-                                       keyCol, (Expr *) arr);
+                                       keyCol, (Expr *) elems);
    }
    else
    {
-       /* If there are no partition values, we don't need an = ANY expr */
+       /*
+        * If there are no partition values, we don't need an operator
+        * expression.
+        */
        opexpr = NULL;
    }
 
index ef0906776e83681afe5c5be4512c74446765ce87..e554ec484449f49d52c2ae35d52197aee81ecca1 100644 (file)
@@ -742,7 +742,7 @@ CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
  a      | text    |           |          |         | extended |              | 
  b      | integer |           | not null | 1       | plain    |              | 
 Partition of: parted FOR VALUES IN ('b')
-Partition constraint: ((a IS NOT NULL) AND (a = ANY (ARRAY['b'::text])))
+Partition constraint: ((a IS NOT NULL) AND (a = 'b'::text))
 Check constraints:
     "check_a" CHECK (length(a) > 0)
     "part_b_b_check" CHECK (b >= 0)
@@ -755,7 +755,7 @@ Check constraints:
  a      | text    |           |          |         | extended |              | 
  b      | integer |           | not null | 0       | plain    |              | 
 Partition of: parted FOR VALUES IN ('c')
-Partition constraint: ((a IS NOT NULL) AND (a = ANY (ARRAY['c'::text])))
+Partition constraint: ((a IS NOT NULL) AND (a = 'c'::text))
 Partition key: RANGE (b)
 Check constraints:
     "check_a" CHECK (length(a) > 0)
@@ -769,7 +769,7 @@ Partitions: part_c_1_10 FOR VALUES FROM (1) TO (10)
  a      | text    |           |          |         | extended |              | 
  b      | integer |           | not null | 0       | plain    |              | 
 Partition of: part_c FOR VALUES FROM (1) TO (10)
-Partition constraint: ((a IS NOT NULL) AND (a = ANY (ARRAY['c'::text])) AND (b IS NOT NULL) AND (b >= 1) AND (b < 10))
+Partition constraint: ((a IS NOT NULL) AND (a = 'c'::text) AND (b IS NOT NULL) AND (b >= 1) AND (b < 10))
 Check constraints:
     "check_a" CHECK (length(a) > 0)
 
@@ -868,3 +868,15 @@ Partition key: LIST (a)
 Number of partitions: 0
 
 DROP TABLE parted_col_comment;
+-- list partitioning on array type column
+CREATE TABLE arrlp (a int[]) PARTITION BY LIST (a);
+CREATE TABLE arrlp12 PARTITION OF arrlp FOR VALUES IN ('{1}', '{2}');
+\d+ arrlp12
+                                   Table "public.arrlp12"
+ Column |   Type    | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+-----------+-----------+----------+---------+----------+--------------+-------------
+ 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[])))
+
+DROP TABLE arrlp;
index d2c184f2cfe771e591faecff905f54279bd9bf30..6a1b278e5a627667d0deaae0e52b561388d6f304 100644 (file)
@@ -1863,7 +1863,7 @@ Partitions: pt2_1 FOR VALUES IN (1)
  c2     | text    |           |          |         |             | extended |              | 
  c3     | date    |           |          |         |             | plain    |              | 
 Partition of: pt2 FOR VALUES IN (1)
-Partition constraint: ((c1 IS NOT NULL) AND (c1 = ANY (ARRAY[1])))
+Partition constraint: ((c1 IS NOT NULL) AND (c1 = 1))
 Server: s0
 FDW options: (delimiter ',', quote '"', "be quoted" 'value')
 
@@ -1935,7 +1935,7 @@ Partitions: pt2_1 FOR VALUES IN (1)
  c2     | text    |           |          |         |             | extended |              | 
  c3     | date    |           |          |         |             | plain    |              | 
 Partition of: pt2 FOR VALUES IN (1)
-Partition constraint: ((c1 IS NOT NULL) AND (c1 = ANY (ARRAY[1])))
+Partition constraint: ((c1 IS NOT NULL) AND (c1 = 1))
 Server: s0
 FDW options: (delimiter ',', quote '"', "be quoted" 'value')
 
@@ -1963,7 +1963,7 @@ Partitions: pt2_1 FOR VALUES IN (1)
  c2     | text    |           |          |         |             | extended |              | 
  c3     | date    |           | not null |         |             | plain    |              | 
 Partition of: pt2 FOR VALUES IN (1)
-Partition constraint: ((c1 IS NOT NULL) AND (c1 = ANY (ARRAY[1])))
+Partition constraint: ((c1 IS NOT NULL) AND (c1 = 1))
 Check constraints:
     "p21chk" CHECK (c2 <> ''::text)
 Server: s0
index aabb0240a92f84bcb507733aa86422cdecf30a53..348719bd6253fe97b98bfe6e275d4e9cd8df8d76 100644 (file)
@@ -1014,23 +1014,19 @@ explain (costs off) select * from boolpart where a = false;
  Append
    ->  Seq Scan on boolpart_f
          Filter: (NOT a)
-   ->  Seq Scan on boolpart_t
-         Filter: (NOT a)
    ->  Seq Scan on boolpart_default
          Filter: (NOT a)
-(7 rows)
+(5 rows)
 
 explain (costs off) select * from boolpart where not a = false;
              QUERY PLAN             
 ------------------------------------
  Append
-   ->  Seq Scan on boolpart_f
-         Filter: a
    ->  Seq Scan on boolpart_t
          Filter: a
    ->  Seq Scan on boolpart_default
          Filter: a
-(7 rows)
+(5 rows)
 
 explain (costs off) select * from boolpart where a is true or a is not true;
                     QUERY PLAN                    
index 10e5d49e8e5f5773df5ab8c00e1bd84bf2f0dc78..a71d9ae7abc337f5389d3a158cca310073cb6283 100644 (file)
@@ -711,3 +711,9 @@ COMMENT ON COLUMN parted_col_comment.a IS 'Partition key';
 SELECT obj_description('parted_col_comment'::regclass);
 \d+ parted_col_comment
 DROP TABLE parted_col_comment;
+
+-- list partitioning on array type column
+CREATE TABLE arrlp (a int[]) PARTITION BY LIST (a);
+CREATE TABLE arrlp12 PARTITION OF arrlp FOR VALUES IN ('{1}', '{2}');
+\d+ arrlp12
+DROP TABLE arrlp;