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