Skip to main content

drizzle_core/traits/
table.rs

1use crate::prelude::*;
2use crate::{SQL, SQLColumnInfo, SQLParam, SQLSchema, SQLSchemaType, ToSQL};
3use core::any::Any;
4
5pub trait SQLModel<'a, V: SQLParam>: ToSQL<'a, V> {
6    /// Columns referenced by this model.
7    /// Static models can return a borrowed slice; dynamic models can allocate.
8    fn columns(&self) -> Cow<'static, [&'static dyn SQLColumnInfo]>;
9    fn values(&self) -> SQL<'a, V>;
10}
11
12/// Trait for models that support partial selection of fields
13pub trait SQLPartial<'a, Value: SQLParam> {
14    /// The type representing a partial model where all fields are optional
15    /// for selective querying
16    type Partial: SQLModel<'a, Value> + Default + 'a;
17
18    fn partial() -> Self::Partial {
19        Default::default()
20    }
21}
22
23pub trait SQLTable<'a, Type: SQLSchemaType, Value: SQLParam + 'a>:
24    SQLSchema<'a, Type, Value> + SQLTableInfo + Default + Clone
25{
26    type Select: SQLModel<'a, Value> + SQLPartial<'a, Value> + Default + 'a;
27
28    /// The type representing a model for INSERT operations on this table.
29    /// Uses PhantomData with tuple markers to track which fields are set
30    type Insert<T>: SQLModel<'a, Value> + Default;
31
32    /// The type representing a model for UPDATE operations on this table.
33    /// This would be generated by the table macro.
34    type Update: SQLModel<'a, Value> + Default + 'a;
35
36    /// The aliased version of this table for self-joins and CTEs.
37    /// For a table `Users`, this would be `AliasedUsers`.
38    type Aliased: SQLTable<'a, Type, Value>;
39
40    /// Creates an aliased version of this table with the given name.
41    /// Used for self-joins and CTEs.
42    fn alias(name: &'static str) -> Self::Aliased;
43}
44
45pub trait SQLTableInfo: Any + Send + Sync {
46    /// Unqualified table name.
47    fn name(&self) -> &str;
48
49    /// Optional schema/catalog namespace for this table.
50    fn schema(&self) -> Option<&str> {
51        None
52    }
53
54    /// Fully-qualified table name when schema is present.
55    fn qualified_name(&self) -> Cow<'static, str> {
56        match self.schema() {
57            Some(schema) => Cow::Owned(format!("{schema}.{}", self.name())),
58            None => Cow::Owned(self.name().to_string()),
59        }
60    }
61
62    fn columns(&self) -> &'static [&'static dyn SQLColumnInfo];
63    fn dependencies(&self) -> &'static [&'static dyn SQLTableInfo];
64
65    /// Lookup a column by name.
66    fn column_named(&self, name: &str) -> Option<&'static dyn SQLColumnInfo> {
67        self.columns()
68            .iter()
69            .copied()
70            .find(|col| col.name() == name)
71    }
72}
73
74// Blanket implementation for static references
75impl<T: SQLTableInfo> SQLTableInfo for &'static T {
76    fn name(&self) -> &str {
77        (*self).name()
78    }
79
80    fn schema(&self) -> Option<&str> {
81        (*self).schema()
82    }
83
84    fn qualified_name(&self) -> Cow<'static, str> {
85        (*self).qualified_name()
86    }
87
88    fn columns(&self) -> &'static [&'static dyn SQLColumnInfo] {
89        (*self).columns()
90    }
91
92    fn dependencies(&self) -> &'static [&'static dyn SQLTableInfo] {
93        (*self).dependencies()
94    }
95}
96
97impl core::fmt::Debug for dyn SQLTableInfo {
98    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
99        f.debug_struct("SQLTableInfo")
100            .field("name", &self.name())
101            .field("schema", &self.schema())
102            .field("qualified_name", &self.qualified_name())
103            .field("columns", &self.columns())
104            .finish()
105    }
106}
107
108pub trait AsTableInfo: Sized + SQLTableInfo {
109    fn as_table(&self) -> &dyn SQLTableInfo {
110        self
111    }
112}