Skip to main content

drizzle_types/sqlite/ddl/
table.rs

1//! `SQLite` Table DDL types
2//!
3//! This module provides two complementary types:
4//! - [`TableDef`] - A const-friendly definition type for compile-time schema definitions
5//! - [`Table`] - A runtime type for serde serialization/deserialization
6
7use crate::alloc_prelude::*;
8
9#[cfg(feature = "serde")]
10use crate::serde_helpers::cow_from_string;
11
12// =============================================================================
13// Const-friendly Definition Type
14// =============================================================================
15
16/// Const-friendly table definition for compile-time schema definitions.
17///
18/// This type uses only `Copy` types (`&'static str`, `bool`) so it can be
19/// used in const contexts. Use [`TableDef::into_table`] to convert to
20/// the runtime [`Table`] type when needed.
21///
22/// # Examples
23///
24/// ```
25/// use drizzle_types::sqlite::ddl::TableDef;
26///
27/// // Fully const - can be used in const contexts
28/// const USERS: TableDef = TableDef::new("users").strict();
29///
30/// assert_eq!(USERS.name, "users");
31/// assert!(USERS.strict);
32/// ```
33#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
34pub struct TableDef {
35    /// Table name
36    pub name: &'static str,
37    /// Is this a STRICT table?
38    pub strict: bool,
39    /// Is this a WITHOUT ROWID table?
40    pub without_rowid: bool,
41}
42
43impl TableDef {
44    /// Create a new table definition
45    #[must_use]
46    pub const fn new(name: &'static str) -> Self {
47        Self {
48            name,
49            strict: false,
50            without_rowid: false,
51        }
52    }
53
54    /// Set STRICT mode
55    #[must_use]
56    pub const fn strict(self) -> Self {
57        Self {
58            name: self.name,
59            strict: true,
60            without_rowid: self.without_rowid,
61        }
62    }
63
64    /// Set WITHOUT ROWID mode
65    #[must_use]
66    pub const fn without_rowid(self) -> Self {
67        Self {
68            name: self.name,
69            strict: self.strict,
70            without_rowid: true,
71        }
72    }
73
74    /// Convert to runtime [`Table`] type
75    #[must_use]
76    pub const fn into_table(self) -> Table {
77        Table {
78            name: Cow::Borrowed(self.name),
79            strict: self.strict,
80            without_rowid: self.without_rowid,
81        }
82    }
83}
84
85impl Default for TableDef {
86    fn default() -> Self {
87        Self::new("")
88    }
89}
90
91// =============================================================================
92// Runtime Type for Serde
93// =============================================================================
94
95/// Runtime table entity for serde serialization.
96///
97/// This type uses `Cow<'static, str>` to support both borrowed and owned strings,
98/// making it suitable for JSON serialization/deserialization.
99///
100/// For compile-time definitions, use [`TableDef`] instead.
101///
102/// # Examples
103///
104/// ## From `TableDef`
105///
106/// ```
107/// use drizzle_types::sqlite::ddl::{TableDef, Table};
108///
109/// const DEF: TableDef = TableDef::new("users").strict();
110/// let table: Table = DEF.into_table();
111/// assert_eq!(table.name(), "users");
112/// ```
113///
114/// ## Runtime construction
115///
116/// ```
117/// use drizzle_types::sqlite::ddl::Table;
118///
119/// let table = Table::new("dynamic_table");
120/// ```
121#[derive(Clone, Debug, PartialEq, Eq)]
122#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
123#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
124pub struct Table {
125    /// Table name
126    #[cfg_attr(feature = "serde", serde(deserialize_with = "cow_from_string"))]
127    pub name: Cow<'static, str>,
128
129    /// Is this a STRICT table?
130    #[cfg_attr(
131        feature = "serde",
132        serde(default, skip_serializing_if = "core::ops::Not::not")
133    )]
134    pub strict: bool,
135
136    /// Is this a WITHOUT ROWID table?
137    #[cfg_attr(
138        feature = "serde",
139        serde(default, skip_serializing_if = "core::ops::Not::not")
140    )]
141    pub without_rowid: bool,
142}
143
144impl Table {
145    /// Create a new table (runtime)
146    #[must_use]
147    pub fn new(name: impl Into<Cow<'static, str>>) -> Self {
148        Self {
149            name: name.into(),
150            strict: false,
151            without_rowid: false,
152        }
153    }
154
155    /// Set STRICT mode
156    #[must_use]
157    pub const fn strict(mut self) -> Self {
158        self.strict = true;
159        self
160    }
161
162    /// Set WITHOUT ROWID mode
163    #[must_use]
164    pub const fn without_rowid(mut self) -> Self {
165        self.without_rowid = true;
166        self
167    }
168
169    /// Get the table name as a string slice
170    #[inline]
171    #[must_use]
172    pub fn name(&self) -> &str {
173        &self.name
174    }
175}
176
177impl Default for Table {
178    fn default() -> Self {
179        Self::new("")
180    }
181}
182
183impl From<TableDef> for Table {
184    fn from(def: TableDef) -> Self {
185        def.into_table()
186    }
187}
188
189#[cfg(test)]
190mod tests {
191    use super::*;
192
193    #[test]
194    fn test_const_table_def() {
195        const TABLE: TableDef = TableDef::new("users").strict().without_rowid();
196        assert_eq!(TABLE.name, "users");
197        const {
198            assert!(TABLE.strict);
199        }
200        const {
201            assert!(TABLE.without_rowid);
202        }
203    }
204
205    #[test]
206    fn test_table_def_to_table() {
207        const DEF: TableDef = TableDef::new("users").strict();
208        let table = DEF.into_table();
209        assert_eq!(table.name(), "users");
210        assert!(table.strict);
211    }
212
213    #[test]
214    fn test_runtime_table() {
215        let table = Table::new("posts").strict();
216        assert_eq!(table.name(), "posts");
217        assert!(table.strict);
218        assert!(!table.without_rowid);
219    }
220
221    #[cfg(feature = "serde")]
222    #[test]
223    fn test_serde_roundtrip() {
224        let table = Table::new("users").strict();
225        let json = serde_json::to_string(&table).unwrap();
226        let parsed: Table = serde_json::from_str(&json).unwrap();
227        assert_eq!(parsed.name(), "users");
228        assert!(parsed.strict);
229    }
230}