Skip to main content

drizzle_types/postgres/ddl/
table.rs

1//! `PostgreSQL` 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, cow_option_from_string};
11
12// =============================================================================
13// Const-friendly Definition Type
14// =============================================================================
15
16/// Const-friendly table definition for compile-time schema definitions.
17#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
18pub struct TableDef {
19    /// Schema name
20    pub schema: &'static str,
21    /// Table name
22    pub name: &'static str,
23    /// Create as an UNLOGGED table?
24    pub is_unlogged: bool,
25    /// Create as a TEMPORARY table?
26    pub is_temporary: bool,
27    /// Parent table for INHERITS clause.
28    pub inherits: Option<&'static str>,
29    /// Tablespace for the table.
30    pub tablespace: Option<&'static str>,
31    /// Is Row-Level Security enabled?
32    pub is_rls_enabled: bool,
33    /// Table comment emitted through COMMENT ON TABLE.
34    pub comment: Option<&'static str>,
35}
36
37impl TableDef {
38    /// Create a new table definition
39    #[must_use]
40    pub const fn new(schema: &'static str, name: &'static str) -> Self {
41        Self {
42            schema,
43            name,
44            is_unlogged: false,
45            is_temporary: false,
46            inherits: None,
47            tablespace: None,
48            is_rls_enabled: false,
49            comment: None,
50        }
51    }
52
53    /// Set UNLOGGED table storage.
54    #[must_use]
55    pub const fn unlogged(self) -> Self {
56        Self {
57            is_unlogged: true,
58            is_temporary: false,
59            ..self
60        }
61    }
62
63    /// Set TEMPORARY table storage.
64    #[must_use]
65    pub const fn temporary(self) -> Self {
66        Self {
67            is_temporary: true,
68            is_unlogged: false,
69            ..self
70        }
71    }
72
73    /// Set INHERITS parent table.
74    #[must_use]
75    pub const fn inherits(self, parent: &'static str) -> Self {
76        Self {
77            inherits: Some(parent),
78            ..self
79        }
80    }
81
82    /// Set table tablespace.
83    #[must_use]
84    pub const fn tablespace(self, tablespace: &'static str) -> Self {
85        Self {
86            tablespace: Some(tablespace),
87            ..self
88        }
89    }
90
91    /// Set Row-Level Security enabled
92    #[must_use]
93    pub const fn rls_enabled(self) -> Self {
94        Self {
95            is_rls_enabled: true,
96            ..self
97        }
98    }
99
100    /// Set the table comment.
101    #[must_use]
102    pub const fn comment(self, comment: &'static str) -> Self {
103        Self {
104            comment: Some(comment),
105            ..self
106        }
107    }
108
109    /// Convert to runtime [`Table`] type
110    #[must_use]
111    pub const fn into_table(self) -> Table {
112        Table {
113            schema: Cow::Borrowed(self.schema),
114            name: Cow::Borrowed(self.name),
115            is_unlogged: if self.is_unlogged { Some(true) } else { None },
116            is_temporary: if self.is_temporary { Some(true) } else { None },
117            inherits: match self.inherits {
118                Some(parent) => Some(Cow::Borrowed(parent)),
119                None => None,
120            },
121            tablespace: match self.tablespace {
122                Some(tablespace) => Some(Cow::Borrowed(tablespace)),
123                None => None,
124            },
125            is_rls_enabled: if self.is_rls_enabled {
126                Some(true)
127            } else {
128                None
129            },
130            comment: match self.comment {
131                Some(comment) => Some(Cow::Borrowed(comment)),
132                None => None,
133            },
134        }
135    }
136}
137
138impl Default for TableDef {
139    fn default() -> Self {
140        Self::new("public", "")
141    }
142}
143
144// =============================================================================
145// Runtime Type for Serde
146// =============================================================================
147
148/// Runtime table entity for serde serialization.
149#[derive(Clone, Debug, PartialEq, Eq)]
150#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
151#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
152pub struct Table {
153    /// Schema name
154    #[cfg_attr(feature = "serde", serde(deserialize_with = "cow_from_string"))]
155    pub schema: Cow<'static, str>,
156
157    /// Table name
158    #[cfg_attr(feature = "serde", serde(deserialize_with = "cow_from_string"))]
159    pub name: Cow<'static, str>,
160
161    /// Create as an UNLOGGED table?
162    #[cfg_attr(
163        feature = "serde",
164        serde(default, skip_serializing_if = "Option::is_none")
165    )]
166    pub is_unlogged: Option<bool>,
167
168    /// Create as a TEMPORARY table?
169    #[cfg_attr(
170        feature = "serde",
171        serde(default, skip_serializing_if = "Option::is_none")
172    )]
173    pub is_temporary: Option<bool>,
174
175    /// Parent table for INHERITS clause.
176    #[cfg_attr(
177        feature = "serde",
178        serde(
179            default,
180            skip_serializing_if = "Option::is_none",
181            deserialize_with = "cow_option_from_string"
182        )
183    )]
184    pub inherits: Option<Cow<'static, str>>,
185
186    /// Tablespace for the table.
187    #[cfg_attr(
188        feature = "serde",
189        serde(
190            default,
191            skip_serializing_if = "Option::is_none",
192            deserialize_with = "cow_option_from_string"
193        )
194    )]
195    pub tablespace: Option<Cow<'static, str>>,
196
197    /// Is Row-Level Security enabled?
198    #[cfg_attr(
199        feature = "serde",
200        serde(default, skip_serializing_if = "Option::is_none")
201    )]
202    pub is_rls_enabled: Option<bool>,
203
204    /// Table comment emitted through COMMENT ON TABLE.
205    #[cfg_attr(
206        feature = "serde",
207        serde(
208            default,
209            skip_serializing_if = "Option::is_none",
210            deserialize_with = "cow_option_from_string"
211        )
212    )]
213    pub comment: Option<Cow<'static, str>>,
214}
215
216impl Table {
217    /// Create a new table (runtime)
218    #[must_use]
219    pub fn new(schema: impl Into<Cow<'static, str>>, name: impl Into<Cow<'static, str>>) -> Self {
220        Self {
221            schema: schema.into(),
222            name: name.into(),
223            is_unlogged: None,
224            is_temporary: None,
225            inherits: None,
226            tablespace: None,
227            is_rls_enabled: None,
228            comment: None,
229        }
230    }
231
232    /// Set UNLOGGED table storage.
233    #[must_use]
234    pub const fn unlogged(mut self) -> Self {
235        self.is_unlogged = Some(true);
236        self.is_temporary = None;
237        self
238    }
239
240    /// Set TEMPORARY table storage.
241    #[must_use]
242    pub const fn temporary(mut self) -> Self {
243        self.is_temporary = Some(true);
244        self.is_unlogged = None;
245        self
246    }
247
248    /// Set INHERITS parent table.
249    #[must_use]
250    pub fn inherits(mut self, parent: impl Into<Cow<'static, str>>) -> Self {
251        self.inherits = Some(parent.into());
252        self
253    }
254
255    /// Set table tablespace.
256    #[must_use]
257    pub fn tablespace(mut self, tablespace: impl Into<Cow<'static, str>>) -> Self {
258        self.tablespace = Some(tablespace.into());
259        self
260    }
261
262    /// Set Row-Level Security enabled
263    #[must_use]
264    pub const fn rls_enabled(mut self) -> Self {
265        self.is_rls_enabled = Some(true);
266        self
267    }
268
269    /// Set the table comment.
270    #[must_use]
271    pub fn comment(mut self, comment: impl Into<Cow<'static, str>>) -> Self {
272        self.comment = Some(comment.into());
273        self
274    }
275
276    /// Get the schema name
277    #[inline]
278    #[must_use]
279    pub fn schema(&self) -> &str {
280        &self.schema
281    }
282
283    /// Get the table name
284    #[inline]
285    #[must_use]
286    pub fn name(&self) -> &str {
287        &self.name
288    }
289}
290
291impl Default for Table {
292    fn default() -> Self {
293        Self::new("public", "")
294    }
295}
296
297impl From<TableDef> for Table {
298    fn from(def: TableDef) -> Self {
299        def.into_table()
300    }
301}
302
303#[cfg(test)]
304mod tests {
305    use super::*;
306
307    #[test]
308    fn test_const_table_def() {
309        const TABLE: TableDef = TableDef::new("public", "users").rls_enabled();
310
311        assert_eq!(TABLE.schema, "public");
312        assert_eq!(TABLE.name, "users");
313        const {
314            assert!(TABLE.is_rls_enabled);
315        }
316    }
317
318    #[test]
319    fn test_table_def_storage_attrs() {
320        const TABLE: TableDef = TableDef::new("public", "events")
321            .unlogged()
322            .inherits("base_events")
323            .tablespace("fast_ssd");
324
325        const { assert!(TABLE.is_unlogged) };
326        assert_eq!(TABLE.inherits, Some("base_events"));
327        assert_eq!(TABLE.tablespace, Some("fast_ssd"));
328
329        let table = TABLE.into_table();
330        assert_eq!(table.is_unlogged, Some(true));
331        assert_eq!(table.inherits.as_deref(), Some("base_events"));
332        assert_eq!(table.tablespace.as_deref(), Some("fast_ssd"));
333    }
334
335    #[test]
336    fn test_table_def_to_table() {
337        const DEF: TableDef = TableDef::new("public", "users");
338        let table = DEF.into_table();
339        assert_eq!(table.schema(), "public");
340        assert_eq!(table.name(), "users");
341    }
342}