From 7ef184ea40b56592b12a2ae5357d30568db33cf1 Mon Sep 17 00:00:00 2001 From: Matheus Alcantara Date: Fri, 23 Oct 2026 10:51:18 -0300 Subject: [PATCH v1] Fix MCV extended statistics with boolean clauses on domains over bool statext_is_compatible_clause_internal() looks through a top-level RelabelType before deciding what kind of clause it has, so a boolean column whose type is a domain over bool, used as a bare condition (WHERE a = 1 AND b), is accepted as a plain Var reference. mcv_get_match_bitmap() however received the clause with the RelabelType still on top. The clause was not recognized as a Var, so it was treated as a bare boolean expression, and the lookup in mcv_match_expression() failed with "expression not found in statistics object". The same happened for NOT b, b = true, b = false, and for a statistics expression whose type is a domain over bool used as a bare condition. Before e33ae53ddec, the error was "unknown clause type". Fix by looking through the RelabelType in mcv_get_match_bitmap() the same way the compatibility check does, and adjust the assertion in the Var branch to accept a domain over bool. Reported-by: Alperen Keles Discussion: https://www.postgresql.org/message-id/19789-b6592eeba8c6f6e1@postgresql.org Backpatch-through: 14 --- src/backend/statistics/mcv.c | 9 ++++- src/test/regress/expected/stats_ext.out | 48 +++++++++++++++++++++++++ src/test/regress/sql/stats_ext.sql | 34 ++++++++++++++++++ 3 files changed, 90 insertions(+), 1 deletion(-) diff --git a/src/backend/statistics/mcv.c b/src/backend/statistics/mcv.c index 10a91c97aeb..bbe7de7c72c 100644 --- a/src/backend/statistics/mcv.c +++ b/src/backend/statistics/mcv.c @@ -1621,6 +1621,13 @@ mcv_get_match_bitmap(PlannerInfo *root, List *clauses, if (IsA(clause, RestrictInfo)) clause = (Node *) ((RestrictInfo *) clause)->clause; + /* + * Look inside any binary-compatible relabeling, as + * statext_is_compatible_clause_internal does. + */ + if (IsA(clause, RelabelType)) + clause = (Node *) ((RelabelType *) clause)->arg; + /* * Handle the various types of clauses - OpClause, NullTest and * AND/OR/NOT @@ -1913,7 +1920,7 @@ mcv_get_match_bitmap(PlannerInfo *root, List *clauses, /* match the attribute to a dimension of the statistic */ int idx = bms_member_index(keys, var->varattno); - Assert(var->vartype == BOOLOID); + Assert(getBaseType(var->vartype) == BOOLOID); /* * Walk through the MCV items and evaluate the current clause. We diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out index 4512670d525..d2756cda485 100644 --- a/src/test/regress/expected/stats_ext.out +++ b/src/test/regress/expected/stats_ext.out @@ -2804,6 +2804,54 @@ SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_bool WHERE NOT a AND 1 | 0 (1 row) +-- mcv with domains over bool +CREATE DOMAIN mcv_bool_domain AS bool; +CREATE TABLE mcv_lists_bool_domain ( + a mcv_bool_domain, + b mcv_bool_domain, + c INT +) +WITH (autovacuum_enabled = off); +INSERT INTO mcv_lists_bool_domain (a, b, c) + SELECT + (mod(i,2) = 0), (mod(i,4) = 0), mod(i,8) + FROM generate_series(1,10000) s(i); +CREATE STATISTICS mcv_lists_bool_domain_stats (mcv) + ON a, b, c, ((c > 4)::mcv_bool_domain) + FROM mcv_lists_bool_domain; +ANALYZE mcv_lists_bool_domain; +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_bool_domain WHERE a AND b AND c = 0'); + estimated | actual +-----------+-------- + 1250 | 1250 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_bool_domain WHERE NOT a AND b'); + estimated | actual +-----------+-------- + 1 | 0 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_bool_domain WHERE a = true AND b = false'); + estimated | actual +-----------+-------- + 2500 | 2500 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_bool_domain WHERE (a OR b) AND c = 1'); + estimated | actual +-----------+-------- + 1 | 0 +(1 row) + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_bool_domain WHERE a AND (c > 4)::mcv_bool_domain'); + estimated | actual +-----------+-------- + 1250 | 1250 +(1 row) + +DROP TABLE mcv_lists_bool_domain; +DROP DOMAIN mcv_bool_domain; -- mcv covering just a small fraction of data CREATE TABLE mcv_lists_partial ( a INT, diff --git a/src/test/regress/sql/stats_ext.sql b/src/test/regress/sql/stats_ext.sql index a9176e74f3c..8676dbcd284 100644 --- a/src/test/regress/sql/stats_ext.sql +++ b/src/test/regress/sql/stats_ext.sql @@ -1395,6 +1395,40 @@ SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_bool WHERE NOT a AND SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_bool WHERE NOT a AND b AND NOT c'); +-- mcv with domains over bool +CREATE DOMAIN mcv_bool_domain AS bool; + +CREATE TABLE mcv_lists_bool_domain ( + a mcv_bool_domain, + b mcv_bool_domain, + c INT +) +WITH (autovacuum_enabled = off); + +INSERT INTO mcv_lists_bool_domain (a, b, c) + SELECT + (mod(i,2) = 0), (mod(i,4) = 0), mod(i,8) + FROM generate_series(1,10000) s(i); + +CREATE STATISTICS mcv_lists_bool_domain_stats (mcv) + ON a, b, c, ((c > 4)::mcv_bool_domain) + FROM mcv_lists_bool_domain; + +ANALYZE mcv_lists_bool_domain; + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_bool_domain WHERE a AND b AND c = 0'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_bool_domain WHERE NOT a AND b'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_bool_domain WHERE a = true AND b = false'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_bool_domain WHERE (a OR b) AND c = 1'); + +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_bool_domain WHERE a AND (c > 4)::mcv_bool_domain'); + +DROP TABLE mcv_lists_bool_domain; +DROP DOMAIN mcv_bool_domain; + -- mcv covering just a small fraction of data CREATE TABLE mcv_lists_partial ( a INT, -- 2.50.1 (Apple Git-155)