cargo format

This commit is contained in:
Yuriy Dupyn 2024-01-28 22:40:41 +01:00
parent 4d45da0cd1
commit 845db102c2
33 changed files with 885 additions and 530 deletions

View file

@ -1,16 +1,22 @@
use minisql::{operation::Operation, interpreter::DbSchema};
use crate::syntax::RawQuerySyntax;
use minisql::{interpreter::DbSchema, operation::Operation};
use nom::{branch::alt, IResult};
use thiserror::Error;
use crate::{parsing::{create::parse_create, delete::parse_delete, index::parse_create_index, insert::parse_insert, select::parse_select}, validation::{validate_operation, ValidationError}};
use crate::{
parsing::{
create::parse_create, delete::parse_delete, index::parse_create_index,
insert::parse_insert, select::parse_select,
},
validation::{validate_operation, ValidationError},
};
#[derive(Debug, Error)]
pub enum Error {
#[error("parsing error: {0}")]
ParsingError(String),
#[error("validation error: {0}")]
ValidationError(#[from] ValidationError)
ValidationError(#[from] ValidationError),
}
fn parse_statement(input: &str) -> IResult<&str, RawQuerySyntax> {
@ -21,15 +27,13 @@ fn parse_statement(input: &str) -> IResult<&str, RawQuerySyntax> {
//parse_drop,
parse_select,
// parse_update,
parse_create_index
parse_create_index,
))(input)
}
pub fn parse_and_validate(str_query: String, db_schema: &DbSchema) -> Result<Operation, Error> {
let (_, op) = parse_statement(str_query.as_str())
.map_err(|err| {
Error::ParsingError(err.to_string())
})?;
let (_, op) =
parse_statement(str_query.as_str()).map_err(|err| Error::ParsingError(err.to_string()))?;
Ok(validate_operation(op, db_schema)?)
}

View file

@ -1,8 +1,7 @@
mod parsing;
mod validation;
mod core;
mod parsing;
mod syntax;
mod validation;
pub use core::parse_and_validate;
pub use core::Error;

View file

@ -1,20 +1,21 @@
use minisql::type_system::DbType;
use nom::{
character::complete::{alphanumeric1, char, multispace0, anychar, multispace1},
branch::alt,
bytes::complete::tag,
character::complete::{alphanumeric1, anychar, char, multispace0, multispace1},
combinator::peek,
error::make_error,
sequence::{delimited, terminated},
bytes::complete::tag,
IResult, branch::alt,
IResult,
};
use minisql::type_system::DbType;
use crate::syntax::Condition;
use super::literal::parse_db_value;
use crate::syntax::Condition;
pub fn parse_table_name(input: &str) -> IResult<&str, &str> {
alt((
delimited(char('"'), alphanumeric1, char('"')),
parse_identifier
parse_identifier,
))(input)
}
@ -24,7 +25,10 @@ pub fn parse_identifier(input: &str) -> IResult<&str, &str> {
if first.is_alphabetic() {
alphanumeric1(input)
} else {
Err(nom::Err::Error(make_error(input, nom::error::ErrorKind::Alpha)))
Err(nom::Err::Error(make_error(
input,
nom::error::ErrorKind::Alpha,
)))
}
}
@ -39,7 +43,12 @@ pub fn parse_db_type(input: &str) -> IResult<&str, DbType> {
"INT" => DbType::Int,
"UUID" => DbType::Uuid,
"NUMBER" => DbType::Number,
_ => return Err(nom::Err::Failure(make_error(input, nom::error::ErrorKind::IsNot)))
_ => {
return Err(nom::Err::Failure(make_error(
input,
nom::error::ErrorKind::IsNot,
)))
}
};
Ok((input, db_type))
}
@ -51,9 +60,7 @@ pub fn parse_condition(input: &str) -> IResult<&str, Option<Condition>> {
let (input, condition) = parse_equality(input)?;
Ok((input, Some(condition)))
}
Err(_) => {
Ok((input, None))
}
Err(_) => Ok((input, None)),
}
}
@ -70,9 +77,9 @@ fn parse_equality(input: &str) -> IResult<&str, Condition> {
mod tests {
use minisql::type_system::DbType;
use crate::syntax::Condition;
use crate::parsing::common::{parse_db_type, parse_equality};
use crate::syntax::Condition;
#[test]
fn test_parse_equality() {
use minisql::type_system::{IndexableValue, Value};
@ -89,10 +96,22 @@ mod tests {
#[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("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(_)));
}
}

View file

@ -1,13 +1,14 @@
use nom::{
bytes::complete::tag,
character::complete::{char, multispace0, multispace1},
combinator::opt,
multi::separated_list0,
sequence::terminated,
IResult, combinator::opt,
IResult,
};
use super::common::{parse_table_name, parse_identifier, parse_db_type};
use crate::syntax::{RawTableSchema, ColumnSchema, RawQuerySyntax};
use super::common::{parse_db_type, parse_identifier, parse_table_name};
use crate::syntax::{ColumnSchema, RawQuerySyntax, RawTableSchema};
pub fn parse_create(input: &str) -> IResult<&str, RawQuerySyntax> {
let (input, _) = tag("CREATE")(input)?;
@ -27,10 +28,7 @@ pub fn parse_create(input: &str) -> IResult<&str, RawQuerySyntax> {
table_name: table_name.to_string(),
columns: column_definitions,
};
Ok((
input,
RawQuerySyntax::CreateTable(schema),
))
Ok((input, RawQuerySyntax::CreateTable(schema)))
}
fn parse_column_definitions(input: &str) -> IResult<&str, Vec<ColumnSchema>> {
@ -51,7 +49,14 @@ fn parse_column_definition(input: &str) -> IResult<&str, ColumnSchema> {
let (input, db_type) = parse_db_type(input)?;
let (input, pk) = opt(parse_primary_key)(input).map(|(input, pk)| (input, pk.is_some()))?;
let (input, _) = multispace0(input)?;
Ok((input, ColumnSchema { column_name: identifier.to_string(), type_: db_type, is_primary: pk }))
Ok((
input,
ColumnSchema {
column_name: identifier.to_string(),
type_: db_type,
is_primary: pk,
},
))
}
#[cfg(test)]
@ -66,22 +71,28 @@ mod tests {
#[test]
fn test_parse_create_primary_key() {
parse_create("CREATE TABLE \"Table1\"(id UUID PRIMARY KEY,column1 INT);").expect("should parse");
parse_create("CREATE TABLE \"Table1\"(id UUID PRIMARY KEY,column1 INT);")
.expect("should parse");
}
#[test]
fn test_parse_create_no_quotes_table_name() {
parse_create("CREATE TABLE Table1(id UUID PRIMARY KEY,column1 INT);").expect("should parse");
parse_create("CREATE TABLE Table1(id UUID PRIMARY KEY,column1 INT);")
.expect("should parse");
}
#[test]
fn test_parse_create_primary_key_with_spaces() {
parse_create("CREATE TABLE \"Table1\" ( id UUID PRIMARY KEY , column1 INT ) ;").expect("should parse");
parse_create(
"CREATE TABLE \"Table1\" ( id UUID PRIMARY KEY , column1 INT ) ;",
)
.expect("should parse");
}
#[test]
fn test_parse_create() {
let (_, create) = parse_create("CREATE TABLE \"Table1\"( id UUID , column1 INT );").expect("should parse");
let (_, create) = parse_create("CREATE TABLE \"Table1\"( id UUID , column1 INT );")
.expect("should parse");
assert!(matches!(create, RawQuerySyntax::CreateTable(_)));
match create {
RawQuerySyntax::CreateTable(schema) => {
@ -95,7 +106,9 @@ mod tests {
let result_column1 = schema.get_column(&"column1".to_string());
assert!(matches!(result_column1, Some(_)));
let Some(column1_column) = result_column1 else { panic!() };
let Some(column1_column) = result_column1 else {
panic!()
};
assert_eq!(column1_column.column_name, "column1".to_string());
}
_ => {}

View file

@ -4,8 +4,8 @@ use nom::{
IResult,
};
use super::common::{parse_condition, parse_table_name};
use crate::syntax::RawQuerySyntax;
use super::common::{parse_table_name, parse_condition};
pub fn parse_delete(input: &str) -> IResult<&str, RawQuerySyntax> {
let (input, _) = tag("DELETE")(input)?;
@ -25,14 +25,15 @@ pub fn parse_delete(input: &str) -> IResult<&str, RawQuerySyntax> {
#[cfg(test)]
mod tests {
use crate::syntax::RawQuerySyntax;
use crate::parsing::delete::parse_delete;
use crate::syntax::RawQuerySyntax;
#[test]
fn test_parse_delete() {
let (_, operation) = parse_delete("DELETE FROM \"T1\" WHERE id = 1 ;").expect("should parse");
let (_, operation) =
parse_delete("DELETE FROM \"T1\" WHERE id = 1 ;").expect("should parse");
assert!(matches!(operation, RawQuerySyntax::Delete(_, _)))
}
// TODO: add test with condition
// TODO: add test with condition
}

View file

@ -2,7 +2,8 @@ use crate::syntax::RawQuerySyntax;
use nom::{
bytes::complete::tag,
character::complete::{char, multispace0, multispace1},
IResult, combinator::opt,
combinator::opt,
IResult,
};
use super::common::{parse_identifier, parse_table_name};
@ -35,16 +36,16 @@ pub fn parse_create_index(input: &str) -> IResult<&str, RawQuerySyntax> {
Ok((input, operation))
}
#[cfg(test)]
mod tests {
use crate::syntax::RawQuerySyntax;
use crate::parsing::index::parse_create_index;
use crate::syntax::RawQuerySyntax;
#[test]
fn test_create_index() {
let (_, syntax) = parse_create_index("CREATE UNIQUE INDEX idxcontactsemail ON \"contacts\" (email);").expect("should parse");
let (_, syntax) =
parse_create_index("CREATE UNIQUE INDEX idxcontactsemail ON \"contacts\" (email);")
.expect("should parse");
assert!(matches!(syntax, RawQuerySyntax::CreateIndex(_, _)));
match syntax {
RawQuerySyntax::CreateIndex(table_name, column_name) => {
@ -57,7 +58,10 @@ mod tests {
#[test]
fn test_create_index_with_spaces() {
let (_, syntax) = parse_create_index("CREATE UNIQUE INDEX idxcontactsemail ON \"contacts\" ( email ) ;").expect("should parse");
let (_, syntax) = parse_create_index(
"CREATE UNIQUE INDEX idxcontactsemail ON \"contacts\" ( email ) ;",
)
.expect("should parse");
assert!(matches!(syntax, RawQuerySyntax::CreateIndex(_, _)));
match syntax {
RawQuerySyntax::CreateIndex(table_name, column_name) => {

View file

@ -1,9 +1,12 @@
use super::{literal::parse_db_value, common::{parse_table_name, parse_identifier}};
use super::{
common::{parse_identifier, parse_table_name},
literal::parse_db_value,
};
use crate::syntax::RawQuerySyntax;
use minisql::type_system::Value;
use nom::{
bytes::complete::tag,
character::complete::{multispace0, multispace1, char},
character::complete::{char, multispace0, multispace1},
combinator::map,
multi::separated_list0,
sequence::terminated,
@ -14,7 +17,7 @@ pub fn parse_insert(input: &str) -> IResult<&str, RawQuerySyntax> {
let (input, _) = tag("INSERT")(input)?;
let (input, _) = multispace1(input)?;
let (input, _) = tag("INTO")(input)?;
let (input, _) = multispace1(input)?;
let (input, _) = multispace1(input)?;
let (input, table_name) = parse_table_name(input)?;
let (input, _) = multispace1(input)?;
let (input, _) = char('(')(input)?;
@ -34,27 +37,31 @@ pub fn parse_insert(input: &str) -> IResult<&str, RawQuerySyntax> {
let (input, _) = char(';')(input)?;
Ok((
input,
RawQuerySyntax::Insert(table_name.to_string(), column_names.into_iter().zip(values).collect()),
RawQuerySyntax::Insert(
table_name.to_string(),
column_names.into_iter().zip(values).collect(),
),
))
}
pub fn parse_columns(input: &str) -> IResult<&str, Vec<String>> {
separated_list0(terminated(char(','), multispace0), map(parse_identifier, |name|name.to_string()))(input)
separated_list0(
terminated(char(','), multispace0),
map(parse_identifier, |name| name.to_string()),
)(input)
}
pub fn parse_values(input: &str) -> IResult<&str, Vec<Value>> {
separated_list0(terminated(char(','), multispace0), parse_db_value)(input)
}
#[cfg(test)]
mod tests {
use minisql::type_system::{IndexableValue, Value};
use crate::syntax::RawQuerySyntax;
use super::parse_insert;
use crate::syntax::RawQuerySyntax;
#[test]
fn test_parse_insert() {
let sql = "INSERT INTO \"MyTable\" (id, data) VALUES(1, \"Text\");";
@ -63,11 +70,15 @@ mod tests {
("", RawQuerySyntax::Insert(table_name, insertion_values)) => {
assert_eq!(table_name, "MyTable");
assert_eq!(
insertion_values,
insertion_values,
vec![
("id".to_string(), Value::Indexable(IndexableValue::Int(1))),
("data".to_string(), Value::Indexable(IndexableValue::String("Text".to_string())))
]);
(
"data".to_string(),
Value::Indexable(IndexableValue::String("Text".to_string()))
)
]
);
}
_ => {
unreachable!()
@ -77,16 +88,22 @@ mod tests {
#[test]
fn test_parse_insert_with_spaces() {
let sql = "INSERT INTO \"MyTable\" ( id, data ) VALUES ( 1, \"Text\" ) ;";
let sql =
"INSERT INTO \"MyTable\" ( id, data ) VALUES ( 1, \"Text\" ) ;";
let operation = parse_insert(sql).expect("should parse");
match operation {
("", RawQuerySyntax::Insert(table_name, insertion_values)) => {
assert_eq!(table_name, "MyTable");
assert_eq!(insertion_values,
assert_eq!(
insertion_values,
vec![
("id".to_string(), Value::Indexable(IndexableValue::Int(1))),
("data".to_string(), Value::Indexable(IndexableValue::String("Text".to_string())))
]);
(
"data".to_string(),
Value::Indexable(IndexableValue::String("Text".to_string()))
)
]
);
}
_ => {
unreachable!()

View file

@ -1,20 +1,16 @@
use minisql::type_system::{IndexableValue, Value};
use nom::{
branch::alt,
character::complete::{u64, char, digit1, none_of},
character::complete::{char, digit1, none_of, u64},
combinator::opt,
error::make_error,
multi::many0,
sequence::{delimited, pair, preceded},
IResult, error::make_error
IResult,
};
pub fn parse_db_value(input: &str) -> IResult<&str, Value> {
alt((
parse_string,
parse_number,
parse_int,
parse_uuid,
))(input)
alt((parse_string, parse_number, parse_int, parse_uuid))(input)
}
pub fn parse_number(input: &str) -> IResult<&str, Value> {
@ -27,56 +23,47 @@ pub fn parse_number(input: &str) -> IResult<&str, Value> {
match frac_part {
Some((_fsign, fdigits)) => {
// Combine integer and fractional parts
let combined_parts = format!(
"{}{}.{}",
sign.unwrap_or('+'),
digits,
fdigits
);
let combined_parts = format!("{}{}.{}", sign.unwrap_or('+'), digits, fdigits);
// Parse the combined parts as a floating-point number
let value = combined_parts.parse::<f64>()
.map_err(|_| {
nom::Err::Failure(make_error(input, nom::error::ErrorKind::Fail))
})?;
let value = combined_parts
.parse::<f64>()
.map_err(|_| nom::Err::Failure(make_error(input, nom::error::ErrorKind::Fail)))?;
Ok((input, Value::Number(value)))
}
None => {
let value = format!("{}{}", sign.unwrap_or('+'), digits).parse::<u64>()
.map_err(|_| {
nom::Err::Failure(make_error(input, nom::error::ErrorKind::Fail))
})?;
let value = format!("{}{}", sign.unwrap_or('+'), digits)
.parse::<u64>()
.map_err(|_| nom::Err::Failure(make_error(input, nom::error::ErrorKind::Fail)))?;
Ok((input, Value::Indexable(IndexableValue::Int(value))))
}
}
}
pub fn parse_int(input: &str) -> IResult<&str, Value> {
u64(input).map(|(input, v)| {
(input, Value::Indexable(IndexableValue::Int(v)))
})
u64(input).map(|(input, v)| (input, Value::Indexable(IndexableValue::Int(v))))
}
fn escape_tab(input:&str) -> IResult<&str, char> {
fn escape_tab(input: &str) -> IResult<&str, char> {
let (input, _) = preceded(char('\\'), char('t'))(input)?;
Ok((input, '\t'))
}
fn escape_backslash(input:&str) -> IResult<&str, char> {
fn escape_backslash(input: &str) -> IResult<&str, char> {
let (input, _) = preceded(char('\\'), char('\\'))(input)?;
Ok((input, '\\'))
}
fn escape_newline(input:&str) -> IResult<&str, char> {
fn escape_newline(input: &str) -> IResult<&str, char> {
let (input, _) = preceded(char('\\'), char('n'))(input)?;
Ok((input, '\n'))
}
fn escape_carriegereturn(input:&str) -> IResult<&str, char> {
fn escape_carriegereturn(input: &str) -> IResult<&str, char> {
let (input, _) = preceded(char('\\'), char('r'))(input)?;
Ok((input, '\r'))
}
fn escape_doublequote(input:&str) -> IResult<&str, char> {
fn escape_doublequote(input: &str) -> IResult<&str, char> {
preceded(char('\\'), char('"'))(input)
}
@ -90,7 +77,7 @@ pub fn parse_string(input: &str) -> IResult<&str, Value> {
escape_newline,
escape_doublequote,
escape_tab,
none_of(r#"\""#)
none_of(r#"\""#),
))),
char('"'),
)(input)?;
@ -102,23 +89,39 @@ pub fn parse_string(input: &str) -> IResult<&str, Value> {
}
pub fn parse_uuid(input: &str) -> IResult<&str, Value> {
let (input, value) = pair(char('u'), u64)(input).map(|(input, (_, v))| {
(input, Value::Indexable(IndexableValue::Uuid(v)))
})?;
let (input, value) = pair(char('u'), u64)(input)
.map(|(input, (_, v))| (input, Value::Indexable(IndexableValue::Uuid(v))))?;
Ok((input, value))
}
#[cfg(test)]
mod tests {
use minisql::type_system::{IndexableValue, Value};
use crate::parsing::literal::{parse_db_value, parse_string, parse_uuid};
use minisql::type_system::{IndexableValue, Value};
#[test]
fn test_string_parser() {
assert_eq!(parse_string(r#""simple""#), Ok(("", Value::Indexable(IndexableValue::String(String::from("simple"))))));
assert_eq!(parse_string(r#""\"\t\r\n\\""#), Ok(("", Value::Indexable(IndexableValue::String(String::from("\"\t\r\n\\"))))));
assert_eq!(parse_string(r#""name is \"John\".""#), Ok(("", Value::Indexable(IndexableValue::String(String::from("name is \"John\"."))))));
assert_eq!(
parse_string(r#""simple""#),
Ok((
"",
Value::Indexable(IndexableValue::String(String::from("simple")))
))
);
assert_eq!(
parse_string(r#""\"\t\r\n\\""#),
Ok((
"",
Value::Indexable(IndexableValue::String(String::from("\"\t\r\n\\")))
))
);
assert_eq!(
parse_string(r#""name is \"John\".""#),
Ok((
"",
Value::Indexable(IndexableValue::String(String::from("name is \"John\".")))
))
);
}
#[test]
@ -132,39 +135,63 @@ mod tests {
assert_eq!(value, Value::Number(5.5));
let (_, _) = parse_db_value("\"STRING\"").expect("should parse");
let (input, value) = parse_db_value("\"abcdefghkjklmnopqrstuvwxyz!@#$%^&*()_+ \"").expect("should parse");
let (input, value) =
parse_db_value("\"abcdefghkjklmnopqrstuvwxyz!@#$%^&*()_+ \"").expect("should parse");
assert_eq!(input, "");
assert_eq!(value, Value::Indexable(IndexableValue::String("abcdefghkjklmnopqrstuvwxyz!@#$%^&*()_+ ".to_string())));
assert_eq!(
value,
Value::Indexable(IndexableValue::String(
"abcdefghkjklmnopqrstuvwxyz!@#$%^&*()_+ ".to_string()
))
);
}
#[test]
fn test_parse_positive_float() {
assert_eq!(parse_db_value("23.213313"), Ok(("", Value::Number(23.213313))));
assert_eq!(parse_db_value("2241.9734"), Ok(("", Value::Number(2241.9734))));
assert_eq!(
parse_db_value("23.213313"),
Ok(("", Value::Number(23.213313)))
);
assert_eq!(
parse_db_value("2241.9734"),
Ok(("", Value::Number(2241.9734)))
);
}
#[test]
fn test_parse_negative_float() {
assert_eq!(parse_db_value("-9241.873654"), Ok(("", Value::Number(-9241.873654))));
assert_eq!(parse_db_value("-62625.0"), Ok(("", Value::Number(-62625.0))));
assert_eq!(
parse_db_value("-9241.873654"),
Ok(("", Value::Number(-9241.873654)))
);
assert_eq!(
parse_db_value("-62625.0"),
Ok(("", Value::Number(-62625.0)))
);
}
#[test]
fn test_parse_float_between_0_and_1() {
assert_eq!(parse_db_value("0.873654"), Ok(("", Value::Number(0.873654))));
assert_eq!(
parse_db_value("0.873654"),
Ok(("", Value::Number(0.873654)))
);
assert_eq!(parse_db_value("0.62625"), Ok(("", Value::Number(0.62625))));
}
#[test]
fn test_parse_int() {
assert_eq!(parse_db_value("5134616"), Ok(("", Value::Indexable(IndexableValue::Int(5134616)))));
assert_eq!(
parse_db_value("5134616"),
Ok(("", Value::Indexable(IndexableValue::Int(5134616))))
);
}
#[test]
fn test_parse_uuid() {
assert_eq!(parse_uuid("u131515"), Ok(("", Value::Indexable(IndexableValue::Uuid(131515)))))
assert_eq!(
parse_uuid("u131515"),
Ok(("", Value::Indexable(IndexableValue::Uuid(131515))))
)
}
}

View file

@ -1,7 +1,7 @@
pub(crate) mod literal;
pub(crate) mod select;
pub(crate) mod common;
pub(crate) mod create;
pub(crate) mod insert;
pub(crate) mod delete;
pub(crate) mod index;
pub(crate) mod insert;
pub(crate) mod literal;
pub(crate) mod select;

View file

@ -1,9 +1,9 @@
use super::common::{parse_table_name, parse_column_name, parse_condition};
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::{multispace0, multispace1, char},
character::complete::{char, multispace0, multispace1},
combinator::map,
error::Error,
multi::separated_list0,
@ -44,10 +44,12 @@ pub fn try_parse_column_selection(input: &str) -> IResult<&str, ColumnSelection>
#[cfg(test)]
mod tests {
use crate::parsing::{
common::{parse_column_name, parse_table_name},
select::parse_select,
};
use crate::syntax::{ColumnSelection, RawQuerySyntax};
use crate::parsing::{common::{parse_column_name, parse_table_name}, select::parse_select};
#[test]
fn test_parse_select_all() {
let sql = "SELECT * FROM \"MyTable\";";

View file

@ -1,4 +1,7 @@
use minisql::{type_system::{Value, DbType}, schema::{ColumnName, TableName}};
use minisql::{
schema::{ColumnName, TableName},
type_system::{DbType, Value},
};
// ===Table Schema===
#[derive(Debug, Clone, PartialEq)]
@ -53,10 +56,16 @@ impl RawTableSchema {
}
pub fn get_column(&self, column_name: &ColumnName) -> Option<ColumnSchema> {
self.columns.iter().find(|column_schema| column_name == &column_schema.column_name).cloned()
self.columns
.iter()
.find(|column_schema| column_name == &column_schema.column_name)
.cloned()
}
pub fn get_columns(&self) -> Vec<&ColumnName> {
self.columns.iter().map(|ColumnSchema { column_name, .. }| column_name).collect()
self.columns
.iter()
.map(|ColumnSchema { column_name, .. }| column_name)
.collect()
}
}

View file

@ -1,10 +1,16 @@
use std::collections::{HashSet, BTreeMap};
use std::collections::{BTreeMap, HashSet};
use thiserror::Error;
use crate::syntax;
use crate::syntax::{RawTableSchema, ColumnSchema, RawQuerySyntax};
use crate::syntax::{ColumnSchema, RawQuerySyntax, RawTableSchema};
use minisql::operation;
use minisql::{operation::Operation, type_system::Value, schema::{TableSchema, ColumnName, Column, TableName, TablePosition}, type_system::DbType, interpreter::DbSchema};
use minisql::{
interpreter::DbSchema,
operation::Operation,
schema::{Column, ColumnName, TableName, TablePosition, TableSchema},
type_system::DbType,
type_system::Value,
};
#[derive(Debug, Error)]
pub enum ValidationError {
@ -29,37 +35,46 @@ pub enum ValidationError {
expected_type: DbType,
},
#[error("values for required columns {0:?} are missing")]
RequiredColumnsAreMissing(Vec<ColumnName>)
RequiredColumnsAreMissing(Vec<ColumnName>),
}
/// Validates and converts the raw syntax into a proper interpreter operation based on db schema.
pub fn validate_operation(syntax: RawQuerySyntax, db_schema: &DbSchema) -> Result<Operation, ValidationError> {
pub fn validate_operation(
syntax: RawQuerySyntax,
db_schema: &DbSchema,
) -> Result<Operation, ValidationError> {
match syntax {
RawQuerySyntax::Select(table_name, column_selection, condition) => {
validate_select(table_name, column_selection, condition, db_schema)
},
}
RawQuerySyntax::Insert(table_name, insertion_values) => {
validate_insert(table_name, insertion_values, db_schema)
},
}
RawQuerySyntax::Delete(table_name, condition) => {
validate_delete(table_name, condition, db_schema)
},
RawQuerySyntax::CreateTable(schema) => {
validate_create_table(schema, db_schema)
},
}
RawQuerySyntax::CreateTable(schema) => validate_create_table(schema, db_schema),
RawQuerySyntax::CreateIndex(table_name, column_name) => {
validate_create_index(table_name, column_name, db_schema)
},
}
}
}
fn validate_table_exists<'a>(db_schema: &DbSchema<'a>, table_name: &'a TableName) -> Result<(TablePosition, &'a TableSchema), ValidationError> {
db_schema.iter().find(|(tname, _, _)| table_name.eq(tname))
fn validate_table_exists<'a>(
db_schema: &DbSchema<'a>,
table_name: &'a TableName,
) -> Result<(TablePosition, &'a TableSchema), ValidationError> {
db_schema
.iter()
.find(|(tname, _, _)| table_name.eq(tname))
.ok_or(ValidationError::TableDoesNotExist(table_name.to_string()))
.map(|(_, table_position, table_schema)| (*table_position, *table_schema))
}
fn validate_create_table(raw_table_schema: RawTableSchema, db_schema: &DbSchema) -> Result<Operation, ValidationError> {
fn validate_create_table(
raw_table_schema: RawTableSchema,
db_schema: &DbSchema,
) -> Result<Operation, ValidationError> {
let table_name: &TableName = &raw_table_schema.table_name;
if get_table_schema(db_schema, table_name).is_some() {
return Err(ValidationError::TableAlreadyExists(table_name.to_string()));
@ -71,16 +86,24 @@ fn validate_create_table(raw_table_schema: RawTableSchema, db_schema: &DbSchema)
fn validate_table_schema(raw_table_schema: RawTableSchema) -> Result<TableSchema, ValidationError> {
// check for duplicate columns
find_first_duplicate(&raw_table_schema.get_columns())
.map_or_else(
|| Ok(()),
|duplicate_column| Err(ValidationError::DuplicateColumn(duplicate_column.to_string()))
)?;
find_first_duplicate(&raw_table_schema.get_columns()).map_or_else(
|| Ok(()),
|duplicate_column| {
Err(ValidationError::DuplicateColumn(
duplicate_column.to_string(),
))
},
)?;
let mut primary_keys: Vec<(ColumnName, DbType)> = vec![];
let mut columns: Vec<ColumnName> = vec![];
let mut types: Vec<DbType> = vec![];
for ColumnSchema { column_name, type_, is_primary } in raw_table_schema.columns {
for ColumnSchema {
column_name,
type_,
is_primary,
} in raw_table_schema.columns
{
if is_primary {
primary_keys.push((column_name.clone(), type_))
}
@ -91,13 +114,22 @@ fn validate_table_schema(raw_table_schema: RawTableSchema) -> Result<TableSchema
// Ensure it has exactly one primary key that has correct type.
let number_of_primary_keys = primary_keys.len();
if number_of_primary_keys == 0 {
Err(ValidationError::PrimaryKeyMissing(raw_table_schema.table_name.clone()))
Err(ValidationError::PrimaryKeyMissing(
raw_table_schema.table_name.clone(),
))
} else if number_of_primary_keys > 1 {
Err(ValidationError::MultiplePrimaryKeysFound(raw_table_schema.table_name.clone()))
Err(ValidationError::MultiplePrimaryKeysFound(
raw_table_schema.table_name.clone(),
))
} else {
let (primary_column_name, primary_key_type) = primary_keys[0].clone();
if primary_key_type == DbType::Uuid {
Ok(TableSchema::new(raw_table_schema.table_name, primary_column_name, columns, types))
Ok(TableSchema::new(
raw_table_schema.table_name,
primary_column_name,
columns,
types,
))
} else {
Err(ValidationError::TypeMismatch {
column_name: raw_table_schema.table_name.clone(),
@ -108,121 +140,189 @@ fn validate_table_schema(raw_table_schema: RawTableSchema) -> Result<TableSchema
}
}
fn validate_select(table_name: TableName, column_selection: syntax::ColumnSelection, condition: Option<syntax::Condition>, db_schema: &DbSchema) -> Result<Operation, ValidationError> {
fn validate_select(
table_name: TableName,
column_selection: syntax::ColumnSelection,
condition: Option<syntax::Condition>,
db_schema: &DbSchema,
) -> Result<Operation, ValidationError> {
let (table_position, schema) = validate_table_exists(db_schema, &table_name)?;
match column_selection {
syntax::ColumnSelection::Columns(columns) => {
let non_existant_columns: Vec<ColumnName> =
columns.iter().filter_map(|column|
let non_existant_columns: Vec<ColumnName> = columns
.iter()
.filter_map(|column| {
if schema.does_column_exist(column) {
None
} else {
Some(column.clone())
}).collect();
}
})
.collect();
if non_existant_columns.is_empty() {
let selection: operation::ColumnSelection =
columns.iter().filter_map(|column_name| schema.get_column(column_name)).collect();
let selection: operation::ColumnSelection = columns
.iter()
.filter_map(|column_name| schema.get_column(column_name))
.collect();
let validated_condition = validate_condition(condition, schema)?;
Ok(Operation::Select(table_position, selection, validated_condition))
Ok(Operation::Select(
table_position,
selection,
validated_condition,
))
} else {
Err(ValidationError::ColumnsDoNotExist(non_existant_columns))
}
}
syntax::ColumnSelection::All => {
let validated_condition = validate_condition(condition, schema)?;
Ok(Operation::Select(table_position, schema.all_selection(), validated_condition))
Ok(Operation::Select(
table_position,
schema.all_selection(),
validated_condition,
))
}
}
}
fn validate_insert(table_name: TableName, insertion_values: syntax::InsertionValues, db_schema: &DbSchema) -> Result<Operation, ValidationError> {
fn validate_insert(
table_name: TableName,
insertion_values: syntax::InsertionValues,
db_schema: &DbSchema,
) -> Result<Operation, ValidationError> {
let (table_position, schema) = validate_table_exists(db_schema, &table_name)?;
// Check for duplicate columns in insertion_values.
let columns_in_query_vec: Vec<&ColumnName> = insertion_values.iter().map(|(column_name, _)| column_name).collect();
find_first_duplicate(&columns_in_query_vec)
.map_or_else(
|| Ok(()),
|duplicate_column| Err(ValidationError::DuplicateColumn(duplicate_column.to_string()))
)?;
let columns_in_query_vec: Vec<&ColumnName> = insertion_values
.iter()
.map(|(column_name, _)| column_name)
.collect();
find_first_duplicate(&columns_in_query_vec).map_or_else(
|| Ok(()),
|duplicate_column| {
Err(ValidationError::DuplicateColumn(
duplicate_column.to_string(),
))
},
)?;
// Check that the set of columns in the insertion_values is the same as the set of required columns of the table.
let columns_in_query: HashSet<&ColumnName> = HashSet::from_iter(columns_in_query_vec);
let columns_in_schema: HashSet<&ColumnName> = HashSet::from_iter(schema.get_columns());
let non_existant_columns = Vec::from_iter(columns_in_query.difference(&columns_in_schema));
if !non_existant_columns.is_empty() {
return Err(ValidationError::ColumnsDoNotExist(non_existant_columns.iter().map(|column_name| column_name.to_string()).collect()));
return Err(ValidationError::ColumnsDoNotExist(
non_existant_columns
.iter()
.map(|column_name| column_name.to_string())
.collect(),
));
}
let missing_required_columns = Vec::from_iter(columns_in_schema.difference(&columns_in_query));
if !missing_required_columns.is_empty() {
return Err(ValidationError::RequiredColumnsAreMissing(missing_required_columns.iter().map(|str| str.to_string()).collect()));
return Err(ValidationError::RequiredColumnsAreMissing(
missing_required_columns
.iter()
.map(|str| str.to_string())
.collect(),
));
}
// Check types and prepare for creation of InsertionValues for the interpreter
let mut values_map: BTreeMap<Column, Value> = BTreeMap::new(); // The reason for using BTreeMap
// instead of HashMap is that we need
// to get the values in a vector
// sorted by the key.
// instead of HashMap is that we need
// to get the values in a vector
// sorted by the key.
for (column_name, value) in insertion_values {
let (column, expected_type) = schema.get_typed_column(&column_name).ok_or(ValidationError::ColumnsDoNotExist(vec![column_name.to_string()]))?; // By the previous validation steps this is never gonna trigger an error.
let (column, expected_type) =
schema
.get_typed_column(&column_name)
.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();
if value_type != expected_type {
return Err(ValidationError::TypeMismatch { column_name: column_name.to_string(), received_type: value_type, expected_type });
return Err(ValidationError::TypeMismatch {
column_name: column_name.to_string(),
received_type: value_type,
expected_type,
});
}
values_map.insert(column, value);
}
// WARNING: If you use `values_map: HashMap<_,_>`, this is not gonna sort values by key.
let values: operation::InsertionValues = values_map.into_values().collect();
let values: operation::InsertionValues = values_map.into_values().collect();
// Note that one of the values is id.
Ok(Operation::Insert(table_position, values))
}
fn validate_delete(table_name: TableName, condition: Option<syntax::Condition>, db_schema: &DbSchema) -> Result<Operation, ValidationError> {
fn validate_delete(
table_name: TableName,
condition: Option<syntax::Condition>,
db_schema: &DbSchema,
) -> Result<Operation, ValidationError> {
let (table_position, schema) = validate_table_exists(db_schema, &table_name)?;
let validated_condition = validate_condition(condition, schema)?;
Ok(Operation::Delete(table_position, validated_condition))
}
fn validate_condition(condition: Option<syntax::Condition>, schema: &TableSchema) -> Result<Option<operation::Condition>, ValidationError> {
fn validate_condition(
condition: Option<syntax::Condition>,
schema: &TableSchema,
) -> Result<Option<operation::Condition>, ValidationError> {
match condition {
Some(condition) => {
match condition {
syntax::Condition::Eq(column_name, value) => {
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();
if expected_type.eq(&value_type) {
Ok(Some(operation::Condition::Eq(column, value)))
} else {
Err(ValidationError::TypeMismatch { column_name: column_name.to_string(), received_type: value_type, expected_type })
}
Some(condition) => match condition {
syntax::Condition::Eq(column_name, value) => {
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();
if expected_type.eq(&value_type) {
Ok(Some(operation::Condition::Eq(column, value)))
} else {
Err(ValidationError::TypeMismatch {
column_name: column_name.to_string(),
received_type: value_type,
expected_type,
})
}
}
}
None => Ok(None)
},
None => Ok(None),
}
}
fn validate_create_index(table_name: TableName, column_name: ColumnName, db_schema: &DbSchema) -> Result<Operation, ValidationError> {
fn validate_create_index(
table_name: TableName,
column_name: ColumnName,
db_schema: &DbSchema,
) -> Result<Operation, ValidationError> {
let (table_position, schema) = validate_table_exists(db_schema, &table_name)?;
schema
.get_typed_column(&column_name)
.map_or_else(
|| Err(ValidationError::ColumnsDoNotExist(vec![column_name.to_string()])),
|(column, type_)| {
if type_.is_indexable() {
Ok(Operation::CreateIndex(table_position, column))
} else {
Err(ValidationError::AttemptToIndexNonIndexableColumn(column_name.clone(), table_name))
}
schema.get_typed_column(&column_name).map_or_else(
|| {
Err(ValidationError::ColumnsDoNotExist(vec![
column_name.to_string()
]))
},
|(column, type_)| {
if type_.is_indexable() {
Ok(Operation::CreateIndex(table_position, column))
} else {
Err(ValidationError::AttemptToIndexNonIndexableColumn(
column_name.clone(),
table_name,
))
}
)
},
)
}
// ===Helpers===
fn find_first_duplicate<T>(ts: &[T]) -> Option<&T>
where T: Eq + std::hash::Hash
fn find_first_duplicate<T>(ts: &[T]) -> Option<&T>
where
T: Eq + std::hash::Hash,
{
let mut already_seen_elements: HashSet<&T> = HashSet::new();
for t in ts {
@ -235,34 +335,35 @@ where T: Eq + std::hash::Hash
None
}
fn get_table_schema<'a>(db_schema: &DbSchema<'a>, table_name: &'a TableName) -> Option<&'a TableSchema> {
let (_, _, table_schema) = db_schema.iter().find(|(tname, _, _)| table_name.eq(tname))?;
fn get_table_schema<'a>(
db_schema: &DbSchema<'a>,
table_name: &'a TableName,
) -> Option<&'a TableSchema> {
let (_, _, table_schema) = db_schema
.iter()
.find(|(tname, _, _)| table_name.eq(tname))?;
Some(table_schema)
}
#[cfg(test)]
mod tests {
use crate::syntax::{RawTableSchema, ColumnSchema, RawQuerySyntax, ColumnSelection, Condition};
use minisql::type_system::{Value, IndexableValue};
use minisql::operation::Operation;
use minisql::operation;
use minisql::schema::TableSchema;
use super::*;
use crate::syntax::{ColumnSchema, ColumnSelection, Condition, RawQuerySyntax, RawTableSchema};
use minisql::operation;
use minisql::operation::Operation;
use minisql::schema::TableSchema;
use minisql::type_system::{IndexableValue, Value};
use Condition::*;
use IndexableValue::*;
use RawQuerySyntax::*;
use Value::*;
use IndexableValue::*;
use Condition::*;
fn users_schema() -> TableSchema {
TableSchema::new(
"users".to_string(),
"id".to_string(),
vec!(
"id".to_string(),
"name".to_string(),
"age".to_string(),
),
vec!["id".to_string(), "name".to_string(), "age".to_string()],
vec![DbType::Uuid, DbType::String, DbType::Int],
)
}
@ -271,18 +372,27 @@ mod tests {
RawTableSchema {
table_name: "users".to_string(),
columns: vec![
ColumnSchema { column_name: "id".to_string(), type_: DbType::Uuid, is_primary: true },
ColumnSchema { column_name: "name".to_string(), type_: DbType::String, is_primary: false },
ColumnSchema { column_name: "age".to_string(), type_: DbType::Int, is_primary: false },
ColumnSchema {
column_name: "id".to_string(),
type_: DbType::Uuid,
is_primary: true,
},
ColumnSchema {
column_name: "name".to_string(),
type_: DbType::String,
is_primary: false,
},
ColumnSchema {
column_name: "age".to_string(),
type_: DbType::Int,
is_primary: false,
},
],
}
}
fn db_schema(users_schema: &TableSchema) -> DbSchema {
vec![
("users".to_string(), 0, users_schema),
]
vec![("users".to_string(), 0, users_schema)]
}
fn empty_db_schema() -> DbSchema<'static> {
@ -297,7 +407,9 @@ mod tests {
let result = validate_operation(syntax, &db_schema);
assert!(matches!(result, Ok(Operation::CreateTable(_))));
let Ok(Operation::CreateTable(schema)) = result else { panic!() };
let Ok(Operation::CreateTable(schema)) = result else {
panic!()
};
assert!(schema.table_name() == "users");
}
@ -306,9 +418,21 @@ mod tests {
let raw_users_schema = RawTableSchema {
table_name: "users".to_string(),
columns: vec![
ColumnSchema { column_name: "id".to_string(), type_: DbType::Uuid, is_primary: true },
ColumnSchema { column_name: "name".to_string(), type_: DbType::String, is_primary: false },
ColumnSchema { column_name: "name".to_string(), type_: DbType::Number, is_primary: false },
ColumnSchema {
column_name: "id".to_string(),
type_: DbType::Uuid,
is_primary: true,
},
ColumnSchema {
column_name: "name".to_string(),
type_: DbType::String,
is_primary: false,
},
ColumnSchema {
column_name: "name".to_string(),
type_: DbType::Number,
is_primary: false,
},
],
};
@ -325,9 +449,21 @@ mod tests {
let raw_users_schema = RawTableSchema {
table_name: "users".to_string(),
columns: vec![
ColumnSchema { column_name: "id".to_string(), type_: DbType::Int, is_primary: true },
ColumnSchema { column_name: "name".to_string(), type_: DbType::String, is_primary: false },
ColumnSchema { column_name: "age".to_string(), type_: DbType::Int, is_primary: false },
ColumnSchema {
column_name: "id".to_string(),
type_: DbType::Int,
is_primary: true,
},
ColumnSchema {
column_name: "name".to_string(),
type_: DbType::String,
is_primary: false,
},
ColumnSchema {
column_name: "age".to_string(),
type_: DbType::Int,
is_primary: false,
},
],
};
@ -343,9 +479,21 @@ mod tests {
let raw_users_schema = RawTableSchema {
table_name: "users".to_string(),
columns: vec![
ColumnSchema { column_name: "id".to_string(), type_: DbType::Int, is_primary: true },
ColumnSchema { column_name: "name".to_string(), type_: DbType::String, is_primary: true },
ColumnSchema { column_name: "age".to_string(), type_: DbType::Int, is_primary: false },
ColumnSchema {
column_name: "id".to_string(),
type_: DbType::Int,
is_primary: true,
},
ColumnSchema {
column_name: "name".to_string(),
type_: DbType::String,
is_primary: true,
},
ColumnSchema {
column_name: "age".to_string(),
type_: DbType::Int,
is_primary: false,
},
],
};
@ -353,7 +501,10 @@ mod tests {
let syntax: RawQuerySyntax = CreateTable(raw_users_schema);
let result = validate_operation(syntax, &db_schema);
assert!(matches!(result, Err(ValidationError::MultiplePrimaryKeysFound(_))));
assert!(matches!(
result,
Err(ValidationError::MultiplePrimaryKeysFound(_))
));
}
#[test]
@ -363,7 +514,10 @@ mod tests {
let syntax: RawQuerySyntax = CreateTable(raw_users_schema());
let result = validate_operation(syntax, &db_schema);
assert!(matches!(result, Err(ValidationError::TableAlreadyExists(_))));
assert!(matches!(
result,
Err(ValidationError::TableAlreadyExists(_))
));
}
// ====Select====
@ -380,7 +534,9 @@ mod tests {
let result = validate_operation(syntax, &db_schema);
assert!(matches!(result, Ok(Operation::Select(_, _, _))));
let Ok(Operation::Select(table_position, column_selection, condition)) = result else { panic!() };
let Ok(Operation::Select(table_position, column_selection, condition)) = result else {
panic!()
};
assert!(table_position == users_position);
assert!(condition == None);
@ -392,7 +548,8 @@ mod tests {
let users_schema: TableSchema = users_schema();
let db_schema: DbSchema = db_schema(&users_schema);
let syntax: RawQuerySyntax = Select("does_not_exist".to_string(), ColumnSelection::All, None);
let syntax: RawQuerySyntax =
Select("does_not_exist".to_string(), ColumnSelection::All, None);
let result = validate_operation(syntax, &db_schema);
assert!(matches!(result, Err(ValidationError::TableDoesNotExist(_))));
}
@ -407,11 +564,17 @@ mod tests {
let name = 1;
let age = 2;
let syntax: RawQuerySyntax = Select("users".to_string(), ColumnSelection::All, Some(Eq("age".to_string(), Indexable(Int(25)))));
let syntax: RawQuerySyntax = Select(
"users".to_string(),
ColumnSelection::All,
Some(Eq("age".to_string(), Indexable(Int(25)))),
);
let result = validate_operation(syntax, &db_schema);
assert!(matches!(result, Ok(Operation::Select(_, _, _))));
let Ok(Operation::Select(table_position, column_selection, condition)) = result else { panic!() };
let Ok(Operation::Select(table_position, column_selection, condition)) = result else {
panic!()
};
assert!(table_position == users_position);
assert!(column_selection == vec![id, name, age]);
@ -428,11 +591,21 @@ mod tests {
let name = 1;
let age = 2;
let syntax: RawQuerySyntax = Select("users".to_string(), ColumnSelection::Columns(vec!["age".to_string(), "name".to_string(), "age".to_string()]), None);
let syntax: RawQuerySyntax = Select(
"users".to_string(),
ColumnSelection::Columns(vec![
"age".to_string(),
"name".to_string(),
"age".to_string(),
]),
None,
);
let result = validate_operation(syntax, &db_schema);
assert!(matches!(result, Ok(Operation::Select(_, _, _))));
let Ok(Operation::Select(table_position, column_selection, condition)) = result else { panic!() };
let Ok(Operation::Select(table_position, column_selection, condition)) = result else {
panic!()
};
assert!(table_position == users_position);
assert!(column_selection == vec![age, name, age]);
@ -444,7 +617,11 @@ mod tests {
let users_schema: TableSchema = users_schema();
let db_schema: DbSchema = db_schema(&users_schema);
let syntax: RawQuerySyntax = Select("users".to_string(), ColumnSelection::Columns(vec!["age".to_string(), "does_not_exist".to_string()]), None);
let syntax: RawQuerySyntax = Select(
"users".to_string(),
ColumnSelection::Columns(vec!["age".to_string(), "does_not_exist".to_string()]),
None,
);
let result = validate_operation(syntax, &db_schema);
assert!(matches!(result, Err(ValidationError::ColumnsDoNotExist(_))));
}
@ -454,7 +631,11 @@ mod tests {
let users_schema: TableSchema = users_schema();
let db_schema: DbSchema = db_schema(&users_schema);
let syntax: RawQuerySyntax = Select("users".to_string(), ColumnSelection::All, Some(Eq("does_not_exist".to_string(), Indexable(Int(25)))));
let syntax: RawQuerySyntax = Select(
"users".to_string(),
ColumnSelection::All,
Some(Eq("does_not_exist".to_string(), Indexable(Int(25)))),
);
let result = validate_operation(syntax, &db_schema);
assert!(matches!(result, Err(ValidationError::ColumnsDoNotExist(_))));
}
@ -464,7 +645,11 @@ mod tests {
let users_schema: TableSchema = users_schema();
let db_schema: DbSchema = db_schema(&users_schema);
let syntax: RawQuerySyntax = Select("users".to_string(), ColumnSelection::All, Some(Eq("age".to_string(), Indexable(String("25".to_string())))));
let syntax: RawQuerySyntax = Select(
"users".to_string(),
ColumnSelection::All,
Some(Eq("age".to_string(), Indexable(String("25".to_string())))),
);
let result = validate_operation(syntax, &db_schema);
assert!(matches!(result, Err(ValidationError::TypeMismatch { .. })));
}
@ -483,18 +668,28 @@ mod tests {
("name".to_string(), Indexable(String("Alice".to_string()))),
("id".to_string(), Indexable(Uuid(0))),
("age".to_string(), Indexable(Int(25))),
]);
],
);
let result = validate_operation(syntax, &db_schema);
assert!(matches!(result, Ok(Operation::Insert(_, _))));
let Ok(Operation::Insert(table_position, values)) = result else { panic!() };
let Ok(Operation::Insert(table_position, values)) = result else {
panic!()
};
assert!(table_position == users_position);
// Recall the order is
// let id = 0;
// let name = 1;
// let age = 2;
assert!(values == vec![Indexable(Uuid(0)), Indexable(String("Alice".to_string())), Indexable(Int(25))]);
assert!(
values
== vec![
Indexable(Uuid(0)),
Indexable(String("Alice".to_string())),
Indexable(Int(25))
]
);
}
#[test]
@ -509,7 +704,8 @@ mod tests {
("id".to_string(), Indexable(Uuid(0))),
("age".to_string(), Indexable(Int(25))),
("does_not_exist".to_string(), Indexable(Int(25))),
]);
],
);
let result = validate_operation(syntax, &db_schema);
assert!(matches!(result, Err(ValidationError::ColumnsDoNotExist(_))));
}
@ -525,7 +721,8 @@ mod tests {
("name".to_string(), Indexable(String("Alice".to_string()))),
("id".to_string(), Indexable(Uuid(0))),
("age".to_string(), Number(25.0)),
]);
],
);
let result = validate_operation(syntax, &db_schema);
assert!(matches!(result, Err(ValidationError::TypeMismatch { .. })));
}
@ -542,7 +739,9 @@ mod tests {
let result = validate_operation(syntax, &db_schema);
assert!(matches!(result, Ok(Operation::Delete(_, None))));
let Ok(Operation::Delete(table_position, _)) = result else { panic!() };
let Ok(Operation::Delete(table_position, _)) = result else {
panic!()
};
assert!(table_position == users_position);
}
@ -555,11 +754,21 @@ mod tests {
let users_position = 0;
let age = 2;
let syntax: RawQuerySyntax = Delete("users".to_string(), Some(Eq("age".to_string(), Indexable(Int(25)))));
let syntax: RawQuerySyntax = Delete(
"users".to_string(),
Some(Eq("age".to_string(), Indexable(Int(25)))),
);
let result = validate_operation(syntax, &db_schema);
assert!(matches!(result, Ok(Operation::Delete(_, Some(operation::Condition::Eq(_, _))))));
assert!(matches!(
result,
Ok(Operation::Delete(_, Some(operation::Condition::Eq(_, _))))
));
let Ok(Operation::Delete(table_position, Some(operation::Condition::Eq(column, value)))) = result else { panic!() };
let Ok(Operation::Delete(table_position, Some(operation::Condition::Eq(column, value)))) =
result
else {
panic!()
};
assert!(table_position == users_position);
assert!(column == age);
@ -579,7 +788,9 @@ mod tests {
let result = validate_operation(syntax, &db_schema);
assert!(matches!(result, Ok(Operation::CreateIndex(_, _))));
let Ok(Operation::CreateIndex(table_position, column)) = result else { panic!() };
let Ok(Operation::CreateIndex(table_position, column)) = result else {
panic!()
};
assert!(table_position == users_position);
assert!(column == age);