scyllax_parser/
lib.rs

1//! A parser for CQL queries
2//! See the source code and tests for examples of usage (for now).
3pub mod comment;
4pub mod common;
5pub mod create_keyspace;
6pub mod delete;
7pub mod reserved;
8pub mod select;
9pub mod r#where;
10
11use comment::parse_comment;
12pub use common::{Column, Value, Variable};
13use create_keyspace::CreateKeyspaceQuery;
14pub use delete::DeleteQuery;
15pub use r#where::{ComparisonOperator, WhereClause};
16pub use select::SelectQuery;
17
18use nom::{branch::alt, combinator::map, error::Error, multi::many0, Err, IResult};
19
20/// Represents a query
21/// ```rust
22/// use scyllax_parser::*;
23///
24/// let query = Query::try_from("select id, name from person where id = ?");
25/// assert_eq!(
26///     query,
27///     Ok(Query::Select(SelectQuery {
28///         table: "person".to_string(),
29///         columns: vec![
30///             Column::Identifier("id".to_string()),
31///             Column::Identifier("name".to_string()),
32///         ],
33///         condition: vec![
34///             WhereClause {
35///                 column: Column::Identifier("id".to_string()),
36///                 operator: ComparisonOperator::Equal,
37///                 value: Value::Variable(Variable::Placeholder),
38///             },
39///         ],
40///         limit: None,
41///     }))
42/// );
43/// ```
44#[derive(Debug, PartialEq)]
45pub enum Query {
46    /// A select query
47    Select(SelectQuery),
48    /// A delete query
49    Delete(DeleteQuery),
50    /// A create keyspace query
51    CreateKeyspace(CreateKeyspaceQuery),
52}
53
54/// Parse a CQL query.
55pub fn parse_query(input: &str) -> IResult<&str, Query> {
56    // trim whitespace
57    let input = input.trim();
58    // strip comments
59    let (input, _) = many0(parse_comment)(input)?;
60    let input = input.trim();
61    println!("input: {input:#?}");
62
63    alt((
64        map(select::parse_select, Query::Select),
65        map(delete::parse_delete, Query::Delete),
66        map(
67            create_keyspace::parse_create_keyspace,
68            Query::CreateKeyspace,
69        ),
70    ))(input)
71}
72
73impl<'a> TryFrom<&'a str> for Query {
74    type Error = Err<Error<&'a str>>;
75
76    fn try_from(value: &'a str) -> Result<Self, Self::Error> {
77        Ok(parse_query(value)?.1)
78    }
79}
80
81/// Parse a file that can contain multiple CQL queries. The queries are separated by a semicolon.
82/// There may be an indeterminate number of newlines between the semicolon and the next query.
83pub fn parse_query_file(input: &str) -> IResult<&str, Vec<Query>> {
84    let trimmed = input.trim();
85
86    let (input, queries) = nom::multi::separated_list1(
87        nom::character::complete::multispace0,
88        nom::sequence::terminated(parse_query, nom::character::complete::multispace0),
89    )(trimmed)?;
90
91    Ok((input, queries))
92}
93
94#[cfg(test)]
95mod test {
96    use super::*;
97    use pretty_assertions::assert_eq;
98
99    #[test]
100    fn test_query_select() {
101        let query = "/* this is a comment */ select id, name, age
102        from person
103        where id = :id
104        and name = :name
105        and age > ?
106        limit 10";
107        println!("query: {:#?}", query);
108
109        let query = Query::try_from(query);
110
111        assert_eq!(
112            query,
113            Ok(Query::Select(SelectQuery {
114                table: "person".to_string(),
115                columns: vec![
116                    Column::Identifier("id".to_string()),
117                    Column::Identifier("name".to_string()),
118                    Column::Identifier("age".to_string()),
119                ],
120                condition: vec![
121                    WhereClause {
122                        column: Column::Identifier("id".to_string()),
123                        operator: ComparisonOperator::Equal,
124                        value: Value::Variable(Variable::NamedVariable("id".to_string())),
125                    },
126                    WhereClause {
127                        column: Column::Identifier("name".to_string()),
128                        operator: ComparisonOperator::Equal,
129                        value: Value::Variable(Variable::NamedVariable("name".to_string())),
130                    },
131                    WhereClause {
132                        column: Column::Identifier("age".to_string()),
133                        operator: ComparisonOperator::GreaterThan,
134                        value: Value::Variable(Variable::Placeholder),
135                    },
136                ],
137                limit: Some(Value::Number(10)),
138            }))
139        );
140    }
141}