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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
//! Module: query::intent::mutation
//! Responsibility: query-intent mutation helpers for scalar/grouped/load/delete intent state.
//! Does not own: final planner validation or executor route/runtime semantics.
//! Boundary: applies fluent/query API mutations to internal intent state contracts.
#[cfg(any(test, feature = "sql"))]
use crate::db::predicate::Predicate;
#[cfg(feature = "sql")]
use crate::db::query::plan::expr::ProjectionSelection;
use crate::db::query::{
intent::{
IntentError,
state::{GroupedIntent, NormalizedFilter, QueryIntent},
},
plan::{
FieldSlot, GroupAggregateSpec, OrderSpec, OrderTerm,
expr::{BinaryOp, Expr, canonicalize_grouped_having_bool_expr, normalize_bool_expr},
},
};
impl QueryIntent {
/// Append one normalized scalar filter expression to intent state,
/// implicitly AND-ing multiple scalar filter clauses.
pub(in crate::db::query::intent) fn append_filter_expr(&mut self, expr: Expr) {
self.append_normalized_filter(NormalizedFilter::from_normalized_expr(expr));
}
/// Append one already-normalized filter predicate to scalar intent,
/// implicitly AND-ing chains.
#[cfg(any(test, feature = "sql"))]
pub(in crate::db::query::intent) fn append_predicate(&mut self, predicate: Predicate) {
self.append_normalized_filter(NormalizedFilter::from_normalized_predicate(predicate));
}
/// Append one normalized scalar filter with both semantic views already
/// prepared by the caller.
#[cfg(any(test, feature = "sql"))]
pub(in crate::db::query::intent) fn append_filter_with_predicate_subset(
&mut self,
expr: Expr,
predicate: Predicate,
) {
self.append_normalized_filter(NormalizedFilter::from_normalized_expr_and_predicate_subset(
expr, predicate,
));
}
// Store scalar filters through the single normalized-filter seam so later
// planning never has to reconcile independently-mutated filter fields.
fn append_normalized_filter(&mut self, filter: NormalizedFilter) {
let scalar = self.scalar_mut();
match scalar.filter.as_mut() {
Some(existing) => existing.append(filter),
None => scalar.filter = Some(filter),
}
}
/// Append one already-lowered ORDER BY term to scalar intent.
pub(in crate::db::query::intent) fn push_order_term(&mut self, term: OrderTerm) {
let scalar = self.scalar_mut();
scalar.order = Some(match scalar.order.take() {
Some(mut spec) => {
spec.fields.push(term);
spec
}
None => OrderSpec { fields: vec![term] },
});
}
/// Override scalar ORDER BY with one validated order specification.
#[cfg(any(test, feature = "sql"))]
pub(in crate::db::query::intent) fn set_order_spec(&mut self, order: OrderSpec) {
self.scalar_mut().order = Some(order);
}
/// Enable DISTINCT semantics in scalar intent state.
pub(in crate::db::query::intent) const fn set_distinct(&mut self) {
self.scalar_mut().distinct = true;
}
/// Override scalar projection selection with one explicit planner contract.
#[cfg(feature = "sql")]
pub(in crate::db::query::intent) fn set_projection_selection(
&mut self,
projection_selection: ProjectionSelection,
) {
self.scalar_mut().projection_selection = projection_selection;
}
/// Record one grouped key slot while preserving grouped-delete policy semantics.
pub(in crate::db::query::intent) fn push_group_field_slot(&mut self, field_slot: FieldSlot) {
let Some(grouped) = self.grouped_mutation_target() else {
return;
};
let group = &mut grouped.group;
if !group
.group_fields
.iter()
.any(|existing| existing.index() == field_slot.index())
{
group.group_fields.push(field_slot);
}
}
/// Record one grouped aggregate terminal while preserving delete policy flags.
pub(in crate::db::query::intent) fn push_group_aggregate(
&mut self,
aggregate: GroupAggregateSpec,
) {
let Some(grouped) = self.grouped_mutation_target() else {
return;
};
grouped.group.aggregates.push(aggregate);
}
/// Record one grouped HAVING expression while preserving the caller-owned
/// canonical grouped shape instead of re-running searched-CASE semantic
/// canonicalization during append.
#[cfg(feature = "sql")]
pub(in crate::db::query::intent) fn push_having_expr_preserving_shape(
&mut self,
expr: Expr,
) -> Result<(), IntentError> {
self.push_having_expr_with_policy(expr, false)
}
// Keep grouped HAVING append-order normalization on one seam while letting
// fluent grouped builders and SQL-lowered grouped queries choose whether
// searched-CASE semantic canonicalization should run at append time.
fn push_having_expr_with_policy(
&mut self,
expr: Expr,
canonicalize_case_semantics: bool,
) -> Result<(), IntentError> {
if matches!(self, Self::Delete(_)) {
if self.is_grouped() {
self.mark_delete_grouping_requested();
return Ok(());
}
return Err(IntentError::having_requires_group_by());
}
let Some(grouped) = self.grouped_mut() else {
return Err(IntentError::having_requires_group_by());
};
let combined = match grouped.having_expr.take() {
Some(existing) => Expr::Binary {
op: BinaryOp::And,
left: Box::new(existing),
right: Box::new(expr),
},
None => expr,
};
let canonical = if canonicalize_case_semantics {
canonicalize_grouped_having_bool_expr(combined)
} else {
normalize_bool_expr(combined)
};
// Grouped HAVING still normalizes on one append seam, and callers can
// opt into the shipped searched-CASE semantic canonicalization there
// when the grouped expression shape is allowed to collapse. Grouped
// boolean trees may still carry aggregate leaves that the scalar-only
// normalized-shape checker rejects.
grouped.having_expr = Some(canonical);
Ok(())
}
// Record key-access origin and detect conflicting key-only builder usage.
// Route grouped declaration mutations onto one materialized grouped shape,
// or preserve delete-mode grouping policy when grouped state is forbidden.
fn grouped_mutation_target(&mut self) -> Option<&mut GroupedIntent> {
if matches!(self, Self::Delete(_)) {
self.mark_delete_grouping_requested();
return None;
}
self.ensure_grouped_mut()
}
}