Skip to main content

drizzle_core/traits/
table.rs

1use crate::prelude::*;
2use crate::{ColumnRef, SQL, SQLParam, SQLSchema, SQLSchemaType, TableRef, ToSQL};
3use core::marker::PhantomData;
4use core::ops::Deref;
5
6/// Marker: no fields have been set on this update model.
7pub struct Empty;
8/// Marker: at least one field has been set on this update model.
9pub struct NonEmpty;
10
11/// Type-level name marker used for strongly-typed aliases/CTEs.
12pub trait Tag {
13    const NAME: &'static str;
14}
15
16/// Define a [`Tag`] in one line.
17///
18/// ```
19/// # use drizzle_core::tag;
20/// tag!(UsersAlias, "u");
21///
22/// let u = UsersAlias; // zero-sized, used as a type parameter
23/// assert_eq!(<UsersAlias as drizzle_core::Tag>::NAME, "u");
24/// ```
25///
26/// Visibility is supported:
27///
28/// ```
29/// # use drizzle_core::tag;
30/// tag!(pub MyTag, "my_tag");
31/// ```
32#[macro_export]
33macro_rules! tag {
34    ($vis:vis $name:ident, $sql_name:expr) => {
35        $vis struct $name;
36        impl $crate::Tag for $name {
37            const NAME: &'static str = $sql_name;
38        }
39    };
40}
41
42/// Generic typed wrapper that binds a value to a compile-time [`Tag`].
43#[derive(Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
44pub struct Tagged<T, Name: Tag> {
45    inner: T,
46    _tag: PhantomData<fn() -> Name>,
47}
48
49impl<T: Copy, Name: Tag> Copy for Tagged<T, Name> {}
50
51impl<T: Copy, Name: Tag> Clone for Tagged<T, Name> {
52    fn clone(&self) -> Self {
53        *self
54    }
55}
56
57impl<T, Name: Tag> Tagged<T, Name> {
58    pub const fn new(inner: T) -> Self {
59        Self {
60            inner,
61            _tag: PhantomData,
62        }
63    }
64
65    pub fn into_inner(self) -> T {
66        self.inner
67    }
68}
69
70impl<T, Name: Tag> Deref for Tagged<T, Name> {
71    type Target = T;
72
73    fn deref(&self) -> &Self::Target {
74        &self.inner
75    }
76}
77
78#[diagnostic::on_unimplemented(
79    message = "`{Self}` is not a SQL model (Select, Insert, or Update)",
80    label = "this type cannot be used as a query model"
81)]
82pub trait SQLModel<'a, V: SQLParam>: ToSQL<'a, V> {
83    /// Columns referenced by this model.
84    /// Static models can return a borrowed slice; dynamic models can allocate.
85    fn columns(&self) -> Cow<'static, [ColumnRef]>;
86    fn values(&self) -> SQL<'a, V>;
87}
88
89/// Trait for models that support partial selection of fields
90#[diagnostic::on_unimplemented(
91    message = "`{Self}` does not support partial field selection",
92    label = "this table's Select model does not implement SQLPartial"
93)]
94pub trait SQLPartial<'a, Value: SQLParam> {
95    /// The type representing a partial model where all fields are optional
96    /// for selective querying
97    type Partial: SQLModel<'a, Value> + Default + 'a;
98
99    #[must_use]
100    fn partial() -> Self::Partial {
101        Default::default()
102    }
103}
104
105#[diagnostic::on_unimplemented(
106    message = "`{Self}` is not a SQL table for this dialect",
107    label = "ensure this type was derived with #[SQLiteTable] or #[PostgresTable]"
108)]
109pub trait SQLTable<'a, Type: SQLSchemaType, Value: SQLParam + 'a>:
110    SQLSchema<'a, Type, Value> + SQLTableInfo + Default + Clone + Copy
111{
112    type Select: SQLModel<'a, Value> + SQLPartial<'a, Value> + 'a;
113    type ForeignKeys;
114    type PrimaryKey;
115    type Constraints;
116
117    /// The type representing a model for INSERT operations on this table.
118    /// Uses `PhantomData` with tuple markers to track which fields are set
119    type Insert<T>: SQLModel<'a, Value> + Default;
120
121    /// The type representing a model for UPDATE operations on this table.
122    /// This would be generated by the table macro.
123    type Update: SQLModel<'a, Value> + 'a;
124
125    /// The aliased version of this table for self-joins and CTEs.
126    type Aliased<Name: Tag + 'static>: SQLTable<'a, Type, Value>;
127
128    /// Creates a strongly-typed alias using the compile-time [`Tag`] name.
129    fn alias<Name: Tag + 'static>() -> Self::Aliased<Name>;
130}
131
132/// Compile-time table metadata.
133///
134/// Provides table name, schema, columns, keys, and constraints as associated
135/// constants. Implementing this trait automatically provides [`SQLTableInfo`]
136/// via a blanket implementation.
137pub trait DrizzleTable: Send + Sync + 'static {
138    /// Unqualified table name.
139    const NAME: &'static str;
140
141    /// Fully-qualified table name (e.g. `schema.table`).
142    const QUALIFIED_NAME: &'static str;
143
144    /// Schema namespace, if any.
145    const SCHEMA: Option<&'static str> = None;
146
147    /// Names of tables this table depends on via foreign keys.
148    const DEPENDENCY_NAMES: &'static [&'static str] = &[];
149
150    /// Full table metadata as a const Copy struct.
151    const TABLE_REF: TableRef;
152}
153
154/// Blanket: any `DrizzleTable` automatically satisfies `SQLTableInfo`.
155impl<T: DrizzleTable> SQLTableInfo for T {
156    fn name(&self) -> &'static str {
157        T::NAME
158    }
159
160    fn schema(&self) -> Option<&'static str> {
161        T::SCHEMA
162    }
163
164    fn qualified_name(&self) -> Cow<'static, str> {
165        Cow::Borrowed(T::QUALIFIED_NAME)
166    }
167}
168
169impl<'a, Type, Value, T> SQLTable<'a, Type, Value> for &T
170where
171    Type: SQLSchemaType,
172    Value: SQLParam + 'a,
173    T: SQLTable<'a, Type, Value>,
174    for<'r> &'r T: SQLSchema<'a, Type, Value> + SQLTableInfo + Default + Clone,
175{
176    type Select = T::Select;
177    type ForeignKeys = T::ForeignKeys;
178    type PrimaryKey = T::PrimaryKey;
179    type Constraints = T::Constraints;
180    type Insert<I> = T::Insert<I>;
181    type Update = T::Update;
182    type Aliased<Name: Tag + 'static> = T::Aliased<Name>;
183
184    fn alias<Name: Tag + 'static>() -> Self::Aliased<Name> {
185        T::alias::<Name>()
186    }
187}
188
189#[diagnostic::on_unimplemented(
190    message = "`{Self}` does not implement SQLTableInfo",
191    label = "ensure this type was derived with #[SQLiteTable] or #[PostgresTable]"
192)]
193pub trait SQLTableInfo: Send + Sync {
194    /// Unqualified table name.
195    fn name(&self) -> &'static str;
196
197    /// Schema namespace for this table, if any.
198    fn schema(&self) -> Option<&'static str> {
199        None
200    }
201
202    /// Fully-qualified table name.
203    fn qualified_name(&self) -> Cow<'static, str> {
204        self.schema().map_or_else(
205            || Cow::Borrowed(self.name()),
206            |schema| Cow::Owned(format!("{schema}.{}", self.name())),
207        )
208    }
209}
210
211impl core::fmt::Debug for dyn SQLTableInfo {
212    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
213        f.debug_struct("SQLTableInfo")
214            .field("name", &self.name())
215            .field("schema", &self.schema())
216            .finish()
217    }
218}