use nom::{
branch::alt,
bytes::complete::{tag, tag_no_case},
character::complete::multispace0,
combinator::{map, opt},
multi::separated_list0,
IResult,
};
use crate::common::{
parse_rust_flavored_variable, parse_string_escaped_rust_flavored_variable, parse_value, Column,
Value,
};
pub fn parse_comparisons(input: &str) -> IResult<&str, Vec<WhereClause>> {
let (input, _) = opt(tag_no_case("and"))(input)?;
let (input, _) = multispace0(input)?;
separated_list0(tag_no_case("and"), parse_where_condition)(input)
}
pub fn parse_where_clause(input: &str) -> IResult<&str, Vec<WhereClause>> {
let (input, _) = tag_no_case("where ")(input)?;
separated_list0(tag_no_case("and"), parse_where_condition)(input)
}
#[derive(Debug, PartialEq)]
pub struct WhereClause {
pub column: Column,
pub operator: ComparisonOperator,
pub value: Value,
}
#[derive(Debug, PartialEq)]
pub enum ComparisonOperator {
Equal,
GreaterThan,
LessThan,
GreaterThanOrEqual,
LessThanOrEqual,
In,
Contains,
ContainsKey,
}
fn parse_where_column(input: &str) -> IResult<&str, String> {
let (input, col) = alt((
map(parse_string_escaped_rust_flavored_variable, |x| {
format!("\"{x}\"")
}),
map(parse_rust_flavored_variable, |x: &str| x.to_string()),
))(input)?;
Ok((input, col.clone()))
}
pub fn parse_where_condition(input: &str) -> IResult<&str, WhereClause> {
let (input, _) = multispace0(input)?;
let (input, column) = parse_where_column(input)?;
let (input, _) = multispace0(input)?;
let (input, operator) = parse_comparison_operator(input)?;
let (input, _) = multispace0(input)?;
let (input, value) = parse_value(input)?;
let (input, _) = multispace0(input)?;
let (input, _) = opt(tag(";"))(input)?;
Ok((
input,
WhereClause {
column: Column::Identifier(column),
operator,
value,
},
))
}
fn parse_comparison_operator(input: &str) -> IResult<&str, ComparisonOperator> {
alt((
map(tag(">="), |_| ComparisonOperator::GreaterThanOrEqual),
map(tag("<="), |_| ComparisonOperator::LessThanOrEqual),
map(tag("="), |_| ComparisonOperator::Equal),
map(tag(">"), |_| ComparisonOperator::GreaterThan),
map(tag("<"), |_| ComparisonOperator::LessThan),
map(tag_no_case("in"), |_| ComparisonOperator::In),
map(tag_no_case("contains key"), |_| {
ComparisonOperator::ContainsKey
}),
map(tag_no_case("contains"), |_| ComparisonOperator::Contains),
))(input)
}
#[cfg(test)]
mod test {
use super::*;
use crate::*;
use pretty_assertions::assert_eq;
#[test]
fn test_funky_casing() {
assert_eq!(
parse_where_clause(
r#"where "userId" = ? and "actionOperation" = ? and "timeBucket" = ?"#
),
Ok((
"",
vec![
WhereClause {
column: Column::Identifier(r#""userId""#.to_string()),
operator: ComparisonOperator::Equal,
value: Value::Variable(Variable::Placeholder)
},
WhereClause {
column: Column::Identifier(r#""actionOperation""#.to_string()),
operator: ComparisonOperator::Equal,
value: Value::Variable(Variable::Placeholder)
},
WhereClause {
column: Column::Identifier(r#""timeBucket""#.to_string()),
operator: ComparisonOperator::Equal,
value: Value::Variable(Variable::Placeholder)
}
]
))
);
}
#[test]
fn test_parse_single_where_clause() {
assert_eq!(
parse_where_clause("where id = ?"),
Ok((
"",
vec![WhereClause {
column: Column::Identifier("id".to_string()),
operator: ComparisonOperator::Equal,
value: Value::Variable(Variable::Placeholder)
}]
))
);
}
#[test]
fn test_parse_multiple_where_clauses() {
assert_eq!(
parse_where_clause("where id = ? and name > :name"),
Ok((
"",
vec![
WhereClause {
column: Column::Identifier("id".to_string()),
operator: ComparisonOperator::Equal,
value: Value::Variable(Variable::Placeholder)
},
WhereClause {
column: Column::Identifier("name".to_string()),
operator: ComparisonOperator::GreaterThan,
value: Value::Variable(Variable::NamedVariable("name".to_string()))
}
]
))
);
assert_eq!(
parse_where_clause("where id = :id and name = :name and age > 10"),
Ok((
"",
vec![
WhereClause {
column: Column::Identifier("id".to_string()),
operator: ComparisonOperator::Equal,
value: Value::Variable(Variable::NamedVariable("id".to_string()))
},
WhereClause {
column: Column::Identifier("name".to_string()),
operator: ComparisonOperator::Equal,
value: Value::Variable(Variable::NamedVariable("name".to_string()))
},
WhereClause {
column: Column::Identifier("age".to_string()),
operator: ComparisonOperator::GreaterThan,
value: Value::Number(10)
}
]
))
);
}
#[test]
fn test_parse_comparison_operator() {
assert_eq!(
parse_comparison_operator("="),
Ok(("", ComparisonOperator::Equal))
);
assert_eq!(
parse_comparison_operator(">"),
Ok(("", ComparisonOperator::GreaterThan))
);
assert_eq!(
parse_comparison_operator("<"),
Ok(("", ComparisonOperator::LessThan))
);
assert_eq!(
parse_comparison_operator(">="),
Ok(("", ComparisonOperator::GreaterThanOrEqual))
);
assert_eq!(
parse_comparison_operator("<="),
Ok(("", ComparisonOperator::LessThanOrEqual))
);
assert_eq!(
parse_comparison_operator("in"),
Ok(("", ComparisonOperator::In))
);
assert_eq!(
parse_comparison_operator("contains"),
Ok(("", ComparisonOperator::Contains))
);
assert_eq!(
parse_comparison_operator("contains key"),
Ok(("", ComparisonOperator::ContainsKey))
);
}
}