Merge branch 'main' into server-work
This commit is contained in:
commit
e87b11f19f
12 changed files with 190 additions and 138 deletions
|
|
@ -12,8 +12,8 @@ use std::collections::HashMap;
|
||||||
pub struct TableSchema {
|
pub struct TableSchema {
|
||||||
table_name: TableName, // used for descriptive errors
|
table_name: TableName, // used for descriptive errors
|
||||||
primary_key: ColumnPosition,
|
primary_key: ColumnPosition,
|
||||||
pub column_name_position_mapping: BiMap<ColumnName, ColumnPosition>,
|
column_name_position_mapping: BiMap<ColumnName, ColumnPosition>,
|
||||||
pub types: Vec<DbType>,
|
types: Vec<DbType>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type TableName = String;
|
pub type TableName = String;
|
||||||
|
|
@ -36,6 +36,24 @@ impl TableSchema {
|
||||||
self.types[column_position]
|
self.types[column_position]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_columns(&self) -> Vec<&ColumnName> {
|
||||||
|
self.column_name_position_mapping.iter().map(|(name, _)| name).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn does_column_exist(&self, column_name: &ColumnName) -> bool {
|
||||||
|
self.column_name_position_mapping.contains_left(column_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_column_position(&self, column_name: &ColumnName) -> Option<ColumnPosition> {
|
||||||
|
self.column_name_position_mapping.get_by_left(column_name).copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_type_at(&self, column_name: &ColumnName) -> Option<DbType> {
|
||||||
|
let position = self.get_column_position(column_name)?;
|
||||||
|
self.types.get(position).copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Get rid of this after validation is merged
|
||||||
fn get_column(&self, column_name: &ColumnName) -> DbResult<(DbType, ColumnPosition)> {
|
fn get_column(&self, column_name: &ColumnName) -> DbResult<(DbType, ColumnPosition)> {
|
||||||
match self.column_name_position_mapping.get_by_left(column_name) {
|
match self.column_name_position_mapping.get_by_left(column_name) {
|
||||||
Some(column_position) => match self.types.get(*column_position) {
|
Some(column_position) => match self.types.get(*column_position) {
|
||||||
|
|
@ -52,6 +70,7 @@ impl TableSchema {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Get rid of this after validation is merged
|
||||||
pub fn column_position_from_column_name(
|
pub fn column_position_from_column_name(
|
||||||
&self,
|
&self,
|
||||||
column_name: &ColumnName,
|
column_name: &ColumnName,
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,15 @@
|
||||||
use minisql::{operation::Operation, schema::TableSchema};
|
use minisql::{operation::Operation, schema::TableSchema};
|
||||||
use nom::{branch::alt, multi::many0, IResult};
|
use nom::{branch::alt, multi::many0, IResult};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::{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)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
#[error("parsing error: {0}")]
|
||||||
ParsingError(String),
|
ParsingError(String),
|
||||||
ValidationError(ValidationError)
|
#[error("validation error: {0}")]
|
||||||
|
ValidationError(#[from] ValidationError)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_statement<'a>(input: &'a str) -> IResult<&str, Operation> {
|
pub fn parse_statement<'a>(input: &'a str) -> IResult<&str, Operation> {
|
||||||
|
|
@ -31,7 +34,7 @@ pub fn parse_and_validate(query: String, db_metadata: &Vec<(String, &TableSchema
|
||||||
Error::ParsingError(err.to_string())
|
Error::ParsingError(err.to_string())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
validate_operation(&op, db_metadata).map_err(|err| Error::ValidationError(err))?;
|
validate_operation(&op, db_metadata)?;
|
||||||
Ok(op)
|
Ok(op)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,5 @@
|
||||||
|
|
||||||
mod literal;
|
mod parsing;
|
||||||
mod select;
|
|
||||||
mod common;
|
|
||||||
mod create;
|
|
||||||
mod insert;
|
|
||||||
mod delete;
|
|
||||||
mod index;
|
|
||||||
mod validation;
|
mod validation;
|
||||||
mod core;
|
mod core;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ use nom::{
|
||||||
};
|
};
|
||||||
use minisql::{operation::Condition, type_system::DbType};
|
use minisql::{operation::Condition, type_system::DbType};
|
||||||
|
|
||||||
use crate::literal::parse_db_value;
|
use super::literal::parse_db_value;
|
||||||
|
|
||||||
pub fn parse_table_name(input: &str) -> IResult<&str, &str> {
|
pub fn parse_table_name(input: &str) -> IResult<&str, &str> {
|
||||||
alt((
|
alt((
|
||||||
|
|
@ -32,12 +32,12 @@ 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("Float"), tag("UUID")))(input)?;
|
let (input, type_name) = alt((tag("STRING"), tag("INT"), tag("NUMBER"), tag("UUID")))(input)?;
|
||||||
let db_type = match type_name {
|
let db_type = match type_name {
|
||||||
"STRING" => DbType::String,
|
"STRING" => DbType::String,
|
||||||
"INT" => DbType::Int,
|
"INT" => DbType::Int,
|
||||||
"UUID" => DbType::Uuid,
|
"UUID" => DbType::Uuid,
|
||||||
"Float" => DbType::Number,
|
"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))
|
Ok((input, db_type))
|
||||||
|
|
@ -68,7 +68,7 @@ fn parse_equality(input: &str) -> IResult<&str, Condition> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use minisql::{operation::Condition, type_system::DbType};
|
use minisql::{operation::Condition, type_system::DbType};
|
||||||
use crate::common::{parse_db_type, parse_equality};
|
use crate::parsing::common::{parse_db_type, parse_equality};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_equality() {
|
fn test_parse_equality() {
|
||||||
|
|
@ -7,7 +7,7 @@ use nom::{
|
||||||
IResult, combinator::opt,
|
IResult, combinator::opt,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::common::{parse_table_name, parse_identifier, parse_db_type};
|
use super::common::{parse_table_name, parse_identifier, parse_db_type};
|
||||||
|
|
||||||
pub fn parse_create(input: &str) -> IResult<&str, Operation> {
|
pub fn parse_create(input: &str) -> IResult<&str, Operation> {
|
||||||
let (input, _) = tag("CREATE")(input)?;
|
let (input, _) = tag("CREATE")(input)?;
|
||||||
|
|
@ -69,7 +69,7 @@ pub fn parse_column_definition(input: &str) -> IResult<&str, (ColumnName, DbType
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use minisql::operation::Operation;
|
use minisql::operation::Operation;
|
||||||
use crate::create::parse_create;
|
use crate::parsing::create::parse_create;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_create_no_spaces() {
|
fn test_parse_create_no_spaces() {
|
||||||
|
|
@ -5,7 +5,7 @@ use nom::{
|
||||||
IResult,
|
IResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::common::{parse_table_name, parse_condition};
|
use super::common::{parse_table_name, parse_condition};
|
||||||
|
|
||||||
pub fn parse_delete(input: &str) -> IResult<&str, Operation> {
|
pub fn parse_delete(input: &str) -> IResult<&str, Operation> {
|
||||||
let (input, _) = tag("DELETE")(input)?;
|
let (input, _) = tag("DELETE")(input)?;
|
||||||
|
|
@ -26,7 +26,7 @@ pub fn parse_delete(input: &str) -> IResult<&str, Operation> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use minisql::operation::Operation;
|
use minisql::operation::Operation;
|
||||||
use crate::delete::parse_delete;
|
use crate::parsing::delete::parse_delete;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_delete() {
|
fn test_parse_delete() {
|
||||||
|
|
@ -5,7 +5,7 @@ use nom::{
|
||||||
IResult, combinator::opt,
|
IResult, combinator::opt,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::common::{parse_identifier, parse_table_name};
|
use super::common::{parse_identifier, parse_table_name};
|
||||||
|
|
||||||
pub fn parse_create_index(input: &str) -> IResult<&str, Operation> {
|
pub fn parse_create_index(input: &str) -> IResult<&str, Operation> {
|
||||||
let (input, _) = tag("CREATE")(input)?;
|
let (input, _) = tag("CREATE")(input)?;
|
||||||
|
|
@ -39,7 +39,7 @@ pub fn parse_create_index(input: &str) -> IResult<&str, Operation> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use minisql::operation::Operation;
|
use minisql::operation::Operation;
|
||||||
use crate::index::parse_create_index;
|
use crate::parsing::index::parse_create_index;
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{literal::parse_db_value, common::{parse_table_name, parse_identifier}};
|
use super::{literal::parse_db_value, common::{parse_table_name, parse_identifier}};
|
||||||
use minisql::{operation::Operation, type_system::Value};
|
use minisql::{operation::Operation, type_system::Value};
|
||||||
use nom::{
|
use nom::{
|
||||||
bytes::complete::tag,
|
bytes::complete::tag,
|
||||||
|
|
@ -100,17 +100,17 @@ pub fn parse_string(input: &str) -> IResult<&str, Value> {
|
||||||
Ok((input, Value::Indexable(IndexableValue::String(value))))
|
Ok((input, Value::Indexable(IndexableValue::String(value))))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_uuid(input: &str) -> IResult<&str, Value> {
|
pub fn parse_uuid(input: &str) -> IResult<&str, Value> {
|
||||||
// TODO: make it actually uuid
|
let (input, value) = pair(char('u'), u64)(input).map(|(input, (_, v))| {
|
||||||
u64(input).map(|(input, v)| {
|
|
||||||
(input, Value::Indexable(IndexableValue::Uuid(v)))
|
(input, Value::Indexable(IndexableValue::Uuid(v)))
|
||||||
})
|
})?;
|
||||||
|
Ok((input, value))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use minisql::type_system::{IndexableValue, Value};
|
use minisql::type_system::{IndexableValue, Value};
|
||||||
use crate::literal::{parse_db_value, parse_string};
|
use crate::parsing::literal::{parse_db_value, parse_string, parse_uuid};
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -161,4 +161,9 @@ mod tests {
|
||||||
fn test_parse_int() {
|
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)))))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
7
parser/src/parsing/mod.rs
Normal file
7
parser/src/parsing/mod.rs
Normal file
|
|
@ -0,0 +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;
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::common::{parse_table_name, parse_column_name, parse_condition};
|
use super::common::{parse_table_name, parse_column_name, parse_condition};
|
||||||
use minisql::operation::{ColumnSelection, Operation};
|
use minisql::operation::{ColumnSelection, Operation};
|
||||||
use nom::{
|
use nom::{
|
||||||
branch::alt,
|
branch::alt,
|
||||||
|
|
@ -45,7 +45,7 @@ pub fn try_parse_column_selection(input: &str) -> IResult<&str, ColumnSelection>
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use minisql::operation::{ColumnSelection, Operation};
|
use minisql::operation::{ColumnSelection, Operation};
|
||||||
use crate::{common::{parse_column_name, parse_table_name}, select::parse_select};
|
use crate::parsing::{common::{parse_column_name, parse_table_name}, select::parse_select};
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -1,98 +1,111 @@
|
||||||
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
use minisql::{operation::{ColumnSelection, Condition, InsertionValues, Operation}, schema::TableSchema, type_system::{DbType, IndexableValue, Value}};
|
use minisql::{operation::{ColumnSelection, Condition, InsertionValues, Operation}, schema::{TableSchema, ColumnName, TableName}, type_system::DbType};
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Error)]
|
||||||
pub enum ValidationError {
|
pub enum ValidationError {
|
||||||
TableDoesNotExist(String),
|
#[error("table {0} does not exist")]
|
||||||
TableExists(String),
|
TableDoesNotExist(TableName),
|
||||||
ColumnDoesNotExist(String),
|
#[error("table {0} already exists")]
|
||||||
BadColumnPosition(usize),
|
TableAlreadyExists(TableName),
|
||||||
DuplicateColumn(String),
|
#[error("columns {0:?} do not exist")]
|
||||||
TypeMismatch,
|
ColumnsDoNotExist(Vec<ColumnName>),
|
||||||
ValueForRequiredColumnIsMissing(String)
|
#[error("duplicate column {0}")]
|
||||||
|
DuplicateColumn(ColumnName),
|
||||||
|
#[error("type mismatch at column `{column_name:?}` (expected {expected_type:?}, found {received_type:?})")]
|
||||||
|
TypeMismatch {
|
||||||
|
column_name: ColumnName,
|
||||||
|
received_type: DbType,
|
||||||
|
expected_type: DbType,
|
||||||
|
},
|
||||||
|
#[error("values for required columns {0:?} are missing")]
|
||||||
|
RequiredColumnsAreMissing(Vec<ColumnName>)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn type_of(value: &Value) -> DbType {
|
pub type DbSchema<'a> = Vec<(TableName, &'a TableSchema)>;
|
||||||
match value {
|
|
||||||
Value::Indexable(IndexableValue::Int(_)) => DbType::Int,
|
|
||||||
Value::Indexable(IndexableValue::String(_)) => DbType::String,
|
|
||||||
Value::Number(_) => DbType::Number,
|
|
||||||
Value::Indexable(IndexableValue::Uuid(_)) => DbType::Uuid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Validates the operation based on db_metadata
|
/// Validates the operation based on db_metadata
|
||||||
pub fn validate_operation(operation: &Operation, db_metadata: &Vec<(String, &TableSchema)>) -> Result<(), ValidationError> {
|
pub fn validate_operation(operation: &Operation, db_schema: &DbSchema) -> Result<(), ValidationError> {
|
||||||
match operation {
|
match operation {
|
||||||
Operation::Select(table_name, column_selection, condition) => {
|
Operation::Select(table_name, column_selection, condition) => {
|
||||||
validate_select(table_name, column_selection, condition, db_metadata)?;
|
validate_select(table_name, column_selection, condition, db_schema)?;
|
||||||
},
|
},
|
||||||
Operation::Insert(table_name, insertion_values) => {
|
Operation::Insert(table_name, insertion_values) => {
|
||||||
validate_insert(&table_name, insertion_values, db_metadata)?;
|
validate_insert(&table_name, insertion_values, db_schema)?;
|
||||||
},
|
},
|
||||||
Operation::Delete(table_name, condition) => {
|
Operation::Delete(table_name, condition) => {
|
||||||
validate_delete(table_name, condition, db_metadata)?;
|
validate_delete(table_name, condition, db_schema)?;
|
||||||
},
|
},
|
||||||
// Operation::Update(table_name, insertion_values, condition) => {
|
// Operation::Update(table_name, insertion_values, condition) => {
|
||||||
// validate_update(table_name, insertion_values, db_metadata)?;
|
// validate_update(table_name, insertion_values, db_metadata)?;
|
||||||
// },
|
// },
|
||||||
Operation::CreateTable(table_name, schema) => {
|
Operation::CreateTable(table_name, schema) => {
|
||||||
validate_create(table_name, schema, db_metadata)?;
|
validate_create(table_name, schema, db_schema)?;
|
||||||
},
|
},
|
||||||
Operation::CreateIndex(table_name, column_name) => {
|
Operation::CreateIndex(table_name, column_name) => {
|
||||||
validate_create_index(table_name, column_name, db_metadata)?;
|
validate_create_index(table_name, column_name, db_schema)?;
|
||||||
},
|
},
|
||||||
// Operation::DropTable(table_name) => {
|
// Operation::DropTable(table_name) => {
|
||||||
// validate_drop(table_name, db_metadata)?;
|
// validate_drop(table_name, db_schema)?;
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn validate_table_exists<'a>(db_schema: &DbSchema<'a>, table_name: &'a TableName) -> Result<&'a TableSchema, ValidationError> {
|
||||||
|
db_schema.iter().find(|(tname, _)| table_name.eq(tname))
|
||||||
|
.ok_or(ValidationError::TableDoesNotExist(table_name.to_string()))
|
||||||
|
.map(|(_, table_schema)| table_schema).copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// pub fn validate_drop(table_name: &str, db_metadata: &Vec<(String, TableSchema)>) -> Result<(), ValidationError> {
|
// pub fn validate_drop(table_name: &str, db_metadata: &Vec<(String, TableSchema)>) -> Result<(), ValidationError> {
|
||||||
// db_metadata.iter().find(|(tname, _)| table_name.eq(tname))
|
// db_metadata.iter().find(|(tname, _)| table_name.eq(tname))
|
||||||
// .ok_or(ValidationError::TableDoesNotExist(table_name.to_string()))?;
|
// .ok_or(ValidationError::TableDoesNotExist(table_name.to_string()))?;
|
||||||
// Ok(())
|
// Ok(())
|
||||||
// }
|
// }
|
||||||
|
|
||||||
pub fn validate_create(table_name: &str, schema: &TableSchema, db_metadata: &Vec<(String, &TableSchema)>) -> Result<(), ValidationError> {
|
pub fn validate_create(table_name: &TableName, schema: &TableSchema, db_schema: &DbSchema) -> Result<(), ValidationError> {
|
||||||
if db_metadata.iter().find(|(tname, _)| table_name.eq(tname)).is_some() {
|
if let Some(_) = get_table_schema(db_schema, table_name) {
|
||||||
return Err(ValidationError::TableExists(table_name.to_string()));
|
return Err(ValidationError::TableAlreadyExists(table_name.to_string()));
|
||||||
}
|
|
||||||
let mut column_names = HashSet::new();
|
|
||||||
for (name, _) in &schema.column_name_position_mapping {
|
|
||||||
if column_names.contains(name) {
|
|
||||||
return Err(ValidationError::DuplicateColumn(name.clone()));
|
|
||||||
} else {
|
|
||||||
column_names.insert(name.clone());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
find_first_duplicate(&schema.get_columns())
|
||||||
|
.map_or_else(
|
||||||
|
|| Ok(()),
|
||||||
|
|duplicate_column| Err(ValidationError::DuplicateColumn(duplicate_column.to_string()))
|
||||||
|
)?;
|
||||||
|
|
||||||
// TODO: Ensure it has a primary key??
|
// TODO: Ensure it has a primary key??
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn validate_select(table_name: &str, column_selection: &ColumnSelection, condition: &Option<Condition>, db_metadata: &Vec<(String, &TableSchema)>) -> Result<(), ValidationError> {
|
pub fn validate_select(table_name: &TableName, column_selection: &ColumnSelection, condition: &Option<Condition>, db_schema: &Vec<(TableName, &TableSchema)>) -> Result<(), ValidationError> {
|
||||||
let (_, schema) = db_metadata.iter().find(|(tname, _)| table_name.eq(tname))
|
let schema = validate_table_exists(db_schema, table_name)?;
|
||||||
.ok_or(ValidationError::TableDoesNotExist(table_name.to_string()))?;
|
|
||||||
match column_selection {
|
match column_selection {
|
||||||
ColumnSelection::Columns(columns) => {
|
ColumnSelection::Columns(columns) => {
|
||||||
columns.iter().find(|c| {
|
let non_existant_columns: Vec<String> =
|
||||||
!schema.column_name_position_mapping.contains_left(*c)
|
columns.iter().filter_map(|column|
|
||||||
}).map_or_else(||Ok(()), |c| Err(ValidationError::ColumnDoesNotExist(c.to_string())))?;
|
if schema.does_column_exist(&column) {
|
||||||
|
Some(column.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}).collect();
|
||||||
|
if non_existant_columns.len() > 0 {
|
||||||
|
Err(ValidationError::ColumnsDoNotExist(non_existant_columns))
|
||||||
|
} else {
|
||||||
|
validate_condition(condition, schema)
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
validate_condition(condition, schema)?;
|
ColumnSelection::All => Ok(())
|
||||||
Ok(())
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub fn validate_update(table_name: &str, insertion_values: &InsertionValues, db_metadata: &Vec<(String, TableSchema)>) -> Result<(), ValidationError> {
|
// pub fn validate_update(table_name: &str, insertion_values: &InsertionValues, db_metadata: &Vec<(String, TableSchema)>) -> Result<(), ValidationError> {
|
||||||
// let (_, schema) = db_metadata.iter().find(|(tname, _)| table_name.eq(tname))
|
// let schema = validate_table_exists(db_schema, table_name)?;
|
||||||
// .ok_or(ValidationError::TableDoesNotExist(table_name.to_string()))?;
|
|
||||||
// let mut column_names = HashSet::new();
|
// let mut column_names = HashSet::new();
|
||||||
// // Find duplicate columns
|
// // Find duplicate columns
|
||||||
// for (name, _) in insertion_values {
|
// for (name, _) in insertion_values {
|
||||||
|
|
@ -111,7 +124,7 @@ pub fn validate_select(table_name: &str, column_selection: &ColumnSelection, con
|
||||||
// if let Some((name, _, _)) = column_value_type.iter().find(|(_, _, t)| {
|
// if let Some((name, _, _)) = column_value_type.iter().find(|(_, _, t)| {
|
||||||
// t.is_none()
|
// t.is_none()
|
||||||
// }) {
|
// }) {
|
||||||
// return Err(ValidationError::ColumnDoesNotExist((*name).clone()));
|
// return Err(ValidationError::ColumnsDoNotExist(vec![(*name).clone())]);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// // Check types
|
// // Check types
|
||||||
|
|
@ -128,63 +141,56 @@ pub fn validate_select(table_name: &str, column_selection: &ColumnSelection, con
|
||||||
// Ok(())
|
// Ok(())
|
||||||
// }
|
// }
|
||||||
|
|
||||||
pub fn validate_insert(table_name: &str, insertion_values: &InsertionValues, db_metadata: &Vec<(String, &TableSchema)>) -> Result<(), ValidationError> {
|
pub fn validate_insert(table_name: &TableName, insertion_values: &InsertionValues, db_schema: &DbSchema) -> Result<(), ValidationError> {
|
||||||
let (_, schema) = db_metadata.iter().find(|(tname, _)| table_name.eq(tname))
|
let schema = validate_table_exists(db_schema, table_name)?;
|
||||||
.ok_or(ValidationError::TableDoesNotExist(table_name.to_string()))?;
|
|
||||||
let inserted_columns: HashSet<String> = HashSet::from_iter(insertion_values.iter().map(|(name, _)| name.clone()));
|
// Check for duplicate columns in insertion_values.
|
||||||
// TODO: primary key is not required
|
let columns_in_query_vec: Vec<&ColumnName> = insertion_values.iter().map(|(column_name, _)| column_name).collect();
|
||||||
for (column_name, _) in &schema.column_name_position_mapping {
|
find_first_duplicate(&columns_in_query_vec)
|
||||||
if !inserted_columns.contains(column_name) {
|
.map_or_else(
|
||||||
return Err(ValidationError::ValueForRequiredColumnIsMissing(column_name.clone()))
|
|| 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.len() > 0 {
|
||||||
|
return Err(ValidationError::ColumnsDoNotExist(non_existant_columns.iter().map(|str| str.to_string()).collect()));
|
||||||
}
|
}
|
||||||
}
|
let missing_required_columns = Vec::from_iter(columns_in_schema.difference(&columns_in_query));
|
||||||
// Ensure columns exist in schema
|
if missing_required_columns.len() > 0 {
|
||||||
let column_value_type: Vec<_> = insertion_values.iter().map(|(column, value)| {
|
return Err(ValidationError::RequiredColumnsAreMissing(missing_required_columns.iter().map(|str| str.to_string()).collect()));
|
||||||
(column, value, schema.column_name_position_mapping.iter().find(|(name, _) | {
|
|
||||||
(*name).eq(column)
|
|
||||||
}).map(|(_, t)| schema.types.get(*t as usize)))
|
|
||||||
}).collect();
|
|
||||||
if let Some((name, _, _)) = column_value_type.iter().find(|(_, _, t)| {
|
|
||||||
match t {
|
|
||||||
Some(Some(_)) => false,
|
|
||||||
_ => true
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
return Err(ValidationError::ColumnDoesNotExist((*name).clone()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check types
|
// Check types
|
||||||
if let Some((_, _, _)) = column_value_type.iter().find(|(_, value, t)| {
|
for (column_name, value) in insertion_values {
|
||||||
if let Some(Some(t)) = t {
|
let expected_type = schema.get_type_at(column_name).ok_or(ValidationError::ColumnsDoNotExist(vec![column_name.to_string()]))?; // By the previous validation steps this is never gonna trigger an error.
|
||||||
!type_of(value).eq(t)
|
let value_type = value.to_type();
|
||||||
} else {
|
if value_type != expected_type {
|
||||||
false
|
return Err(ValidationError::TypeMismatch { column_name: column_name.to_string(), received_type: value_type, expected_type });
|
||||||
}
|
}
|
||||||
}) {
|
|
||||||
// TODO: Add column name information
|
|
||||||
return Err(ValidationError::TypeMismatch);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn validate_delete(table_name: &str, condition: &Option<Condition>, db_metadata: &Vec<(String, &TableSchema)>) -> Result<(), ValidationError> {
|
pub fn validate_delete(table_name: &TableName, condition: &Option<Condition>, db_schema: &DbSchema) -> Result<(), ValidationError> {
|
||||||
let (_, schema) = db_metadata.iter().find(|(tname, _)| table_name.eq(tname))
|
let schema = validate_table_exists(db_schema, table_name)?;
|
||||||
.ok_or(ValidationError::TableDoesNotExist(table_name.to_string()))?;
|
|
||||||
validate_condition(condition, schema)?;
|
validate_condition(condition, schema)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_condition(condition: &Option<Condition>, schema: &TableSchema) -> Result<(), ValidationError> {
|
fn validate_condition(condition: &Option<Condition>, schema: &TableSchema) -> Result<(), ValidationError> {
|
||||||
match condition {
|
match condition {
|
||||||
Some(c) => {
|
Some(condition) => {
|
||||||
match c {
|
match condition {
|
||||||
Condition::Eq(left, right) => {
|
Condition::Eq(column_name, value) => {
|
||||||
let position = schema.column_name_position_mapping.get_by_left(left)
|
let expected_type: DbType = schema.get_type_at(column_name).ok_or(ValidationError::ColumnsDoNotExist(vec![column_name.to_string()]))?;
|
||||||
.ok_or(ValidationError::ColumnDoesNotExist(left.clone()))?;
|
let value_type: DbType = value.to_type();
|
||||||
let column_type = schema.types.get(*position as usize)
|
if !expected_type.eq(&value_type) {
|
||||||
.ok_or(ValidationError::BadColumnPosition(*position))?;
|
return Err(ValidationError::TypeMismatch { column_name: column_name.to_string(), received_type: value_type, expected_type });
|
||||||
if !column_type.eq(&type_of(right)) {
|
|
||||||
return Err(ValidationError::TypeMismatch);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -194,13 +200,31 @@ fn validate_condition(condition: &Option<Condition>, schema: &TableSchema) -> Re
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_create_index(table_name: &str, column_name: &str, db_metadata: &Vec<(String, &TableSchema)>) -> Result<(), ValidationError> {
|
fn validate_create_index(table_name: &TableName, column_name: &ColumnName, db_schema: &DbSchema) -> Result<(), ValidationError> {
|
||||||
// Ensure table exists
|
let schema = validate_table_exists(db_schema, table_name)?;
|
||||||
let (_, schema) = db_metadata.iter().find(|(tname, _)| table_name.eq(tname))
|
if schema.does_column_exist(column_name) {
|
||||||
.ok_or(ValidationError::TableDoesNotExist(table_name.to_string()))?;
|
|
||||||
// Ensure column exists
|
|
||||||
if !schema.column_name_position_mapping.contains_left(column_name) {
|
|
||||||
return Err(ValidationError::ColumnDoesNotExist(column_name.to_string()));
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(ValidationError::ColumnsDoNotExist(vec![column_name.to_string()]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===Helpers===
|
||||||
|
fn find_first_duplicate<A>(xs: &[A]) -> Option<&A>
|
||||||
|
where A: Eq + std::hash::Hash
|
||||||
|
{
|
||||||
|
let mut already_seen_elements: HashSet<&A> = HashSet::new();
|
||||||
|
for x in xs {
|
||||||
|
if already_seen_elements.contains(x) {
|
||||||
|
return Some(x);
|
||||||
|
} else {
|
||||||
|
already_seen_elements.insert(&x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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))?;
|
||||||
|
Some(table_schema)
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue