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

Commit f6daa91

Browse files
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 a359d37 commit f6daa91

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
@@ -3823,3 +3823,183 @@ select * from tuplesest_tab join
38233823

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

src/test/regress/sql/inherit.sql

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

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

0 commit comments

Comments
 (0)