use super::common::{parse_column_name, parse_condition, parse_table_name}; use crate::syntax::{ColumnSelection, RawQuerySyntax}; use nom::{ branch::alt, bytes::complete::tag, character::complete::{char, multispace0, multispace1}, combinator::map, error::Error, multi::separated_list0, sequence::terminated, IResult, }; pub fn parse_select(input: &str) -> IResult<&str, RawQuerySyntax> { let (input, _) = tag("SELECT")(input)?; let (input, _) = multispace1(input)?; let (input, column_selection) = try_parse_column_selection(input)?; let (input, _) = multispace0(input)?; let (input, _) = tag("FROM")(input)?; let (input, _) = multispace1(input)?; let (input, table_name) = parse_table_name(input)?; let (input, _) = multispace0(input)?; let (input, condition) = parse_condition(input)?; let (input, _) = multispace0(input)?; // TODO: make it optional? let (input, _) = tag(";")(input)?; Ok(( input, RawQuerySyntax::Select(table_name.to_string(), column_selection, condition), )) } pub fn try_parse_column_selection(input: &str) -> IResult<&str, ColumnSelection> { let all_parser = map(tag::<&str, &str, Error<&str>>("*"), |_| { ColumnSelection::All }); let columns_parser = map( separated_list0(terminated(char(','), multispace0), parse_column_name), ColumnSelection::Columns, ); alt((all_parser, columns_parser))(input) } #[cfg(test)] mod tests { use crate::parsing::{ common::{parse_column_name, parse_table_name}, select::parse_select, }; use crate::syntax::{ColumnSelection, RawQuerySyntax}; #[test] fn test_parse_select_all() { let sql = "SELECT * FROM \"MyTable\";"; let operation = parse_select(sql).expect("should parse"); match operation { ("", RawQuerySyntax::Select(table_name, column_selection, maybe_condition)) => { assert_eq!(table_name, "MyTable"); assert!(matches!(column_selection, ColumnSelection::All)); assert!(matches!(maybe_condition, None)); } (input, _) => { println!("Input to be parsed: {}", input); panic!("expected select operation") } } } #[test] fn test_parse_column_name() { parse_column_name("1abc").expect_err("variable names should not start with number"); } #[test] fn test_parse_table_name() { parse_table_name("\"\"").expect_err("Empty table names are not allowed"); } #[test] fn test_parse_select_columns() { let sql = "SELECT name , email FROM \"AddressBook\" ;"; let operation = parse_select(sql).expect("should parse"); match operation { ("", RawQuerySyntax::Select(table_name, column_selection, maybe_condition)) => { assert_eq!(table_name, "AddressBook"); assert!(matches!(column_selection, ColumnSelection::Columns(_))); match column_selection { ColumnSelection::Columns(column_names) => { assert_eq!(column_names, vec!["name", "email"]); } _ => { panic!("should select columns") } } assert!(matches!(maybe_condition, None)); } (input, _) => { println!("Input to be parsed: {}", input); panic!("expected select operation") } } } #[test] fn test_parse_select_where() { use crate::syntax::Condition; let sql = "SELECT * FROM \"AddressBook\" WHERE id = 5 ;"; let operation = parse_select(sql).expect("should parse"); match operation { ("", RawQuerySyntax::Select(table_name, column_selection, maybe_condition)) => { assert_eq!(table_name, "AddressBook"); assert!(matches!(column_selection, ColumnSelection::All)); assert!(matches!(maybe_condition, Some(Condition::Eq(_, _)))); } (input, _) => { println!("Input to be parsed: {}", input); panic!("expected select operation") } } } // TODO: a test with multiple statements // TODO: allow underscores in identifiers }