Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitbead29d

Browse files
author
Etsuro Fujita
committed
Fix some issues with step generation in partition pruning.
In the case of range partitioning, get_steps_using_prefix() assumes thatthe passed-in prefix list contains at least one clause for each of thepartition keys earlier than one specified in the passed-instep_lastkeyno, but the caller (ie, gen_prune_steps_from_opexps())didn't take it into account, which led to a server crash or incorrectresults when the list contained no clauses for such partition keys, asreported in bug #16500 and #16501 from Kobayashi Hisanori. Update thecaller to call that function only when the list created there containsat least one clause for each of the earlier partition keys in the caseof range partitioning.While at it, fix some other issues:* The list to pass to get_steps_using_prefix() is allowed to contain multiple clauses for the same partition key, as described in the comment for that function, but that function actually assumed that the list contained just a single clause for each of middle partition keys, which led to an assertion failure when the list contained multiple clauses for such partition keys. Update that function to match the comment.* In the case of hash partitioning, partition keys are allowed to be NULL, in which case the list to pass to get_steps_using_prefix() contains no clauses for NULL partition keys, but that function treats that case as like the case of range partitioning, which led to the assertion failure. Update the assertion test to take into account NULL partition keys in the case of hash partitioning.* Fix a typo in a comment in get_steps_using_prefix_recurse().* gen_partprune_steps() failed to detect self-contradiction from strict-qual clauses and an IS NULL clause for the same partition key in some cases, producing incorrect partition-pruning steps, which led to incorrect results of partition pruning, but didn't cause any user-visible problems fortunately, as the self-contradiction is detected later in the query planning. Update that function to detect the self-contradiction.Per bug #16500 and #16501 from Kobayashi Hisanori. Patch by me, initialdiagnosis for the reported issue and review by Dmitry Dolgov.Back-patch to v11, where partition pruning was introduced.Discussion:https://postgr.es/m/16500-d1613f2a78e1e090%40postgresql.orgDiscussion:https://postgr.es/m/16501-5234a9a0394f6754%40postgresql.org
1 parent202fc4c commitbead29d

File tree

3 files changed

+298
-56
lines changed

3 files changed

+298
-56
lines changed

‎src/backend/partitioning/partprune.c

Lines changed: 131 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1051,8 +1051,12 @@ gen_partprune_steps_internal(GeneratePruningStepsContext *context,
10511051
casePARTCLAUSE_MATCH_NULLNESS:
10521052
if (!clause_is_not_null)
10531053
{
1054-
/* check for conflicting IS NOT NULL */
1055-
if (bms_is_member(i,notnullkeys))
1054+
/*
1055+
* check for conflicting IS NOT NULL as well as
1056+
* contradicting strict clauses
1057+
*/
1058+
if (bms_is_member(i,notnullkeys)||
1059+
keyclauses[i]!=NIL)
10561060
{
10571061
context->contradictory= true;
10581062
returnNIL;
@@ -1301,34 +1305,18 @@ gen_prune_steps_from_opexps(GeneratePruningStepsContext *context,
13011305
{
13021306
casePARTITION_STRATEGY_LIST:
13031307
casePARTITION_STRATEGY_RANGE:
1304-
{
1305-
PartClauseInfo*last=NULL;
1306-
1307-
/*
1308-
* Add this clause to the list of clauses to be used
1309-
* for pruning if this is the first such key for this
1310-
* operator strategy or if it is consecutively next to
1311-
* the last column for which a clause with this
1312-
* operator strategy was matched.
1313-
*/
1314-
if (btree_clauses[pc->op_strategy]!=NIL)
1315-
last=llast(btree_clauses[pc->op_strategy]);
1316-
1317-
if (last==NULL||
1318-
i==last->keyno||i==last->keyno+1)
1319-
btree_clauses[pc->op_strategy]=
1320-
lappend(btree_clauses[pc->op_strategy],pc);
1308+
btree_clauses[pc->op_strategy]=
1309+
lappend(btree_clauses[pc->op_strategy],pc);
13211310

1322-
/*
1323-
* We can't consider subsequent partition keys if the
1324-
* clause for the current key contains a non-inclusive
1325-
* operator.
1326-
*/
1327-
if (pc->op_strategy==BTLessStrategyNumber||
1328-
pc->op_strategy==BTGreaterStrategyNumber)
1329-
consider_next_key= false;
1330-
break;
1331-
}
1311+
/*
1312+
* We can't consider subsequent partition keys if the
1313+
* clause for the current key contains a non-inclusive
1314+
* operator.
1315+
*/
1316+
if (pc->op_strategy==BTLessStrategyNumber||
1317+
pc->op_strategy==BTGreaterStrategyNumber)
1318+
consider_next_key= false;
1319+
break;
13321320

13331321
casePARTITION_STRATEGY_HASH:
13341322
if (pc->op_strategy!=HTEqualStrategyNumber)
@@ -1367,6 +1355,7 @@ gen_prune_steps_from_opexps(GeneratePruningStepsContext *context,
13671355
List*eq_clauses=btree_clauses[BTEqualStrategyNumber];
13681356
List*le_clauses=btree_clauses[BTLessEqualStrategyNumber];
13691357
List*ge_clauses=btree_clauses[BTGreaterEqualStrategyNumber];
1358+
boolpk_has_clauses[PARTITION_MAX_KEYS];
13701359
intstrat;
13711360

13721361
/*
@@ -1389,6 +1378,35 @@ gen_prune_steps_from_opexps(GeneratePruningStepsContext *context,
13891378
ListCell*lc1;
13901379
List*prefix=NIL;
13911380
List*pc_steps;
1381+
boolprefix_valid= true;
1382+
1383+
/*
1384+
* If this is a clause for the first partition key,
1385+
* there are no preceding expressions; generate a
1386+
* pruning step without a prefix.
1387+
*
1388+
* Note that we pass NULL for step_nullkeys, because
1389+
* we don't search list/range partition bounds where
1390+
* some keys are NULL.
1391+
*/
1392+
if (pc->keyno==0)
1393+
{
1394+
Assert(pc->op_strategy==strat);
1395+
pc_steps=get_steps_using_prefix(context,strat,
1396+
pc->op_is_ne,
1397+
pc->expr,
1398+
pc->cmpfn,
1399+
0,
1400+
NULL,
1401+
NIL);
1402+
opsteps=list_concat(opsteps,pc_steps);
1403+
continue;
1404+
}
1405+
1406+
/* (Re-)initialize the pk_has_clauses array */
1407+
Assert(pc->keyno>0);
1408+
for (i=0;i<pc->keyno;i++)
1409+
pk_has_clauses[i]= false;
13921410

13931411
/*
13941412
* Expressions from = clauses can always be in the
@@ -1401,7 +1419,10 @@ gen_prune_steps_from_opexps(GeneratePruningStepsContext *context,
14011419
if (eqpc->keyno==pc->keyno)
14021420
break;
14031421
if (eqpc->keyno<pc->keyno)
1422+
{
14041423
prefix=lappend(prefix,eqpc);
1424+
pk_has_clauses[eqpc->keyno]= true;
1425+
}
14051426
}
14061427

14071428
/*
@@ -1419,7 +1440,10 @@ gen_prune_steps_from_opexps(GeneratePruningStepsContext *context,
14191440
if (lepc->keyno==pc->keyno)
14201441
break;
14211442
if (lepc->keyno<pc->keyno)
1443+
{
14221444
prefix=lappend(prefix,lepc);
1445+
pk_has_clauses[lepc->keyno]= true;
1446+
}
14231447
}
14241448
}
14251449

@@ -1438,11 +1462,33 @@ gen_prune_steps_from_opexps(GeneratePruningStepsContext *context,
14381462
if (gepc->keyno==pc->keyno)
14391463
break;
14401464
if (gepc->keyno<pc->keyno)
1465+
{
14411466
prefix=lappend(prefix,gepc);
1467+
pk_has_clauses[gepc->keyno]= true;
1468+
}
14421469
}
14431470
}
14441471

14451472
/*
1473+
* Check whether every earlier partition key has at
1474+
* least one clause.
1475+
*/
1476+
for (i=0;i<pc->keyno;i++)
1477+
{
1478+
if (!pk_has_clauses[i])
1479+
{
1480+
prefix_valid= false;
1481+
break;
1482+
}
1483+
}
1484+
1485+
/*
1486+
* If prefix_valid, generate PartitionPruneStepOps.
1487+
* Otherwise, we would not find clauses for a valid
1488+
* subset of the partition keys anymore for the
1489+
* strategy; give up on generating partition pruning
1490+
* steps further for the strategy.
1491+
*
14461492
* As mentioned above, if 'prefix' contains multiple
14471493
* expressions for the same key, the following will
14481494
* generate multiple steps, one for each combination
@@ -1452,15 +1498,20 @@ gen_prune_steps_from_opexps(GeneratePruningStepsContext *context,
14521498
* we don't search list/range partition bounds where
14531499
* some keys are NULL.
14541500
*/
1455-
Assert(pc->op_strategy==strat);
1456-
pc_steps=get_steps_using_prefix(context,strat,
1457-
pc->op_is_ne,
1458-
pc->expr,
1459-
pc->cmpfn,
1460-
pc->keyno,
1461-
NULL,
1462-
prefix);
1463-
opsteps=list_concat(opsteps,list_copy(pc_steps));
1501+
if (prefix_valid)
1502+
{
1503+
Assert(pc->op_strategy==strat);
1504+
pc_steps=get_steps_using_prefix(context,strat,
1505+
pc->op_is_ne,
1506+
pc->expr,
1507+
pc->cmpfn,
1508+
pc->keyno,
1509+
NULL,
1510+
prefix);
1511+
opsteps=list_concat(opsteps,pc_steps);
1512+
}
1513+
else
1514+
break;
14641515
}
14651516
}
14661517
break;
@@ -2175,6 +2226,14 @@ match_clause_to_partition_key(GeneratePruningStepsContext *context,
21752226
* 'prefix'. Actually, since 'prefix' may contain multiple clauses for the
21762227
* same partition key column, we must generate steps for various combinations
21772228
* of the clauses of different keys.
2229+
*
2230+
* For list/range partitioning, callers must ensure that step_nullkeys is
2231+
* NULL, and that prefix contains at least one clause for each of the
2232+
* partition keys earlier than one specified in step_lastkeyno if it's
2233+
* greater than zero. For hash partitioning, step_nullkeys is allowed to be
2234+
* non-NULL, but they must ensure that prefix contains at least one clause
2235+
* for each of the partition keys other than those specified in step_nullkeys
2236+
* and step_lastkeyno.
21782237
*/
21792238
staticList*
21802239
get_steps_using_prefix(GeneratePruningStepsContext*context,
@@ -2186,6 +2245,9 @@ get_steps_using_prefix(GeneratePruningStepsContext *context,
21862245
Bitmapset*step_nullkeys,
21872246
List*prefix)
21882247
{
2248+
Assert(step_nullkeys==NULL||
2249+
context->rel->part_scheme->strategy==PARTITION_STRATEGY_HASH);
2250+
21892251
/* Quick exit if there are no values to prefix with. */
21902252
if (list_length(prefix)==0)
21912253
{
@@ -2251,7 +2313,7 @@ get_steps_using_prefix_recurse(GeneratePruningStepsContext *context,
22512313
ListCell*next_start;
22522314

22532315
/*
2254-
* For each clause with cur_keyno,adds its expr and cmpfn to
2316+
* For each clause with cur_keyno,add its expr and cmpfn to
22552317
* step_exprs and step_cmpfns, respectively, and recurse after setting
22562318
* next_start to the ListCell of the first clause for the next
22572319
* partition key.
@@ -2268,23 +2330,19 @@ get_steps_using_prefix_recurse(GeneratePruningStepsContext *context,
22682330
for_each_cell(lc,start)
22692331
{
22702332
List*moresteps;
2333+
List*step_exprs1,
2334+
*step_cmpfns1;
22712335

22722336
pc=lfirst(lc);
22732337
if (pc->keyno==cur_keyno)
22742338
{
2275-
/* clean up before starting a new recursion cycle. */
2276-
if (cur_keyno==0)
2277-
{
2278-
list_free(step_exprs);
2279-
list_free(step_cmpfns);
2280-
step_exprs=list_make1(pc->expr);
2281-
step_cmpfns=list_make1_oid(pc->cmpfn);
2282-
}
2283-
else
2284-
{
2285-
step_exprs=lappend(step_exprs,pc->expr);
2286-
step_cmpfns=lappend_oid(step_cmpfns,pc->cmpfn);
2287-
}
2339+
/* Leave the original step_exprs unmodified. */
2340+
step_exprs1=list_copy(step_exprs);
2341+
step_exprs1=lappend(step_exprs1,pc->expr);
2342+
2343+
/* Leave the original step_cmpfns unmodified. */
2344+
step_cmpfns1=list_copy(step_cmpfns);
2345+
step_cmpfns1=lappend_oid(step_cmpfns1,pc->cmpfn);
22882346
}
22892347
else
22902348
{
@@ -2300,19 +2358,36 @@ get_steps_using_prefix_recurse(GeneratePruningStepsContext *context,
23002358
step_lastkeyno,
23012359
step_nullkeys,
23022360
next_start,
2303-
step_exprs,
2304-
step_cmpfns);
2361+
step_exprs1,
2362+
step_cmpfns1);
23052363
result=list_concat(result,moresteps);
2364+
2365+
list_free(step_exprs1);
2366+
list_free(step_cmpfns1);
23062367
}
23072368
}
23082369
else
23092370
{
23102371
/*
23112372
* End the current recursion cycle and start generating steps, one for
23122373
* each clause with cur_keyno, which is all clauses from here onward
2313-
* till the end of the list.
2374+
* till the end of the list. Note that for hash partitioning,
2375+
* step_nullkeys is allowed to be non-empty, in which case step_exprs
2376+
* would only contain expressions for the earlier partition keys that
2377+
* are not specified in step_nullkeys.
23142378
*/
2315-
Assert(list_length(step_exprs)==cur_keyno);
2379+
Assert(list_length(step_exprs)==cur_keyno||
2380+
!bms_is_empty(step_nullkeys));
2381+
/*
2382+
* Note also that for hash partitioning, each partition key should
2383+
* have either equality clauses or an IS NULL clause, so if a
2384+
* partition key doesn't have an expression, it would be specified
2385+
* in step_nullkeys.
2386+
*/
2387+
Assert(context->rel->part_scheme->strategy
2388+
!=PARTITION_STRATEGY_HASH||
2389+
list_length(step_exprs)+2+bms_num_members(step_nullkeys)==
2390+
context->rel->part_scheme->partnatts);
23162391
for_each_cell(lc,start)
23172392
{
23182393
PartClauseInfo*pc=lfirst(lc);

‎src/test/regress/expected/partition_prune.out

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3704,3 +3704,99 @@ explain (costs off) update listp1 set a = 1 where a = 2;
37043704
reset constraint_exclusion;
37053705
reset enable_partition_pruning;
37063706
drop table listp;
3707+
--
3708+
-- Check that gen_prune_steps_from_opexps() works well for various cases of
3709+
-- clauses for different partition keys
3710+
--
3711+
create table rp_prefix_test1 (a int, b varchar) partition by range(a, b);
3712+
create table rp_prefix_test1_p1 partition of rp_prefix_test1 for values from (1, 'a') to (1, 'b');
3713+
create table rp_prefix_test1_p2 partition of rp_prefix_test1 for values from (2, 'a') to (2, 'b');
3714+
-- Don't call get_steps_using_prefix() with the last partition key b plus
3715+
-- an empty prefix
3716+
explain (costs off) select * from rp_prefix_test1 where a <= 1 and b = 'a';
3717+
QUERY PLAN
3718+
--------------------------------------------------------
3719+
Append
3720+
-> Seq Scan on rp_prefix_test1_p1
3721+
Filter: ((a <= 1) AND ((b)::text = 'a'::text))
3722+
(3 rows)
3723+
3724+
create table rp_prefix_test2 (a int, b int, c int) partition by range(a, b, c);
3725+
create table rp_prefix_test2_p1 partition of rp_prefix_test2 for values from (1, 1, 0) to (1, 1, 10);
3726+
create table rp_prefix_test2_p2 partition of rp_prefix_test2 for values from (2, 2, 0) to (2, 2, 10);
3727+
-- Don't call get_steps_using_prefix() with the last partition key c plus
3728+
-- an invalid prefix (ie, b = 1)
3729+
explain (costs off) select * from rp_prefix_test2 where a <= 1 and b = 1 and c >= 0;
3730+
QUERY PLAN
3731+
-----------------------------------------------------
3732+
Append
3733+
-> Seq Scan on rp_prefix_test2_p1
3734+
Filter: ((a <= 1) AND (c >= 0) AND (b = 1))
3735+
(3 rows)
3736+
3737+
create table rp_prefix_test3 (a int, b int, c int, d int) partition by range(a, b, c, d);
3738+
create table rp_prefix_test3_p1 partition of rp_prefix_test3 for values from (1, 1, 1, 0) to (1, 1, 1, 10);
3739+
create table rp_prefix_test3_p2 partition of rp_prefix_test3 for values from (2, 2, 2, 0) to (2, 2, 2, 10);
3740+
-- Test that get_steps_using_prefix() handles a prefix that contains multiple
3741+
-- clauses for the partition key b (ie, b >= 1 and b >= 2)
3742+
explain (costs off) select * from rp_prefix_test3 where a >= 1 and b >= 1 and b >= 2 and c >= 2 and d >= 0;
3743+
QUERY PLAN
3744+
--------------------------------------------------------------------------------
3745+
Append
3746+
-> Seq Scan on rp_prefix_test3_p2
3747+
Filter: ((a >= 1) AND (b >= 1) AND (b >= 2) AND (c >= 2) AND (d >= 0))
3748+
(3 rows)
3749+
3750+
create table hp_prefix_test (a int, b int, c int, d int) partition by hash (a part_test_int4_ops, b part_test_int4_ops, c part_test_int4_ops, d part_test_int4_ops);
3751+
create table hp_prefix_test_p1 partition of hp_prefix_test for values with (modulus 2, remainder 0);
3752+
create table hp_prefix_test_p2 partition of hp_prefix_test for values with (modulus 2, remainder 1);
3753+
-- Test that get_steps_using_prefix() handles non-NULL step_nullkeys
3754+
explain (costs off) select * from hp_prefix_test where a = 1 and b is null and c = 1 and d = 1;
3755+
QUERY PLAN
3756+
-------------------------------------------------------------------
3757+
Append
3758+
-> Seq Scan on hp_prefix_test_p1
3759+
Filter: ((b IS NULL) AND (a = 1) AND (c = 1) AND (d = 1))
3760+
(3 rows)
3761+
3762+
drop table rp_prefix_test1;
3763+
drop table rp_prefix_test2;
3764+
drop table rp_prefix_test3;
3765+
drop table hp_prefix_test;
3766+
--
3767+
-- Check that gen_partprune_steps() detects self-contradiction from clauses
3768+
-- regardless of the order of the clauses (Here we use a custom operator to
3769+
-- prevent the equivclass.c machinery from reordering the clauses)
3770+
--
3771+
create operator === (
3772+
leftarg = int4,
3773+
rightarg = int4,
3774+
procedure = int4eq,
3775+
commutator = ===,
3776+
hashes
3777+
);
3778+
create operator class part_test_int4_ops2
3779+
for type int4
3780+
using hash as
3781+
operator 1 ===,
3782+
function 2 part_hashint4_noop(int4, int8);
3783+
create table hp_contradict_test (a int, b int) partition by hash (a part_test_int4_ops2, b part_test_int4_ops2);
3784+
create table hp_contradict_test_p1 partition of hp_contradict_test for values with (modulus 2, remainder 0);
3785+
create table hp_contradict_test_p2 partition of hp_contradict_test for values with (modulus 2, remainder 1);
3786+
explain (costs off) select * from hp_contradict_test where a is null and a === 1 and b === 1;
3787+
QUERY PLAN
3788+
--------------------------
3789+
Result
3790+
One-Time Filter: false
3791+
(2 rows)
3792+
3793+
explain (costs off) select * from hp_contradict_test where a === 1 and b === 1 and a is null;
3794+
QUERY PLAN
3795+
--------------------------
3796+
Result
3797+
One-Time Filter: false
3798+
(2 rows)
3799+
3800+
drop table hp_contradict_test;
3801+
drop operator class part_test_int4_ops2 using hash;
3802+
drop operator ===(int4, int4);

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp