scylladb_parse/statements/
views.rs

1use super::*;
2use crate::PrimaryKey;
3
4#[derive(ParseFromStr, Clone, Debug, TryInto, From, ToTokens, PartialEq)]
5#[parse_via(TaggedMaterializedViewStatement)]
6pub enum MaterializedViewStatement {
7    Create(CreateMaterializedViewStatement),
8    Alter(AlterMaterializedViewStatement),
9    Drop(DropMaterializedViewStatement),
10}
11
12impl TryFrom<TaggedMaterializedViewStatement> for MaterializedViewStatement {
13    type Error = anyhow::Error;
14    fn try_from(value: TaggedMaterializedViewStatement) -> Result<Self, Self::Error> {
15        Ok(match value {
16            TaggedMaterializedViewStatement::Create(s) => MaterializedViewStatement::Create(s.try_into()?),
17            TaggedMaterializedViewStatement::Alter(s) => MaterializedViewStatement::Alter(s.try_into()?),
18            TaggedMaterializedViewStatement::Drop(s) => MaterializedViewStatement::Drop(s.try_into()?),
19        })
20    }
21}
22
23#[derive(ParseFromStr, Clone, Debug, TryInto, From, ToTokens, PartialEq)]
24#[tokenize_as(MaterializedViewStatement)]
25pub enum TaggedMaterializedViewStatement {
26    Create(TaggedCreateMaterializedViewStatement),
27    Alter(TaggedAlterMaterializedViewStatement),
28    Drop(TaggedDropMaterializedViewStatement),
29}
30
31impl Parse for TaggedMaterializedViewStatement {
32    type Output = Self;
33    fn parse(s: &mut StatementStream<'_>) -> anyhow::Result<Self::Output> {
34        Ok(if s.check::<CREATE>() {
35            Self::Create(s.parse()?)
36        } else if s.check::<ALTER>() {
37            Self::Alter(s.parse()?)
38        } else if s.check::<DROP>() {
39            Self::Drop(s.parse()?)
40        } else {
41            anyhow::bail!("Expected a materialized view statement, found {}", s.info())
42        })
43    }
44}
45
46impl Display for MaterializedViewStatement {
47    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
48        match self {
49            Self::Create(stmt) => stmt.fmt(f),
50            Self::Alter(stmt) => stmt.fmt(f),
51            Self::Drop(stmt) => stmt.fmt(f),
52        }
53    }
54}
55
56#[derive(ParseFromStr, Builder, Clone, Debug, ToTokens, PartialEq)]
57#[parse_via(TaggedCreateMaterializedViewStatement)]
58#[builder(setter(strip_option))]
59pub struct CreateMaterializedViewStatement {
60    #[builder(setter(name = "set_if_not_exists"), default)]
61    pub if_not_exists: bool,
62    #[builder(setter(into))]
63    pub name: KeyspaceQualifiedName,
64    pub select_statement: SelectStatement,
65    #[builder(setter(into))]
66    pub primary_key: PrimaryKey,
67    #[builder(default)]
68    pub table_opts: Option<TableOpts>,
69}
70
71impl TryFrom<TaggedCreateMaterializedViewStatement> for CreateMaterializedViewStatement {
72    type Error = anyhow::Error;
73    fn try_from(value: TaggedCreateMaterializedViewStatement) -> Result<Self, Self::Error> {
74        Ok(Self {
75            if_not_exists: value.if_not_exists,
76            name: value.name.try_into()?,
77            select_statement: value.select_statement.into_value()?.try_into()?,
78            primary_key: value.primary_key.into_value()?,
79            table_opts: value.table_opts.map(|v| v.into_value()).transpose()?,
80        })
81    }
82}
83
84#[derive(ParseFromStr, Builder, Clone, Debug, ToTokens, PartialEq)]
85#[tokenize_as(CreateMaterializedViewStatement)]
86#[builder(setter(strip_option))]
87pub struct TaggedCreateMaterializedViewStatement {
88    #[builder(setter(name = "set_if_not_exists"), default)]
89    pub if_not_exists: bool,
90    pub name: TaggedKeyspaceQualifiedName,
91    pub select_statement: Tag<TaggedSelectStatement>,
92    pub primary_key: Tag<PrimaryKey>,
93    #[builder(default)]
94    pub table_opts: Option<Tag<TableOpts>>,
95}
96
97impl CreateMaterializedViewStatementBuilder {
98    /// Set IF NOT EXISTS on the statement.
99    /// To undo this, use `set_if_not_exists(false)`.
100    pub fn if_not_exists(&mut self) -> &mut Self {
101        self.if_not_exists.replace(true);
102        self
103    }
104}
105
106impl Parse for TaggedCreateMaterializedViewStatement {
107    type Output = Self;
108    fn parse(s: &mut StatementStream<'_>) -> anyhow::Result<Self::Output> {
109        s.parse::<(CREATE, MATERIALIZED, VIEW)>()?;
110        let mut res = TaggedCreateMaterializedViewStatementBuilder::default();
111        res.set_if_not_exists(s.parse::<Option<(IF, NOT, EXISTS)>>()?.is_some())
112            .name(s.parse()?)
113            .select_statement(s.parse::<(AS, _)>()?.1)
114            .primary_key(s.parse_from::<((PRIMARY, KEY), Parens<Tag<PrimaryKey>>)>()?.1);
115        if let Some(p) = s.parse_from::<If<WITH, Tag<TableOpts>>>()? {
116            res.table_opts(p);
117        }
118        s.parse::<Option<Semicolon>>()?;
119        Ok(res
120            .build()
121            .map_err(|e| anyhow::anyhow!("Invalid CREATE MATERIALIZED VIEW statement: {}", e))?)
122    }
123}
124
125impl Display for CreateMaterializedViewStatement {
126    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
127        write!(
128            f,
129            "CREATE MATERIALIZED VIEW{} {} AS {} PRIMARY KEY ({}){}",
130            if self.if_not_exists { " IF NOT EXISTS" } else { "" },
131            self.name,
132            self.select_statement,
133            self.primary_key,
134            match self.table_opts.as_ref() {
135                Some(opts) => format!(" WITH {}", opts),
136                None => "".to_string(),
137            }
138        )
139    }
140}
141
142#[derive(ParseFromStr, Builder, Clone, Debug, ToTokens, PartialEq)]
143#[parse_via(TaggedAlterMaterializedViewStatement)]
144pub struct AlterMaterializedViewStatement {
145    #[builder(setter(into))]
146    pub name: KeyspaceQualifiedName,
147    pub table_opts: TableOpts,
148}
149
150impl TryFrom<TaggedAlterMaterializedViewStatement> for AlterMaterializedViewStatement {
151    type Error = anyhow::Error;
152    fn try_from(value: TaggedAlterMaterializedViewStatement) -> Result<Self, Self::Error> {
153        Ok(Self {
154            name: value.name.try_into()?,
155            table_opts: value.table_opts.into_value()?,
156        })
157    }
158}
159
160#[derive(ParseFromStr, Builder, Clone, Debug, ToTokens, PartialEq)]
161#[tokenize_as(AlterMaterializedViewStatement)]
162pub struct TaggedAlterMaterializedViewStatement {
163    pub name: TaggedKeyspaceQualifiedName,
164    pub table_opts: Tag<TableOpts>,
165}
166
167impl Parse for TaggedAlterMaterializedViewStatement {
168    type Output = Self;
169    fn parse(s: &mut StatementStream<'_>) -> anyhow::Result<Self::Output> {
170        s.parse::<(ALTER, MATERIALIZED, VIEW)>()?;
171        let mut res = TaggedAlterMaterializedViewStatementBuilder::default();
172        res.name(s.parse()?).table_opts(s.parse::<(WITH, _)>()?.1);
173        s.parse::<Option<Semicolon>>()?;
174        Ok(res
175            .build()
176            .map_err(|e| anyhow::anyhow!("Invalid ALTER MATERIALIZED VIEW statement: {}", e))?)
177    }
178}
179
180impl Display for AlterMaterializedViewStatement {
181    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
182        write!(f, "ALTER MATERIALIZED VIEW {} WITH {}", self.name, self.table_opts)
183    }
184}
185
186#[derive(ParseFromStr, Builder, Clone, Debug, ToTokens, PartialEq, Eq)]
187#[parse_via(TaggedDropMaterializedViewStatement)]
188pub struct DropMaterializedViewStatement {
189    #[builder(setter(name = "set_if_exists"), default)]
190    pub if_exists: bool,
191    #[builder(setter(into))]
192    pub name: KeyspaceQualifiedName,
193}
194
195impl TryFrom<TaggedDropMaterializedViewStatement> for DropMaterializedViewStatement {
196    type Error = anyhow::Error;
197    fn try_from(value: TaggedDropMaterializedViewStatement) -> Result<Self, Self::Error> {
198        Ok(Self {
199            if_exists: value.if_exists,
200            name: value.name.try_into()?,
201        })
202    }
203}
204
205#[derive(ParseFromStr, Builder, Clone, Debug, ToTokens, PartialEq, Eq)]
206#[tokenize_as(DropMaterializedViewStatement)]
207pub struct TaggedDropMaterializedViewStatement {
208    #[builder(setter(name = "set_if_exists"), default)]
209    pub if_exists: bool,
210    pub name: TaggedKeyspaceQualifiedName,
211}
212
213impl DropMaterializedViewStatementBuilder {
214    /// Set IF EXISTS on the statement.
215    /// To undo this, use `set_if_exists(false)`.
216    pub fn if_exists(&mut self) -> &mut Self {
217        self.if_exists.replace(true);
218        self
219    }
220}
221
222impl Parse for TaggedDropMaterializedViewStatement {
223    type Output = Self;
224    fn parse(s: &mut StatementStream<'_>) -> anyhow::Result<Self::Output> {
225        s.parse::<(DROP, MATERIALIZED, VIEW)>()?;
226        let mut res = TaggedDropMaterializedViewStatementBuilder::default();
227        res.set_if_exists(s.parse::<Option<(IF, EXISTS)>>()?.is_some())
228            .name(s.parse()?);
229        s.parse::<Option<Semicolon>>()?;
230        Ok(res
231            .build()
232            .map_err(|e| anyhow::anyhow!("Invalid DROP MATERIALIZED VIEW statement: {}", e))?)
233    }
234}
235
236impl Display for DropMaterializedViewStatement {
237    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
238        write!(
239            f,
240            "DROP MATERIALIZED VIEW{} {}",
241            if self.if_exists { " IF EXISTS" } else { "" },
242            self.name
243        )
244    }
245}
246
247#[cfg(test)]
248mod test {
249    use super::*;
250    use crate::KeyspaceQualifyExt;
251
252    #[test]
253    fn test_parse_create_mv() {
254        let mut builder = CreateMaterializedViewStatementBuilder::default();
255        builder.name("my_keyspace".dot("test_mv"));
256        assert!(builder.build().is_err());
257        builder.select_statement(
258            SelectStatementBuilder::default()
259                .select_clause(SelectClause::All)
260                .from("test_table")
261                .where_clause(vec![
262                    Relation::is_not_null("column_1"),
263                    Relation::is_not_null("column 2"),
264                ])
265                .build()
266                .unwrap(),
267        );
268        assert!(builder.build().is_err());
269        builder.primary_key(vec!["column_1", "column 2"]);
270        let statement = builder.build().unwrap().to_string();
271        assert_eq!(builder.build().unwrap(), statement.parse().unwrap());
272        builder.table_opts(
273            crate::TableOptsBuilder::default()
274                .comment(r#"test comment " "#)
275                .build()
276                .unwrap(),
277        );
278        let statement = builder.build().unwrap().to_string();
279        assert_eq!(builder.build().unwrap(), statement.parse().unwrap());
280        builder.primary_key(PrimaryKey::partition_key("column_1").clustering_columns(vec!["column 2", "column_3"]));
281        let statement = builder.build().unwrap().to_string();
282        assert_eq!(builder.build().unwrap(), statement.parse().unwrap());
283    }
284
285    #[test]
286    fn test_parse_alter_mv() {
287        let mut builder = AlterMaterializedViewStatementBuilder::default();
288        builder.name("test mv");
289        assert!(builder.build().is_err());
290        builder.table_opts(
291            crate::TableOptsBuilder::default()
292                .default_time_to_live(100)
293                .build()
294                .unwrap(),
295        );
296        let statement = builder.build().unwrap().to_string();
297        assert_eq!(builder.build().unwrap(), statement.parse().unwrap());
298    }
299
300    #[test]
301    fn test_parse_drop_mv() {
302        let mut builder = DropMaterializedViewStatementBuilder::default();
303        builder.name("test_mv");
304        let statement = builder.build().unwrap().to_string();
305        assert_eq!(builder.build().unwrap(), statement.parse().unwrap());
306        builder.if_exists();
307        let statement = builder.build().unwrap().to_string();
308        assert_eq!(builder.build().unwrap(), statement.parse().unwrap());
309    }
310}