Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 9032b15

Browse files
danolivoCommitfest Bot
authored and
Commitfest Bot
committed
Enhance partition pruning for an array parameter.
It is designed to prune partitions in case when incoming clause looks like the following: 'partkey = ANY($1)'. It seems quite a common case when the array is a parameter of a function. Although the code is covered by tests the code should be carefully reviewed and tested.
1 parent 03c53a7 commit 9032b15

File tree

3 files changed

+311
-4
lines changed

3 files changed

+311
-4
lines changed

src/backend/partitioning/partprune.c

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2179,6 +2179,9 @@ match_clause_to_partition_key(GeneratePruningStepsContext *context,
21792179
List *elem_exprs,
21802180
*elem_clauses;
21812181
ListCell *lc1;
2182+
int strategy;
2183+
Oid lefttype,
2184+
righttype;
21822185

21832186
if (IsA(leftop, RelabelType))
21842187
leftop = ((RelabelType *) leftop)->arg;
@@ -2206,10 +2209,6 @@ match_clause_to_partition_key(GeneratePruningStepsContext *context,
22062209
negator = get_negator(saop_op);
22072210
if (OidIsValid(negator) && op_in_opfamily(negator, partopfamily))
22082211
{
2209-
int strategy;
2210-
Oid lefttype,
2211-
righttype;
2212-
22132212
get_op_opfamily_properties(negator, partopfamily,
22142213
false, &strategy,
22152214
&lefttype, &righttype);
@@ -2219,6 +2218,12 @@ match_clause_to_partition_key(GeneratePruningStepsContext *context,
22192218
else
22202219
return PARTCLAUSE_NOMATCH; /* no useful negator */
22212220
}
2221+
else
2222+
{
2223+
get_op_opfamily_properties(saop_op, partopfamily, false,
2224+
&strategy, &lefttype,
2225+
&righttype);
2226+
}
22222227

22232228
/*
22242229
* Only allow strict operators. This will guarantee nulls are
@@ -2365,6 +2370,64 @@ match_clause_to_partition_key(GeneratePruningStepsContext *context,
23652370
*/
23662371
elem_exprs = arrexpr->elements;
23672372
}
2373+
else if (IsA(rightop, Param))
2374+
{
2375+
Oid cmpfn;
2376+
PartClauseInfo *partclause;
2377+
2378+
if (righttype == part_scheme->partopcintype[partkeyidx])
2379+
cmpfn = part_scheme->partsupfunc[partkeyidx].fn_oid;
2380+
else
2381+
{
2382+
switch (part_scheme->strategy)
2383+
{
2384+
/*
2385+
* For range and list partitioning, we need the ordering
2386+
* procedure with lefttype being the partition key's type,
2387+
* and righttype the clause's operator's right type.
2388+
*/
2389+
case PARTITION_STRATEGY_LIST:
2390+
case PARTITION_STRATEGY_RANGE:
2391+
cmpfn =
2392+
get_opfamily_proc(part_scheme->partopfamily[partkeyidx],
2393+
part_scheme->partopcintype[partkeyidx],
2394+
righttype, BTORDER_PROC);
2395+
break;
2396+
2397+
/*
2398+
* For hash partitioning, we need the hashing procedure
2399+
* for the clause's type.
2400+
*/
2401+
case PARTITION_STRATEGY_HASH:
2402+
cmpfn =
2403+
get_opfamily_proc(part_scheme->partopfamily[partkeyidx],
2404+
righttype, righttype,
2405+
HASHEXTENDED_PROC);
2406+
break;
2407+
2408+
default:
2409+
elog(ERROR, "invalid partition strategy: %c",
2410+
part_scheme->strategy);
2411+
cmpfn = InvalidOid; /* keep compiler quiet */
2412+
break;
2413+
}
2414+
2415+
if (!OidIsValid(cmpfn))
2416+
return PARTCLAUSE_NOMATCH;
2417+
}
2418+
2419+
partclause = (PartClauseInfo *) palloc(sizeof(PartClauseInfo));
2420+
partclause->keyno = partkeyidx;
2421+
partclause->opno = saop_op;
2422+
partclause->op_is_ne = false;
2423+
partclause->op_strategy = strategy;
2424+
partclause->expr = rightop;
2425+
partclause->cmpfn = cmpfn;
2426+
2427+
*pc = partclause;
2428+
2429+
return PARTCLAUSE_MATCH_CLAUSE;
2430+
}
23682431
else
23692432
{
23702433
/* Give up on any other clause types. */

src/test/regress/expected/inherit.out

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3854,3 +3854,183 @@ select * from tuplesest_tab join
38543854

38553855
drop table tuplesest_parted;
38563856
drop table tuplesest_tab;
3857+
--
3858+
-- Test the cases for partition pruning by an expression like:
3859+
-- partkey = ANY($1)
3860+
--
3861+
CREATE TABLE array_prune (id int)
3862+
PARTITION BY HASH(id);
3863+
CREATE TABLE array_prune_t0
3864+
PARTITION OF array_prune FOR VALUES WITH (modulus 2, remainder 0);
3865+
CREATE TABLE array_prune_t1
3866+
PARTITION OF array_prune FOR VALUES WITH (modulus 2, remainder 1);
3867+
CREATE FUNCTION array_prune_fn(oper text, arr text) RETURNS setof text
3868+
LANGUAGE plpgsql AS $$
3869+
DECLARE
3870+
line text;
3871+
query text;
3872+
BEGIN
3873+
query := format('EXPLAIN (COSTS OFF) SELECT * FROM array_prune WHERE id %s (%s)', $1, $2);
3874+
FOR line IN EXECUTE query
3875+
LOOP
3876+
RETURN NEXT line;
3877+
END LOOP;
3878+
END; $$;
3879+
SELECT array_prune_fn('= ANY', 'ARRAY[1]'); -- prune one partition
3880+
array_prune_fn
3881+
-----------------------------------------
3882+
Seq Scan on array_prune_t0 array_prune
3883+
Filter: (id = ANY ('{1}'::integer[]))
3884+
(2 rows)
3885+
3886+
SELECT array_prune_fn('= ANY', 'ARRAY[1,2]'); -- prune one partition
3887+
array_prune_fn
3888+
-------------------------------------------
3889+
Seq Scan on array_prune_t0 array_prune
3890+
Filter: (id = ANY ('{1,2}'::integer[]))
3891+
(2 rows)
3892+
3893+
SELECT array_prune_fn('= ANY', 'ARRAY[1,2,3]'); -- no pruning
3894+
array_prune_fn
3895+
---------------------------------------------------
3896+
Append
3897+
-> Seq Scan on array_prune_t0 array_prune_1
3898+
Filter: (id = ANY ('{1,2,3}'::integer[]))
3899+
-> Seq Scan on array_prune_t1 array_prune_2
3900+
Filter: (id = ANY ('{1,2,3}'::integer[]))
3901+
(5 rows)
3902+
3903+
SELECT array_prune_fn('= ANY', 'ARRAY[1, NULL]'); -- prune
3904+
array_prune_fn
3905+
----------------------------------------------
3906+
Seq Scan on array_prune_t0 array_prune
3907+
Filter: (id = ANY ('{1,NULL}'::integer[]))
3908+
(2 rows)
3909+
3910+
SELECT array_prune_fn('= ANY', 'ARRAY[3, NULL]'); -- prune
3911+
array_prune_fn
3912+
----------------------------------------------
3913+
Seq Scan on array_prune_t1 array_prune
3914+
Filter: (id = ANY ('{3,NULL}'::integer[]))
3915+
(2 rows)
3916+
3917+
SELECT array_prune_fn('= ANY', 'ARRAY[NULL, NULL]'); -- error
3918+
ERROR: operator does not exist: integer = text
3919+
LINE 1: ...IN (COSTS OFF) SELECT * FROM array_prune WHERE id = ANY (ARR...
3920+
^
3921+
HINT: No operator matches the given name and argument types. You might need to add explicit type casts.
3922+
QUERY: EXPLAIN (COSTS OFF) SELECT * FROM array_prune WHERE id = ANY (ARRAY[NULL, NULL])
3923+
CONTEXT: PL/pgSQL function array_prune_fn(text,text) line 7 at FOR over EXECUTE statement
3924+
-- Check case of explicit cast
3925+
SELECT array_prune_fn('= ANY', 'ARRAY[1,2]::numeric[]');
3926+
array_prune_fn
3927+
------------------------------------------------------------
3928+
Append
3929+
-> Seq Scan on array_prune_t0 array_prune_1
3930+
Filter: ((id)::numeric = ANY ('{1,2}'::numeric[]))
3931+
-> Seq Scan on array_prune_t1 array_prune_2
3932+
Filter: ((id)::numeric = ANY ('{1,2}'::numeric[]))
3933+
(5 rows)
3934+
3935+
SELECT array_prune_fn('= ANY', 'ARRAY[1::bigint,2::int]'); -- conversion to bigint
3936+
array_prune_fn
3937+
------------------------------------------
3938+
Seq Scan on array_prune_t0 array_prune
3939+
Filter: (id = ANY ('{1,2}'::bigint[]))
3940+
(2 rows)
3941+
3942+
SELECT array_prune_fn('= ANY', 'ARRAY[1::bigint,2::numeric]'); -- conversion to numeric
3943+
array_prune_fn
3944+
------------------------------------------------------------
3945+
Append
3946+
-> Seq Scan on array_prune_t0 array_prune_1
3947+
Filter: ((id)::numeric = ANY ('{1,2}'::numeric[]))
3948+
-> Seq Scan on array_prune_t1 array_prune_2
3949+
Filter: ((id)::numeric = ANY ('{1,2}'::numeric[]))
3950+
(5 rows)
3951+
3952+
SELECT array_prune_fn('= ANY', 'ARRAY[1::bigint,2::text]'); -- Error. XXX: slightly different error in comparison with the static case
3953+
ERROR: ARRAY types bigint and text cannot be matched
3954+
LINE 1: ...* FROM array_prune WHERE id = ANY (ARRAY[1::bigint,2::text])
3955+
^
3956+
QUERY: EXPLAIN (COSTS OFF) SELECT * FROM array_prune WHERE id = ANY (ARRAY[1::bigint,2::text])
3957+
CONTEXT: PL/pgSQL function array_prune_fn(text,text) line 7 at FOR over EXECUTE statement
3958+
SELECT array_prune_fn('<> ANY', 'ARRAY[1]'); -- no pruning
3959+
array_prune_fn
3960+
------------------------------------------------
3961+
Append
3962+
-> Seq Scan on array_prune_t0 array_prune_1
3963+
Filter: (id <> ANY ('{1}'::integer[]))
3964+
-> Seq Scan on array_prune_t1 array_prune_2
3965+
Filter: (id <> ANY ('{1}'::integer[]))
3966+
(5 rows)
3967+
3968+
DROP TABLE IF EXISTS array_prune CASCADE;
3969+
CREATE TABLE array_prune (id int)
3970+
PARTITION BY RANGE(id);
3971+
CREATE TABLE array_prune_t0
3972+
PARTITION OF array_prune FOR VALUES FROM (1) TO (10);
3973+
CREATE TABLE array_prune_t1
3974+
PARTITION OF array_prune FOR VALUES FROM (10) TO (20);
3975+
SELECT array_prune_fn('= ANY', 'ARRAY[10]'); -- prune
3976+
array_prune_fn
3977+
------------------------------------------
3978+
Seq Scan on array_prune_t1 array_prune
3979+
Filter: (id = ANY ('{10}'::integer[]))
3980+
(2 rows)
3981+
3982+
SELECT array_prune_fn('>= ANY', 'ARRAY[10]'); -- prune
3983+
array_prune_fn
3984+
-------------------------------------------
3985+
Seq Scan on array_prune_t1 array_prune
3986+
Filter: (id >= ANY ('{10}'::integer[]))
3987+
(2 rows)
3988+
3989+
SELECT array_prune_fn('>= ANY', 'ARRAY[9, 10]'); -- do not prune
3990+
array_prune_fn
3991+
---------------------------------------------------
3992+
Append
3993+
-> Seq Scan on array_prune_t0 array_prune_1
3994+
Filter: (id >= ANY ('{9,10}'::integer[]))
3995+
-> Seq Scan on array_prune_t1 array_prune_2
3996+
Filter: (id >= ANY ('{9,10}'::integer[]))
3997+
(5 rows)
3998+
3999+
DROP TABLE IF EXISTS array_prune CASCADE;
4000+
CREATE TABLE array_prune (id int)
4001+
PARTITION BY LIST(id);
4002+
CREATE TABLE array_prune_t0
4003+
PARTITION OF array_prune FOR VALUES IN ('1');
4004+
CREATE TABLE array_prune_t1
4005+
PARTITION OF array_prune FOR VALUES IN ('2');
4006+
SELECT array_prune_fn('= ANY', 'ARRAY[1,1]'); -- prune second
4007+
array_prune_fn
4008+
-------------------------------------------
4009+
Seq Scan on array_prune_t0 array_prune
4010+
Filter: (id = ANY ('{1,1}'::integer[]))
4011+
(2 rows)
4012+
4013+
SELECT array_prune_fn('>= ANY', 'ARRAY[1,2]'); -- do not prune
4014+
array_prune_fn
4015+
--------------------------------------------------
4016+
Append
4017+
-> Seq Scan on array_prune_t0 array_prune_1
4018+
Filter: (id >= ANY ('{1,2}'::integer[]))
4019+
-> Seq Scan on array_prune_t1 array_prune_2
4020+
Filter: (id >= ANY ('{1,2}'::integer[]))
4021+
(5 rows)
4022+
4023+
SELECT array_prune_fn('<> ANY', 'ARRAY[1]'); -- prune second
4024+
array_prune_fn
4025+
------------------------------------------
4026+
Seq Scan on array_prune_t1 array_prune
4027+
Filter: (id <> ANY ('{1}'::integer[]))
4028+
(2 rows)
4029+
4030+
SELECT array_prune_fn('<> ALL', 'ARRAY[1,2]'); -- prune both
4031+
array_prune_fn
4032+
--------------------------
4033+
Result
4034+
One-Time Filter: false
4035+
(2 rows)
4036+

src/test/regress/sql/inherit.sql

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1589,3 +1589,67 @@ select * from tuplesest_tab join
15891589

15901590
drop table tuplesest_parted;
15911591
drop table tuplesest_tab;
1592+
1593+
--
1594+
-- Test the cases for partition pruning by an expression like:
1595+
-- partkey = ANY($1)
1596+
--
1597+
1598+
CREATE TABLE array_prune (id int)
1599+
PARTITION BY HASH(id);
1600+
1601+
CREATE TABLE array_prune_t0
1602+
PARTITION OF array_prune FOR VALUES WITH (modulus 2, remainder 0);
1603+
CREATE TABLE array_prune_t1
1604+
PARTITION OF array_prune FOR VALUES WITH (modulus 2, remainder 1);
1605+
1606+
CREATE FUNCTION array_prune_fn(oper text, arr text) RETURNS setof text
1607+
LANGUAGE plpgsql AS $$
1608+
DECLARE
1609+
line text;
1610+
query text;
1611+
BEGIN
1612+
query := format('EXPLAIN (COSTS OFF) SELECT * FROM array_prune WHERE id %s (%s)', $1, $2);
1613+
FOR line IN EXECUTE query
1614+
LOOP
1615+
RETURN NEXT line;
1616+
END LOOP;
1617+
END; $$;
1618+
1619+
SELECT array_prune_fn('= ANY', 'ARRAY[1]'); -- prune one partition
1620+
SELECT array_prune_fn('= ANY', 'ARRAY[1,2]'); -- prune one partition
1621+
SELECT array_prune_fn('= ANY', 'ARRAY[1,2,3]'); -- no pruning
1622+
SELECT array_prune_fn('= ANY', 'ARRAY[1, NULL]'); -- prune
1623+
SELECT array_prune_fn('= ANY', 'ARRAY[3, NULL]'); -- prune
1624+
SELECT array_prune_fn('= ANY', 'ARRAY[NULL, NULL]'); -- error
1625+
-- Check case of explicit cast
1626+
SELECT array_prune_fn('= ANY', 'ARRAY[1,2]::numeric[]');
1627+
SELECT array_prune_fn('= ANY', 'ARRAY[1::bigint,2::int]'); -- conversion to bigint
1628+
SELECT array_prune_fn('= ANY', 'ARRAY[1::bigint,2::numeric]'); -- conversion to numeric
1629+
SELECT array_prune_fn('= ANY', 'ARRAY[1::bigint,2::text]'); -- Error. XXX: slightly different error in comparison with the static case
1630+
1631+
SELECT array_prune_fn('<> ANY', 'ARRAY[1]'); -- no pruning
1632+
1633+
DROP TABLE IF EXISTS array_prune CASCADE;
1634+
CREATE TABLE array_prune (id int)
1635+
PARTITION BY RANGE(id);
1636+
CREATE TABLE array_prune_t0
1637+
PARTITION OF array_prune FOR VALUES FROM (1) TO (10);
1638+
CREATE TABLE array_prune_t1
1639+
PARTITION OF array_prune FOR VALUES FROM (10) TO (20);
1640+
1641+
SELECT array_prune_fn('= ANY', 'ARRAY[10]'); -- prune
1642+
SELECT array_prune_fn('>= ANY', 'ARRAY[10]'); -- prune
1643+
SELECT array_prune_fn('>= ANY', 'ARRAY[9, 10]'); -- do not prune
1644+
1645+
DROP TABLE IF EXISTS array_prune CASCADE;
1646+
CREATE TABLE array_prune (id int)
1647+
PARTITION BY LIST(id);
1648+
CREATE TABLE array_prune_t0
1649+
PARTITION OF array_prune FOR VALUES IN ('1');
1650+
CREATE TABLE array_prune_t1
1651+
PARTITION OF array_prune FOR VALUES IN ('2');
1652+
SELECT array_prune_fn('= ANY', 'ARRAY[1,1]'); -- prune second
1653+
SELECT array_prune_fn('>= ANY', 'ARRAY[1,2]'); -- do not prune
1654+
SELECT array_prune_fn('<> ANY', 'ARRAY[1]'); -- prune second
1655+
SELECT array_prune_fn('<> ALL', 'ARRAY[1,2]'); -- prune both

0 commit comments

Comments
 (0)