1use 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
112fn 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
131fn 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}