1use crate::filter::{Filter, FilterValue};
10use crate::inputs::scalar::combine_filters;
11use crate::inputs::traits::WhereInput;
12
13pub trait RelationFilterMeta {
21 const PARENT_TABLE: &'static str;
23 const PARENT_PK: &'static str;
25 const CHILD_TABLE: &'static str;
27 const CHILD_FK: &'static str;
29}
30
31#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
33#[serde(rename_all = "snake_case")]
34#[serde(bound = "W: serde::Serialize + for<'de2> serde::Deserialize<'de2>")]
35pub struct ListRelationFilter<W> {
36 pub some: Option<W>,
38 pub every: Option<W>,
40 pub none: Option<W>,
42}
43
44#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
46#[serde(rename_all = "snake_case")]
47#[serde(bound = "W: serde::Serialize + for<'de2> serde::Deserialize<'de2>")]
48pub struct SingleRelationFilter<W> {
49 pub is: Option<W>,
51 pub is_not: Option<W>,
53}
54
55pub trait LowerRelationFilter {
61 fn lower<M: RelationFilterMeta>(self) -> Filter;
64}
65
66fn render_inline_filter(inner: Filter) -> (String, Vec<FilterValue>) {
72 let mut sql = String::new();
73 let mut params = Vec::<FilterValue>::new();
74 write_filter(&inner, &mut sql, &mut params);
75 (sql, params)
76}
77
78fn write_filter(f: &Filter, sql: &mut String, params: &mut Vec<FilterValue>) {
79 match f {
80 Filter::None => sql.push_str("TRUE"),
81 Filter::Equals(c, v) => {
82 if matches!(v, FilterValue::Null) {
83 sql.push_str(&format!("{} IS NULL", c));
84 } else {
85 let idx = params.len();
86 params.push(v.clone());
87 sql.push_str(&format!("{} = {{{}}}", c, idx));
88 }
89 }
90 Filter::NotEquals(c, v) => {
91 if matches!(v, FilterValue::Null) {
92 sql.push_str(&format!("{} IS NOT NULL", c));
93 } else {
94 let idx = params.len();
95 params.push(v.clone());
96 sql.push_str(&format!("{} <> {{{}}}", c, idx));
97 }
98 }
99 Filter::Lt(c, v) => {
100 let i = params.len();
101 params.push(v.clone());
102 sql.push_str(&format!("{} < {{{}}}", c, i));
103 }
104 Filter::Lte(c, v) => {
105 let i = params.len();
106 params.push(v.clone());
107 sql.push_str(&format!("{} <= {{{}}}", c, i));
108 }
109 Filter::Gt(c, v) => {
110 let i = params.len();
111 params.push(v.clone());
112 sql.push_str(&format!("{} > {{{}}}", c, i));
113 }
114 Filter::Gte(c, v) => {
115 let i = params.len();
116 params.push(v.clone());
117 sql.push_str(&format!("{} >= {{{}}}", c, i));
118 }
119 Filter::IsNull(c) => sql.push_str(&format!("{} IS NULL", c)),
120 Filter::IsNotNull(c) => sql.push_str(&format!("{} IS NOT NULL", c)),
121 Filter::Contains(c, FilterValue::String(s)) => {
122 let i = params.len();
123 params.push(FilterValue::String(format!("%{}%", s)));
124 sql.push_str(&format!("{} LIKE {{{}}}", c, i));
125 }
126 Filter::StartsWith(c, FilterValue::String(s)) => {
127 let i = params.len();
128 params.push(FilterValue::String(format!("{}%", s)));
129 sql.push_str(&format!("{} LIKE {{{}}}", c, i));
130 }
131 Filter::EndsWith(c, FilterValue::String(s)) => {
132 let i = params.len();
133 params.push(FilterValue::String(format!("%{}", s)));
134 sql.push_str(&format!("{} LIKE {{{}}}", c, i));
135 }
136 Filter::Contains(_, _) | Filter::StartsWith(_, _) | Filter::EndsWith(_, _) => {
137 panic!(
138 "inline relation-filter lowering requires String values for Contains / StartsWith / EndsWith \
139 (other FilterValue variants must be expressed via Equals / In)"
140 );
141 }
142 Filter::In(c, values) => {
143 if values.is_empty() {
144 sql.push_str("FALSE");
145 return;
146 }
147 sql.push_str(&format!("{} IN (", c));
148 for (n, v) in values.iter().enumerate() {
149 if n > 0 {
150 sql.push_str(", ");
151 }
152 let i = params.len();
153 params.push(v.clone());
154 sql.push_str(&format!("{{{}}}", i));
155 }
156 sql.push(')');
157 }
158 Filter::NotIn(c, values) => {
159 if values.is_empty() {
160 sql.push_str("TRUE");
161 return;
162 }
163 sql.push_str(&format!("{} NOT IN (", c));
164 for (n, v) in values.iter().enumerate() {
165 if n > 0 {
166 sql.push_str(", ");
167 }
168 let i = params.len();
169 params.push(v.clone());
170 sql.push_str(&format!("{{{}}}", i));
171 }
172 sql.push(')');
173 }
174 Filter::And(parts) => {
175 if parts.is_empty() {
176 sql.push_str("TRUE");
177 return;
178 }
179 sql.push('(');
180 for (n, p) in parts.iter().enumerate() {
181 if n > 0 {
182 sql.push_str(" AND ");
183 }
184 write_filter(p, sql, params);
185 }
186 sql.push(')');
187 }
188 Filter::Or(parts) => {
189 if parts.is_empty() {
190 sql.push_str("FALSE");
191 return;
192 }
193 sql.push('(');
194 for (n, p) in parts.iter().enumerate() {
195 if n > 0 {
196 sql.push_str(" OR ");
197 }
198 write_filter(p, sql, params);
199 }
200 sql.push(')');
201 }
202 Filter::Not(inner) => {
203 sql.push_str("NOT (");
204 write_filter(inner, sql, params);
205 sql.push(')');
206 }
207 Filter::ScalarSubquery { .. } => {
208 panic!(
209 "inline relation-filter lowering does not support nested ScalarSubquery \
210 (the outer to_sql_with_params handles ScalarSubquery; relation bodies must lower to leaf filters first)"
211 );
212 }
213 }
214}
215
216impl<W: WhereInput> LowerRelationFilter for ListRelationFilter<W> {
217 fn lower<M: RelationFilterMeta>(self) -> Filter {
218 let mut clauses: Vec<Filter> = Vec::new();
219
220 if let Some(w) = self.some {
221 let (body, params) = render_inline_filter(w.into_ir());
222 let sql = format!(
223 "EXISTS (SELECT 1 FROM {} WHERE {}.{} = {}.{} AND {})",
224 M::CHILD_TABLE,
225 M::CHILD_TABLE,
226 M::CHILD_FK,
227 M::PARENT_TABLE,
228 M::PARENT_PK,
229 body,
230 );
231 clauses.push(Filter::ScalarSubquery {
232 sql: sql.into(),
233 params,
234 });
235 }
236
237 if let Some(w) = self.every {
238 let (body, params) = render_inline_filter(w.into_ir());
239 let sql = format!(
240 "NOT EXISTS (SELECT 1 FROM {} WHERE {}.{} = {}.{} AND NOT ({}))",
241 M::CHILD_TABLE,
242 M::CHILD_TABLE,
243 M::CHILD_FK,
244 M::PARENT_TABLE,
245 M::PARENT_PK,
246 body,
247 );
248 clauses.push(Filter::ScalarSubquery {
249 sql: sql.into(),
250 params,
251 });
252 }
253
254 if let Some(w) = self.none {
255 let (body, params) = render_inline_filter(w.into_ir());
256 let sql = format!(
257 "NOT EXISTS (SELECT 1 FROM {} WHERE {}.{} = {}.{} AND {})",
258 M::CHILD_TABLE,
259 M::CHILD_TABLE,
260 M::CHILD_FK,
261 M::PARENT_TABLE,
262 M::PARENT_PK,
263 body,
264 );
265 clauses.push(Filter::ScalarSubquery {
266 sql: sql.into(),
267 params,
268 });
269 }
270
271 combine_filters(clauses)
272 }
273}
274
275impl<W: WhereInput> LowerRelationFilter for SingleRelationFilter<W> {
276 fn lower<M: RelationFilterMeta>(self) -> Filter {
277 let mut clauses: Vec<Filter> = Vec::new();
278
279 if let Some(w) = self.is {
280 let (body, params) = render_inline_filter(w.into_ir());
281 let sql = format!(
282 "EXISTS (SELECT 1 FROM {} WHERE {}.{} = {}.{} AND {})",
283 M::CHILD_TABLE,
284 M::CHILD_TABLE,
285 M::CHILD_FK,
286 M::PARENT_TABLE,
287 M::PARENT_PK,
288 body,
289 );
290 clauses.push(Filter::ScalarSubquery {
291 sql: sql.into(),
292 params,
293 });
294 }
295
296 if let Some(w) = self.is_not {
297 let (body, params) = render_inline_filter(w.into_ir());
298 let sql = format!(
299 "NOT EXISTS (SELECT 1 FROM {} WHERE {}.{} = {}.{} AND {})",
300 M::CHILD_TABLE,
301 M::CHILD_TABLE,
302 M::CHILD_FK,
303 M::PARENT_TABLE,
304 M::PARENT_PK,
305 body,
306 );
307 clauses.push(Filter::ScalarSubquery {
308 sql: sql.into(),
309 params,
310 });
311 }
312
313 combine_filters(clauses)
314 }
315}