kassandra/cql/parser/
mod.rs

1use std::{borrow::Cow, str::FromStr};
2
3use eyre::Result;
4use nom::{
5    branch::alt,
6    bytes::complete::tag,
7    character::complete::{alpha1, alphanumeric1, multispace0},
8    combinator::{map, opt, recognize},
9    error::ParseError,
10    multi::{many0_count, separated_list1},
11    sequence::{delimited, pair},
12    IResult, Slice,
13};
14
15use crate::{cql::query::QueryString, error::DbError, frame::response::error::Error};
16
17pub fn query(query: &str) -> Result<QueryString, Error> {
18    let query = if query.contains("/*") {
19        Cow::Owned(filter_comments(query)?)
20    } else {
21        Cow::Borrowed(query)
22    };
23
24    let result = alt((
25        queries::use_query,
26        queries::select_query,
27        queries::insert_query,
28        queries::update_query,
29        queries::delete_query,
30        queries::create_keyspace_query,
31        queries::create_table_query,
32        queries::create_udt_query,
33    ))(query.as_ref())
34    .map(|(_, it)| it)?;
35
36    Ok(result)
37}
38
39fn filter_comments(mut query: &str) -> Result<String, Error> {
40    let mut output = String::new();
41    let start = query
42        .find("/*")
43        .expect("for start of comment to be present");
44    output += query.slice(..start);
45    loop {
46        let Some(finish) = query.find("*/") else {
47            return Err(Error::new(DbError::Invalid, "Unfinished comment"));
48        };
49        query = &query[finish + 2..];
50
51        if let Some(start) = query.find("/*") {
52            output += &query[..start];
53        } else {
54            output += query;
55            break;
56        }
57    }
58
59    Ok(output)
60}
61
62impl FromStr for QueryString {
63    type Err = Error;
64
65    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
66        query(s)
67    }
68}
69
70pub fn identifier(input: &str) -> IResult<&str, String> {
71    let ident = recognize(pair(
72        alt((alpha1, tag("_"))),
73        many0_count(alt((alphanumeric1, tag("_")))),
74    ));
75
76    map(ident, |it: &str| it.to_lowercase())(input)
77}
78
79pub fn cassandra_type(input: &str) -> IResult<&str, String> {
80    let ident = pair(
81        alt((alpha1, tag("_"))),
82        many0_count(alt((alphanumeric1, tag("_")))),
83    );
84    let generics = opt(delimited(
85        tag("<"),
86        separated_list1(ws(tag(",")), ident),
87        tag(">"),
88    ));
89    let ident = pair(
90        alt((alpha1, tag("_"))),
91        many0_count(alt((alphanumeric1, tag("_")))),
92    );
93    map(recognize(pair(ident, generics)), |it: &str| it.to_owned())(input)
94}
95
96pub fn ws<'a, F, O, E>(inner: F) -> impl FnMut(&'a str) -> IResult<&'a str, O, E>
97where
98    F: FnMut(&'a str) -> IResult<&'a str, O, E> + 'a,
99    E: ParseError<&'a str>,
100{
101    delimited(multispace0, inner, multispace0)
102}
103
104mod queries {
105    use nom::{
106        branch::alt,
107        bytes::complete::{tag, tag_no_case},
108        character::complete::{multispace0, multispace1, u32},
109        combinator::{map, opt, value},
110        multi::{many_till, separated_list0, separated_list1},
111        sequence::{delimited, pair, preceded, separated_pair, terminated, tuple},
112        IResult,
113    };
114
115    use super::{cassandra_type, identifier, ws};
116    use crate::cql::{
117        functions::CqlFunction,
118        literal::Literal,
119        query::{
120            ColumnSelector, CreateKeyspaceQuery, CreateTableQuery, CreateTypeQuery, DeleteQuery,
121            InsertQuery, QueryString, QueryValue, SelectExpression, SelectQuery, WhereClosure,
122        },
123        types::PreCqlType,
124    };
125
126    fn query_value(input: &str) -> IResult<&str, QueryValue> {
127        let blank = map(tag("?"), |_| QueryValue::Blankslate);
128        let named_bind = map(preceded(tag(":"), identifier), |_| QueryValue::Blankslate);
129        let literal = map(super::literal::parse, QueryValue::Literal);
130        alt((blank, literal, named_bind))(input)
131    }
132
133    fn select_expression(input: &str) -> IResult<&str, SelectExpression> {
134        let all = map(tag("*"), |_| SelectExpression::All);
135
136        // Column can be 4 cases:
137        // 1. plain column name `column`
138        // 2. aliased column: `column as name`
139        // 3. function applied to a column: `toJson(column)`
140        // 4. aliased function result: `toJson(column) as json`
141        let function = alt((
142            value(CqlFunction::ToJson, tag_no_case("toJson")),
143            value(CqlFunction::FromJson, tag_no_case("fromJson")),
144        ));
145        let column1 = map(identifier, |name| ColumnSelector {
146            name,
147            ..Default::default()
148        });
149        let column3 = pair(function, delimited(tag("("), identifier, tag(")")));
150        let column3 = map(column3, |(function, name)| ColumnSelector {
151            name,
152            function: Some(function),
153            ..Default::default()
154        });
155
156        let column = pair(
157            alt((column3, column1)),
158            opt(preceded(ws(tag_no_case("as")), identifier)),
159        );
160        let column = map(column, |(column, alias)| ColumnSelector { alias, ..column });
161
162        let columns = map(
163            separated_list0(pair(tag(","), multispace0), column),
164            SelectExpression::Columns,
165        );
166
167        alt((all, columns))(input)
168    }
169
170    fn where_closure(input: &str) -> IResult<&str, WhereClosure> {
171        let (rest, _) = terminated(tag_no_case("where"), multispace1)(input)?;
172
173        let statement = separated_pair(identifier, ws(tag("=")), query_value);
174
175        let (rest, statements) = separated_list1(ws(tag("AND")), statement)(rest)?;
176
177        Ok((rest, WhereClosure { statements }))
178    }
179
180    pub fn select_query(input: &str) -> IResult<&str, QueryString> {
181        let (rest, _) = terminated(tag_no_case("select"), multispace1)(input)?;
182        let (rest, json) = map(opt(terminated(tag_no_case("json"), multispace1)), |it| {
183            it.is_some()
184        })(rest)?;
185
186        let (rest, columns) = select_expression(rest)?;
187        let (rest, _) = delimited(multispace0, alt((tag("from"), tag("FROM"))), multispace0)(rest)?;
188        let (rest, keyspace) = opt(terminated(identifier, tag(".")))(rest)?;
189        let (rest, table) = terminated(identifier, multispace0)(rest)?;
190
191        let (rest, closure) = opt(terminated(where_closure, multispace0))(rest)?;
192        // todo: order by
193        let limit = preceded(
194            terminated(tag_no_case("limit"), multispace1),
195            terminated(map(u32, |it| it as usize), multispace0),
196        );
197        let (rest, limit) = opt(limit)(rest)?;
198
199        Ok((
200            rest,
201            QueryString::Select(SelectQuery {
202                table,
203                keyspace,
204                columns,
205                r#where: closure.unwrap_or_default(),
206                limit,
207                json,
208            }),
209        ))
210    }
211
212    pub fn insert_query(input: &str) -> IResult<&str, QueryString> {
213        let (rest, _) = terminated(tag_no_case("insert"), multispace1)(input)?;
214        let (rest, _) = terminated(tag_no_case("into"), multispace1)(rest)?;
215        let (rest, keyspace) = opt(terminated(identifier, tag(".")))(rest)?;
216        let (rest, table) = terminated(identifier, multispace0)(rest)?;
217        let (rest, columns) = terminated(
218            delimited(
219                ws(tag("(")),
220                separated_list0(ws(tag(",")), identifier),
221                ws(tag(")")),
222            ),
223            multispace0,
224        )(rest)?;
225        let (rest, _) = terminated(tag_no_case("values"), multispace0)(rest)?;
226        let (rest, values) = terminated(
227            delimited(
228                ws(tag("(")),
229                separated_list0(ws(tag(",")), query_value),
230                ws(tag(")")),
231            ),
232            multispace0,
233        )(rest)?;
234
235        Ok((
236            rest,
237            QueryString::Insert(InsertQuery {
238                table,
239                keyspace,
240                columns,
241                values,
242            }),
243        ))
244    }
245
246    pub fn use_query(input: &str) -> IResult<&str, QueryString> {
247        let (rest, _) = terminated(alt((tag("use"), tag("USE"))), multispace1)(input)?;
248        let (rest, keyspace) =
249            alt((identifier, delimited(tag("\""), identifier, tag("\""))))(rest)?;
250        Ok((rest, QueryString::Use { keyspace }))
251    }
252
253    pub fn create_keyspace_query(input: &str) -> IResult<&str, QueryString> {
254        let (rest, _) = terminated(
255            alt((tag("create keyspace"), tag("CREATE KEYSPACE"))),
256            multispace1,
257        )(input)?;
258        let (rest, if_not_exists) =
259            opt(terminated(tag_no_case("IF NOT EXISTS"), multispace1))(rest)?;
260
261        let (rest, keyspace) = terminated(identifier, multispace1)(rest)?;
262        let (rest, _) = terminated(alt((tag("with"), tag("WITH"))), multispace1)(rest)?;
263        let replication = alt((tag("replication"), tag("REPLICATION")));
264
265        let (rest, (_, replication)) =
266            separated_pair(replication, ws(tag("=")), super::literal::parse)(rest)?;
267
268        Ok((
269            rest,
270            QueryString::CreateKeyspace(CreateKeyspaceQuery {
271                keyspace,
272                ignore_existence: if_not_exists.is_some(),
273                replication,
274            }),
275        ))
276    }
277
278    pub fn create_table_query(rest: &str) -> IResult<&str, QueryString> {
279        fn table_options(rest: &str) -> IResult<&str, Vec<(String, Literal)>> {
280            let ordering = map(
281                preceded(
282                    tag("CLUSTERING ORDER BY"),
283                    delimited(
284                        ws(tag("(")),
285                        separated_list1(
286                            ws(tag(",")),
287                            pair(
288                                identifier,
289                                alt((
290                                    map(ws(tag("ASC")), |_| Literal::Bool(true)),
291                                    map(ws(tag("DESC")), |_| Literal::Bool(false)),
292                                )),
293                            ),
294                        ),
295                        ws(tag(")")),
296                    ),
297                ),
298                |t| {
299                    (
300                        "clustering order by".to_owned(),
301                        Literal::Map(t.into_iter().collect()),
302                    )
303                },
304            );
305
306            let compact_storage = map(tag("COMPACT STORAGE"), |_| {
307                ("compact storage".to_owned(), Literal::Bool(true))
308            });
309
310            let key_value = separated_pair(identifier, ws(tag("=")), super::literal::parse);
311
312            separated_list1(ws(tag("AND")), alt((ordering, compact_storage, key_value)))(rest)
313        }
314
315        fn column_definition(rest: &str) -> IResult<&str, (String, PreCqlType, Option<&str>)> {
316            tuple((
317                terminated(identifier, multispace0),
318                terminated(super::types::parse, multispace0),
319                opt(terminated(tag("PRIMARY KEY"), multispace0)),
320            ))(rest)
321        }
322
323        fn column_definition_without_primary(rest: &str) -> IResult<&str, (String, PreCqlType)> {
324            tuple((
325                terminated(identifier, multispace0),
326                terminated(super::types::parse, multispace0),
327            ))(rest)
328        }
329
330        // Vec<PartitionKeys> + Vec<ClusteringKeys>
331        fn primary_key_definition(rest: &str) -> IResult<&str, (Vec<String>, Vec<String>)> {
332            let partition_key = delimited(
333                ws(tag("(")),
334                separated_list1(ws(tag(",")), identifier),
335                ws(tag(")")),
336            );
337            let composite_key = delimited(
338                ws(tag("(")),
339                pair(
340                    terminated(partition_key, opt(ws(tag(",")))),
341                    separated_list0(ws(tag(",")), identifier),
342                ),
343                ws(tag(")")),
344            );
345
346            // PRIMARY KEY (ident, ident)
347            let compound_key = map(
348                delimited(
349                    ws(tag("(")),
350                    separated_list1(ws(tag(",")), identifier),
351                    ws(tag(")")),
352                ),
353                |it| {
354                    let (head, tail) = it.split_first().unwrap();
355
356                    (vec![head.clone()], tail.to_vec())
357                },
358            );
359
360            let mut primary_key_definition =
361                preceded(ws(tag("PRIMARY KEY")), alt((composite_key, compound_key)));
362
363            primary_key_definition(rest)
364        }
365
366        let (rest, _) =
367            terminated(alt((tag("create table"), tag("CREATE TABLE"))), multispace1)(rest)?;
368        let (rest, if_not_exists) =
369            opt(terminated(tag_no_case("IF NOT EXISTS"), multispace1))(rest)?;
370        let (rest, keyspace) = opt(terminated(identifier, tag(".")))(rest)?;
371        let (rest, table) = terminated(identifier, multispace0)(rest)?;
372
373        let with_primary_key_definition = map(
374            many_till(
375                terminated(column_definition_without_primary, ws(tag(","))),
376                primary_key_definition,
377            ),
378            |(c, (pk, ck))| (c, pk, ck),
379        );
380        let with_primary_key_inline = map(separated_list1(ws(tag(",")), column_definition), |c| {
381            let pk = c
382                .iter()
383                .find_map(|(name, _, pk)| {
384                    if pk.is_some() {
385                        Some(name.clone())
386                    } else {
387                        None
388                    }
389                })
390                .into_iter()
391                .collect();
392            let columns = c.into_iter().map(|(n, t, _)| (n, t)).collect();
393            (columns, pk, vec![])
394        });
395
396        let (rest, (columns, primary_key, clustering_keys)) = delimited(
397            ws(tag("(")),
398            alt((with_primary_key_definition, with_primary_key_inline)),
399            ws(tag(")")),
400        )(rest)?;
401
402        let (rest, options) = opt(preceded(ws(tag("WITH")), table_options))(rest)?;
403
404        Ok((
405            rest,
406            QueryString::CreateTable(CreateTableQuery {
407                keyspace,
408                table,
409                ignore_existence: if_not_exists.is_some(),
410                columns,
411                partition_keys: primary_key,
412                clustering_keys,
413                options: options.unwrap_or_default(),
414            }),
415        ))
416    }
417
418    pub fn update_query(rest: &str) -> IResult<&str, QueryString> {
419        let (rest, _) = terminated(tag_no_case("update"), multispace1)(rest)?;
420        let (rest, keyspace) = opt(terminated(identifier, tag(".")))(rest)?;
421        let (rest, table) = terminated(identifier, multispace1)(rest)?;
422        let (rest, _) = terminated(tag_no_case("set"), multispace1)(rest)?;
423
424        let (rest, columns_specification) = terminated(
425            separated_list1(
426                ws(tag(",")),
427                separated_pair(identifier, ws(tag("=")), query_value),
428            ),
429            multispace1,
430        )(rest)?;
431        let (rest, _) = terminated(tag_no_case("where"), multispace1)(rest)?;
432
433        let (rest, row_specification) = terminated(
434            separated_list1(
435                ws(tag("AND")),
436                separated_pair(identifier, ws(tag("=")), query_value),
437            ),
438            multispace0,
439        )(rest)?;
440
441        let (columns, values) = columns_specification
442            .into_iter()
443            .chain(row_specification)
444            .unzip();
445
446        Ok((
447            rest,
448            QueryString::Insert(InsertQuery {
449                table,
450                keyspace,
451                columns,
452                values,
453            }),
454        ))
455    }
456
457    pub fn delete_query(rest: &str) -> IResult<&str, QueryString> {
458        let (rest, _) = terminated(tag_no_case("delete"), multispace1)(rest)?;
459
460        let columns_list = terminated(separated_list1(ws(tag(",")), identifier), multispace1);
461
462        let from_tag = terminated(tag_no_case("from"), multispace1);
463        let from_tag_empty = map(terminated(tag_no_case("from"), multispace1), |_| vec![]);
464        let (rest, columns) = alt((terminated(columns_list, from_tag), from_tag_empty))(rest)?;
465
466        let (rest, keyspace) = opt(terminated(identifier, tag(".")))(rest)?;
467        let (rest, table) = terminated(identifier, multispace1)(rest)?;
468        let (rest, _) = terminated(tag_no_case("where"), multispace1)(rest)?;
469
470        let (rest, statements) = terminated(
471            separated_list1(
472                ws(tag("AND")),
473                separated_pair(identifier, ws(tag("=")), query_value),
474            ),
475            multispace0,
476        )(rest)?;
477
478        let r#where = WhereClosure { statements };
479
480        Ok((
481            rest,
482            QueryString::Delete(DeleteQuery {
483                table,
484                keyspace,
485                columns,
486                r#where,
487            }),
488        ))
489    }
490
491    pub fn create_udt_query(rest: &str) -> IResult<&str, QueryString> {
492        let (rest, _) = terminated(tag_no_case("create type"), multispace1)(rest)?;
493        let (rest, _) = opt(terminated(tag_no_case("if not exists"), multispace1))(rest)?;
494
495        let (rest, keyspace) = opt(terminated(identifier, tag(".")))(rest)?;
496        let (rest, table) = terminated(identifier, multispace0)(rest)?;
497
498        let ident_type = tuple((
499            terminated(identifier, multispace0),
500            terminated(cassandra_type, multispace0),
501        ));
502        let (rest, columns) = delimited(
503            ws(tag("(")),
504            separated_list1(ws(tag(",")), ident_type),
505            ws(tag(")")),
506        )(rest)?;
507
508        Ok((
509            rest,
510            QueryString::CreateType(CreateTypeQuery {
511                keyspace,
512                name: table,
513                columns,
514            }),
515        ))
516    }
517
518    #[test]
519    fn test_select_expression() {
520        let (r, p) = select_expression("a, toJson(x) as y, toJson(z), b").unwrap();
521        assert!(r.is_empty());
522        println!("{p:?}");
523    }
524}
525
526mod types {
527    use std::str::FromStr;
528
529    use nom::{
530        bytes::{complete::tag, streaming::take_while},
531        character::is_alphanumeric,
532        error::ErrorKind,
533        multi::separated_list1,
534        sequence::terminated,
535        IResult,
536    };
537
538    use super::{identifier, ws};
539    use crate::cql::types::{NativeType, PreCqlType};
540
541    type ParseResult<'a, T> = IResult<&'a str, T, nom::error::Error<&'a str>>;
542
543    pub fn parse(p: &str) -> ParseResult<PreCqlType> {
544        if let Ok((_rest, _)) = tag::<_, _, nom::error::Error<_>>("frozen<")(p) {
545            let (p, inner_type) = parse(p)?;
546            let frozen_type = inner_type.freeze();
547            Ok((p, frozen_type))
548        } else if let Ok((p, _)) = tag::<_, _, nom::error::Error<_>>("map<")(p) {
549            let (p, key) = terminated(parse, ws(tag(",")))(p)?;
550            let (p, value) = parse(p)?;
551            let (p, _) = tag(">")(p)?;
552
553            let typ = PreCqlType::Map {
554                frozen: false,
555                key: Box::new(key),
556                value: Box::new(value),
557            };
558
559            Ok((p, typ))
560        } else if let Ok((p, _)) = tag::<_, _, nom::error::Error<_>>("list<")(p) {
561            let (p, inner_type) = parse(p)?;
562            let (p, _) = tag(">")(p)?;
563
564            let typ = PreCqlType::List {
565                frozen: false,
566                item: Box::new(inner_type),
567            };
568
569            Ok((p, typ))
570        } else if let Ok((p, _)) = tag::<_, _, nom::error::Error<_>>("set<")(p) {
571            let (p, inner_type) = parse(p)?;
572            let (p, _) = tag(">")(p)?;
573
574            let typ = PreCqlType::Set {
575                frozen: false,
576                item: Box::new(inner_type),
577            };
578
579            Ok((p, typ))
580        } else if let Ok((p, _)) = tag::<_, _, nom::error::Error<_>>("tuple<")(p) {
581            let (p, types) = separated_list1(ws(tag(",")), parse)(p)?;
582            let (p, _) = tag(">")(p)?;
583            Ok((p, PreCqlType::Tuple(types)))
584        } else if let Ok((p, typ)) = parse_native_type(p) {
585            Ok((p, PreCqlType::Native(typ)))
586        } else if let Ok((name, p)) = parse_user_defined_type(p) {
587            let typ = PreCqlType::UserDefinedType {
588                frozen: false,
589                name: name.to_string(),
590            };
591            Ok((p, typ))
592        } else {
593            // Err(p.error(ParseErrorCause::Other("invalid cql type")))
594            panic!("invalid cql type")
595        }
596    }
597
598    fn parse_native_type(p: &str) -> ParseResult<NativeType> {
599        let (p, tok) = identifier(p)?;
600        let typ = NativeType::from_str(&tok)
601            .map_err(|_| nom::Err::Error(nom::error::make_error(p, ErrorKind::Tag)))?;
602        Ok((p, typ))
603    }
604
605    fn parse_user_defined_type(p: &str) -> ParseResult<&str> {
606        // Java identifiers allow letters, underscores and dollar signs at any position
607        // and digits in non-first position. Dots are accepted here because the names
608        // are usually fully qualified.
609        let (p, tok) =
610            take_while(|c| is_alphanumeric(c as u8) || c == '.' || c == '_' || c == '$')(p)?;
611
612        if tok.is_empty() {
613            return Err(nom::Err::Error(nom::error::make_error(p, ErrorKind::Tag)));
614        }
615        Ok((p, tok))
616    }
617}
618
619mod literal {
620    use std::str::FromStr;
621
622    use nom::{
623        branch::alt,
624        bytes::complete::{tag, tag_no_case, take_until, take_while_m_n},
625        character::complete::multispace0,
626        combinator::{map, recognize},
627        multi::separated_list0,
628        sequence::{delimited, separated_pair, terminated, tuple},
629        IResult,
630    };
631    use uuid::Uuid;
632
633    use super::ws;
634    use crate::cql::literal::Literal;
635
636    pub fn parse(input: &str) -> IResult<&str, Literal> {
637        alt((
638            uuid_literal,
639            null_literal,
640            map_literal,
641            string_literal,
642            number_literal,
643            float_literal,
644            list_literal,
645        ))(input)
646    }
647
648    fn string_literal(input: &str) -> IResult<&str, Literal> {
649        map(
650            delimited(tag("'"), take_until("'"), tag("'")),
651            |it: &str| Literal::String(it.to_owned()),
652        )(input)
653    }
654
655    fn number_literal(input: &str) -> IResult<&str, Literal> {
656        map(nom::character::complete::i64, Literal::Number)(input)
657    }
658
659    fn null_literal(input: &str) -> IResult<&str, Literal> {
660        map(tag_no_case("null"), |_| Literal::Null)(input)
661    }
662
663    fn float_literal(input: &str) -> IResult<&str, Literal> {
664        map(nom::number::complete::double, Literal::Float)(input)
665    }
666
667    fn list_literal(input: &str) -> IResult<&str, Literal> {
668        let values = separated_list0(ws(tag(",")), ws(parse));
669        map(delimited(ws(tag("[")), values, ws(tag("]"))), Literal::List)(input)
670    }
671
672    fn map_literal(input: &str) -> IResult<&str, Literal> {
673        let quoted_string = delimited(tag("'"), take_until("'"), tag("'"));
674        let value = separated_pair(ws(quoted_string), tag(":"), ws(parse));
675
676        let values = separated_list0(terminated(tag(","), multispace0), value);
677
678        map(
679            delimited(tag("{"), values, tag("}")),
680            |it: Vec<(&str, Literal)>| {
681                Literal::Map(
682                    it.into_iter()
683                        .map(|(key, value)| (key.to_owned(), value))
684                        .collect(),
685                )
686            },
687        )(input)
688    }
689
690    fn uuid_literal(input: &str) -> IResult<&str, Literal> {
691        let lower_hex = tuple((
692            take_while_m_n(8, 8, is_lower_hex_digit),
693            tag("-"),
694            take_while_m_n(4, 4, is_lower_hex_digit),
695            tag("-"),
696            take_while_m_n(4, 4, is_lower_hex_digit),
697            tag("-"),
698            take_while_m_n(4, 4, is_lower_hex_digit),
699            tag("-"),
700            take_while_m_n(12, 12, is_lower_hex_digit),
701        ));
702        let upper_hex = tuple((
703            take_while_m_n(8, 8, is_upper_hex_digit),
704            tag("-"),
705            take_while_m_n(4, 4, is_upper_hex_digit),
706            tag("-"),
707            take_while_m_n(4, 4, is_upper_hex_digit),
708            tag("-"),
709            take_while_m_n(4, 4, is_upper_hex_digit),
710            tag("-"),
711            take_while_m_n(12, 12, is_upper_hex_digit),
712        ));
713        let parser = alt((lower_hex, upper_hex));
714        let (rest, lit) = recognize(parser)(input)?;
715        let uuid = Uuid::from_str(lit).expect("to be valid uuid");
716        Ok((rest, Literal::Uuid(uuid)))
717    }
718
719    #[inline]
720    fn is_lower_hex_digit(i: char) -> bool {
721        ('a'..='f').contains(&i) || i.is_ascii_digit()
722    }
723
724    #[inline]
725    fn is_upper_hex_digit(i: char) -> bool {
726        ('A'..='F').contains(&i) || i.is_ascii_digit()
727    }
728
729    #[cfg(test)]
730    mod tests {
731        use super::{map_literal, parse};
732
733        #[test]
734        fn test_map() {
735            let v = "{ 'class' : 'SimpleStrategy', 'replication_factor' : 1 }";
736            let (_, m) = map_literal(v).unwrap();
737            println!("{m:?}");
738        }
739
740        #[test]
741        fn test_uuid() {
742            let v = "6ab09bec-e68e-48d9-a5f8-97e6fb4c9b47";
743            let (_, m) = parse(v).unwrap();
744            println!("{m:?}");
745        }
746    }
747}
748
749#[cfg(test)]
750mod tests {
751    use super::query;
752    use crate::cql::{
753        functions::CqlFunction,
754        parser::filter_comments,
755        query::{ColumnSelector, QueryString, SelectExpression, SelectQuery},
756    };
757
758    #[test]
759    fn test_select() {
760        let q = "select keyspace_name, table_name, column_name, kind, position, type from system_schema.columns";
761        let s = query(q).unwrap();
762        println!("{s:#?}");
763    }
764
765    #[test]
766    fn test_select_where() {
767        let q = "SELECT field1,field2,field3 FROM table WHERE field0 = ?";
768        let s = query(q).unwrap();
769        println!("{s:#?}");
770    }
771
772    #[test]
773    fn test_select_where_limit() {
774        let q = "SELECT field1,field2,field3 FROM table WHERE field0 = ? limit 500";
775        let QueryString::Select(s) = query(q).unwrap() else {
776            panic!("was supposed to be parsed as select query")
777        };
778        assert_eq!(s.limit, Some(500));
779        println!("{s:#?}");
780    }
781
782    #[test]
783    fn test_insert_into() {
784        let q = "INSERT INTO table (field1,field2,field3,field4) VALUES (?,?,?,?)";
785        let i = query(q).unwrap();
786        println!("{i:#?}")
787    }
788
789    #[test]
790    fn test_create_keyspace() {
791        let q = "CREATE KEYSPACE keyspace WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 }";
792        let k = query(q).unwrap();
793        println!("{k:#?}");
794    }
795
796    #[test]
797    fn test_update_query() {
798        let q = "UPDATE table SET field1=?,field2=?,field3=? WHERE field0=?";
799        let k = query(q).unwrap();
800        println!("{k:#?}");
801    }
802
803    #[test]
804    fn test_delete_row_query() {
805        let q = "DELETE FROM table WHERE field1=? AND field2=? AND field3=?";
806        let k = query(q).unwrap();
807        println!("{k:#?}");
808    }
809
810    #[test]
811    fn test_delete_columns_query() {
812        let q = "DELETE field4, field5 FROM table WHERE field1=? AND field2=? AND field3=?";
813        let k = query(q).unwrap();
814        println!("{k:#?}");
815    }
816
817    #[test]
818    fn test_create_table() {
819        let q = r#"CREATE TABLE keyspace.table (
820                field1 uuid,
821                field2 text,
822                field3 text,
823                field4 text,
824                field5 timestamp,
825                field6 boolean,
826                field7 text,
827                field8 timestamp,
828                PRIMARY KEY ((field1, field2))
829            ) WITH bloom_filter_fp_chance = 0.01
830                AND comment = ''
831                AND parameter = {'key1': 'value1', 'key2': 'value2', 'key3': 'value3'}
832                AND crc_check_chance = 1.0
833                AND gc_grace_seconds = 1234
834                AND speculative_retry = '99PERCENTILE'
835        "#;
836
837        let k = query(q).unwrap();
838        println!("{k:#?}");
839    }
840
841    #[test]
842    fn test_udt() {
843        let q = r#"CREATE TYPE cycling.basic_info (
844              birthday timestamp,
845              nationality text,
846              weight text,
847              height text
848            );
849        "#;
850        let k = query(q).unwrap();
851        println!("{k:#?}");
852    }
853
854    #[test]
855    fn test_named_bind() {
856        let q = "INSERT INTO table (field1,field2,field3,field4,field5,field6) VALUES (:field1,:field2,:field3,:field4,:field5,:field6)";
857        let k = query(q).unwrap();
858        println!("{k:#?}")
859    }
860
861    #[test]
862    fn test_update_with_lit_null() {
863        let q = "UPDATE table SET field1=null,field2=null WHERE field3=? AND field4=?;";
864        let k = query(q).unwrap();
865        println!("{k:?}");
866    }
867
868    #[test]
869    fn test_regressions() {
870        let qs = [ "CREATE TABLE cycling.cyclist_name (     id UUID PRIMARY KEY,     lastname text,     firstname text );", "INSERT INTO cycling.cyclist_name (id, lastname, firstname) VALUES (6ab09bec-e68e-48d9-a5f8-97e6fb4c9b47, 'KRUIKSWIJK','Steven');"];
871
872        for q in qs {
873            let _ = query(q).unwrap();
874        }
875    }
876
877    #[test]
878    fn test_select_json() {
879        let q = "SELECT JSON field1,field2,field3 FROM table WHERE field0 = ? limit 500";
880        let QueryString::Select(s) = query(q).unwrap() else {
881            panic!("was supposed to be parsed as select query")
882        };
883        assert!(s.json);
884    }
885
886    #[test]
887    fn name_alias() {
888        let q = "SELECT field1 as field2 FROM table";
889        let QueryString::Select(SelectQuery {
890            columns: SelectExpression::Columns(c),
891            ..
892        }) = query(q).unwrap()
893        else {
894            panic!("was supposed to be parsed as select query")
895        };
896        assert_eq!(
897            c[0],
898            ColumnSelector {
899                name: "field1".to_string(),
900                alias: Some("field2".to_string()),
901                function: None,
902            }
903        )
904    }
905
906    #[test]
907    fn function() {
908        let q = "SELECT toJson(field1), field2 FROM table";
909        let QueryString::Select(SelectQuery {
910            columns: SelectExpression::Columns(c),
911            ..
912        }) = query(q).unwrap()
913        else {
914            panic!("was supposed to be parsed as select query")
915        };
916        assert_eq!(
917            c[0],
918            ColumnSelector {
919                name: "field1".to_string(),
920                alias: None,
921                function: Some(CqlFunction::ToJson),
922            }
923        )
924    }
925
926    #[test]
927    fn test_filter_comments() {
928        let s = "hello /* blabla */ world /* blabla */!";
929        assert_eq!(filter_comments(s).unwrap(), "hello  world !");
930    }
931
932    #[test]
933    fn query_with_comment() {
934        let q = "SELECT table_name AS name,\n       comment,\n       bloom_filter_fp_chance,\n       toJson(caching) as caching,\n       /* cdc, */\n       toJson(compaction) as compaction,\n       toJson(compression) as compression,\n       crc_check_chance,\n       dclocal_read_repair_chance,\n       default_time_to_live,\n       speculative_retry,\n       /* additional_write_policy, */\n       gc_grace_seconds,\n       max_index_interval,\n       memtable_flush_period_in_ms,\n       min_index_interval,\n       read_repair_chance\nFROM system_schema.tables\nWHERE keyspace_name = ?";
935        let k = query(q).unwrap();
936        println!("{k:?}");
937    }
938}