Skip to main content

drizzle_types/postgres/ddl/
table.rs

1//! `PostgreSQL` tables: [`TableDef`] (const) and [`Table`] (runtime).
2
3use crate::alloc_prelude::*;
4
5#[cfg(feature = "serde")]
6use crate::serde_helpers::{cow_from_string, cow_option_from_string};
7
8// =============================================================================
9// Const-friendly Definition Type
10// =============================================================================
11
12/// A table definition that can be built in a `const`. Columns and
13/// constraints are separate entities that name the table.
14#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
15pub struct TableDef {
16    /// Schema name
17    pub schema: &'static str,
18    /// Table name
19    pub name: &'static str,
20    /// Create as an UNLOGGED table?
21    pub is_unlogged: bool,
22    /// Create as a TEMPORARY table?
23    pub is_temporary: bool,
24    /// Parent table for INHERITS clause.
25    pub inherits: Option<&'static str>,
26    /// Tablespace for the table.
27    pub tablespace: Option<&'static str>,
28    /// Is Row-Level Security enabled?
29    pub is_rls_enabled: bool,
30    /// Table comment emitted through COMMENT ON TABLE.
31    pub comment: Option<&'static str>,
32}
33
34impl TableDef {
35    /// Creates a plain table in `schema`.
36    #[must_use]
37    pub const fn new(schema: &'static str, name: &'static str) -> Self {
38        Self {
39            schema,
40            name,
41            is_unlogged: false,
42            is_temporary: false,
43            inherits: None,
44            tablespace: None,
45            is_rls_enabled: false,
46            comment: None,
47        }
48    }
49
50    /// Makes the table `UNLOGGED` (and not `TEMPORARY`).
51    #[must_use]
52    pub const fn unlogged(self) -> Self {
53        Self {
54            is_unlogged: true,
55            is_temporary: false,
56            ..self
57        }
58    }
59
60    /// Makes the table `TEMPORARY` (and not `UNLOGGED`).
61    #[must_use]
62    pub const fn temporary(self) -> Self {
63        Self {
64            is_temporary: true,
65            is_unlogged: false,
66            ..self
67        }
68    }
69
70    /// Sets the `INHERITS` parent table.
71    #[must_use]
72    pub const fn inherits(self, parent: &'static str) -> Self {
73        Self {
74            inherits: Some(parent),
75            ..self
76        }
77    }
78
79    /// Sets the `TABLESPACE`.
80    #[must_use]
81    pub const fn tablespace(self, tablespace: &'static str) -> Self {
82        Self {
83            tablespace: Some(tablespace),
84            ..self
85        }
86    }
87
88    /// Enables row-level security.
89    #[must_use]
90    pub const fn rls_enabled(self) -> Self {
91        Self {
92            is_rls_enabled: true,
93            ..self
94        }
95    }
96
97    /// Sets the table comment.
98    #[must_use]
99    pub const fn comment(self, comment: &'static str) -> Self {
100        Self {
101            comment: Some(comment),
102            ..self
103        }
104    }
105
106    /// Converts to the runtime [`Table`].
107    #[must_use]
108    pub const fn into_table(self) -> Table {
109        Table {
110            schema: Cow::Borrowed(self.schema),
111            name: Cow::Borrowed(self.name),
112            is_unlogged: if self.is_unlogged { Some(true) } else { None },
113            is_temporary: if self.is_temporary { Some(true) } else { None },
114            inherits: match self.inherits {
115                Some(parent) => Some(Cow::Borrowed(parent)),
116                None => None,
117            },
118            tablespace: match self.tablespace {
119                Some(tablespace) => Some(Cow::Borrowed(tablespace)),
120                None => None,
121            },
122            is_rls_enabled: if self.is_rls_enabled {
123                Some(true)
124            } else {
125                None
126            },
127            comment: match self.comment {
128                Some(comment) => Some(Cow::Borrowed(comment)),
129                None => None,
130            },
131        }
132    }
133}
134
135impl Default for TableDef {
136    fn default() -> Self {
137        Self::new("public", "")
138    }
139}
140
141// =============================================================================
142// Runtime Type for Serde
143// =============================================================================
144
145/// A table, as stored in migration snapshots.
146///
147/// The flags are `Option<bool>` to match the snapshot format; `None` means
148/// unset.
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    /// Creates a plain table in `schema`.
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    /// Makes the table `UNLOGGED` (and not `TEMPORARY`).
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    /// Makes the table `TEMPORARY` (and not `UNLOGGED`).
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    /// Sets the `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    /// Sets the `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    /// Enables row-level security.
263    #[must_use]
264    pub const fn rls_enabled(mut self) -> Self {
265        self.is_rls_enabled = Some(true);
266        self
267    }
268
269    /// Sets 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    /// Returns the schema name.
277    #[inline]
278    #[must_use]
279    pub fn schema(&self) -> &str {
280        &self.schema
281    }
282
283    /// Returns 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}