Skip to content

Commit d758d97

Browse files
committed
Fix assorted partition pruning bugs
match_clause_to_partition_key failed to consider COERCION_PATH_ARRAYCOERCE cases in scalar-op-array expressions, so it was possible to crash the server easily. To handle this case properly (ie. prune partitions) we would need to run a bit of executor code during planning. Maybe it can be improved, but for now let's just not crash. Add a test case that used to trigger the crash. Author: Michaël Paquier match_clause_to_partition_key failed to indicate that operators that don't have a commutator in a btree opclass are unsupported. It is possible for this to cause a crash later if such an operator is used in a scalar-op-array expression. Add a test case that used to the crash. Author: Amit Langote One caller of gen_partprune_steps_internal in match_clause_to_partition_key was too optimistic about the former never returning an empty step list. Rid it of its innocence. (Having fixed the bug above, I no longer know how to exploit this, so no test case for it, but it remained a bug.) Revise code flow a little bit, for succintness. Author: Álvaro Herrera Reported-by: Marina Polyakova Reviewed-by: Michaël Paquier Reviewed-by: Amit Langote Reviewed-by: Álvaro Herrera Discussion: https://postgr.es/m/ff8f9bfa485ff961d6bb43e54120485b@postgrespro.ru
1 parent 35361ee commit d758d97

File tree

3 files changed

+106
-32
lines changed

3 files changed

+106
-32
lines changed

src/backend/partitioning/partprune.c

Lines changed: 26 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -573,8 +573,9 @@ get_matching_partitions(PartitionPruneContext *context, List *pruning_steps)
573573
* For BoolExpr clauses, we recursively generate steps for each argument, and
574574
* return a PartitionPruneStepCombine of their results.
575575
*
576-
* The generated steps are added to the context's steps list. Each step is
577-
* assigned a step identifier, unique even across recursive calls.
576+
* The return value is a list of the steps generated, which are also added to
577+
* the context's steps list. Each step is assigned a step identifier, unique
578+
* even across recursive calls.
578579
*
579580
* If we find clauses that are mutually contradictory, or a pseudoconstant
580581
* clause that contains false, we set *contradictory to true and return NIL
@@ -1601,6 +1602,7 @@ match_clause_to_partition_key(RelOptInfo *rel,
16011602
List *elem_exprs,
16021603
*elem_clauses;
16031604
ListCell *lc1;
1605+
bool contradictory;
16041606

16051607
if (IsA(leftop, RelabelType))
16061608
leftop = ((RelabelType *) leftop)->arg;
@@ -1619,7 +1621,7 @@ match_clause_to_partition_key(RelOptInfo *rel,
16191621
* Only allow strict operators. This will guarantee nulls are
16201622
* filtered.
16211623
*/
1622-
if (!op_strict(saop->opno))
1624+
if (!op_strict(saop_op))
16231625
return PARTCLAUSE_UNSUPPORTED;
16241626

16251627
/* Useless if the array has any volatile functions. */
@@ -1652,6 +1654,8 @@ match_clause_to_partition_key(RelOptInfo *rel,
16521654
if (strategy != BTEqualStrategyNumber)
16531655
return PARTCLAUSE_UNSUPPORTED;
16541656
}
1657+
else
1658+
return PARTCLAUSE_UNSUPPORTED; /* no useful negator */
16551659
}
16561660

16571661
/*
@@ -1692,7 +1696,7 @@ match_clause_to_partition_key(RelOptInfo *rel,
16921696
elem_exprs = lappend(elem_exprs, elem_expr);
16931697
}
16941698
}
1695-
else
1699+
else if (IsA(rightop, ArrayExpr))
16961700
{
16971701
ArrayExpr *arrexpr = castNode(ArrayExpr, rightop);
16981702

@@ -1706,6 +1710,11 @@ match_clause_to_partition_key(RelOptInfo *rel,
17061710

17071711
elem_exprs = arrexpr->elements;
17081712
}
1713+
else
1714+
{
1715+
/* Give up on any other clause types. */
1716+
return PARTCLAUSE_UNSUPPORTED;
1717+
}
17091718

17101719
/*
17111720
* Now generate a list of clauses, one for each array element, of the
@@ -1724,36 +1733,21 @@ match_clause_to_partition_key(RelOptInfo *rel,
17241733
}
17251734

17261735
/*
1727-
* Build a combine step as if for an OR clause or add the clauses to
1728-
* the end of the list that's being processed currently.
1736+
* If we have an ANY clause and multiple elements, first turn the list
1737+
* of clauses into an OR expression.
17291738
*/
17301739
if (saop->useOr && list_length(elem_clauses) > 1)
1731-
{
1732-
Expr *orexpr;
1733-
bool contradictory;
1734-
1735-
orexpr = makeBoolExpr(OR_EXPR, elem_clauses, -1);
1736-
*clause_steps =
1737-
gen_partprune_steps_internal(context, rel, list_make1(orexpr),
1738-
&contradictory);
1739-
if (contradictory)
1740-
return PARTCLAUSE_MATCH_CONTRADICT;
1741-
1742-
Assert(list_length(*clause_steps) == 1);
1743-
return PARTCLAUSE_MATCH_STEPS;
1744-
}
1745-
else
1746-
{
1747-
bool contradictory;
1748-
1749-
*clause_steps =
1750-
gen_partprune_steps_internal(context, rel, elem_clauses,
1751-
&contradictory);
1752-
if (contradictory)
1753-
return PARTCLAUSE_MATCH_CONTRADICT;
1754-
Assert(list_length(*clause_steps) >= 1);
1755-
return PARTCLAUSE_MATCH_STEPS;
1756-
}
1740+
elem_clauses = list_make1(makeBoolExpr(OR_EXPR, elem_clauses, -1));
1741+
1742+
/* Finally, generate steps */
1743+
*clause_steps =
1744+
gen_partprune_steps_internal(context, rel, elem_clauses,
1745+
&contradictory);
1746+
if (contradictory)
1747+
return PARTCLAUSE_MATCH_CONTRADICT;
1748+
else if (*clause_steps == NIL)
1749+
return PARTCLAUSE_UNSUPPORTED; /* step generation failed */
1750+
return PARTCLAUSE_MATCH_STEPS;
17571751
}
17581752
else if (IsA(clause, NullTest))
17591753
{

src/test/regress/expected/partition_prune.out

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1073,6 +1073,72 @@ explain (costs off) select * from boolpart where a is not unknown;
10731073
Filter: (a IS NOT UNKNOWN)
10741074
(7 rows)
10751075

1076+
-- test scalar-to-array operators
1077+
create table coercepart (a varchar) partition by list (a);
1078+
create table coercepart_ab partition of coercepart for values in ('ab');
1079+
create table coercepart_bc partition of coercepart for values in ('bc');
1080+
create table coercepart_cd partition of coercepart for values in ('cd');
1081+
explain (costs off) select * from coercepart where a in ('ab', to_char(125, '999'));
1082+
QUERY PLAN
1083+
------------------------------------------------------------------------------------------------------------------------------
1084+
Append
1085+
-> Seq Scan on coercepart_ab
1086+
Filter: ((a)::text = ANY ((ARRAY['ab'::character varying, (to_char(125, '999'::text))::character varying])::text[]))
1087+
-> Seq Scan on coercepart_bc
1088+
Filter: ((a)::text = ANY ((ARRAY['ab'::character varying, (to_char(125, '999'::text))::character varying])::text[]))
1089+
-> Seq Scan on coercepart_cd
1090+
Filter: ((a)::text = ANY ((ARRAY['ab'::character varying, (to_char(125, '999'::text))::character varying])::text[]))
1091+
(7 rows)
1092+
1093+
explain (costs off) select * from coercepart where a ~ any ('{ab}');
1094+
QUERY PLAN
1095+
----------------------------------------------------
1096+
Append
1097+
-> Seq Scan on coercepart_ab
1098+
Filter: ((a)::text ~ ANY ('{ab}'::text[]))
1099+
-> Seq Scan on coercepart_bc
1100+
Filter: ((a)::text ~ ANY ('{ab}'::text[]))
1101+
-> Seq Scan on coercepart_cd
1102+
Filter: ((a)::text ~ ANY ('{ab}'::text[]))
1103+
(7 rows)
1104+
1105+
explain (costs off) select * from coercepart where a !~ all ('{ab}');
1106+
QUERY PLAN
1107+
-----------------------------------------------------
1108+
Append
1109+
-> Seq Scan on coercepart_ab
1110+
Filter: ((a)::text !~ ALL ('{ab}'::text[]))
1111+
-> Seq Scan on coercepart_bc
1112+
Filter: ((a)::text !~ ALL ('{ab}'::text[]))
1113+
-> Seq Scan on coercepart_cd
1114+
Filter: ((a)::text !~ ALL ('{ab}'::text[]))
1115+
(7 rows)
1116+
1117+
explain (costs off) select * from coercepart where a ~ any ('{ab,bc}');
1118+
QUERY PLAN
1119+
-------------------------------------------------------
1120+
Append
1121+
-> Seq Scan on coercepart_ab
1122+
Filter: ((a)::text ~ ANY ('{ab,bc}'::text[]))
1123+
-> Seq Scan on coercepart_bc
1124+
Filter: ((a)::text ~ ANY ('{ab,bc}'::text[]))
1125+
-> Seq Scan on coercepart_cd
1126+
Filter: ((a)::text ~ ANY ('{ab,bc}'::text[]))
1127+
(7 rows)
1128+
1129+
explain (costs off) select * from coercepart where a !~ all ('{ab,bc}');
1130+
QUERY PLAN
1131+
--------------------------------------------------------
1132+
Append
1133+
-> Seq Scan on coercepart_ab
1134+
Filter: ((a)::text !~ ALL ('{ab,bc}'::text[]))
1135+
-> Seq Scan on coercepart_bc
1136+
Filter: ((a)::text !~ ALL ('{ab,bc}'::text[]))
1137+
-> Seq Scan on coercepart_cd
1138+
Filter: ((a)::text !~ ALL ('{ab,bc}'::text[]))
1139+
(7 rows)
1140+
1141+
drop table coercepart;
10761142
--
10771143
-- some more cases
10781144
--

src/test/regress/sql/partition_prune.sql

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,20 @@ explain (costs off) select * from boolpart where a is not true and a is not fals
152152
explain (costs off) select * from boolpart where a is unknown;
153153
explain (costs off) select * from boolpart where a is not unknown;
154154

155+
-- test scalar-to-array operators
156+
create table coercepart (a varchar) partition by list (a);
157+
create table coercepart_ab partition of coercepart for values in ('ab');
158+
create table coercepart_bc partition of coercepart for values in ('bc');
159+
create table coercepart_cd partition of coercepart for values in ('cd');
160+
161+
explain (costs off) select * from coercepart where a in ('ab', to_char(125, '999'));
162+
explain (costs off) select * from coercepart where a ~ any ('{ab}');
163+
explain (costs off) select * from coercepart where a !~ all ('{ab}');
164+
explain (costs off) select * from coercepart where a ~ any ('{ab,bc}');
165+
explain (costs off) select * from coercepart where a !~ all ('{ab,bc}');
166+
167+
drop table coercepart;
168+
155169
--
156170
-- some more cases
157171
--

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy