scylladb_parse/statements/
index.rs

1use super::*;
2
3#[derive(ParseFromStr, Clone, Debug, TryInto, From, ToTokens, PartialEq, Eq)]
4#[parse_via(TaggedSecondaryIndexStatement)]
5pub enum SecondaryIndexStatement {
6    Create(CreateIndexStatement),
7    Drop(DropIndexStatement),
8}
9
10impl TryFrom<TaggedSecondaryIndexStatement> for SecondaryIndexStatement {
11    type Error = anyhow::Error;
12    fn try_from(statement: TaggedSecondaryIndexStatement) -> Result<Self, Self::Error> {
13        match statement {
14            TaggedSecondaryIndexStatement::Create(statement) => {
15                Ok(SecondaryIndexStatement::Create(statement.try_into()?))
16            }
17            TaggedSecondaryIndexStatement::Drop(statement) => Ok(SecondaryIndexStatement::Drop(statement.try_into()?)),
18        }
19    }
20}
21
22#[derive(ParseFromStr, Clone, Debug, TryInto, From, ToTokens, PartialEq, Eq)]
23#[tokenize_as(SecondaryIndexStatement)]
24pub enum TaggedSecondaryIndexStatement {
25    Create(TaggedCreateIndexStatement),
26    Drop(TaggedDropIndexStatement),
27}
28
29impl Parse for TaggedSecondaryIndexStatement {
30    type Output = Self;
31    fn parse(s: &mut StatementStream<'_>) -> anyhow::Result<Self::Output> {
32        Ok(if s.check::<CREATE>() {
33            Self::Create(s.parse()?)
34        } else if s.check::<DROP>() {
35            Self::Drop(s.parse()?)
36        } else {
37            anyhow::bail!("Expected a secondary index statement, found {}", s.info())
38        })
39    }
40}
41
42impl Display for SecondaryIndexStatement {
43    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
44        match self {
45            Self::Create(stmt) => stmt.fmt(f),
46            Self::Drop(stmt) => stmt.fmt(f),
47        }
48    }
49}
50
51#[derive(ParseFromStr, Builder, Clone, Debug, ToTokens, PartialEq, Eq)]
52#[builder(setter(strip_option))]
53#[parse_via(TaggedCreateIndexStatement)]
54pub struct CreateIndexStatement {
55    #[builder(setter(name = "set_custom"), default)]
56    pub custom: bool,
57    #[builder(setter(name = "set_if_not_exists"), default)]
58    pub if_not_exists: bool,
59    #[builder(setter(into), default)]
60    pub name: Option<Name>,
61    #[builder(setter(into))]
62    pub table: KeyspaceQualifiedName,
63    #[builder(setter(into))]
64    pub index_id: IndexIdentifier,
65    #[builder(default)]
66    pub using: Option<IndexClass>,
67}
68
69impl TryFrom<TaggedCreateIndexStatement> for CreateIndexStatement {
70    type Error = anyhow::Error;
71    fn try_from(statement: TaggedCreateIndexStatement) -> Result<Self, Self::Error> {
72        Ok(Self {
73            custom: statement.custom,
74            if_not_exists: statement.if_not_exists,
75            name: statement.name.map(|v| v.into_value()).transpose()?,
76            table: statement.table.try_into()?,
77            index_id: statement.index_id.into_value()?,
78            using: statement.using.map(|v| v.into_value()).transpose()?,
79        })
80    }
81}
82
83#[derive(ParseFromStr, Builder, Clone, Debug, ToTokens, PartialEq, Eq)]
84#[builder(setter(strip_option))]
85#[tokenize_as(CreateIndexStatement)]
86pub struct TaggedCreateIndexStatement {
87    #[builder(setter(name = "set_custom"), default)]
88    pub custom: bool,
89    #[builder(setter(name = "set_if_not_exists"), default)]
90    pub if_not_exists: bool,
91    #[builder(default)]
92    pub name: Option<Tag<Name>>,
93    pub table: TaggedKeyspaceQualifiedName,
94    pub index_id: Tag<IndexIdentifier>,
95    #[builder(default)]
96    pub using: Option<Tag<IndexClass>>,
97}
98
99impl CreateIndexStatementBuilder {
100    /// Set IF NOT EXISTS on the statement.
101    /// To undo this, use `set_if_not_exists(false)`.
102    pub fn if_not_exists(&mut self) -> &mut Self {
103        self.if_not_exists.replace(true);
104        self
105    }
106
107    // Set CUSTOM on the statement
108    // To undo this, use `set_custom(false)`.
109    pub fn custom(&mut self) -> &mut Self {
110        self.custom.replace(true);
111        self
112    }
113}
114
115impl Parse for TaggedCreateIndexStatement {
116    type Output = Self;
117    fn parse(s: &mut StatementStream<'_>) -> anyhow::Result<Self::Output> {
118        s.parse::<CREATE>()?;
119        let mut res = TaggedCreateIndexStatementBuilder::default();
120        res.set_custom(s.parse::<Option<CUSTOM>>()?.is_some());
121        s.parse::<INDEX>()?;
122        res.set_if_not_exists(s.parse::<Option<(IF, NOT, EXISTS)>>()?.is_some());
123        if let Some(n) = s.parse()? {
124            res.name(n);
125        }
126        s.parse::<ON>()?;
127        res.table(s.parse()?)
128            .index_id(s.parse_from::<Parens<Tag<IndexIdentifier>>>()?);
129        if let Some(u) = s.parse()? {
130            res.using(u);
131        }
132        s.parse::<Option<Semicolon>>()?;
133        Ok(res
134            .build()
135            .map_err(|e| anyhow::anyhow!("Invalid CREATE INDEX statement: {}", e))?)
136    }
137}
138
139impl Display for CreateIndexStatement {
140    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
141        write!(
142            f,
143            "CREATE{} INDEX{}{} ON {}({})",
144            if self.custom { " CUSTOM" } else { "" },
145            if self.if_not_exists { " IF NOT EXISTS" } else { "" },
146            if let Some(ref name) = self.name {
147                format!(" {}", name)
148            } else {
149                String::new()
150            },
151            self.table,
152            self.index_id
153        )?;
154        if let Some(ref using) = self.using {
155            write!(f, " USING {}", using)?;
156        }
157        Ok(())
158    }
159}
160
161#[derive(ParseFromStr, Clone, Debug, ToTokens, PartialEq, Eq)]
162pub enum IndexIdentifier {
163    Column(Name),
164    Qualified(IndexQualifier, Name),
165}
166
167impl IndexIdentifier {
168    pub fn keys(name: impl Into<Name>) -> Self {
169        Self::Qualified(IndexQualifier::Keys, name.into())
170    }
171
172    pub fn values(name: impl Into<Name>) -> Self {
173        Self::Qualified(IndexQualifier::Values, name.into())
174    }
175
176    pub fn entries(name: impl Into<Name>) -> Self {
177        Self::Qualified(IndexQualifier::Entries, name.into())
178    }
179
180    pub fn full(name: impl Into<Name>) -> Self {
181        Self::Qualified(IndexQualifier::Full, name.into())
182    }
183}
184
185impl Parse for IndexIdentifier {
186    type Output = Self;
187    fn parse(s: &mut StatementStream<'_>) -> anyhow::Result<Self::Output> {
188        Ok(if let Some(name) = s.parse()? {
189            IndexIdentifier::Column(name)
190        } else {
191            IndexIdentifier::Qualified(s.parse()?, s.parse_from::<Parens<Name>>()?)
192        })
193    }
194}
195
196impl Display for IndexIdentifier {
197    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
198        match self {
199            IndexIdentifier::Column(name) => name.fmt(f),
200            IndexIdentifier::Qualified(qualifier, name) => write!(f, "{} ({})", qualifier, name),
201        }
202    }
203}
204
205impl<N: Into<Name>> From<N> for IndexIdentifier {
206    fn from(name: N) -> Self {
207        IndexIdentifier::Column(name.into())
208    }
209}
210
211#[derive(ParseFromStr, Clone, Debug, ToTokens, PartialEq, Eq)]
212pub enum IndexQualifier {
213    Keys,
214    Values,
215    Entries,
216    Full,
217}
218
219impl Parse for IndexQualifier {
220    type Output = Self;
221    fn parse(s: &mut StatementStream<'_>) -> anyhow::Result<Self::Output> {
222        Ok(if s.parse::<Option<KEYS>>()?.is_some() {
223            IndexQualifier::Keys
224        } else if s.parse::<Option<VALUES>>()?.is_some() {
225            IndexQualifier::Values
226        } else if s.parse::<Option<ENTRIES>>()?.is_some() {
227            IndexQualifier::Entries
228        } else if s.parse::<Option<FULL>>()?.is_some() {
229            IndexQualifier::Full
230        } else {
231            anyhow::bail!("Expected an index qualifier, found {}", s.info())
232        })
233    }
234}
235
236impl Display for IndexQualifier {
237    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
238        match self {
239            IndexQualifier::Keys => write!(f, "KEYS"),
240            IndexQualifier::Values => write!(f, "VALUES"),
241            IndexQualifier::Entries => write!(f, "ENTRIES"),
242            IndexQualifier::Full => write!(f, "FULL"),
243        }
244    }
245}
246
247#[derive(ParseFromStr, Clone, Debug, ToTokens, PartialEq, Eq)]
248pub struct IndexClass {
249    pub path: LitStr,
250    pub options: Option<MapLiteral>,
251}
252
253impl IndexClass {
254    pub fn new(path: impl Into<LitStr>) -> Self {
255        Self {
256            path: path.into(),
257            options: None,
258        }
259    }
260
261    pub fn options(mut self, options: impl Into<MapLiteral>) -> Self {
262        self.options = Some(options.into());
263        self
264    }
265}
266
267impl Parse for IndexClass {
268    type Output = Self;
269    fn parse(s: &mut StatementStream<'_>) -> anyhow::Result<Self::Output> {
270        s.parse::<USING>()?;
271        Ok(IndexClass {
272            path: s.parse()?,
273            options: s.parse::<Option<(WITH, OPTIONS, Equals, _)>>()?.map(|i| i.3),
274        })
275    }
276}
277
278impl Display for IndexClass {
279    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
280        write!(f, "{}", self.path)?;
281        if let Some(ref options) = self.options {
282            write!(f, " WITH OPTIONS = {}", options)?;
283        }
284        Ok(())
285    }
286}
287
288#[derive(ParseFromStr, Builder, Clone, Debug, ToTokens, PartialEq, Eq)]
289#[parse_via(TaggedDropIndexStatement)]
290pub struct DropIndexStatement {
291    #[builder(setter(name = "set_if_exists"), default)]
292    pub if_exists: bool,
293    #[builder(setter(into))]
294    pub name: Name,
295}
296
297impl TryFrom<TaggedDropIndexStatement> for DropIndexStatement {
298    type Error = anyhow::Error;
299
300    fn try_from(value: TaggedDropIndexStatement) -> anyhow::Result<Self> {
301        Ok(Self {
302            if_exists: value.if_exists,
303            name: value.name.into_value()?,
304        })
305    }
306}
307
308#[derive(ParseFromStr, Builder, Clone, Debug, ToTokens, PartialEq, Eq)]
309#[tokenize_as(DropIndexStatement)]
310pub struct TaggedDropIndexStatement {
311    #[builder(setter(name = "set_if_exists"), default)]
312    pub if_exists: bool,
313    pub name: Tag<Name>,
314}
315
316impl DropIndexStatementBuilder {
317    /// Set IF EXISTS on the statement.
318    /// To undo this, use `set_if_exists(false)`.
319    pub fn if_exists(&mut self) -> &mut Self {
320        self.if_exists.replace(true);
321        self
322    }
323}
324
325impl Parse for TaggedDropIndexStatement {
326    type Output = Self;
327    fn parse(s: &mut StatementStream<'_>) -> anyhow::Result<Self::Output> {
328        s.parse::<(DROP, INDEX)>()?;
329        let mut res = TaggedDropIndexStatementBuilder::default();
330        res.set_if_exists(s.parse::<Option<(IF, EXISTS)>>()?.is_some());
331        if let Some(n) = s.parse()? {
332            res.name(n);
333        }
334        s.parse::<Option<Semicolon>>()?;
335        Ok(res
336            .build()
337            .map_err(|e| anyhow::anyhow!("Invalid DROP INDEX statement: {}", e))?)
338    }
339}
340
341impl Display for DropIndexStatement {
342    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
343        write!(
344            f,
345            "DROP INDEX{} {}",
346            if self.if_exists { " IF EXISTS" } else { "" },
347            self.name
348        )?;
349        Ok(())
350    }
351}
352
353#[cfg(test)]
354mod test {
355    use super::*;
356
357    #[test]
358    fn test_parse_create_index() {
359        let mut builder = CreateIndexStatementBuilder::default();
360        builder.table("test");
361        assert!(builder.build().is_err());
362        builder.index_id("my_index_id");
363        let statement = builder.build().unwrap().to_string();
364        assert_eq!(builder.build().unwrap(), statement.parse().unwrap());
365        builder.name("my_index_name");
366        let statement = builder.build().unwrap().to_string();
367        assert_eq!(builder.build().unwrap(), statement.parse().unwrap());
368        builder.custom();
369        let statement = builder.build().unwrap().to_string();
370        assert_eq!(builder.build().unwrap(), statement.parse().unwrap());
371        builder.if_not_exists();
372        let statement = builder.build().unwrap().to_string();
373        assert_eq!(builder.build().unwrap(), statement.parse().unwrap());
374        builder.using(IndexClass::new("path.to.the.IndexClass").options(maplit::hashmap! {
375            LitStr::from("storage") => LitStr::from("/mnt/ssd/indexes/")
376        }));
377        let statement = builder.build().unwrap().to_string();
378        assert_eq!(builder.build().unwrap(), statement.parse().unwrap());
379    }
380
381    #[test]
382    fn test_parse_drop_index() {
383        let mut builder = DropIndexStatementBuilder::default();
384        builder.name("my_index_name");
385        let statement = builder.build().unwrap().to_string();
386        assert_eq!(builder.build().unwrap(), statement.parse().unwrap());
387        builder.if_exists();
388        let statement = builder.build().unwrap().to_string();
389        assert_eq!(builder.build().unwrap(), statement.parse().unwrap());
390    }
391}