Commit 518442c7 authored by Tomas Vondra's avatar Tomas Vondra

Fix handling of clauses incompatible with extended statistics

Handling of incompatible clauses while applying extended statistics was
a bit confused - while handling a mix of compatible and incompatible
clauses it sometimes incorrectly treated the incompatible clauses as
compatible, resulting in a crash.

Fixed by reworking the code applying the selected statistics object to
make it easier to understand, and adding a proper compatibility check.

Reported-by: David Rowley
Discussion: https://postgr.es/m/CAApHDvpYT10-nkSp8xXe-nbO3jmoaRyRFHbzh-RWMfAJynqgpQ%40mail.gmail.com
parent 7ab96cf6
......@@ -1255,6 +1255,10 @@ choose_best_statistics(List *stats, char requiredkind,
/*
* Collect attributes and expressions in remaining (unestimated)
* clauses fully covered by this statistic object.
*
* We know already estimated clauses have both clause_attnums and
* clause_exprs set to NULL. We leave the pointers NULL if already
* estimated, or we reset them to NULL after estimating the clause.
*/
for (i = 0; i < nclauses; i++)
{
......@@ -1758,19 +1762,44 @@ statext_mcv_clauselist_selectivity(PlannerInfo *root, List *clauses, int varReli
/* record which clauses are simple (single column or expression) */
simple_clauses = NULL;
listidx = 0;
listidx = -1;
foreach(l, clauses)
{
/* Increment the index before we decide if to skip the clause. */
listidx++;
/*
* If the clause is not already estimated and is compatible with
* the selected statistics object (all attributes and expressions
* covered), mark it as estimated and add it to the list to
* estimate.
* Ignore clauses from which we did not extract any attnums or
* expressions (this needs to be consistent with what we do in
* choose_best_statistics).
*
* This also eliminates already estimated clauses - both those
* estimated before and during applying extended statistics.
*
* XXX This check is needed because both bms_is_subset and
* stat_covers_expressions return true for empty attnums and
* expressions.
*/
if (!bms_is_member(listidx, *estimatedclauses) &&
bms_is_subset(list_attnums[listidx], stat->keys) &&
stat_covers_expressions(stat, list_exprs[listidx], NULL))
{
if (!list_attnums[listidx] && !list_exprs[listidx])
continue;
/*
* The clause was not estimated yet, and we've extracted either
* attnums of expressions from it. Ignore it if it's not fully
* covered by the chosen statistics.
*
* We need to check both attributes and expressions, and reject
* if either is not covered.
*/
if (!bms_is_subset(list_attnums[listidx], stat->keys) ||
!stat_covers_expressions(stat, list_exprs[listidx], NULL))
continue;
/*
* Now we know the clause is compatible (we have either atttnums
* or expressions extracted from it), and was not estimated yet.
*/
/* record simple clauses (single column or expression) */
if ((list_attnums[listidx] == NULL &&
list_length(list_exprs[listidx]) == 1) ||
......@@ -1779,10 +1808,14 @@ statext_mcv_clauselist_selectivity(PlannerInfo *root, List *clauses, int varReli
simple_clauses = bms_add_member(simple_clauses,
list_length(stat_clauses));
/* add clause to list and mark as estimated */
/* add clause to list and mark it as estimated */
stat_clauses = lappend(stat_clauses, (Node *) lfirst(l));
*estimatedclauses = bms_add_member(*estimatedclauses, listidx);
/*
* Reset the pointers, so that choose_best_statistics knows this
* clause was estimated and does not consider it again.
*/
bms_free(list_attnums[listidx]);
list_attnums[listidx] = NULL;
......@@ -1790,9 +1823,6 @@ statext_mcv_clauselist_selectivity(PlannerInfo *root, List *clauses, int varReli
list_exprs[listidx] = NULL;
}
listidx++;
}
if (is_or)
{
bool *or_matches = NULL;
......
......@@ -1575,6 +1575,8 @@ mcv_match_expression(Node *expr, Bitmapset *keys, List *exprs, Oid *collid)
(idx <= bms_num_members(keys) + list_length(exprs)));
}
Assert((idx >= 0) && (idx < bms_num_members(keys) + list_length(exprs)));
return idx;
}
......@@ -1654,6 +1656,8 @@ mcv_get_match_bitmap(PlannerInfo *root, List *clauses,
/* match the attribute/expression to a dimension of the statistic */
idx = mcv_match_expression(clause_expr, keys, exprs, &collid);
Assert((idx >= 0) && (idx < bms_num_members(keys) + list_length(exprs)));
/*
* Walk through the MCV items and evaluate the current clause. We
* can skip items that were already ruled out, and terminate if
......
......@@ -2938,6 +2938,25 @@ SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE a = 0 AND (b
(1 row)
DROP TABLE expr_stats;
-- test handling of a mix of compatible and incompatible expressions
CREATE TABLE expr_stats_incompatible_test (
c0 double precision,
c1 boolean NOT NULL
);
CREATE STATISTICS expr_stat_comp_1 ON c0, c1 FROM expr_stats_incompatible_test;
INSERT INTO expr_stats_incompatible_test VALUES (1234,false), (5678,true);
ANALYZE expr_stats_incompatible_test;
SELECT c0 FROM ONLY expr_stats_incompatible_test WHERE
(
upper('x') LIKE ('x'||('[0,1]'::int4range))
AND
(c0 IN (0, 1) OR c1)
);
c0
----
(0 rows)
DROP TABLE expr_stats_incompatible_test;
-- Permission tests. Users should not be able to see specific data values in
-- the extended statistics, if they lack permission to see those values in
-- the underlying table.
......
......@@ -1470,6 +1470,25 @@ SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE a = 0 AND (b
DROP TABLE expr_stats;
-- test handling of a mix of compatible and incompatible expressions
CREATE TABLE expr_stats_incompatible_test (
c0 double precision,
c1 boolean NOT NULL
);
CREATE STATISTICS expr_stat_comp_1 ON c0, c1 FROM expr_stats_incompatible_test;
INSERT INTO expr_stats_incompatible_test VALUES (1234,false), (5678,true);
ANALYZE expr_stats_incompatible_test;
SELECT c0 FROM ONLY expr_stats_incompatible_test WHERE
(
upper('x') LIKE ('x'||('[0,1]'::int4range))
AND
(c0 IN (0, 1) OR c1)
);
DROP TABLE expr_stats_incompatible_test;
-- Permission tests. Users should not be able to see specific data values in
-- the extended statistics, if they lack permission to see those values in
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment