1use crate::validate::assert_valid_sql_identifier;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7#[non_exhaustive]
8pub enum Operator {
9 Eq,
11 Ne,
13 Gt,
15 Gte,
17 Lt,
19 Lte,
21 In,
23 NotIn,
25 Regex,
27 Like,
29 ILike,
31 StartsWith,
33 EndsWith,
35 Contains,
37 Between,
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43#[non_exhaustive]
44pub enum LogicalOp {
45 And,
47 Or,
49 Not,
51}
52
53#[derive(Debug, Clone, PartialEq)]
55#[non_exhaustive]
56pub enum FilterExpr {
57 Simple(Filter),
59 Compound(CompoundFilter),
61}
62
63#[derive(Debug, Clone, PartialEq)]
65#[non_exhaustive]
66pub struct CompoundFilter {
67 pub op: LogicalOp,
69 pub filters: Vec<FilterExpr>,
71}
72
73impl CompoundFilter {
74 #[must_use]
76 pub const fn and(filters: Vec<FilterExpr>) -> Self {
77 Self {
78 op: LogicalOp::And,
79 filters,
80 }
81 }
82
83 #[must_use]
85 pub const fn or(filters: Vec<FilterExpr>) -> Self {
86 Self {
87 op: LogicalOp::Or,
88 filters,
89 }
90 }
91
92 #[must_use]
94 pub fn not(filter: FilterExpr) -> Self {
95 Self {
96 op: LogicalOp::Not,
97 filters: vec![filter],
98 }
99 }
100}
101
102impl FilterExpr {
103 #[must_use]
108 pub fn collect_filters(&self) -> Vec<Filter> {
109 let mut result = Vec::new();
110 self.collect_filters_into(&mut result);
111 result
112 }
113
114 fn collect_filters_into(&self, result: &mut Vec<Filter>) {
115 match self {
116 Self::Simple(f) => result.push(f.clone()),
117 Self::Compound(c) => {
118 for expr in &c.filters {
119 expr.collect_filters_into(result);
120 }
121 },
122 }
123 }
124
125 #[must_use]
129 pub fn iter(&self) -> FilterExprIter {
130 self.into_iter()
131 }
132}
133
134#[derive(Debug)]
136pub struct FilterExprIter {
137 filters: std::vec::IntoIter<Filter>,
138}
139
140impl Iterator for FilterExprIter {
141 type Item = Filter;
142
143 fn next(&mut self) -> Option<Self::Item> {
144 self.filters.next()
145 }
146
147 fn size_hint(&self) -> (usize, Option<usize>) {
148 self.filters.size_hint()
149 }
150}
151
152impl IntoIterator for &FilterExpr {
153 type Item = Filter;
154 type IntoIter = FilterExprIter;
155
156 fn into_iter(self) -> Self::IntoIter {
157 FilterExprIter {
158 filters: self.collect_filters().into_iter(),
159 }
160 }
161}
162
163#[derive(Debug, Clone, Copy, PartialEq, Eq)]
165#[non_exhaustive]
166pub enum AggregateFunc {
167 Count,
169 CountDistinct,
171 Sum,
173 Avg,
175 Min,
177 Max,
179}
180
181#[derive(Debug, Clone, PartialEq, Eq)]
183#[non_exhaustive]
184pub struct Aggregate {
185 pub func: AggregateFunc,
187 pub field: Option<String>,
189 pub alias: Option<String>,
191}
192
193impl Aggregate {
194 #[must_use]
196 pub fn count() -> Self {
197 Self {
198 func: AggregateFunc::Count,
199 field: None,
200 alias: Some("count".to_string()),
201 }
202 }
203
204 pub fn count_field(field: impl Into<String>) -> Self {
210 let field = field.into();
211 assert_valid_sql_identifier(&field, "aggregate field");
212 Self {
213 func: AggregateFunc::Count,
214 field: Some(field),
215 alias: None,
216 }
217 }
218
219 pub fn count_distinct(field: impl Into<String>) -> Self {
225 let field = field.into();
226 assert_valid_sql_identifier(&field, "aggregate field");
227 Self {
228 func: AggregateFunc::CountDistinct,
229 field: Some(field),
230 alias: None,
231 }
232 }
233
234 pub fn sum(field: impl Into<String>) -> Self {
240 let field = field.into();
241 assert_valid_sql_identifier(&field, "aggregate field");
242 Self {
243 func: AggregateFunc::Sum,
244 field: Some(field),
245 alias: None,
246 }
247 }
248
249 pub fn avg(field: impl Into<String>) -> Self {
255 let field = field.into();
256 assert_valid_sql_identifier(&field, "aggregate field");
257 Self {
258 func: AggregateFunc::Avg,
259 field: Some(field),
260 alias: None,
261 }
262 }
263
264 pub fn min(field: impl Into<String>) -> Self {
270 let field = field.into();
271 assert_valid_sql_identifier(&field, "aggregate field");
272 Self {
273 func: AggregateFunc::Min,
274 field: Some(field),
275 alias: None,
276 }
277 }
278
279 pub fn max(field: impl Into<String>) -> Self {
285 let field = field.into();
286 assert_valid_sql_identifier(&field, "aggregate field");
287 Self {
288 func: AggregateFunc::Max,
289 field: Some(field),
290 alias: None,
291 }
292 }
293
294 pub fn as_alias(mut self, alias: impl Into<String>) -> Self {
300 let alias = alias.into();
301 assert_valid_sql_identifier(&alias, "aggregate alias");
302 self.alias = Some(alias);
303 self
304 }
305
306 #[must_use]
308 pub fn to_sql(&self) -> String {
309 let expr = match (&self.func, &self.field) {
310 (AggregateFunc::Count, None) => "COUNT(*)".to_string(),
311 (AggregateFunc::Count, Some(f)) => format!("COUNT({f})"),
312 (AggregateFunc::CountDistinct, Some(f)) => format!("COUNT(DISTINCT {f})"),
313 (AggregateFunc::Sum, Some(f)) => format!("SUM({f})"),
314 (AggregateFunc::Avg, Some(f)) => format!("AVG({f})"),
315 (AggregateFunc::Min, Some(f)) => format!("MIN({f})"),
316 (AggregateFunc::Max, Some(f)) => format!("MAX({f})"),
317 _ => "COUNT(*)".to_string(),
318 };
319
320 match &self.alias {
321 Some(a) => format!("{expr} AS {a}"),
322 None => expr,
323 }
324 }
325}
326
327#[derive(Debug, Clone, PartialEq)]
329#[non_exhaustive]
330pub enum Value {
331 Null,
333 Bool(bool),
335 Int(i64),
337 Float(f64),
339 String(String),
341 Array(Vec<Self>),
343}
344
345#[derive(Debug, Clone, Copy, PartialEq, Eq)]
347#[non_exhaustive]
348pub enum SortDir {
349 Asc,
351 Desc,
353}
354
355#[derive(Debug, Clone, PartialEq, Eq)]
357#[non_exhaustive]
358pub struct SortField {
359 pub field: String,
361 pub dir: SortDir,
363}
364
365impl SortField {
366 pub fn new(field: impl Into<String>, dir: SortDir) -> Self {
368 Self {
369 field: field.into(),
370 dir,
371 }
372 }
373
374 pub fn parse_sort_string(sort: &str, allowed: &[&str]) -> Result<Vec<Self>, String> {
384 let mut result = Vec::new();
385
386 for part in sort.split(',') {
387 let part = part.trim();
388 if part.is_empty() {
389 continue;
390 }
391
392 let (field, dir) = part
393 .strip_prefix('-')
394 .map_or((part, SortDir::Asc), |stripped| (stripped, SortDir::Desc));
395
396 if !allowed.is_empty() && !allowed.contains(&field) {
398 return Err(format!(
399 "Sort field '{field}' not allowed. Allowed: {allowed:?}"
400 ));
401 }
402
403 result.push(Self::new(field, dir));
404 }
405
406 Ok(result)
407 }
408}
409
410#[derive(Debug, Clone, PartialEq)]
412#[non_exhaustive]
413pub struct Filter {
414 pub field: String,
416 pub op: Operator,
418 pub value: Value,
420}
421
422impl Filter {
423 #[must_use]
425 pub fn new(field: impl Into<String>, op: Operator, value: Value) -> Self {
426 Self {
427 field: field.into(),
428 op,
429 value,
430 }
431 }
432}
433
434#[derive(Debug, Clone, PartialEq)]
436#[non_exhaustive]
437#[must_use = "QueryResult must be used to execute the query"]
438pub struct QueryResult {
439 pub sql: String,
441 pub params: Vec<Value>,
443}
444
445impl QueryResult {
446 #[must_use]
448 pub fn new(sql: impl Into<String>, params: Vec<Value>) -> Self {
449 Self {
450 sql: sql.into(),
451 params,
452 }
453 }
454}
455
456#[derive(Debug, Clone, PartialEq, Eq)]
458#[non_exhaustive]
459pub struct ComputedField {
460 pub alias: String,
462 pub expression: String,
464}
465
466impl ComputedField {
467 pub fn new(alias: impl Into<String>, expression: impl Into<String>) -> Self {
469 Self {
470 alias: alias.into(),
471 expression: expression.into(),
472 }
473 }
474
475 #[must_use]
477 pub fn to_sql(&self) -> String {
478 format!("({}) AS {}", self.expression, self.alias)
479 }
480}
481
482#[derive(Debug, Clone, Copy, PartialEq, Eq)]
484#[non_exhaustive]
485pub enum CursorDirection {
486 After,
488 Before,
490}
491
492pub fn simple(field: impl Into<String>, op: Operator, value: Value) -> FilterExpr {
498 let field = field.into();
499 assert_valid_sql_identifier(&field, "filter field");
500 FilterExpr::Simple(Filter { field, op, value })
501}
502
503#[must_use]
505pub const fn and(filters: Vec<FilterExpr>) -> FilterExpr {
506 FilterExpr::Compound(CompoundFilter::and(filters))
507}
508
509#[must_use]
511pub const fn or(filters: Vec<FilterExpr>) -> FilterExpr {
512 FilterExpr::Compound(CompoundFilter::or(filters))
513}
514
515#[must_use]
517pub fn not(filter: FilterExpr) -> FilterExpr {
518 FilterExpr::Compound(CompoundFilter::not(filter))
519}