1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
//! v7.38.13 — a bare `GROUP BY` is a `DISTINCT`, and costs 63 % more.
//!
//! `SELECT k FROM t GROUP BY k ORDER BY k` and
//! `SELECT DISTINCT k FROM t ORDER BY k` return byte-identical output —
//! verified against PostgreSQL 18 on both spellings, all four md5s
//! equal. SPG answered the first in 155.7 ms and the second in 95.2,
//! against PG's 111.3 for either: `uses_aggregate` returns true for any
//! statement carrying a GROUP BY, so a query with no aggregate in it
//! still went through the aggregate executor and paid for the
//! machinery it never used.
//!
//! No sweep cell covers the GROUP BY spelling, which is why a 40 % loss
//! sat there while a neighbouring cell that flickers with the machine
//! cost three releases a round each.
//!
//! The rewrite re-enters the ordinary path, the way
//! `desugar_using_natural` does; clearing `group_by` makes the second
//! pass a no-op.
//!
//! ## What the gate has to rule out
//!
//! The equivalence is NOT general. `SELECT a FROM t GROUP BY a, b`
//! yields one row per (a, b) pair and so may repeat `a`, which
//! `SELECT DISTINCT a` would collapse. So the projected expressions and
//! the group keys must be the SAME SET, not merely overlap. HAVING
//! filters groups and has no DISTINCT spelling. A window function is
//! evaluated after grouping and would see a different input. An
//! ordinal group key (`GROUP BY 1`) refers to a select-list position
//! and is left alone rather than reasoned about.
use alloc::vec::Vec;
use spg_sql::ast::{Expr, SelectItem, SelectStatement};
/// The statement rewritten as a `DISTINCT`, or `None` when it is not
/// exactly one.
pub(crate) fn as_distinct(stmt: &SelectStatement) -> Option<SelectStatement> {
let keys = stmt.group_by.as_ref()?;
if keys.is_empty()
|| stmt.group_by_all
|| stmt.having.is_some()
|| stmt.distinct
|| !stmt.distinct_on.is_empty()
{
return None;
}
// An ordinal key names a select-list position, not a value.
if keys.iter().any(is_ordinal) {
return None;
}
// Aggregates anywhere put this back on the aggregate path, which is
// where it belongs.
if crate::aggregate::uses_aggregate_ignoring_group_by(stmt) {
return None;
}
let mut projected: Vec<&Expr> = Vec::with_capacity(stmt.items.len());
for item in &stmt.items {
match item {
// A wildcard's expansion is not known here, so it is not a
// set this can compare against the keys.
SelectItem::Wildcard | SelectItem::QualifiedWildcard(_) => return None,
SelectItem::Expr { expr, .. } => {
if crate::window::expr_has_window_pub(expr) {
return None;
}
projected.push(expr);
}
}
}
// Same SET both ways — see the module note on `GROUP BY a, b`.
if !projected.iter().all(|p| keys.contains(p)) {
return None;
}
if !keys.iter().all(|k| projected.contains(&k)) {
return None;
}
// ORDER BY may only name a group key (or a position); anything else
// is not a column this query can order by at all.
if !stmt
.order_by
.iter()
.all(|o| is_ordinal(&o.expr) || keys.contains(&o.expr))
{
return None;
}
let mut out = stmt.clone();
out.group_by = None;
out.distinct = true;
Some(out)
}
/// `GROUP BY 1` / `ORDER BY 2` — a select-list position.
fn is_ordinal(e: &Expr) -> bool {
matches!(e, Expr::Literal(spg_sql::ast::Literal::Integer(_)))
}