scyllax_parser/
create_keyspace.rs

1//! Parses a create keyspace query.
2//! ```ignore
3//! create_keyspace_statement: CREATE KEYSPACE [ IF NOT EXISTS ] `keyspace_name` WITH `options`
4//! ```
5//! ## Examples
6//! ```cql,ignore
7//! CREATE KEYSPACE Excalibur
8//! WITH replication = {'class': 'NetworkTopologyStrategy', 'DC1' : 1, 'DC2' : 3}
9//! AND durable_writes = true;
10//! ```
11//!
12//! ```cql,ignore
13//! CREATE KEYSPACE Excelsior
14//! WITH replication = {'class': 'SimpleStrategy', 'replication_factor' : 3};
15//! ```
16
17use std::collections::HashMap;
18
19use nom::{
20    branch::alt,
21    bytes::complete::{tag, tag_no_case, take_while_m_n},
22    character::complete::{alphanumeric0, char, multispace0},
23    combinator::opt,
24    error::Error,
25    multi::separated_list0,
26    sequence::delimited,
27    Err, IResult,
28};
29
30use crate::{common::parse_rust_flavored_variable, r#where::parse_comparisons, Column, Value};
31#[derive(Debug, PartialEq)]
32pub struct CreateKeyspaceQuery {
33    pub name: String,
34    pub if_not_exists: bool,
35    pub replication: ReplicationOption,
36    pub durable_writes: Option<bool>,
37}
38
39#[derive(Debug, PartialEq)]
40pub enum ReplicationOption {
41    SimpleStrategy(i32),
42    NetworkTopologyStrategy(HashMap<String, i32>),
43}
44
45impl<'a> TryFrom<&'a str> for CreateKeyspaceQuery {
46    type Error = Err<Error<&'a str>>;
47
48    fn try_from(value: &'a str) -> Result<Self, Self::Error> {
49        Ok(parse_create_keyspace(value)?.1)
50    }
51}
52
53pub fn parse_create_keyspace(input: &str) -> IResult<&str, CreateKeyspaceQuery> {
54    let (input, _) = tag_no_case("create keyspace ")(input)?;
55    let (input, if_not_exists) = parse_if_not_exists(input)?;
56
57    let (input, name) = parse_keyspace_name(input)?;
58
59    let (input, _) = multispace0(input)?;
60    let (input, replication) = parse_replication(input)?;
61    let (input, _) = multispace0(input)?;
62    let (input, durable_writes) = parse_durable_writes(input)?;
63    let (input, _) = opt(tag(";"))(input)?;
64
65    Ok((
66        input,
67        CreateKeyspaceQuery {
68            name,
69            if_not_exists,
70            replication,
71            durable_writes,
72        },
73    ))
74}
75
76fn parse_if_not_exists(input: &str) -> IResult<&str, bool> {
77    let (input, exists) = opt(tag_no_case("if not exists "))(input)?;
78    Ok((input, exists.is_some()))
79}
80
81fn parse_keyspace_name(input: &str) -> IResult<&str, String> {
82    let (input, name) = parse_rust_flavored_variable(input)?;
83    Ok((input, name.to_string()))
84}
85
86fn parse_replication(input: &str) -> IResult<&str, ReplicationOption> {
87    let (input, _) = tag_no_case("with replication =")(input)?;
88    let (input, strategy) = parse_replication_object(input)?;
89
90    let class = strategy.get("class").unwrap();
91    match *class {
92        "SimpleStrategy" => {
93            let replication_factor = strategy.get("replication_factor").unwrap();
94            let replication_factor = replication_factor.parse::<i32>().unwrap();
95            Ok((input, ReplicationOption::SimpleStrategy(replication_factor)))
96        }
97        "NetworkTopologyStrategy" => {
98            let mut map = HashMap::new();
99            for (key, value) in strategy {
100                if key == "class" {
101                    continue;
102                }
103                let value = value.parse::<i32>().unwrap();
104                map.insert(key.to_string(), value);
105            }
106            Ok((input, ReplicationOption::NetworkTopologyStrategy(map)))
107        }
108        _ => panic!("Unknown replication strategy: {}", class),
109    }
110}
111
112/// parse the weird json like replication strategy
113/// eg: `{'class': 'NetworkTopologyStrategy', 'DC1' : 1, 'DC2' : 3}`
114/// remember to parse the single quotes
115fn parse_replication_object(input: &str) -> IResult<&str, HashMap<&str, &str>> {
116    let (input, _) = multispace0(input)?;
117    let (input, _) = tag("{")(input)?;
118    let (input, _) = multispace0(input)?;
119    let (input, pairs) = separated_list0(tag(","), parse_replication_pair)(input)?;
120    let (input, _) = multispace0(input)?;
121    let (input, _) = tag("}")(input)?;
122
123    let mut map = HashMap::new();
124    for (key, value) in pairs {
125        map.insert(key, value);
126    }
127
128    Ok((input, map))
129}
130
131// - 'class': 'NetworkTopologyStrategy'
132// - 'DC1' : 1
133// - 'DC2' : 3
134/// remember to parse the single quotes and colon and command whitespaces
135fn parse_replication_pair(input: &str) -> IResult<&str, (&str, &str)> {
136    let (input, _) = multispace0(input)?;
137
138    let (input, key) = delimited(char('\''), parse_rust_flavored_variable, char('\''))(input)?;
139
140    let (input, _) = multispace0(input)?;
141    let (input, _) = tag(":")(input)?;
142    let (input, _) = multispace0(input)?;
143
144    let string_value = delimited(char('\''), alphanumeric0, char('\''));
145    let int_value = take_while_m_n(1, usize::MAX, char::is_numeric);
146
147    let (input, value) = alt((string_value, int_value))(input)?;
148
149    let (input, _) = multispace0(input)?;
150
151    Ok((input, (key, value)))
152}
153
154fn parse_durable_writes(input: &str) -> IResult<&str, Option<bool>> {
155    let (input, comparisons) = opt(parse_comparisons)(input)?;
156
157    let durable_writes = comparisons.and_then(|x| {
158        x.into_iter().find_map(|x| match x.column {
159            Column::Identifier(ref name) if name == "durable_writes" => Some(match x.value {
160                Value::Boolean(value) => value,
161                _ => panic!("Expected a boolean value for durable_writes"),
162            }),
163            _ => None,
164        })
165    });
166
167    Ok((input, durable_writes))
168}
169
170#[cfg(test)]
171mod test {
172    use super::*;
173    use pretty_assertions::assert_eq;
174
175    #[test]
176    fn test_simple_strategy() {
177        assert_eq!(
178            parse_create_keyspace(
179                "CREATE KEYSPACE Excalibur WITH replication = { 'class' : 'SimpleStrategy', 'replication_factor' : 3  } AND durable_writes = true;"
180            ),
181            Ok((
182                "",
183                CreateKeyspaceQuery {
184                    name: "Excalibur".to_string(),
185                    if_not_exists: false,
186                    replication: ReplicationOption::SimpleStrategy(3),
187                    durable_writes: Some(true)
188                }
189            ))
190        );
191    }
192
193    #[test]
194    fn test_network_topology_strategy() {
195        assert_eq!(
196            parse_create_keyspace(
197                r#"CREATE KEYSPACE Excelsior WITH replication = {    'class': 'NetworkTopologyStrategy', 'DC1' : 1, 'DC2' : 3};"#
198            ),
199            Ok((
200                "",
201                CreateKeyspaceQuery {
202                    name: "Excelsior".to_string(),
203                    if_not_exists: false,
204                    replication: ReplicationOption::NetworkTopologyStrategy(
205                        vec![("DC1".to_string(), 1), ("DC2".to_string(), 3)]
206                            .into_iter()
207                            .collect()
208                    ),
209                    durable_writes: None
210                }
211            ))
212        );
213    }
214
215    #[test]
216    fn test_if_not_exists() {
217        assert_eq!(
218            parse_create_keyspace(
219                r#"CREATE KEYSPACE IF NOT EXISTS Excelsior WITH replication = {'class': 'NetworkTopologyStrategy', 'DC1' : 1, 'DC2' : 3};"#
220            ),
221            Ok((
222                "",
223                CreateKeyspaceQuery {
224                    name: "Excelsior".to_string(),
225                    if_not_exists: true,
226                    replication: ReplicationOption::NetworkTopologyStrategy(
227                        vec![("DC1".to_string(), 1), ("DC2".to_string(), 3)]
228                            .into_iter()
229                            .collect()
230                    ),
231                    durable_writes: None
232                }
233            ))
234        );
235    }
236
237    #[test]
238    fn test_durable_writes() {
239        assert_eq!(
240            parse_create_keyspace(
241                r#"CREATE KEYSPACE Excelsior WITH replication = {'class': 'NetworkTopologyStrategy', 'DC1' : 1, 'DC2' : 3} AND durable_writes = true;"#
242            ),
243            Ok((
244                "",
245                CreateKeyspaceQuery {
246                    name: "Excelsior".to_string(),
247                    if_not_exists: false,
248                    replication: ReplicationOption::NetworkTopologyStrategy(
249                        vec![("DC1".to_string(), 1), ("DC2".to_string(), 3)]
250                            .into_iter()
251                            .collect()
252                    ),
253                    durable_writes: Some(true)
254                }
255            ))
256        );
257    }
258
259    #[test]
260    fn test_parse_replication_object() {
261        let res: HashMap<&str, &str> = vec![
262            ("class", "NetworkTopologyStrategy"),
263            ("DC1", "1"),
264            ("DC2", "3"),
265        ]
266        .into_iter()
267        .collect();
268
269        assert_eq!(
270            parse_replication_object(
271                r#"{ 'class' : 'NetworkTopologyStrategy', 'DC1' : 1, 'DC2': 3}"#
272            ),
273            Ok(("", res))
274        );
275    }
276}