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/[email protected]
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

+26-32
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

+66
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

+14
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)