From 796d062727f16abb9e60f643a5bb041a240963df Mon Sep 17 00:00:00 2001 From: William Bernbaum Date: Wed, 26 Aug 2026 20:46:56 -0700 Subject: [PATCH v1 16/16] Test deduplication under non-deterministic collation --- .../regress/expected/collate.icu.utf8.out | 95 +++++++++++++++++++ src/test/regress/sql/collate.icu.utf8.sql | 55 +++++++++++ 2 files changed, 150 insertions(+) diff --git a/src/test/regress/expected/collate.icu.utf8.out b/src/test/regress/expected/collate.icu.utf8.out index fcfcc658bea..46d21a69cb8 100644 --- a/src/test/regress/expected/collate.icu.utf8.out +++ b/src/test/regress/expected/collate.icu.utf8.out @@ -3648,6 +3648,101 @@ GROUP BY t1.id, t1.val; DROP TABLE eager_agg_t1; DROP TABLE eager_agg_t2; -- +-- Test that a deduplication is not pushed below a join on such a grouping +-- key either. It would decide which of two equal values survives, and equal +-- values are distinguishable under this collation. +-- +CREATE TABLE eager_dedup_d (id int, + ci text COLLATE case_insensitive, + c text COLLATE "C"); +CREATE TABLE eager_dedup_f (id int, d_id int); +INSERT INTO eager_dedup_d + SELECT i, CASE WHEN i % 2 = 0 THEN 'a' ELSE 'A' END, 'c' || i + FROM generate_series(1, 100) i; +INSERT INTO eager_dedup_f + SELECT i, i % 100 + 1 FROM generate_series(1, 2000) i; +ANALYZE eager_dedup_d; +ANALYZE eager_dedup_f; +-- Twenty rows of f per row of d, so a deduplication would pay +EXPLAIN (COSTS OFF) +SELECT DISTINCT d.ci + FROM eager_dedup_d d + JOIN eager_dedup_f f ON f.d_id = d.id; + QUERY PLAN +----------------------------------------------- + HashAggregate + Group Key: d.ci + -> Hash Join + Hash Cond: (f.d_id = d.id) + -> Seq Scan on eager_dedup_f f + -> Hash + -> Seq Scan on eager_dedup_d d +(7 rows) + +SELECT count(*) FROM ( + SELECT DISTINCT d.ci + FROM eager_dedup_d d + JOIN eager_dedup_f f ON f.d_id = d.id) s; + count +------- + 1 +(1 row) + +-- Nor may such a value become a grouping key through a DISTINCT aggregate +EXPLAIN (COSTS OFF) +SELECT count(DISTINCT d.ci) + FROM eager_dedup_d d + JOIN eager_dedup_f f ON f.d_id = d.id; + QUERY PLAN +----------------------------------------------------- + Aggregate + -> Sort + Sort Key: d.ci COLLATE case_insensitive + -> Hash Join + Hash Cond: (f.d_id = d.id) + -> Seq Scan on eager_dedup_f f + -> Hash + -> Seq Scan on eager_dedup_d d +(8 rows) + +SELECT count(DISTINCT d.ci) + FROM eager_dedup_d d + JOIN eager_dedup_f f ON f.d_id = d.id; + count +------- + 1 +(1 row) + +-- The same query on the deterministic column does push a deduplication down +EXPLAIN (COSTS OFF) +SELECT DISTINCT d.c + FROM eager_dedup_d d + JOIN eager_dedup_f f ON f.d_id = d.id; + QUERY PLAN +----------------------------------------------------- + HashAggregate + Group Key: d.c + -> Hash Join + Hash Cond: (d.id = f.d_id) + -> Seq Scan on eager_dedup_d d + -> Hash + -> Partial HashAggregate + Group Key: f.d_id + -> Seq Scan on eager_dedup_f f +(9 rows) + +SELECT count(*) FROM ( + SELECT DISTINCT d.c + FROM eager_dedup_d d + JOIN eager_dedup_f f ON f.d_id = d.id) s; + count +------- + 100 +(1 row) + +DROP TABLE eager_dedup_d; +DROP TABLE eager_dedup_f; +-- -- A unique index can prove functional dependency for GROUP BY column -- removal only if its per-column collation agrees on equality with -- the GROUP BY column's collation. An index built under a different diff --git a/src/test/regress/sql/collate.icu.utf8.sql b/src/test/regress/sql/collate.icu.utf8.sql index ce4e2bb3ffd..27bc96c51d6 100644 --- a/src/test/regress/sql/collate.icu.utf8.sql +++ b/src/test/regress/sql/collate.icu.utf8.sql @@ -1355,6 +1355,61 @@ GROUP BY t1.id, t1.val; DROP TABLE eager_agg_t1; DROP TABLE eager_agg_t2; +-- +-- Test that a deduplication is not pushed below a join on such a grouping +-- key either. It would decide which of two equal values survives, and equal +-- values are distinguishable under this collation. +-- + +CREATE TABLE eager_dedup_d (id int, + ci text COLLATE case_insensitive, + c text COLLATE "C"); +CREATE TABLE eager_dedup_f (id int, d_id int); + +INSERT INTO eager_dedup_d + SELECT i, CASE WHEN i % 2 = 0 THEN 'a' ELSE 'A' END, 'c' || i + FROM generate_series(1, 100) i; +INSERT INTO eager_dedup_f + SELECT i, i % 100 + 1 FROM generate_series(1, 2000) i; + +ANALYZE eager_dedup_d; +ANALYZE eager_dedup_f; + +-- Twenty rows of f per row of d, so a deduplication would pay +EXPLAIN (COSTS OFF) +SELECT DISTINCT d.ci + FROM eager_dedup_d d + JOIN eager_dedup_f f ON f.d_id = d.id; + +SELECT count(*) FROM ( + SELECT DISTINCT d.ci + FROM eager_dedup_d d + JOIN eager_dedup_f f ON f.d_id = d.id) s; + +-- Nor may such a value become a grouping key through a DISTINCT aggregate +EXPLAIN (COSTS OFF) +SELECT count(DISTINCT d.ci) + FROM eager_dedup_d d + JOIN eager_dedup_f f ON f.d_id = d.id; + +SELECT count(DISTINCT d.ci) + FROM eager_dedup_d d + JOIN eager_dedup_f f ON f.d_id = d.id; + +-- The same query on the deterministic column does push a deduplication down +EXPLAIN (COSTS OFF) +SELECT DISTINCT d.c + FROM eager_dedup_d d + JOIN eager_dedup_f f ON f.d_id = d.id; + +SELECT count(*) FROM ( + SELECT DISTINCT d.c + FROM eager_dedup_d d + JOIN eager_dedup_f f ON f.d_id = d.id) s; + +DROP TABLE eager_dedup_d; +DROP TABLE eager_dedup_f; + -- -- A unique index can prove functional dependency for GROUP BY column -- removal only if its per-column collation agrees on equality with