use minisql::type_system::DbType; use nom::{ branch::alt, bytes::complete::{tag, take_while}, character::{complete::{alphanumeric1, anychar, char, multispace0, multispace1}, is_alphanumeric}, combinator::peek, error::make_error, sequence::{delimited, terminated}, IResult, Parser }; use crate::syntax::Condition; use super::literal::parse_literal; pub fn parse_table_name(input: &str) -> IResult<&str, &str> { alt(( delimited(char('"'), alphanumeric1, char('"')), parse_identifier, ))(input) } pub fn parse_identifier(input: &str) -> IResult<&str, &str> { let (_, first) = peek(anychar)(input)?; if first.is_alphabetic() || first == '_' { take_while(|c: char| { is_alphanumeric(c as u8) || c == '_' })(input) } else { Err(nom::Err::Error(make_error( input, nom::error::ErrorKind::Alpha, ))) } } pub fn parse_column_name(input: &str) -> IResult<&str, String> { terminated(parse_identifier, multispace0)(input).map(|(rest, name)| (rest, name.to_string())) } pub fn parse_db_type(input: &str) -> IResult<&str, DbType> { let (input, db_type) = alt( ( tag("STRING") .map(|_| DbType::String), tag("INT") .map(|_| DbType::Int), tag("NUMBER") .map(|_| DbType::Number), tag("UUID") .map(|_| DbType::Uuid), delimited(tag("Option("), parse_db_type, tag(")")) .map(|ty| { DbType::Option(Box::new(ty)) }) ) )(input)?; Ok((input, db_type)) } pub fn parse_condition(input: &str) -> IResult<&str, Option> { match tag::<&str, &str, nom::error::Error<&str>>("WHERE")(input) { Ok((input, _)) => { let (input, _) = multispace1(input)?; let (input, condition) = parse_equality(input)?; Ok((input, Some(condition))) } Err(_) => Ok((input, None)), } } fn parse_equality(input: &str) -> IResult<&str, Condition> { let (input, column_name) = parse_column_name(input)?; let (input, _) = multispace0(input)?; let (input, _) = char('=')(input)?; let (input, _) = multispace0(input)?; let (input, lit) = parse_literal(input)?; Ok((input, Condition::Eq(column_name, lit))) } #[cfg(test)] mod tests { use minisql::type_system::DbType; use crate::parsing::common::{parse_db_type, parse_equality, parse_identifier}; use crate::parsing::literal::Literal; use crate::syntax::Condition; #[test] fn test_parse_equality() { match parse_equality("id = 1") { Ok(("", Condition::Eq(column_name, value))) => { assert!(column_name.eq("id")); assert_eq!(value, Literal::Int(1)) } _ => { panic!("should parse"); } } } #[test] fn test_parse_db_type() { assert!(matches!( parse_db_type("INT").expect("should parse").1, DbType::Int )); assert!(matches!( parse_db_type("STRING").expect("should parse").1, DbType::String )); assert!(matches!( parse_db_type("UUID").expect("should parse").1, DbType::Uuid )); assert!(matches!( parse_db_type("NUMBER").expect("should parse").1, DbType::Number )); assert!(matches!(parse_db_type("Unknown"), Err(_))); } #[test] fn test_parse_identifier() { assert_eq!( parse_identifier("_variable__Test").expect("should parse").1, "_variable__Test" ); assert!(matches!( parse_identifier("123_variable__Test"), Err(_) )); } #[test] fn test_parse_option_string_type() { assert_eq!( parse_db_type("Option(STRING)").expect("should parse").1, DbType::Option(Box::new(DbType::String)) ); } #[test] fn test_parse_nested_option_int_type() { assert_eq!( parse_db_type("Option(Option(Option(INT)))").expect("should parse").1, DbType::Option(Box::new(DbType::Option(Box::new(DbType::Option(Box::new(DbType::Int)))))) ); } }