Skip to main content

drizzle_types/sqlite/ddl/
view.rs

1//! `SQLite` views: [`ViewDef`] (const) and [`View`] (runtime).
2//!
3//! Mirrors drizzle-kit's
4//! [`ddl.ts`](https://github.com/drizzle-team/drizzle-orm/blob/beta/drizzle-kit/src/dialects/sqlite/ddl.ts).
5
6use crate::alloc_prelude::*;
7
8#[cfg(feature = "serde")]
9use crate::serde_helpers::{cow_from_string, cow_option_from_string};
10
11// =============================================================================
12// Const-friendly Definition Type
13// =============================================================================
14
15/// A view that can be built in a `const`.
16///
17/// A view with `is_existing` set is managed outside drizzle: it is referenced
18/// but never created or dropped.
19///
20/// # Examples
21///
22/// ```
23/// use drizzle_types::sqlite::ddl::ViewDef;
24///
25/// const VIEW: ViewDef = ViewDef::new("active_users")
26///     .definition("SELECT * FROM users WHERE active = 1");
27/// ```
28#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
29pub struct ViewDef {
30    /// View name
31    pub name: &'static str,
32    /// The view's `SELECT` statement (the part after `AS`)
33    pub definition: Option<&'static str>,
34    /// Whether this is an existing view (not managed by drizzle)
35    pub is_existing: bool,
36    /// Error message if the view failed to parse/validate
37    pub error: Option<&'static str>,
38}
39
40impl ViewDef {
41    /// Creates a view with no definition.
42    #[must_use]
43    pub const fn new(name: &'static str) -> Self {
44        Self {
45            name,
46            definition: None,
47            is_existing: false,
48            error: None,
49        }
50    }
51
52    /// Sets the view's `SELECT` statement.
53    #[must_use]
54    pub const fn definition(self, sql: &'static str) -> Self {
55        Self {
56            definition: Some(sql),
57            ..self
58        }
59    }
60
61    /// Marks the view as existing outside drizzle's control.
62    #[must_use]
63    pub const fn existing(self) -> Self {
64        Self {
65            is_existing: true,
66            ..self
67        }
68    }
69
70    /// Records an error message for a view that failed to parse or validate.
71    #[must_use]
72    pub const fn error(self, error: &'static str) -> Self {
73        Self {
74            error: Some(error),
75            ..self
76        }
77    }
78
79    /// Converts to the runtime [`View`].
80    #[must_use]
81    pub const fn into_view(self) -> View {
82        View {
83            name: Cow::Borrowed(self.name),
84            definition: match self.definition {
85                Some(d) => Some(Cow::Borrowed(d)),
86                None => None,
87            },
88            is_existing: self.is_existing,
89            error: match self.error {
90                Some(e) => Some(Cow::Borrowed(e)),
91                None => None,
92            },
93        }
94    }
95}
96
97impl Default for ViewDef {
98    fn default() -> Self {
99        Self::new("")
100    }
101}
102
103// =============================================================================
104// Runtime Type for Serde
105// =============================================================================
106
107/// A view, as stored in migration snapshots.
108#[derive(Clone, Debug, PartialEq, Eq)]
109#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
110#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
111pub struct View {
112    /// View name
113    #[cfg_attr(feature = "serde", serde(deserialize_with = "cow_from_string"))]
114    pub name: Cow<'static, str>,
115
116    /// The view's `SELECT` statement (the part after `AS`)
117    #[cfg_attr(
118        feature = "serde",
119        serde(
120            default,
121            skip_serializing_if = "Option::is_none",
122            deserialize_with = "cow_option_from_string"
123        )
124    )]
125    pub definition: Option<Cow<'static, str>>,
126
127    /// Whether this is an existing view (not managed by drizzle)
128    #[cfg_attr(feature = "serde", serde(default))]
129    pub is_existing: bool,
130
131    /// Error message if the view failed to parse/validate
132    #[cfg_attr(
133        feature = "serde",
134        serde(
135            default,
136            skip_serializing_if = "Option::is_none",
137            deserialize_with = "cow_option_from_string"
138        )
139    )]
140    pub error: Option<Cow<'static, str>>,
141}
142
143impl View {
144    /// Creates a view with no definition.
145    #[must_use]
146    pub fn new(name: impl Into<Cow<'static, str>>) -> Self {
147        Self {
148            name: name.into(),
149            definition: None,
150            is_existing: false,
151            error: None,
152        }
153    }
154
155    /// Returns the view name.
156    #[inline]
157    #[must_use]
158    pub fn name(&self) -> &str {
159        &self.name
160    }
161}
162
163impl Default for View {
164    fn default() -> Self {
165        Self::new("")
166    }
167}
168
169impl From<ViewDef> for View {
170    fn from(def: ViewDef) -> Self {
171        def.into_view()
172    }
173}
174
175#[cfg(test)]
176mod tests {
177    use super::*;
178
179    #[test]
180    fn test_const_view_def() {
181        const VIEW: ViewDef =
182            ViewDef::new("active_users").definition("SELECT * FROM users WHERE active = 1");
183
184        assert_eq!(VIEW.name, "active_users");
185        assert!(VIEW.definition.is_some());
186        const {
187            assert!(!VIEW.is_existing);
188        }
189    }
190
191    #[test]
192    fn test_view_def_to_view() {
193        const DEF: ViewDef = ViewDef::new("active_users").existing();
194
195        let view = DEF.into_view();
196        assert_eq!(view.name(), "active_users");
197        assert!(view.is_existing);
198    }
199}