Skip to main content

turso_orm/entity/
column.rs

1//! Columns, modeled by [`ColumnTrait`] and [`ColumnDef`].
2//!
3//! [`ColumnDef`] is the static description the derive macro produces for
4//! each field and that [`Schema`](super::Schema) consumes to emit DDL.
5//! [`ColumnTrait`] is implemented on the generated `Column` enum and carries
6//! the condition builders (`Column::Name.eq("x")`,
7//! `Column::Age.gt(18)`).
8//!
9//! Two decisions shape the builders. First, every builder emits a qualified
10//! `table.column` reference through [`ColumnTrait::as_column_ref`], so a
11//! condition on `user.id` never clashes with `post.id` once the query joins
12//! both tables. Second, the generated `Column` enum deliberately does not
13//! derive `PartialEq`: with it, `Column::X.eq(v)` would resolve to
14//! `PartialEq::eq` and silently produce a `bool` instead of an expression.
15
16use turso_sql::{ColumnRef, ColumnType, Expr, Value};
17
18use super::iden::{IdenStatic, Iterable};
19
20/// The static description of a column, used to generate schema.
21#[derive(Clone, Debug, PartialEq)]
22pub struct ColumnDef {
23    /// The column type.
24    pub ty: ColumnType,
25    /// Whether the column accepts `NULL`.
26    pub nullable: bool,
27    /// Whether the column carries a `UNIQUE` constraint.
28    pub unique: bool,
29    /// Whether a secondary index is generated for the column.
30    pub indexed: bool,
31    /// The default value expression, inlined as a literal in DDL.
32    pub default: Option<Expr>,
33}
34
35impl ColumnDef {
36    /// A non-null column of the given type with no constraints.
37    pub const fn new(ty: ColumnType) -> Self {
38        Self {
39            ty,
40            nullable: false,
41            unique: false,
42            indexed: false,
43            default: None,
44        }
45    }
46
47    /// Sets whether the column accepts `NULL`.
48    #[must_use]
49    pub fn nullable(mut self, nullable: bool) -> Self {
50        self.nullable = nullable;
51        self
52    }
53
54    /// Marks the column `UNIQUE`.
55    #[must_use]
56    pub fn unique(mut self) -> Self {
57        self.unique = true;
58        self
59    }
60
61    /// Marks the column as needing a secondary index.
62    #[must_use]
63    pub fn indexed(mut self) -> Self {
64        self.indexed = true;
65        self
66    }
67
68    /// Sets the default value expression.
69    #[must_use]
70    pub fn default(mut self, value: impl Into<Expr>) -> Self {
71        self.default = Some(value.into());
72        self
73    }
74}
75
76/// A column of an entity, derived on the `Column` enum.
77///
78/// Provides the condition builders: `Column::Name.eq("x")`,
79/// `Column::Age.gt(18)` and so on. Every builder renders the column as
80/// `table.column`, so conditions stay unambiguous after a join.
81pub trait ColumnTrait: IdenStatic + Iterable {
82    /// The table this column belongs to.
83    const TABLE: &'static str;
84
85    /// The static definition used for schema generation.
86    fn def(&self) -> ColumnDef;
87
88    /// The column as a qualified `table.column` reference.
89    fn as_column_ref(&self) -> ColumnRef {
90        ColumnRef::TableColumn(Self::TABLE.into(), self.as_str().into())
91    }
92
93    /// The column as an expression.
94    fn into_expr(self) -> Expr {
95        Expr::Column(self.as_column_ref())
96    }
97
98    /// Builds `col = v`.
99    fn eq<V: Into<Value>>(&self, v: V) -> Expr {
100        self.into_expr().eq(Expr::val(v))
101    }
102
103    /// Builds `col <> v`.
104    fn ne<V: Into<Value>>(&self, v: V) -> Expr {
105        self.into_expr().ne(Expr::val(v))
106    }
107
108    /// Builds `col > v`.
109    fn gt<V: Into<Value>>(&self, v: V) -> Expr {
110        self.into_expr().gt(Expr::val(v))
111    }
112
113    /// Builds `col >= v`.
114    fn gte<V: Into<Value>>(&self, v: V) -> Expr {
115        self.into_expr().gte(Expr::val(v))
116    }
117
118    /// Builds `col < v`.
119    fn lt<V: Into<Value>>(&self, v: V) -> Expr {
120        self.into_expr().lt(Expr::val(v))
121    }
122
123    /// Builds `col <= v`.
124    fn lte<V: Into<Value>>(&self, v: V) -> Expr {
125        self.into_expr().lte(Expr::val(v))
126    }
127
128    /// Builds `col BETWEEN a AND b`.
129    fn between<V: Into<Value>>(&self, a: V, b: V) -> Expr {
130        self.into_expr().between(Expr::val(a), Expr::val(b))
131    }
132
133    /// Builds `col NOT BETWEEN a AND b`.
134    fn not_between<V: Into<Value>>(&self, a: V, b: V) -> Expr {
135        self.into_expr().not_between(Expr::val(a), Expr::val(b))
136    }
137
138    /// Builds `col LIKE pattern`.
139    fn like(&self, pattern: &str) -> Expr {
140        self.into_expr().like(pattern)
141    }
142
143    /// Builds `col NOT LIKE pattern`.
144    fn not_like(&self, pattern: &str) -> Expr {
145        self.into_expr().not_like(pattern)
146    }
147
148    /// Builds `col LIKE 's%'`.
149    fn starts_with(&self, s: &str) -> Expr {
150        self.into_expr().starts_with(s)
151    }
152
153    /// Builds `col LIKE '%s'`.
154    fn ends_with(&self, s: &str) -> Expr {
155        self.into_expr().ends_with(s)
156    }
157
158    /// Builds `col LIKE '%s%'`.
159    fn contains(&self, s: &str) -> Expr {
160        self.into_expr().contains(s)
161    }
162
163    /// Builds `col IS NULL`.
164    fn is_null(&self) -> Expr {
165        self.into_expr().is_null()
166    }
167
168    /// Builds `col IS NOT NULL`.
169    fn is_not_null(&self) -> Expr {
170        self.into_expr().is_not_null()
171    }
172
173    /// Builds `col IN (values)`.
174    fn is_in<V: Into<Value>, I: IntoIterator<Item = V>>(&self, values: I) -> Expr {
175        self.into_expr().is_in(values)
176    }
177
178    /// Builds `col NOT IN (values)`.
179    fn is_not_in<V: Into<Value>, I: IntoIterator<Item = V>>(&self, values: I) -> Expr {
180        self.into_expr().is_not_in(values)
181    }
182
183    /// Builds `col IN (subquery)`.
184    fn in_subquery(&self, select: turso_sql::Select) -> Expr {
185        self.into_expr().in_subquery(select)
186    }
187
188    /// Builds `col NOT IN (subquery)`.
189    fn not_in_subquery(&self, select: turso_sql::Select) -> Expr {
190        self.into_expr().not_in_subquery(select)
191    }
192
193    /// Builds `col = other.col`, comparing two columns rather than a column
194    /// and a value, for example across a join.
195    fn eq_col<C: ColumnTrait>(&self, other: C) -> Expr {
196        self.into_expr().eq(other.into_expr())
197    }
198
199    /// Builds `col MATCH query` for full-text search.
200    fn matches(&self, query: &str) -> Expr {
201        self.into_expr().matches(query)
202    }
203
204    /// Builds `IFNULL(col, v)`.
205    fn if_null<V: Into<Value>>(&self, v: V) -> Expr {
206        turso_sql::Func::if_null(self.into_expr(), Expr::val(v))
207    }
208
209    /// Builds `MAX(col)`.
210    fn max(&self) -> Expr {
211        turso_sql::Func::max(self.into_expr())
212    }
213
214    /// Builds `MIN(col)`.
215    fn min(&self) -> Expr {
216        turso_sql::Func::min(self.into_expr())
217    }
218
219    /// Builds `SUM(col)`.
220    fn sum(&self) -> Expr {
221        turso_sql::Func::sum(self.into_expr())
222    }
223
224    /// Builds `COUNT(col)`.
225    fn count(&self) -> Expr {
226        turso_sql::Func::count(self.into_expr())
227    }
228
229    /// Builds `AVG(col)`.
230    fn avg(&self) -> Expr {
231        turso_sql::Func::avg(self.into_expr())
232    }
233}