scyllax_parser/
where.rs

1//! Parses where clauses in CQL statements
2//! ```ignore
3//! where_clause: `relation` ( AND `relation` )*
4//! relation: `column_name` `operator` `term`
5//!         : '(' `column_name` ( ',' `column_name` )* ')' `operator` `tuple_literal`
6//!         : TOKEN '(' `column_name` ( ',' `column_name` )* ')' `operator` `term`
7//! ```
8use nom::{
9    branch::alt,
10    bytes::complete::{tag, tag_no_case},
11    character::complete::multispace0,
12    combinator::{map, opt},
13    multi::separated_list0,
14    IResult,
15};
16
17use crate::common::{
18    parse_rust_flavored_variable, parse_string_escaped_rust_flavored_variable, parse_value, Column,
19    Value,
20};
21
22/// Parses a condition.
23/// - eg: `id = ?`
24/// - eg: durable_writes = true
25pub fn parse_comparisons(input: &str) -> IResult<&str, Vec<WhereClause>> {
26    // remove leading and if exists
27    let (input, _) = opt(tag_no_case("and"))(input)?;
28    let (input, _) = multispace0(input)?;
29
30    separated_list0(tag_no_case("and"), parse_where_condition)(input)
31}
32
33/// Parses a where clause with the following format:
34///
35/// `where <identifier> <operator> <varialbe>`
36/// - eg: `where id = ?`
37/// - eg: `where id = :id and name = ?`
38/// - eg: `where id in :ids`
39/// - eg: `where id in ? and name = :name`
40/// - eg: `where id > :id`
41pub fn parse_where_clause(input: &str) -> IResult<&str, Vec<WhereClause>> {
42    let (input, _) = tag_no_case("where ")(input)?;
43
44    separated_list0(tag_no_case("and"), parse_where_condition)(input)
45}
46
47/// Represents a single `where` clause on a CQL statement
48#[derive(Debug, PartialEq)]
49pub struct WhereClause {
50    /// The column being queried
51    pub column: Column,
52    /// The operator being used
53    pub operator: ComparisonOperator,
54    /// The variable being compared
55    pub value: Value,
56}
57
58/// Represents a comparison operator
59#[derive(Debug, PartialEq)]
60pub enum ComparisonOperator {
61    /// The comparison operator is `=`
62    Equal,
63    /// The comparison operator is `>`
64    GreaterThan,
65    /// The comparison operator is `<`
66    LessThan,
67    /// The comparison operator is `>=`
68    GreaterThanOrEqual,
69    /// The comparison operator is `<=`
70    LessThanOrEqual,
71
72    /// The comparison operator is `in`
73    In,
74    /// The comparison operator is `contains`
75    Contains,
76    /// The comparison operator is `contains_key`
77    ContainsKey,
78}
79
80/// Parses the column in a where statement, considering it may be wrapped in quotes.
81fn parse_where_column(input: &str) -> IResult<&str, String> {
82    let (input, col) = alt((
83        map(parse_string_escaped_rust_flavored_variable, |x| {
84            format!("\"{x}\"")
85        }),
86        map(parse_rust_flavored_variable, |x: &str| x.to_string()),
87    ))(input)?;
88
89    Ok((input, col.clone()))
90}
91
92/// Parses a single where condition
93pub fn parse_where_condition(input: &str) -> IResult<&str, WhereClause> {
94    // eat leading whitespace
95    let (input, _) = multispace0(input)?;
96
97    let (input, column) = parse_where_column(input)?;
98    let (input, _) = multispace0(input)?;
99    let (input, operator) = parse_comparison_operator(input)?;
100    let (input, _) = multispace0(input)?;
101    let (input, value) = parse_value(input)?;
102
103    // eat trailing whitespace
104    let (input, _) = multispace0(input)?;
105    // eat trailing semicolon
106    let (input, _) = opt(tag(";"))(input)?;
107
108    Ok((
109        input,
110        WhereClause {
111            column: Column::Identifier(column),
112            operator,
113            value,
114        },
115    ))
116}
117
118/// Parses a comparison operator
119fn parse_comparison_operator(input: &str) -> IResult<&str, ComparisonOperator> {
120    alt((
121        map(tag(">="), |_| ComparisonOperator::GreaterThanOrEqual),
122        map(tag("<="), |_| ComparisonOperator::LessThanOrEqual),
123        map(tag("="), |_| ComparisonOperator::Equal),
124        map(tag(">"), |_| ComparisonOperator::GreaterThan),
125        map(tag("<"), |_| ComparisonOperator::LessThan),
126        map(tag_no_case("in"), |_| ComparisonOperator::In),
127        map(tag_no_case("contains key"), |_| {
128            ComparisonOperator::ContainsKey
129        }),
130        map(tag_no_case("contains"), |_| ComparisonOperator::Contains),
131    ))(input)
132}
133
134#[cfg(test)]
135mod test {
136    use super::*;
137    use crate::*;
138    use pretty_assertions::assert_eq;
139
140    #[test]
141    fn test_funky_casing() {
142        assert_eq!(
143            parse_where_clause(
144                r#"where "userId" = ? and "actionOperation" = ? and "timeBucket" = ?"#
145            ),
146            Ok((
147                "",
148                vec![
149                    WhereClause {
150                        column: Column::Identifier(r#""userId""#.to_string()),
151                        operator: ComparisonOperator::Equal,
152                        value: Value::Variable(Variable::Placeholder)
153                    },
154                    WhereClause {
155                        column: Column::Identifier(r#""actionOperation""#.to_string()),
156                        operator: ComparisonOperator::Equal,
157                        value: Value::Variable(Variable::Placeholder)
158                    },
159                    WhereClause {
160                        column: Column::Identifier(r#""timeBucket""#.to_string()),
161                        operator: ComparisonOperator::Equal,
162                        value: Value::Variable(Variable::Placeholder)
163                    }
164                ]
165            ))
166        );
167    }
168
169    #[test]
170    fn test_parse_single_where_clause() {
171        assert_eq!(
172            parse_where_clause("where id = ?"),
173            Ok((
174                "",
175                vec![WhereClause {
176                    column: Column::Identifier("id".to_string()),
177                    operator: ComparisonOperator::Equal,
178                    value: Value::Variable(Variable::Placeholder)
179                }]
180            ))
181        );
182    }
183
184    #[test]
185    fn test_parse_multiple_where_clauses() {
186        assert_eq!(
187            parse_where_clause("where id = ? and name > :name"),
188            Ok((
189                "",
190                vec![
191                    WhereClause {
192                        column: Column::Identifier("id".to_string()),
193                        operator: ComparisonOperator::Equal,
194                        value: Value::Variable(Variable::Placeholder)
195                    },
196                    WhereClause {
197                        column: Column::Identifier("name".to_string()),
198                        operator: ComparisonOperator::GreaterThan,
199                        value: Value::Variable(Variable::NamedVariable("name".to_string()))
200                    }
201                ]
202            ))
203        );
204
205        assert_eq!(
206            parse_where_clause("where id = :id and name = :name and age > 10"),
207            Ok((
208                "",
209                vec![
210                    WhereClause {
211                        column: Column::Identifier("id".to_string()),
212                        operator: ComparisonOperator::Equal,
213                        value: Value::Variable(Variable::NamedVariable("id".to_string()))
214                    },
215                    WhereClause {
216                        column: Column::Identifier("name".to_string()),
217                        operator: ComparisonOperator::Equal,
218                        value: Value::Variable(Variable::NamedVariable("name".to_string()))
219                    },
220                    WhereClause {
221                        column: Column::Identifier("age".to_string()),
222                        operator: ComparisonOperator::GreaterThan,
223                        value: Value::Number(10)
224                    }
225                ]
226            ))
227        );
228    }
229
230    #[test]
231    fn test_parse_comparison_operator() {
232        assert_eq!(
233            parse_comparison_operator("="),
234            Ok(("", ComparisonOperator::Equal))
235        );
236        assert_eq!(
237            parse_comparison_operator(">"),
238            Ok(("", ComparisonOperator::GreaterThan))
239        );
240        assert_eq!(
241            parse_comparison_operator("<"),
242            Ok(("", ComparisonOperator::LessThan))
243        );
244        assert_eq!(
245            parse_comparison_operator(">="),
246            Ok(("", ComparisonOperator::GreaterThanOrEqual))
247        );
248        assert_eq!(
249            parse_comparison_operator("<="),
250            Ok(("", ComparisonOperator::LessThanOrEqual))
251        );
252        assert_eq!(
253            parse_comparison_operator("in"),
254            Ok(("", ComparisonOperator::In))
255        );
256        assert_eq!(
257            parse_comparison_operator("contains"),
258            Ok(("", ComparisonOperator::Contains))
259        );
260        assert_eq!(
261            parse_comparison_operator("contains key"),
262            Ok(("", ComparisonOperator::ContainsKey))
263        );
264    }
265}