drizzle_core/
traits.rs

1use std::any::Any;
2mod tuple;
3
4#[cfg(any(feature = "rusqlite", feature = "libsql", feature = "turso"))]
5use crate::error::DrizzleError;
6use crate::{SQL, SQLSchemaType, ToSQL};
7
8/// A marker trait for types that can be used as SQL parameters.
9///
10/// This trait is used as a bound on the parameter type in SQL fragments.
11/// It ensures type safety when building SQL queries with parameters.
12pub trait SQLParam: Clone + std::fmt::Debug {}
13
14// Implement SQLParam for common types
15impl SQLParam for String {}
16impl SQLParam for &str {}
17impl SQLParam for i8 {}
18impl SQLParam for i16 {}
19impl SQLParam for i32 {}
20impl SQLParam for i64 {}
21impl SQLParam for isize {}
22impl SQLParam for u8 {}
23impl SQLParam for u16 {}
24impl SQLParam for u32 {}
25impl SQLParam for u64 {}
26impl SQLParam for usize {}
27impl SQLParam for f32 {}
28impl SQLParam for f64 {}
29impl SQLParam for bool {}
30impl<T: SQLParam> SQLParam for Option<T> {}
31impl<T: SQLParam> SQLParam for Vec<T> {}
32impl<T: SQLParam> SQLParam for &[T] {}
33impl<T: SQLParam> SQLParam for &T {}
34impl<const N: usize, T: SQLParam> SQLParam for [T; N] {}
35
36pub trait SQLSchema<'a, T, V: SQLParam + 'a>: ToSQL<'a, V> {
37    const NAME: &'a str;
38    const TYPE: T;
39    const SQL: SQL<'a, V>;
40
41    // Optional runtime SQL generation for tables with dynamic constraints
42    fn sql(&self) -> SQL<'a, V> {
43        Self::SQL
44    }
45}
46
47#[cfg(feature = "libsql")]
48use libsql::Connection;
49#[cfg(feature = "rusqlite")]
50use rusqlite::Connection;
51#[cfg(feature = "turso")]
52use turso::Connection;
53
54pub trait SQLColumnInfo: Any + Send + Sync {
55    fn is_not_null(&self) -> bool;
56    fn is_primary_key(&self) -> bool;
57    fn is_unique(&self) -> bool;
58    fn name(&self) -> &str;
59    fn r#type(&self) -> &str;
60    fn table(&self) -> &dyn SQLTableInfo;
61    fn has_default(&self) -> bool;
62}
63
64pub trait SQLSchemaImpl: Any + Send + Sync {
65    #[cfg(feature = "rusqlite")]
66    fn create(&self, conn: &Connection) -> Result<(), DrizzleError>;
67    #[cfg(any(feature = "turso", feature = "libsql"))]
68    fn create(&self, conn: &Connection) -> impl Future<Output = Result<(), DrizzleError>>;
69}
70
71pub trait AsColumnInfo: SQLColumnInfo {
72    fn as_column(&self) -> &dyn SQLColumnInfo;
73}
74
75impl<T: SQLColumnInfo> AsColumnInfo for T {
76    fn as_column(&self) -> &dyn SQLColumnInfo {
77        self
78    }
79}
80
81pub trait SQLColumn<'a, Value: SQLParam + 'a>:
82    SQLColumnInfo + Default + SQLSchema<'a, &'a str, Value>
83{
84    type Table: SQLTable<'a, Value>;
85    type Type: TryInto<Value>;
86
87    const PRIMARY_KEY: bool = false;
88    const NOT_NULL: bool = false;
89    const UNIQUE: bool = false;
90    const DEFAULT: Option<Self::Type> = None;
91
92    fn default_fn(&'a self) -> Option<impl Fn() -> Self::Type> {
93        None::<fn() -> Self::Type>
94    }
95}
96
97impl std::fmt::Debug for dyn SQLColumnInfo {
98    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99        f.debug_struct("SQLColumnInfo")
100            .field("name", &self.name())
101            .field("type", &self.r#type())
102            .field("not_null", &self.is_not_null())
103            .field("primary_key", &self.is_primary_key())
104            .field("unique", &self.is_unique())
105            .field("table", &self.table())
106            .field("has_default", &self.has_default())
107            .finish()
108    }
109}
110
111pub trait SQLModel<'a, V: SQLParam>: ToSQL<'a, V> {
112    fn columns(&self) -> Box<[&'static dyn SQLColumnInfo]>;
113    fn values(&self) -> crate::SQL<'a, V>;
114}
115
116/// Trait for models that support partial selection of fields
117pub trait SQLPartial<'a, Value: SQLParam> {
118    /// The type representing a partial model where all fields are optional
119    /// for selective querying
120    type Partial: ToSQL<'a, Value> + SQLModel<'a, Value> + Default + 'a;
121
122    fn partial() -> Self::Partial {
123        Default::default()
124    }
125}
126
127pub trait SQLTable<'a, Value: SQLParam + 'a>:
128    SQLSchema<'a, SQLSchemaType, Value> + SQLTableInfo + Default + Clone
129{
130    /// The type representing a model for SELECT operations on this table.
131    /// This would be generated by the table macro.
132    #[cfg(not(any(feature = "turso", feature = "libsql")))]
133    type Select: SQLModel<'a, Value> + SQLPartial<'a, Value> + Default + 'a;
134    #[cfg(any(feature = "turso", feature = "libsql"))]
135    type Select: SQLModel<'a, Value> + Default + 'a;
136
137    /// The type representing a model for INSERT operations on this table.
138    /// Uses PhantomData with tuple markers to track which fields are set
139    type Insert<T>: SQLModel<'a, Value> + Default;
140
141    /// The type representing a model for UPDATE operations on this table.
142    /// This would be generated by the table macro.
143    type Update: SQLModel<'a, Value> + Default + 'a;
144}
145
146pub trait SQLTableInfo: Any + Send + Sync {
147    fn name(&self) -> &str;
148    fn r#type(&self) -> SQLSchemaType;
149    fn columns(&self) -> Box<[&'static dyn SQLColumnInfo]>;
150    fn without_rowid(&self) -> bool;
151    fn strict(&self) -> bool;
152}
153
154impl std::fmt::Debug for dyn SQLTableInfo {
155    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156        f.debug_struct("SQLTableInfo")
157            .field("name", &self.name())
158            .field("type", &self.r#type())
159            .field("columns", &self.columns())
160            .finish()
161    }
162}
163
164pub trait AsTableInfo: SQLTableInfo {
165    fn as_table(&self) -> &dyn SQLTableInfo;
166}
167
168impl<T: SQLTableInfo> AsTableInfo for T {
169    fn as_table(&self) -> &dyn SQLTableInfo {
170        self
171    }
172}
173
174/// Marker trait for types that can be compared in SQL expressions.
175pub trait SQLComparable<'a, V: SQLParam, Rhs> {}
176
177pub trait SQLAlias {
178    /// Gets the alias name for this table
179    fn alias(&self) -> &'static str;
180}
181
182// ============================================================================
183// SQLComparable Implementations
184// ============================================================================
185/// Column-to-Column comparison (most specific, type-safe)
186/// Only allows comparisons between columns with the same underlying type
187// impl<'a, V, L, R, T> SQLComparable<'a, V, R> for L
188// where
189//     V: SQLParam + 'a,
190//     L: SQLColumn<'a, V, Type = T> + ToSQL<'a, V>,
191//     R: SQLColumn<'a, V, Type = T> + ToSQL<'a, V>,
192//     T: PartialEq, // Ensures the underlying types can be compared
193// {
194// }
195/// Column-to-Value comparison (type-safe)
196/// Only allows comparisons between a column and a value of the same type
197// impl<'a, V, C, T> SQLComparable<'a, V, T> for C
198// where
199//     V: SQLParam + 'a,
200//     C: SQLColumn<'a, V, Type = T> + ToSQL<'a, V>,
201//     T: Into<V> + ToSQL<'a, V> + PartialEq,
202// {
203// }
204/// Value-to-Value comparison (most general, permissive)
205/// Allows any two values that can convert to SQL parameters
206// impl<'a, V, L, R> SQLComparable<'a, V, R> for L
207// where
208//     V: SQLParam + 'a,
209//     L: Into<V> + ToSQL<'a, V>,
210//     R: Into<V> + ToSQL<'a, V>,
211// {
212// }
213/// Blanket implementation for all compatible types
214/// This covers all three cases:
215/// 1. Column-to-Column (when both L and R are SQLColumn with same Type)
216/// 2. Column-to-Value (when L is SQLColumn and R converts to same type)  
217/// 3. Value-to-Value (when both convert to SQL values)
218impl<'a, V, L, R> SQLComparable<'a, V, R> for L
219where
220    V: SQLParam + 'a,
221    L: ToSQL<'a, V>,
222    R: ToSQL<'a, V> + Into<V>,
223{
224}
225
226#[cfg(test)]
227mod tests {
228    use super::*;
229
230    #[test]
231    fn test_sql_param_implementations() {
232        // Test that common types implement SQLParam
233        fn assert_sql_param<T: SQLParam>(_: &T) {}
234
235        assert_sql_param(&String::new());
236        assert_sql_param(&"test");
237        assert_sql_param(&42i32);
238        assert_sql_param(&42i64);
239        assert_sql_param(&true);
240        assert_sql_param(&Some(42));
241        assert_sql_param(&None::<i32>);
242        assert_sql_param(&vec![1, 2, 3]);
243    }
244
245    #[test]
246    fn test_option_sql_param() {
247        fn accepts_sql_param<T: SQLParam>(_: T) {}
248
249        accepts_sql_param(Some(42i32));
250        accepts_sql_param(None::<String>);
251        accepts_sql_param(Some("test".to_string()));
252    }
253
254    #[test]
255    fn test_vec_sql_param() {
256        fn accepts_sql_param<T: SQLParam>(_: T) {}
257
258        accepts_sql_param(vec![1, 2, 3]);
259        accepts_sql_param(vec!["a", "b"]);
260        accepts_sql_param(Vec::<i32>::new());
261    }
262}
263
264/// Marker trait indicating that a table `T` is part of a schema represented by the marker type `S`.
265pub trait IsInSchema<S> {}
266
267/// Trait for types that represent database indexes.
268/// Implemented by tuple structs like `struct UserEmailIdx(User::email);`
269pub trait SQLIndex<'a, Value: SQLParam + 'a>: ToSQL<'a, Value> {
270    /// The table type this index is associated with
271    type Table: SQLTable<'a, Value>;
272
273    /// The name of this index (for DROP INDEX statements)
274    fn name(&self) -> &'static str;
275
276    /// Whether this is a unique index
277    fn is_unique(&self) -> bool {
278        false
279    }
280}