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 pub fn if_not_exists(&mut self) -> &mut Self {
103 self.if_not_exists.replace(true);
104 self
105 }
106
107 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 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}