Merge branch 'parsing-TODOs-and-optional' into 'main'
Resolve TODOs in parsing and add Optional parsing & validation See merge request x433485/minisql!29
This commit is contained in:
commit
9106e23d61
10 changed files with 395 additions and 144 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::syntax::RawQuerySyntax;
|
use crate::syntax::RawQuerySyntax;
|
||||||
use minisql::{interpreter::DbSchema, operation::Operation};
|
use minisql::{interpreter::DbSchema, operation::Operation};
|
||||||
use nom::{branch::alt, IResult};
|
use nom::{branch::alt, character::complete::{multispace0, char}, multi::many1, sequence::{delimited, terminated}, IResult};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -19,6 +19,7 @@ pub enum Error {
|
||||||
ValidationError(#[from] ValidationError),
|
ValidationError(#[from] ValidationError),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse single statement
|
||||||
fn parse_statement(input: &str) -> IResult<&str, RawQuerySyntax> {
|
fn parse_statement(input: &str) -> IResult<&str, RawQuerySyntax> {
|
||||||
alt((
|
alt((
|
||||||
parse_insert,
|
parse_insert,
|
||||||
|
|
@ -31,6 +32,12 @@ fn parse_statement(input: &str) -> IResult<&str, RawQuerySyntax> {
|
||||||
))(input)
|
))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse one or more statements
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn parse_statement1(input: &str) -> IResult<&str, Vec<RawQuerySyntax>> {
|
||||||
|
many1(terminated(parse_statement, delimited(multispace0, char(';'), multispace0)))(input)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_and_validate(str_query: String, db_schema: &DbSchema) -> Result<Operation, Error> {
|
pub fn parse_and_validate(str_query: String, db_schema: &DbSchema) -> Result<Operation, Error> {
|
||||||
let (_, op) =
|
let (_, op) =
|
||||||
parse_statement(str_query.as_str()).map_err(|err| Error::ParsingError(err.to_string()))?;
|
parse_statement(str_query.as_str()).map_err(|err| Error::ParsingError(err.to_string()))?;
|
||||||
|
|
@ -38,7 +45,34 @@ pub fn parse_and_validate(str_query: String, db_schema: &DbSchema) -> Result<Ope
|
||||||
Ok(validate_operation(op, db_schema)?)
|
Ok(validate_operation(op, db_schema)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[test]
|
#[cfg(test)]
|
||||||
// fn test_select() {
|
mod test {
|
||||||
// parse_and_validate("SELECT * FROM users;".to_string(), &Vec::new()).unwrap();
|
use crate::core::parse_statement1;
|
||||||
// }
|
use crate::parse_and_validate;
|
||||||
|
use crate::Error;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_two_select() {
|
||||||
|
let (rest, sntx) = parse_statement1("SELECT * FROM users ; SELECT * FROM cities ; ").expect("should parse");
|
||||||
|
assert_eq!(
|
||||||
|
sntx.len(),
|
||||||
|
2
|
||||||
|
);
|
||||||
|
assert_eq!(rest, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_three_insert_one_select() {
|
||||||
|
let (rest, sntx) = parse_statement1(
|
||||||
|
r#"INSERT INTO table1 (id, data) VALUES (u1, 2);
|
||||||
|
SELECT * FROM users ;
|
||||||
|
INSERT INTO table1 (id, data) VALUES (u4, 30) ;
|
||||||
|
INSERT INTO table1 (id, data) VALUES (u5, 40) ;
|
||||||
|
"#).expect("should parse");
|
||||||
|
assert_eq!(
|
||||||
|
sntx.len(),
|
||||||
|
4
|
||||||
|
);
|
||||||
|
assert_eq!(rest, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,10 @@
|
||||||
use minisql::type_system::DbType;
|
use minisql::type_system::DbType;
|
||||||
use nom::{
|
use nom::{
|
||||||
branch::alt,
|
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
|
||||||
bytes::complete::tag,
|
|
||||||
character::complete::{alphanumeric1, anychar, char, multispace0, multispace1},
|
|
||||||
combinator::peek,
|
|
||||||
error::make_error,
|
|
||||||
sequence::{delimited, terminated},
|
|
||||||
IResult,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::literal::parse_db_value;
|
|
||||||
use crate::syntax::Condition;
|
use crate::syntax::Condition;
|
||||||
|
use super::literal::parse_literal;
|
||||||
|
|
||||||
pub fn parse_table_name(input: &str) -> IResult<&str, &str> {
|
pub fn parse_table_name(input: &str) -> IResult<&str, &str> {
|
||||||
alt((
|
alt((
|
||||||
|
|
@ -20,10 +14,11 @@ pub fn parse_table_name(input: &str) -> IResult<&str, &str> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_identifier(input: &str) -> IResult<&str, &str> {
|
pub fn parse_identifier(input: &str) -> IResult<&str, &str> {
|
||||||
// TODO: allow underscores
|
|
||||||
let (_, first) = peek(anychar)(input)?;
|
let (_, first) = peek(anychar)(input)?;
|
||||||
if first.is_alphabetic() {
|
if first.is_alphabetic() || first == '_' {
|
||||||
alphanumeric1(input)
|
take_while(|c: char| {
|
||||||
|
is_alphanumeric(c as u8) || c == '_'
|
||||||
|
})(input)
|
||||||
} else {
|
} else {
|
||||||
Err(nom::Err::Error(make_error(
|
Err(nom::Err::Error(make_error(
|
||||||
input,
|
input,
|
||||||
|
|
@ -37,19 +32,22 @@ pub fn parse_column_name(input: &str) -> IResult<&str, String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_db_type(input: &str) -> IResult<&str, DbType> {
|
pub fn parse_db_type(input: &str) -> IResult<&str, DbType> {
|
||||||
let (input, type_name) = alt((tag("STRING"), tag("INT"), tag("NUMBER"), tag("UUID")))(input)?;
|
let (input, db_type) = alt(
|
||||||
let db_type = match type_name {
|
(
|
||||||
"STRING" => DbType::String,
|
tag("STRING")
|
||||||
"INT" => DbType::Int,
|
.map(|_| DbType::String),
|
||||||
"UUID" => DbType::Uuid,
|
tag("INT")
|
||||||
"NUMBER" => DbType::Number,
|
.map(|_| DbType::Int),
|
||||||
_ => {
|
tag("NUMBER")
|
||||||
return Err(nom::Err::Failure(make_error(
|
.map(|_| DbType::Number),
|
||||||
input,
|
tag("UUID")
|
||||||
nom::error::ErrorKind::IsNot,
|
.map(|_| DbType::Uuid),
|
||||||
)))
|
delimited(tag("Option("), parse_db_type, tag(")"))
|
||||||
}
|
.map(|ty| {
|
||||||
};
|
DbType::Option(Box::new(ty))
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)(input)?;
|
||||||
Ok((input, db_type))
|
Ok((input, db_type))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -69,24 +67,24 @@ fn parse_equality(input: &str) -> IResult<&str, Condition> {
|
||||||
let (input, _) = multispace0(input)?;
|
let (input, _) = multispace0(input)?;
|
||||||
let (input, _) = char('=')(input)?;
|
let (input, _) = char('=')(input)?;
|
||||||
let (input, _) = multispace0(input)?;
|
let (input, _) = multispace0(input)?;
|
||||||
let (input, db_value) = parse_db_value(input)?;
|
let (input, lit) = parse_literal(input)?;
|
||||||
Ok((input, Condition::Eq(column_name, db_value)))
|
Ok((input, Condition::Eq(column_name, lit)))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use minisql::type_system::DbType;
|
use minisql::type_system::DbType;
|
||||||
|
|
||||||
use crate::parsing::common::{parse_db_type, parse_equality};
|
use crate::parsing::common::{parse_db_type, parse_equality, parse_identifier};
|
||||||
|
use crate::parsing::literal::Literal;
|
||||||
use crate::syntax::Condition;
|
use crate::syntax::Condition;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_equality() {
|
fn test_parse_equality() {
|
||||||
use minisql::type_system::Value;
|
|
||||||
match parse_equality("id = 1") {
|
match parse_equality("id = 1") {
|
||||||
Ok(("", Condition::Eq(column_name, value))) => {
|
Ok(("", Condition::Eq(column_name, value))) => {
|
||||||
assert!(column_name.eq("id"));
|
assert!(column_name.eq("id"));
|
||||||
assert_eq!(value, Value::Int(1))
|
assert_eq!(value, Literal::Int(1))
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
panic!("should parse");
|
panic!("should parse");
|
||||||
|
|
@ -114,4 +112,32 @@ mod tests {
|
||||||
));
|
));
|
||||||
assert!(matches!(parse_db_type("Unknown"), Err(_)));
|
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))))))
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,6 @@ pub fn parse_create(input: &str) -> IResult<&str, RawQuerySyntax> {
|
||||||
let (input, column_definitions) = parse_column_definitions(input)?;
|
let (input, column_definitions) = parse_column_definitions(input)?;
|
||||||
|
|
||||||
let (input, _) = char(')')(input)?;
|
let (input, _) = char(')')(input)?;
|
||||||
let (input, _) = multispace0(input)?;
|
|
||||||
let (input, _) = char(';')(input)?;
|
|
||||||
let schema = RawTableSchema {
|
let schema = RawTableSchema {
|
||||||
table_name: table_name.to_string(),
|
table_name: table_name.to_string(),
|
||||||
columns: column_definitions,
|
columns: column_definitions,
|
||||||
|
|
@ -61,37 +59,39 @@ fn parse_column_definition(input: &str) -> IResult<&str, ColumnSchema> {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use minisql::type_system::DbType;
|
||||||
|
|
||||||
use crate::parsing::create::parse_create;
|
use crate::parsing::create::parse_create;
|
||||||
use crate::syntax::RawQuerySyntax;
|
use crate::syntax::RawQuerySyntax;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_create_no_spaces() {
|
fn test_parse_create_no_spaces() {
|
||||||
parse_create("CREATE TABLE \"Table1\"(id UUID ,column1 INT);").expect("should parse");
|
parse_create("CREATE TABLE \"Table1\"(id UUID ,column1 INT)").expect("should parse");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_create_primary_key() {
|
fn test_parse_create_primary_key() {
|
||||||
parse_create("CREATE TABLE \"Table1\"(id UUID PRIMARY KEY,column1 INT);")
|
parse_create("CREATE TABLE \"Table1\"(id UUID PRIMARY KEY,column1 INT)")
|
||||||
.expect("should parse");
|
.expect("should parse");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_create_no_quotes_table_name() {
|
fn test_parse_create_no_quotes_table_name() {
|
||||||
parse_create("CREATE TABLE Table1(id UUID PRIMARY KEY,column1 INT);")
|
parse_create("CREATE TABLE Table1(id UUID PRIMARY KEY,column1 INT)")
|
||||||
.expect("should parse");
|
.expect("should parse");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_create_primary_key_with_spaces() {
|
fn test_parse_create_primary_key_with_spaces() {
|
||||||
parse_create(
|
parse_create(
|
||||||
"CREATE TABLE \"Table1\" ( id UUID PRIMARY KEY , column1 INT ) ;",
|
"CREATE TABLE \"Table1\" ( id UUID PRIMARY KEY , column1 INT )",
|
||||||
)
|
)
|
||||||
.expect("should parse");
|
.expect("should parse");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_create() {
|
fn test_parse_create() {
|
||||||
let (_, create) = parse_create("CREATE TABLE \"Table1\"( id UUID , column1 INT );")
|
let (_, create) = parse_create("CREATE TABLE \"Table1\"( id UUID , column1 INT )")
|
||||||
.expect("should parse");
|
.expect("should parse");
|
||||||
assert!(matches!(create, RawQuerySyntax::CreateTable(_)));
|
assert!(matches!(create, RawQuerySyntax::CreateTable(_)));
|
||||||
match create {
|
match create {
|
||||||
|
|
@ -114,4 +114,45 @@ mod tests {
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_create_option() {
|
||||||
|
let (_, create) = parse_create("CREATE TABLE games (id UUID PRIMARY KEY, name STRING, year Option(INT), price NUMBER)")
|
||||||
|
.expect("should parse");
|
||||||
|
assert!(matches!(create, RawQuerySyntax::CreateTable(_)));
|
||||||
|
match create {
|
||||||
|
RawQuerySyntax::CreateTable(schema) => {
|
||||||
|
assert_eq!(schema.table_name, "games");
|
||||||
|
assert_eq!(schema.number_of_columns(), 4);
|
||||||
|
|
||||||
|
let result_id = schema.get_column(&"id".to_string());
|
||||||
|
assert!(matches!(result_id, Some(_)));
|
||||||
|
let Some(id_column) = result_id else { panic!() };
|
||||||
|
assert_eq!(id_column.column_name, "id".to_string());
|
||||||
|
|
||||||
|
let result_column1 = schema.get_column(&"name".to_string());
|
||||||
|
assert!(matches!(result_column1, Some(_)));
|
||||||
|
let Some(column1_column) = result_column1 else {
|
||||||
|
panic!()
|
||||||
|
};
|
||||||
|
assert_eq!(column1_column.column_name, "name".to_string());
|
||||||
|
assert_eq!(column1_column.type_, DbType::String);
|
||||||
|
|
||||||
|
let column = schema.get_column(&"year".to_string());
|
||||||
|
let Some(column) = column else {
|
||||||
|
panic!()
|
||||||
|
};
|
||||||
|
assert_eq!(column.column_name, "year".to_string());
|
||||||
|
assert_eq!(column.type_, DbType::Option(Box::new(DbType::Int)));
|
||||||
|
|
||||||
|
let column = schema.get_column(&"price".to_string());
|
||||||
|
let Some(column) = column else {
|
||||||
|
panic!()
|
||||||
|
};
|
||||||
|
assert_eq!(column.column_name, "price".to_string());
|
||||||
|
assert_eq!(column.type_, DbType::Number);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use nom::{
|
use nom::{
|
||||||
bytes::complete::tag,
|
bytes::complete::tag,
|
||||||
character::complete::{char, multispace0, multispace1},
|
character::complete::{multispace0, multispace1},
|
||||||
IResult,
|
IResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -15,8 +15,6 @@ pub fn parse_delete(input: &str) -> IResult<&str, RawQuerySyntax> {
|
||||||
let (input, table_name) = parse_table_name(input)?;
|
let (input, table_name) = parse_table_name(input)?;
|
||||||
let (input, _) = multispace0(input)?;
|
let (input, _) = multispace0(input)?;
|
||||||
let (input, condition) = parse_condition(input)?;
|
let (input, condition) = parse_condition(input)?;
|
||||||
let (input, _) = multispace0(input)?;
|
|
||||||
let (input, _) = char(';')(input)?;
|
|
||||||
Ok((
|
Ok((
|
||||||
input,
|
input,
|
||||||
RawQuerySyntax::Delete(table_name.to_string(), condition),
|
RawQuerySyntax::Delete(table_name.to_string(), condition),
|
||||||
|
|
@ -26,14 +24,33 @@ pub fn parse_delete(input: &str) -> IResult<&str, RawQuerySyntax> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::parsing::delete::parse_delete;
|
use crate::parsing::delete::parse_delete;
|
||||||
use crate::syntax::RawQuerySyntax;
|
use crate::parsing::literal::Literal;
|
||||||
|
use crate::syntax::{Condition, RawQuerySyntax};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_delete() {
|
fn test_parse_delete() {
|
||||||
let (_, operation) =
|
let (_, sntx) =
|
||||||
parse_delete("DELETE FROM \"T1\" WHERE id = 1 ;").expect("should parse");
|
parse_delete("DELETE FROM \"T1\" WHERE id = 1").expect("should parse");
|
||||||
assert!(matches!(operation, RawQuerySyntax::Delete(_, _)))
|
assert!(matches!(sntx, RawQuerySyntax::Delete(_, _)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: add test with condition
|
#[test]
|
||||||
|
fn test_parse_delete_with_spaces() {
|
||||||
|
let (_, sntx) =
|
||||||
|
parse_delete("DELETE FROM T1 WHERE id = 1").expect("should parse");
|
||||||
|
assert!(matches!(sntx, RawQuerySyntax::Delete(_, _)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_delete_none() {
|
||||||
|
let (_, sntx) =
|
||||||
|
parse_delete("DELETE FROM games WHERE year = None").expect("should parse");
|
||||||
|
if let RawQuerySyntax::Delete(tname, Some(Condition::Eq(column_name, lit))) = sntx {
|
||||||
|
assert_eq!(tname, "games".to_string());
|
||||||
|
assert_eq!(column_name, "year".to_string());
|
||||||
|
assert_eq!(lit, Literal::None)
|
||||||
|
} else {
|
||||||
|
panic!()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,6 @@ pub fn parse_create_index(input: &str) -> IResult<&str, RawQuerySyntax> {
|
||||||
let (input, column_name) = parse_identifier(input)?;
|
let (input, column_name) = parse_identifier(input)?;
|
||||||
let (input, _) = multispace0(input)?;
|
let (input, _) = multispace0(input)?;
|
||||||
let (input, _) = char(')')(input)?;
|
let (input, _) = char(')')(input)?;
|
||||||
let (input, _) = multispace0(input)?;
|
|
||||||
let (input, _) = char(';')(input)?;
|
|
||||||
let operation = RawQuerySyntax::CreateIndex(table_name.to_string(), column_name.to_string());
|
let operation = RawQuerySyntax::CreateIndex(table_name.to_string(), column_name.to_string());
|
||||||
Ok((input, operation))
|
Ok((input, operation))
|
||||||
}
|
}
|
||||||
|
|
@ -44,7 +42,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_create_index() {
|
fn test_create_index() {
|
||||||
let (_, syntax) =
|
let (_, syntax) =
|
||||||
parse_create_index("CREATE UNIQUE INDEX idxcontactsemail ON \"contacts\" (email);")
|
parse_create_index("CREATE UNIQUE INDEX idxcontactsemail ON \"contacts\" (email)")
|
||||||
.expect("should parse");
|
.expect("should parse");
|
||||||
assert!(matches!(syntax, RawQuerySyntax::CreateIndex(_, _)));
|
assert!(matches!(syntax, RawQuerySyntax::CreateIndex(_, _)));
|
||||||
match syntax {
|
match syntax {
|
||||||
|
|
@ -59,7 +57,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_create_index_with_spaces() {
|
fn test_create_index_with_spaces() {
|
||||||
let (_, syntax) = parse_create_index(
|
let (_, syntax) = parse_create_index(
|
||||||
"CREATE UNIQUE INDEX idxcontactsemail ON \"contacts\" ( email ) ;",
|
"CREATE UNIQUE INDEX idxcontactsemail ON \"contacts\" ( email )",
|
||||||
)
|
)
|
||||||
.expect("should parse");
|
.expect("should parse");
|
||||||
assert!(matches!(syntax, RawQuerySyntax::CreateIndex(_, _)));
|
assert!(matches!(syntax, RawQuerySyntax::CreateIndex(_, _)));
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
use super::{
|
use super::{
|
||||||
common::{parse_identifier, parse_table_name},
|
common::{parse_identifier, parse_table_name},
|
||||||
literal::parse_db_value,
|
literal::{parse_literal, Literal},
|
||||||
};
|
};
|
||||||
use crate::syntax::RawQuerySyntax;
|
use crate::syntax::RawQuerySyntax;
|
||||||
use minisql::type_system::Value;
|
|
||||||
use nom::{
|
use nom::{
|
||||||
bytes::complete::tag,
|
bytes::complete::tag,
|
||||||
character::complete::{char, multispace0, multispace1},
|
character::complete::{char, multispace0, multispace1},
|
||||||
|
|
@ -33,8 +32,6 @@ pub fn parse_insert(input: &str) -> IResult<&str, RawQuerySyntax> {
|
||||||
let (input, values) = parse_values(input)?;
|
let (input, values) = parse_values(input)?;
|
||||||
let (input, _) = multispace0(input)?;
|
let (input, _) = multispace0(input)?;
|
||||||
let (input, _) = char(')')(input)?;
|
let (input, _) = char(')')(input)?;
|
||||||
let (input, _) = multispace0(input)?;
|
|
||||||
let (input, _) = char(';')(input)?;
|
|
||||||
Ok((
|
Ok((
|
||||||
input,
|
input,
|
||||||
RawQuerySyntax::Insert(
|
RawQuerySyntax::Insert(
|
||||||
|
|
@ -51,20 +48,18 @@ pub fn parse_columns(input: &str) -> IResult<&str, Vec<String>> {
|
||||||
)(input)
|
)(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_values(input: &str) -> IResult<&str, Vec<Value>> {
|
pub fn parse_values(input: &str) -> IResult<&str, Vec<Literal>> {
|
||||||
separated_list0(terminated(char(','), multispace0), parse_db_value)(input)
|
separated_list0(terminated(char(','), multispace0), parse_literal)(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use minisql::type_system::Value;
|
|
||||||
|
|
||||||
use super::parse_insert;
|
use super::parse_insert;
|
||||||
use crate::syntax::RawQuerySyntax;
|
use crate::{parsing::literal::Literal, syntax::RawQuerySyntax};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_insert() {
|
fn test_parse_insert() {
|
||||||
let sql = "INSERT INTO \"MyTable\" (id, data) VALUES(1, \"Text\");";
|
let sql = "INSERT INTO \"MyTable\" (id, data) VALUES(1, \"Text\")";
|
||||||
let syntax = parse_insert(sql).expect("should parse");
|
let syntax = parse_insert(sql).expect("should parse");
|
||||||
match syntax {
|
match syntax {
|
||||||
("", RawQuerySyntax::Insert(table_name, insertion_values)) => {
|
("", RawQuerySyntax::Insert(table_name, insertion_values)) => {
|
||||||
|
|
@ -72,10 +67,10 @@ mod tests {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
insertion_values,
|
insertion_values,
|
||||||
vec![
|
vec![
|
||||||
("id".to_string(), Value::Int(1)),
|
("id".to_string(), Literal::Int(1)),
|
||||||
(
|
(
|
||||||
"data".to_string(),
|
"data".to_string(),
|
||||||
Value::String("Text".to_string())
|
Literal::String("Text".to_string())
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
@ -89,7 +84,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_insert_with_spaces() {
|
fn test_parse_insert_with_spaces() {
|
||||||
let sql =
|
let sql =
|
||||||
"INSERT INTO \"MyTable\" ( id, data ) VALUES ( 1, \"Text\" ) ;";
|
"INSERT INTO \"MyTable\" ( id, data ) VALUES ( 1, \"Text\" )";
|
||||||
let operation = parse_insert(sql).expect("should parse");
|
let operation = parse_insert(sql).expect("should parse");
|
||||||
match operation {
|
match operation {
|
||||||
("", RawQuerySyntax::Insert(table_name, insertion_values)) => {
|
("", RawQuerySyntax::Insert(table_name, insertion_values)) => {
|
||||||
|
|
@ -97,10 +92,10 @@ mod tests {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
insertion_values,
|
insertion_values,
|
||||||
vec![
|
vec![
|
||||||
("id".to_string(), Value::Int(1)),
|
("id".to_string(), Literal::Int(1)),
|
||||||
(
|
(
|
||||||
"data".to_string(),
|
"data".to_string(),
|
||||||
Value::String("Text".to_string())
|
Literal::String("Text".to_string())
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
@ -110,4 +105,36 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_insert_option() {
|
||||||
|
let sql = r#"INSERT INTO games (id, name, year, price) VALUES (u12345, "Doom", Some(1993), 6.5);"#;
|
||||||
|
let syntax = parse_insert(sql).expect("should parse");
|
||||||
|
match syntax {
|
||||||
|
(";", RawQuerySyntax::Insert(table_name, insertion_values)) => {
|
||||||
|
assert_eq!(table_name, "games");
|
||||||
|
assert_eq!(
|
||||||
|
insertion_values,
|
||||||
|
vec![
|
||||||
|
("id".to_string(), Literal::Uuid(12345)),
|
||||||
|
(
|
||||||
|
"name".to_string(),
|
||||||
|
Literal::String("Doom".to_string())
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"year".to_string(),
|
||||||
|
Literal::Some(Box::new(Literal::Int(1993)))
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"price".to_string(),
|
||||||
|
Literal::Number(6.5)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
panic!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,22 @@
|
||||||
use minisql::type_system::Value;
|
|
||||||
use nom::{
|
use nom::{
|
||||||
branch::alt,
|
branch::alt, bytes::complete::tag, character::complete::{char, digit1, none_of, u64}, combinator::opt, error::make_error, multi::many0, sequence::{delimited, pair, preceded}, IResult, Parser
|
||||||
character::complete::{char, digit1, none_of, u64},
|
|
||||||
combinator::opt,
|
|
||||||
error::make_error,
|
|
||||||
multi::many0,
|
|
||||||
sequence::{delimited, pair, preceded},
|
|
||||||
IResult,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn parse_db_value(input: &str) -> IResult<&str, Value> {
|
#[derive(Debug, PartialEq)]
|
||||||
alt((parse_string, parse_number, parse_int, parse_uuid))(input)
|
pub enum Literal {
|
||||||
|
Number(f64),
|
||||||
|
String(String),
|
||||||
|
Int(u64),
|
||||||
|
Uuid(u64),
|
||||||
|
Some(Box<Literal>),
|
||||||
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_number(input: &str) -> IResult<&str, Value> {
|
pub fn parse_literal(input: &str) -> IResult<&str, Literal> {
|
||||||
|
alt((parse_option, parse_string, parse_number, parse_int, parse_uuid))(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_number(input: &str) -> IResult<&str, Literal> {
|
||||||
// Parse the integer part
|
// Parse the integer part
|
||||||
let (input, (sign, digits)) = pair(opt(char('-')), digit1)(input)?;
|
let (input, (sign, digits)) = pair(opt(char('-')), digit1)(input)?;
|
||||||
|
|
||||||
|
|
@ -28,19 +31,19 @@ pub fn parse_number(input: &str) -> IResult<&str, Value> {
|
||||||
let value = combined_parts
|
let value = combined_parts
|
||||||
.parse::<f64>()
|
.parse::<f64>()
|
||||||
.map_err(|_| nom::Err::Failure(make_error(input, nom::error::ErrorKind::Fail)))?;
|
.map_err(|_| nom::Err::Failure(make_error(input, nom::error::ErrorKind::Fail)))?;
|
||||||
Ok((input, Value::Number(value)))
|
Ok((input, Literal::Number(value)))
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let value = format!("{}{}", sign.unwrap_or('+'), digits)
|
let value = format!("{}{}", sign.unwrap_or('+'), digits)
|
||||||
.parse::<u64>()
|
.parse::<u64>()
|
||||||
.map_err(|_| nom::Err::Failure(make_error(input, nom::error::ErrorKind::Fail)))?;
|
.map_err(|_| nom::Err::Failure(make_error(input, nom::error::ErrorKind::Fail)))?;
|
||||||
Ok((input, Value::Int(value)))
|
Ok((input, Literal::Int(value)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_int(input: &str) -> IResult<&str, Value> {
|
pub fn parse_int(input: &str) -> IResult<&str, Literal> {
|
||||||
u64(input).map(|(input, v)| (input, Value::Int(v)))
|
u64(input).map(|(input, v)| (input, Literal::Int(v)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn escape_tab(input: &str) -> IResult<&str, char> {
|
fn escape_tab(input: &str) -> IResult<&str, char> {
|
||||||
|
|
@ -67,7 +70,7 @@ fn escape_doublequote(input: &str) -> IResult<&str, char> {
|
||||||
preceded(char('\\'), char('"'))(input)
|
preceded(char('\\'), char('"'))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_string(input: &str) -> IResult<&str, Value> {
|
pub fn parse_string(input: &str) -> IResult<&str, Literal> {
|
||||||
// Parse the content inside the double quotes
|
// Parse the content inside the double quotes
|
||||||
let (input, content) = delimited(
|
let (input, content) = delimited(
|
||||||
char('"'),
|
char('"'),
|
||||||
|
|
@ -85,19 +88,27 @@ pub fn parse_string(input: &str) -> IResult<&str, Value> {
|
||||||
// Combine the characters into a string
|
// Combine the characters into a string
|
||||||
let value: String = content.into_iter().collect();
|
let value: String = content.into_iter().collect();
|
||||||
|
|
||||||
Ok((input, Value::String(value)))
|
Ok((input, Literal::String(value)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_uuid(input: &str) -> IResult<&str, Value> {
|
pub fn parse_uuid(input: &str) -> IResult<&str, Literal> {
|
||||||
let (input, value) = pair(char('u'), u64)(input)
|
let (input, value) = pair(char('u'), u64)(input)
|
||||||
.map(|(input, (_, v))| (input, Value::Uuid(v)))?;
|
.map(|(input, (_, v))| (input, Literal::Uuid(v)))?;
|
||||||
Ok((input, value))
|
Ok((input, value))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parse_option(input: &str) -> IResult<&str, Literal> {
|
||||||
|
let (input, inner) = alt((tag("None")
|
||||||
|
.map(|_| Literal::None), delimited(tag("Some("), parse_literal, tag(")")).map(|v| {
|
||||||
|
Literal::Some(Box::new(v))
|
||||||
|
})))(input)?;
|
||||||
|
Ok((input, inner))
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::parsing::literal::{parse_db_value, parse_string, parse_uuid};
|
use crate::parsing::literal::{parse_literal, parse_option, parse_string, parse_uuid, Literal};
|
||||||
use minisql::type_system::Value;
|
use minisql::type_system::DbType;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_string_parser() {
|
fn test_string_parser() {
|
||||||
|
|
@ -105,42 +116,42 @@ mod tests {
|
||||||
parse_string(r#""simple""#),
|
parse_string(r#""simple""#),
|
||||||
Ok((
|
Ok((
|
||||||
"",
|
"",
|
||||||
Value::String(String::from("simple"))
|
Literal::String(String::from("simple"))
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_string(r#""\"\t\r\n\\""#),
|
parse_string(r#""\"\t\r\n\\""#),
|
||||||
Ok((
|
Ok((
|
||||||
"",
|
"",
|
||||||
Value::String(String::from("\"\t\r\n\\"))
|
Literal::String(String::from("\"\t\r\n\\"))
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_string(r#""name is \"John\".""#),
|
parse_string(r#""name is \"John\".""#),
|
||||||
Ok((
|
Ok((
|
||||||
"",
|
"",
|
||||||
Value::String(String::from("name is \"John\"."))
|
Literal::String(String::from("name is \"John\"."))
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_db_value() {
|
fn test_parse_db_value() {
|
||||||
let (input, value) = parse_db_value("5").expect("should parse");
|
let (input, value) = parse_literal("5").expect("should parse");
|
||||||
assert_eq!(input, "");
|
assert_eq!(input, "");
|
||||||
assert_eq!(value, Value::Int(5));
|
assert_eq!(value, Literal::Int(5));
|
||||||
|
|
||||||
let (input, value) = parse_db_value("5.5").expect("should parse");
|
let (input, value) = parse_literal("5.5").expect("should parse");
|
||||||
assert_eq!(input, "");
|
assert_eq!(input, "");
|
||||||
assert_eq!(value, Value::Number(5.5));
|
assert_eq!(value, Literal::Number(5.5));
|
||||||
|
|
||||||
let (_, _) = parse_db_value("\"STRING\"").expect("should parse");
|
let (_, _) = parse_literal("\"STRING\"").expect("should parse");
|
||||||
let (input, value) =
|
let (input, value) =
|
||||||
parse_db_value("\"abcdefghkjklmnopqrstuvwxyz!@#$%^&*()_+ \"").expect("should parse");
|
parse_literal("\"abcdefghkjklmnopqrstuvwxyz!@#$%^&*()_+ \"").expect("should parse");
|
||||||
assert_eq!(input, "");
|
assert_eq!(input, "");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
value,
|
value,
|
||||||
Value::String(
|
Literal::String(
|
||||||
"abcdefghkjklmnopqrstuvwxyz!@#$%^&*()_+ ".to_string()
|
"abcdefghkjklmnopqrstuvwxyz!@#$%^&*()_+ ".to_string()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
@ -149,41 +160,41 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_positive_float() {
|
fn test_parse_positive_float() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_db_value("23.213313"),
|
parse_literal("23.213313"),
|
||||||
Ok(("", Value::Number(23.213313)))
|
Ok(("", Literal::Number(23.213313)))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_db_value("2241.9734"),
|
parse_literal("2241.9734"),
|
||||||
Ok(("", Value::Number(2241.9734)))
|
Ok(("", Literal::Number(2241.9734)))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_negative_float() {
|
fn test_parse_negative_float() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_db_value("-9241.873654"),
|
parse_literal("-9241.873654"),
|
||||||
Ok(("", Value::Number(-9241.873654)))
|
Ok(("", Literal::Number(-9241.873654)))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_db_value("-62625.0"),
|
parse_literal("-62625.0"),
|
||||||
Ok(("", Value::Number(-62625.0)))
|
Ok(("", Literal::Number(-62625.0)))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_float_between_0_and_1() {
|
fn test_parse_float_between_0_and_1() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_db_value("0.873654"),
|
parse_literal("0.873654"),
|
||||||
Ok(("", Value::Number(0.873654)))
|
Ok(("", Literal::Number(0.873654)))
|
||||||
);
|
);
|
||||||
assert_eq!(parse_db_value("0.62625"), Ok(("", Value::Number(0.62625))));
|
assert_eq!(parse_literal("0.62625"), Ok(("", Literal::Number(0.62625))));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_int() {
|
fn test_parse_int() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_db_value("5134616"),
|
parse_literal("5134616"),
|
||||||
Ok(("", Value::Int(5134616)))
|
Ok(("", Literal::Int(5134616)))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -191,7 +202,23 @@ mod tests {
|
||||||
fn test_parse_uuid() {
|
fn test_parse_uuid() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_uuid("u131515"),
|
parse_uuid("u131515"),
|
||||||
Ok(("", Value::Uuid(131515)))
|
Ok(("", Literal::Uuid(131515)))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_option_int() {
|
||||||
|
assert_eq!(
|
||||||
|
parse_option("Some(2)"),
|
||||||
|
Ok(("", Literal::Some(Box::new(Literal::Int(2)))))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
parse_option("Some(Some(3))"),
|
||||||
|
Ok(("", Literal::Some(Box::new(Literal::Some(Box::new(Literal::Int(3)))))))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
parse_option("Some(None)"),
|
||||||
|
Ok(("", Literal::Some(Box::new(Literal::None))))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,6 @@ pub fn parse_select(input: &str) -> IResult<&str, RawQuerySyntax> {
|
||||||
let (input, table_name) = parse_table_name(input)?;
|
let (input, table_name) = parse_table_name(input)?;
|
||||||
let (input, _) = multispace0(input)?;
|
let (input, _) = multispace0(input)?;
|
||||||
let (input, condition) = parse_condition(input)?;
|
let (input, condition) = parse_condition(input)?;
|
||||||
let (input, _) = multispace0(input)?;
|
|
||||||
// TODO: make it optional?
|
|
||||||
let (input, _) = tag(";")(input)?;
|
|
||||||
Ok((
|
Ok((
|
||||||
input,
|
input,
|
||||||
RawQuerySyntax::Select(table_name.to_string(), column_selection, condition),
|
RawQuerySyntax::Select(table_name.to_string(), column_selection, condition),
|
||||||
|
|
@ -45,14 +42,13 @@ pub fn try_parse_column_selection(input: &str) -> IResult<&str, ColumnSelection>
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::parsing::{
|
use crate::parsing::{
|
||||||
common::{parse_column_name, parse_table_name},
|
common::{parse_column_name, parse_table_name}, literal::Literal, select::parse_select
|
||||||
select::parse_select,
|
|
||||||
};
|
};
|
||||||
use crate::syntax::{ColumnSelection, RawQuerySyntax};
|
use crate::syntax::{ColumnSelection, RawQuerySyntax};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_select_all() {
|
fn test_parse_select_all() {
|
||||||
let sql = "SELECT * FROM \"MyTable\";";
|
let sql = "SELECT * FROM \"MyTable\"";
|
||||||
let operation = parse_select(sql).expect("should parse");
|
let operation = parse_select(sql).expect("should parse");
|
||||||
match operation {
|
match operation {
|
||||||
("", RawQuerySyntax::Select(table_name, column_selection, maybe_condition)) => {
|
("", RawQuerySyntax::Select(table_name, column_selection, maybe_condition)) => {
|
||||||
|
|
@ -79,7 +75,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_select_columns() {
|
fn test_parse_select_columns() {
|
||||||
let sql = "SELECT name , email FROM \"AddressBook\" ;";
|
let sql = "SELECT name , email FROM \"AddressBook\"";
|
||||||
let operation = parse_select(sql).expect("should parse");
|
let operation = parse_select(sql).expect("should parse");
|
||||||
match operation {
|
match operation {
|
||||||
("", RawQuerySyntax::Select(table_name, column_selection, maybe_condition)) => {
|
("", RawQuerySyntax::Select(table_name, column_selection, maybe_condition)) => {
|
||||||
|
|
@ -105,7 +101,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_select_where() {
|
fn test_parse_select_where() {
|
||||||
use crate::syntax::Condition;
|
use crate::syntax::Condition;
|
||||||
let sql = "SELECT * FROM \"AddressBook\" WHERE id = 5 ;";
|
let sql = "SELECT * FROM \"AddressBook\" WHERE id = 5";
|
||||||
let operation = parse_select(sql).expect("should parse");
|
let operation = parse_select(sql).expect("should parse");
|
||||||
match operation {
|
match operation {
|
||||||
("", RawQuerySyntax::Select(table_name, column_selection, maybe_condition)) => {
|
("", RawQuerySyntax::Select(table_name, column_selection, maybe_condition)) => {
|
||||||
|
|
@ -119,6 +115,49 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO: a test with multiple statements
|
#[test]
|
||||||
// TODO: allow underscores in identifiers
|
fn test_parse_select_option() {
|
||||||
|
use crate::syntax::Condition;
|
||||||
|
let sql = "SELECT * FROM games WHERE year = Some(2006)";
|
||||||
|
let operation = parse_select(sql).expect("should parse");
|
||||||
|
match operation {
|
||||||
|
("", RawQuerySyntax::Select(table_name, column_selection, maybe_condition)) => {
|
||||||
|
assert_eq!(table_name, "games");
|
||||||
|
assert!(matches!(column_selection, ColumnSelection::All));
|
||||||
|
if let Some(Condition::Eq(left, right)) = maybe_condition {
|
||||||
|
assert_eq!(left, "year".to_string());
|
||||||
|
assert_eq!(right, Literal::Some(Box::new(Literal::Int(2006))))
|
||||||
|
} else {
|
||||||
|
panic!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(input, _) => {
|
||||||
|
println!("Input to be parsed: {}", input);
|
||||||
|
panic!("expected select operation")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_select_option_none() {
|
||||||
|
use crate::syntax::Condition;
|
||||||
|
let sql = "SELECT * FROM games WHERE year = None";
|
||||||
|
let operation = parse_select(sql).expect("should parse");
|
||||||
|
match operation {
|
||||||
|
("", RawQuerySyntax::Select(table_name, column_selection, maybe_condition)) => {
|
||||||
|
assert_eq!(table_name, "games");
|
||||||
|
assert!(matches!(column_selection, ColumnSelection::All));
|
||||||
|
if let Some(Condition::Eq(left, right)) = maybe_condition {
|
||||||
|
assert_eq!(left, "year".to_string());
|
||||||
|
assert_eq!(right, Literal::None)
|
||||||
|
} else {
|
||||||
|
panic!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(input, _) => {
|
||||||
|
println!("Input to be parsed: {}", input);
|
||||||
|
panic!("expected select operation")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
use minisql::{
|
use minisql::{
|
||||||
schema::{ColumnName, TableName},
|
schema::{ColumnName, TableName},
|
||||||
type_system::{DbType, Value},
|
type_system::DbType,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::parsing::literal::Literal;
|
||||||
|
|
||||||
// ===Table Schema===
|
// ===Table Schema===
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct RawTableSchema {
|
pub struct RawTableSchema {
|
||||||
|
|
@ -27,7 +29,7 @@ pub enum RawQuerySyntax {
|
||||||
CreateIndex(TableName, ColumnName),
|
CreateIndex(TableName, ColumnName),
|
||||||
// DropTable(TableName),
|
// DropTable(TableName),
|
||||||
}
|
}
|
||||||
pub type InsertionValues = Vec<(ColumnName, Value)>;
|
pub type InsertionValues = Vec<(ColumnName, Literal)>;
|
||||||
|
|
||||||
pub enum ColumnSelection {
|
pub enum ColumnSelection {
|
||||||
All,
|
All,
|
||||||
|
|
@ -38,7 +40,7 @@ pub enum Condition {
|
||||||
// And(Box<Condition>, Box<Condition>),
|
// And(Box<Condition>, Box<Condition>),
|
||||||
// Or(Box<Condition>, Box<Condition>),
|
// Or(Box<Condition>, Box<Condition>),
|
||||||
// Not(Box<Condition>),
|
// Not(Box<Condition>),
|
||||||
Eq(ColumnName, Value),
|
Eq(ColumnName, Literal),
|
||||||
// LessOrEqual(ColumnName, DbValue),
|
// LessOrEqual(ColumnName, DbValue),
|
||||||
// Less(ColumnName, DbValue),
|
// Less(ColumnName, DbValue),
|
||||||
|
|
||||||
|
|
@ -69,3 +71,4 @@ impl RawTableSchema {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use std::collections::{BTreeMap, HashSet};
|
use std::collections::{BTreeMap, HashSet};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use crate::parsing::literal::Literal;
|
||||||
use crate::syntax;
|
use crate::syntax;
|
||||||
use crate::syntax::{ColumnSchema, RawQuerySyntax, RawTableSchema};
|
use crate::syntax::{ColumnSchema, RawQuerySyntax, RawTableSchema};
|
||||||
use minisql::operation;
|
use minisql::operation;
|
||||||
|
|
@ -34,6 +35,8 @@ pub enum ValidationError {
|
||||||
received_type: DbType,
|
received_type: DbType,
|
||||||
expected_type: DbType,
|
expected_type: DbType,
|
||||||
},
|
},
|
||||||
|
#[error("Expected type {expected_type:?}, received None")]
|
||||||
|
UnexpectedNoneValue{ expected_type: DbType },
|
||||||
#[error("values for required columns {0:?} are missing")]
|
#[error("values for required columns {0:?} are missing")]
|
||||||
RequiredColumnsAreMissing(Vec<ColumnName>),
|
RequiredColumnsAreMissing(Vec<ColumnName>),
|
||||||
}
|
}
|
||||||
|
|
@ -240,7 +243,7 @@ fn validate_insert(
|
||||||
.ok_or(ValidationError::ColumnsDoNotExist(vec![
|
.ok_or(ValidationError::ColumnsDoNotExist(vec![
|
||||||
column_name.to_string()
|
column_name.to_string()
|
||||||
]))?; // By the previous validation steps this is never gonna trigger an error.
|
]))?; // By the previous validation steps this is never gonna trigger an error.
|
||||||
let value_type = value.to_type();
|
let value_type = type_from_literal_with_type_hint(&value, &expected_type)?;
|
||||||
if value_type != expected_type {
|
if value_type != expected_type {
|
||||||
return Err(ValidationError::TypeMismatch {
|
return Err(ValidationError::TypeMismatch {
|
||||||
column_name: column_name.to_string(),
|
column_name: column_name.to_string(),
|
||||||
|
|
@ -248,7 +251,7 @@ fn validate_insert(
|
||||||
expected_type,
|
expected_type,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
values_map.insert(column, value);
|
values_map.insert(column, literal_to_value(value, &expected_type));
|
||||||
}
|
}
|
||||||
|
|
||||||
// WARNING: If you use `values_map: HashMap<_,_>`, this is not gonna sort values by key.
|
// WARNING: If you use `values_map: HashMap<_,_>`, this is not gonna sort values by key.
|
||||||
|
|
@ -278,9 +281,9 @@ fn validate_condition(
|
||||||
let (column, expected_type) = schema.get_typed_column(&column_name).ok_or(
|
let (column, expected_type) = schema.get_typed_column(&column_name).ok_or(
|
||||||
ValidationError::ColumnsDoNotExist(vec![column_name.to_string()]),
|
ValidationError::ColumnsDoNotExist(vec![column_name.to_string()]),
|
||||||
)?;
|
)?;
|
||||||
let value_type: DbType = value.to_type();
|
let value_type: DbType = type_from_literal_with_type_hint(&value, &expected_type)?;
|
||||||
if expected_type.eq(&value_type) {
|
if expected_type.eq(&value_type) {
|
||||||
Ok(Some(operation::Condition::Eq(column, value)))
|
Ok(Some(operation::Condition::Eq(column, literal_to_value(value, &expected_type))))
|
||||||
} else {
|
} else {
|
||||||
Err(ValidationError::TypeMismatch {
|
Err(ValidationError::TypeMismatch {
|
||||||
column_name: column_name.to_string(),
|
column_name: column_name.to_string(),
|
||||||
|
|
@ -345,6 +348,42 @@ fn get_table_schema<'a>(
|
||||||
Some(table_schema)
|
Some(table_schema)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn literal_to_value(lit: Literal, hint: &DbType) -> Value {
|
||||||
|
match lit {
|
||||||
|
Literal::Number(v) => Value::Number(v),
|
||||||
|
Literal::String(v) => Value::String(v),
|
||||||
|
Literal::Int(v) => Value::Int(v),
|
||||||
|
Literal::Uuid(v) => Value::Uuid(v),
|
||||||
|
Literal::Some(v) => Value::Some(Box::new(literal_to_value(*v, hint))),
|
||||||
|
Literal::None => {
|
||||||
|
if let DbType::Option(t) = hint {
|
||||||
|
Value::None(*t.clone())
|
||||||
|
} else {
|
||||||
|
// By the time calling current function, hopefully we should be sure about the
|
||||||
|
// type we want from the literal
|
||||||
|
panic!()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn type_from_literal_with_type_hint(lit: &Literal, hint: &DbType) -> Result<DbType, ValidationError> {
|
||||||
|
Ok(match lit {
|
||||||
|
Literal::Number(_) => DbType::Number,
|
||||||
|
Literal::String(_) => DbType::String,
|
||||||
|
Literal::Int(_) => DbType::Int,
|
||||||
|
Literal::Uuid(_) => DbType::Uuid,
|
||||||
|
Literal::Some(l) => DbType::Option(Box::new(type_from_literal_with_type_hint(l, hint)?)),
|
||||||
|
Literal::None => {
|
||||||
|
if matches!(hint, DbType::Option(_)) {
|
||||||
|
hint.clone()
|
||||||
|
} else {
|
||||||
|
return Err(ValidationError::UnexpectedNoneValue { expected_type: hint.clone() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
@ -565,7 +604,7 @@ mod tests {
|
||||||
let syntax: RawQuerySyntax = Select(
|
let syntax: RawQuerySyntax = Select(
|
||||||
"users".to_string(),
|
"users".to_string(),
|
||||||
ColumnSelection::All,
|
ColumnSelection::All,
|
||||||
Some(Eq("age".to_string(), Value::Int(25))),
|
Some(Eq("age".to_string(), Literal::Int(25))),
|
||||||
);
|
);
|
||||||
let result = validate_operation(syntax, &db_schema);
|
let result = validate_operation(syntax, &db_schema);
|
||||||
assert!(matches!(result, Ok(Operation::Select(_, _, _))));
|
assert!(matches!(result, Ok(Operation::Select(_, _, _))));
|
||||||
|
|
@ -632,7 +671,7 @@ mod tests {
|
||||||
let syntax: RawQuerySyntax = Select(
|
let syntax: RawQuerySyntax = Select(
|
||||||
"users".to_string(),
|
"users".to_string(),
|
||||||
ColumnSelection::All,
|
ColumnSelection::All,
|
||||||
Some(Eq("does_not_exist".to_string(), Value::Int(25))),
|
Some(Eq("does_not_exist".to_string(), Literal::Int(25))),
|
||||||
);
|
);
|
||||||
let result = validate_operation(syntax, &db_schema);
|
let result = validate_operation(syntax, &db_schema);
|
||||||
assert!(matches!(result, Err(ValidationError::ColumnsDoNotExist(_))));
|
assert!(matches!(result, Err(ValidationError::ColumnsDoNotExist(_))));
|
||||||
|
|
@ -646,7 +685,7 @@ mod tests {
|
||||||
let syntax: RawQuerySyntax = Select(
|
let syntax: RawQuerySyntax = Select(
|
||||||
"users".to_string(),
|
"users".to_string(),
|
||||||
ColumnSelection::All,
|
ColumnSelection::All,
|
||||||
Some(Eq("age".to_string(), Value::String("25".to_string()))),
|
Some(Eq("age".to_string(), Literal::String("25".to_string()))),
|
||||||
);
|
);
|
||||||
let result = validate_operation(syntax, &db_schema);
|
let result = validate_operation(syntax, &db_schema);
|
||||||
assert!(matches!(result, Err(ValidationError::TypeMismatch { .. })));
|
assert!(matches!(result, Err(ValidationError::TypeMismatch { .. })));
|
||||||
|
|
@ -663,9 +702,9 @@ mod tests {
|
||||||
let syntax: RawQuerySyntax = Insert(
|
let syntax: RawQuerySyntax = Insert(
|
||||||
"users".to_string(),
|
"users".to_string(),
|
||||||
vec![
|
vec![
|
||||||
("name".to_string(), Value::String("Alice".to_string())),
|
("name".to_string(), Literal::String("Alice".to_string())),
|
||||||
("id".to_string(), Value::Uuid(0)),
|
("id".to_string(), Literal::Uuid(0)),
|
||||||
("age".to_string(), Value::Int(25)),
|
("age".to_string(), Literal::Int(25)),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
let result = validate_operation(syntax, &db_schema);
|
let result = validate_operation(syntax, &db_schema);
|
||||||
|
|
@ -698,10 +737,10 @@ mod tests {
|
||||||
let syntax: RawQuerySyntax = Insert(
|
let syntax: RawQuerySyntax = Insert(
|
||||||
"users".to_string(),
|
"users".to_string(),
|
||||||
vec![
|
vec![
|
||||||
("name".to_string(), Value::String("Alice".to_string())),
|
("name".to_string(), Literal::String("Alice".to_string())),
|
||||||
("id".to_string(), Value::Uuid(0)),
|
("id".to_string(), Literal::Uuid(0)),
|
||||||
("age".to_string(), Value::Int(25)),
|
("age".to_string(), Literal::Int(25)),
|
||||||
("does_not_exist".to_string(), Value::Int(25)),
|
("does_not_exist".to_string(), Literal::Int(25)),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
let result = validate_operation(syntax, &db_schema);
|
let result = validate_operation(syntax, &db_schema);
|
||||||
|
|
@ -716,9 +755,9 @@ mod tests {
|
||||||
let syntax: RawQuerySyntax = Insert(
|
let syntax: RawQuerySyntax = Insert(
|
||||||
"users".to_string(),
|
"users".to_string(),
|
||||||
vec![
|
vec![
|
||||||
("name".to_string(), Value::String("Alice".to_string())),
|
("name".to_string(), Literal::String("Alice".to_string())),
|
||||||
("id".to_string(), Value::Uuid(0)),
|
("id".to_string(), Literal::Uuid(0)),
|
||||||
("age".to_string(), Value::Number(25.0)),
|
("age".to_string(), Literal::Number(25.0)),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
let result = validate_operation(syntax, &db_schema);
|
let result = validate_operation(syntax, &db_schema);
|
||||||
|
|
@ -754,7 +793,7 @@ mod tests {
|
||||||
|
|
||||||
let syntax: RawQuerySyntax = Delete(
|
let syntax: RawQuerySyntax = Delete(
|
||||||
"users".to_string(),
|
"users".to_string(),
|
||||||
Some(Eq("age".to_string(), Value::Int(25))),
|
Some(Eq("age".to_string(), Literal::Int(25))),
|
||||||
);
|
);
|
||||||
let result = validate_operation(syntax, &db_schema);
|
let result = validate_operation(syntax, &db_schema);
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue