mirror of
https://github.com/postgres/postgres.git
synced 2025-05-28 05:21:27 +03:00
Better handle pseudotypes as partition keys
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
This commit is contained in:
parent
7f0911f33e
commit
7c644b7d3f
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user