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,6 +1,7 @@
use std::collections::{BTreeMap, HashSet};
use thiserror::Error;
use crate::parsing::literal::Literal;
use crate::syntax;
use crate::syntax::{ColumnSchema, RawQuerySyntax, RawTableSchema};
use minisql::operation;
@ -34,6 +35,8 @@ pub enum ValidationError {
received_type: DbType,
expected_type: DbType,
},
#[error("Expected type {expected_type:?}, received None")]
UnexpectedNoneValue{ expected_type: DbType },
#[error("values for required columns {0:?} are missing")]
RequiredColumnsAreMissing(Vec<ColumnName>),
}
@ -240,7 +243,7 @@ fn validate_insert(
.ok_or(ValidationError::ColumnsDoNotExist(vec![
column_name.to_string()
]))?; // 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 {
return Err(ValidationError::TypeMismatch {
column_name: column_name.to_string(),
@ -248,7 +251,7 @@ fn validate_insert(
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.
@ -278,9 +281,9 @@ fn validate_condition(
let (column, expected_type) = schema.get_typed_column(&column_name).ok_or(
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) {
Ok(Some(operation::Condition::Eq(column, value)))
Ok(Some(operation::Condition::Eq(column, literal_to_value(value, &expected_type))))
} else {
Err(ValidationError::TypeMismatch {
column_name: column_name.to_string(),
@ -345,6 +348,42 @@ fn get_table_schema<'a>(
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)]
mod tests {
use super::*;
@ -565,7 +604,7 @@ mod tests {
let syntax: RawQuerySyntax = Select(
"users".to_string(),
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);
assert!(matches!(result, Ok(Operation::Select(_, _, _))));
@ -632,7 +671,7 @@ mod tests {
let syntax: RawQuerySyntax = Select(
"users".to_string(),
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);
assert!(matches!(result, Err(ValidationError::ColumnsDoNotExist(_))));
@ -646,7 +685,7 @@ mod tests {
let syntax: RawQuerySyntax = Select(
"users".to_string(),
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);
assert!(matches!(result, Err(ValidationError::TypeMismatch { .. })));
@ -663,9 +702,9 @@ mod tests {
let syntax: RawQuerySyntax = Insert(
"users".to_string(),
vec![
("name".to_string(), Value::String("Alice".to_string())),
("id".to_string(), Value::Uuid(0)),
("age".to_string(), Value::Int(25)),
("name".to_string(), Literal::String("Alice".to_string())),
("id".to_string(), Literal::Uuid(0)),
("age".to_string(), Literal::Int(25)),
],
);
let result = validate_operation(syntax, &db_schema);
@ -698,10 +737,10 @@ mod tests {
let syntax: RawQuerySyntax = Insert(
"users".to_string(),
vec![
("name".to_string(), Value::String("Alice".to_string())),
("id".to_string(), Value::Uuid(0)),
("age".to_string(), Value::Int(25)),
("does_not_exist".to_string(), Value::Int(25)),
("name".to_string(), Literal::String("Alice".to_string())),
("id".to_string(), Literal::Uuid(0)),
("age".to_string(), Literal::Int(25)),
("does_not_exist".to_string(), Literal::Int(25)),
],
);
let result = validate_operation(syntax, &db_schema);
@ -716,9 +755,9 @@ mod tests {
let syntax: RawQuerySyntax = Insert(
"users".to_string(),
vec![
("name".to_string(), Value::String("Alice".to_string())),
("id".to_string(), Value::Uuid(0)),
("age".to_string(), Value::Number(25.0)),
("name".to_string(), Literal::String("Alice".to_string())),
("id".to_string(), Literal::Uuid(0)),
("age".to_string(), Literal::Number(25.0)),
],
);
let result = validate_operation(syntax, &db_schema);
@ -754,7 +793,7 @@ mod tests {
let syntax: RawQuerySyntax = Delete(
"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);
assert!(matches!(