Skip to main content

rusticx_core/
query.rs

1use crate::value::Value;
2
3/// A compiled, backend-specific query with its parameter bindings.
4///
5/// Produced by each backend's SQL compiler after transforming a [`QueryBuilder`].
6/// You typically do not construct this directly.
7#[derive(Debug, Clone, Default)]
8pub struct Query {
9    pub sql: String,
10    pub bindings: Vec<Value>,
11}
12
13impl Query {
14    pub fn new(sql: impl Into<String>) -> Self {
15        Self { sql: sql.into(), bindings: vec![] }
16    }
17
18    pub fn bind(mut self, val: impl Into<Value>) -> Self {
19        self.bindings.push(val.into());
20        self
21    }
22}
23
24/// Composable, database-agnostic query builder.
25///
26/// `QueryBuilder` is the primary way to express SELECT / UPDATE / DELETE
27/// conditions in Rusticx without writing raw SQL or MQL. Each backend's
28/// compiler transforms it into parameterized SQL or a BSON filter document.
29///
30/// Obtain one from [`Repository::query`] so the table name is pre-filled,
31/// or construct one directly with [`QueryBuilder::table`].
32///
33/// # Example
34///
35/// ```rust,ignore
36/// let adults = repo.find(
37///     repo.query()
38///         .r#where("age", CondOp::Gte, 18)
39///         .r#where("active", CondOp::Eq, true)
40///         .order_by("name", Direction::Asc)
41///         .limit(20)
42///         .offset(0)
43/// ).await?;
44/// ```
45#[derive(Debug, Clone, Default)]
46pub struct QueryBuilder {
47    pub table: String,
48    pub operation: Operation,
49    pub conditions: Vec<Condition>,
50    pub order_by: Vec<OrderBy>,
51    pub limit: Option<u64>,
52    pub offset: Option<u64>,
53    pub columns: Vec<String>,
54    pub values: Vec<(String, Value)>,
55}
56
57/// The DML operation this [`QueryBuilder`] represents.
58///
59/// Set automatically by methods like [`Repository::update`] and
60/// [`Repository::delete`]; you only need this when building raw queries.
61#[derive(Debug, Clone, Default)]
62pub enum Operation {
63    #[default]
64    Select,
65    Insert,
66    Update,
67    Delete,
68    Count,
69}
70
71#[derive(Debug, Clone)]
72pub struct Condition {
73    pub column: String,
74    pub op: CondOp,
75    pub value: Value,
76    pub conjunction: Conjunction,
77}
78
79#[derive(Debug, Clone, Default)]
80pub enum Conjunction {
81    #[default]
82    And,
83    Or,
84}
85
86/// Comparison operator for a WHERE condition.
87///
88/// Used in [`QueryBuilder::r#where`] and [`QueryBuilder::or_where`].
89///
90/// | Variant | SQL | MongoDB |
91/// |---|---|---|
92/// | `Eq` | `= $1` | `{ field: value }` |
93/// | `Ne` | `!= $1` | `{ field: { $ne: value } }` |
94/// | `Gt` / `Gte` / `Lt` / `Lte` | `>` / `>=` / `<` / `<=` | `$gt` / `$gte` / `$lt` / `$lte` |
95/// | `Like` | `LIKE $1` | regex (`.*` for `%`) |
96/// | `ILike` | `ILIKE $1` (Postgres) | regex with `i` flag |
97/// | `In` | `IN (...)` | `{ $in: [...] }` |
98/// | `IsNull` | `IS NULL` | `{ $type: 10 }` |
99/// | `IsNotNull` | `IS NOT NULL` | `{ $not: { $type: 10 } }` |
100#[derive(Debug, Clone)]
101pub enum CondOp {
102    Eq,
103    Ne,
104    Gt,
105    Gte,
106    Lt,
107    Lte,
108    Like,
109    ILike,
110    In,
111    NotIn,
112    IsNull,
113    IsNotNull,
114}
115
116#[derive(Debug, Clone)]
117pub struct OrderBy {
118    pub column: String,
119    pub direction: Direction,
120}
121
122#[derive(Debug, Clone, Default)]
123pub enum Direction {
124    #[default]
125    Asc,
126    Desc,
127}
128
129impl QueryBuilder {
130    pub fn table(name: impl Into<String>) -> Self {
131        Self { table: name.into(), ..Default::default() }
132    }
133
134    pub fn select(mut self, cols: Vec<impl Into<String>>) -> Self {
135        self.columns = cols.into_iter().map(|c| c.into()).collect();
136        self
137    }
138
139    pub fn r#where(mut self, col: impl Into<String>, op: CondOp, val: impl Into<Value>) -> Self {
140        self.conditions.push(Condition {
141            column: col.into(),
142            op,
143            value: val.into(),
144            conjunction: Conjunction::And,
145        });
146        self
147    }
148
149    pub fn or_where(mut self, col: impl Into<String>, op: CondOp, val: impl Into<Value>) -> Self {
150        self.conditions.push(Condition {
151            column: col.into(),
152            op,
153            value: val.into(),
154            conjunction: Conjunction::Or,
155        });
156        self
157    }
158
159    pub fn order_by(mut self, col: impl Into<String>, dir: Direction) -> Self {
160        self.order_by.push(OrderBy { column: col.into(), direction: dir });
161        self
162    }
163
164    pub fn limit(mut self, n: u64) -> Self {
165        self.limit = Some(n);
166        self
167    }
168
169    pub fn offset(mut self, n: u64) -> Self {
170        self.offset = Some(n);
171        self
172    }
173
174    pub fn set(mut self, col: impl Into<String>, val: impl Into<Value>) -> Self {
175        self.values.push((col.into(), val.into()));
176        self
177    }
178
179    pub fn operation(mut self, op: Operation) -> Self {
180        self.operation = op;
181        self
182    }
183}