use nom::{
branch::alt,
bytes::complete::{tag, tag_no_case},
character::complete::{multispace0, multispace1},
combinator::{map, opt},
error::Error,
multi::separated_list0,
Err, IResult,
};
use crate::{
common::{
parse_identifier, parse_limit_clause, parse_rust_flavored_variable,
parse_string_escaped_rust_flavored_variable,
},
r#where::{parse_where_clause, WhereClause},
Column, Value,
};
#[derive(Debug, PartialEq)]
pub struct SelectQuery {
pub table: String,
pub columns: Vec<Column>,
pub condition: Vec<WhereClause>,
pub limit: Option<Value>,
}
impl<'a> TryFrom<&'a str> for SelectQuery {
type Error = Err<Error<&'a str>>;
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
Ok(parse_select(value)?.1)
}
}
fn parse_select_clause(input: &str) -> IResult<&str, Vec<Column>> {
separated_list0(
tag(", "),
map(parse_identifier, |ident| {
Column::Identifier(ident.to_string())
}),
)(input)
}
fn parse_asterisk(input: &str) -> IResult<&str, Column> {
let (input, _) = tag("*")(input)?;
Ok((input, Column::Asterisk))
}
fn parse_table_name(input: &str) -> IResult<&str, String> {
let (input, table) = alt((
map(parse_string_escaped_rust_flavored_variable, |x| {
format!("\"{x}\"")
}),
map(parse_rust_flavored_variable, |x: &str| x.to_string()),
))(input)?;
Ok((input, table.clone()))
}
pub fn parse_select(input: &str) -> IResult<&str, SelectQuery> {
let (input, _) = tag_no_case("select ")(input)?;
let (input, columns) = alt((
map(parse_asterisk, |_| vec![Column::Asterisk]),
map(parse_select_clause, |cols| cols),
))(input)?;
let (input, _) = multispace1(input)?;
let (input, _) = tag_no_case("from ")(input)?;
let (input, table) = parse_table_name(input)?;
let (input, _) = multispace0(input)?;
let (input, condition) = opt(parse_where_clause)(input)?;
let (input, _) = multispace0(input)?;
let (input, limit) = opt(parse_limit_clause)(input)?;
Ok((
input,
SelectQuery {
table,
columns,
condition: condition.unwrap_or_default(),
limit,
},
))
}
#[cfg(test)]
mod test {
use super::*;
use crate::*;
use pretty_assertions::assert_eq;
fn big() -> (&'static str, SelectQuery) {
(
"SELECT id, name, age FROM person WHERE id = :id AND name = :name AND age > ? LIMIT 10",
SelectQuery {
table: "person".to_string(),
columns: vec![
Column::Identifier("id".to_string()),
Column::Identifier("name".to_string()),
Column::Identifier("age".to_string()),
],
condition: 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::Variable(Variable::Placeholder),
},
],
limit: Some(Value::Number(10)),
},
)
}
#[test]
fn test_parse_asterisk() {
assert_eq!(parse_asterisk("*"), Ok(("", Column::Asterisk)));
}
#[test]
fn test_parse_select_clause() {
assert_eq!(
parse_select_clause("id, name"),
Ok((
"",
vec![
Column::Identifier("id".to_string()),
Column::Identifier("name".to_string()),
]
))
);
}
#[test]
fn test_parse_limit_clause() {
assert_eq!(
parse_limit_clause("limit ?"),
Ok(("", Value::Variable(Variable::Placeholder)))
);
}
#[test]
#[should_panic(expected = "variable `limit` is a reserved keyword")]
fn test_fail_parse_limit_clause() {
parse_limit_clause("limit :limit").unwrap();
}
#[test]
fn test_try_from() {
let (query, res) = big();
assert_eq!(SelectQuery::try_from(query), Ok(res));
}
#[test]
fn test_custom() {
let parsed = parse_select("select * from person_by_email where email = :email limit 1");
assert_eq!(
parsed,
Ok((
"",
SelectQuery {
table: "person_by_email".to_string(),
columns: vec![Column::Asterisk],
condition: vec![WhereClause {
column: Column::Identifier("email".to_string()),
operator: ComparisonOperator::Equal,
value: Value::Variable(Variable::NamedVariable("email".to_string())),
}],
limit: Some(Value::Number(1)),
}
))
);
}
#[test]
fn test_parse_select() {
assert_eq!(
parse_select("select * from users"),
Ok((
"",
SelectQuery {
table: "users".to_string(),
columns: vec![Column::Asterisk],
condition: vec![],
limit: None,
}
))
);
assert_eq!(
parse_select("select id, name from users"),
Ok((
"",
SelectQuery {
table: "users".to_string(),
columns: vec![
Column::Identifier("id".to_string()),
Column::Identifier("name".to_string()),
],
condition: vec![],
limit: None,
}
))
);
assert_eq!(
parse_select("select id, name from users where id = ?"),
Ok((
"",
SelectQuery {
table: "users".to_string(),
columns: vec![
Column::Identifier("id".to_string()),
Column::Identifier("name".to_string()),
],
condition: vec![WhereClause {
column: Column::Identifier("id".to_string()),
operator: r#where::ComparisonOperator::Equal,
value: Value::Variable(Variable::Placeholder),
}],
limit: None,
}
))
);
assert_eq!(
parse_select("select id, name from users where id = :id limit ?"),
Ok((
"",
SelectQuery {
table: "users".to_string(),
columns: vec![
Column::Identifier("id".to_string()),
Column::Identifier("name".to_string()),
],
condition: vec![WhereClause {
column: Column::Identifier("id".to_string()),
operator: r#where::ComparisonOperator::Equal,
value: Value::Variable(Variable::NamedVariable("id".to_string())),
}],
limit: Some(Value::Variable(Variable::Placeholder)),
}
))
);
assert_eq!(
parse_select("select id, name from users where id in :id and age = ? limit ?"),
Ok((
"",
SelectQuery {
table: "users".to_string(),
columns: vec![
Column::Identifier("id".to_string()),
Column::Identifier("name".to_string()),
],
condition: vec![
WhereClause {
column: Column::Identifier("id".to_string()),
operator: r#where::ComparisonOperator::In,
value: Value::Variable(Variable::NamedVariable("id".to_string())),
},
WhereClause {
column: Column::Identifier("age".to_string()),
operator: r#where::ComparisonOperator::Equal,
value: Value::Variable(Variable::Placeholder),
}
],
limit: Some(Value::Variable(Variable::Placeholder)),
}
))
);
let (query, res) = big();
assert_eq!(parse_select(query), Ok(("", res)));
}
}