Parsing and validation for Option

Add option type and option value parsers

value->literal in parser, implement Option literal
This commit is contained in:
Maxim Svistunov 2024-02-04 13:46:31 +01:00
parent de8c6164cf
commit 6245dba4f0
8 changed files with 322 additions and 109 deletions

View file

@ -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, take_while},
character::{complete::{alphanumeric1, anychar, char, multispace0, multispace1}, is_alphanumeric},
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((
@ -38,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))
} }
@ -70,8 +67,8 @@ 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)]
@ -79,15 +76,15 @@ mod tests {
use minisql::type_system::DbType; use minisql::type_system::DbType;
use crate::parsing::common::{parse_db_type, parse_equality, parse_identifier}; 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");
@ -127,4 +124,20 @@ mod tests {
Err(_) 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))))))
);
}
} }

View file

@ -59,6 +59,8 @@ 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;
@ -112,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);
}
_ => {}
}
}
} }

View file

@ -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,
}; };
@ -24,19 +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(_, _)))
} }
#[test] #[test]
fn test_parse_delete_with_spaces() { fn test_parse_delete_with_spaces() {
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(_, _)))
}
#[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!()
}
} }
} }

View file

@ -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},
@ -49,16 +48,14 @@ 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() {
@ -70,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())
) )
] ]
); );
@ -95,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())
) )
] ]
); );
@ -108,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!()
}
}
}
} }

View file

@ -1,19 +1,23 @@
use minisql::type_system::Value; use minisql::type_system::DbType;
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 +32,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 +71,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 +89,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 +117,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 +161,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 +203,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))))
) )
} }
} }

View file

@ -42,8 +42,7 @@ 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};
@ -116,4 +115,49 @@ mod tests {
} }
} }
} }
#[test]
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")
}
}
}
} }

View file

@ -3,6 +3,8 @@ use minisql::{
type_system::{DbType, Value}, type_system::{DbType, Value},
}; };
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()
} }
} }

View file

@ -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) => 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!(