Cleanup validation.rs

This commit is contained in:
Yuriy Dupyn 2024-01-27 17:14:11 +01:00
parent 6000b1f242
commit 9999d67b8f
2 changed files with 134 additions and 95 deletions

View file

@ -36,6 +36,24 @@ impl TableSchema {
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)> {
match self.column_name_position_mapping.get_by_left(column_name) {
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(
&self,
column_name: &ColumnName,

View file

@ -1,98 +1,107 @@
use std::collections::HashSet;
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)]
pub enum ValidationError {
TableDoesNotExist(String),
TableExists(String),
ColumnDoesNotExist(String),
BadColumnPosition(usize),
TableDoesNotExist(TableName),
TableAlreadyExists(TableName),
ColumnsDoNotExist(Vec<ColumnName>),
DuplicateColumn(String),
TypeMismatch,
ValueForRequiredColumnIsMissing(String)
TypeMismatch(TypeMismatch),
RequiredColumnsAreMissing(Vec<ColumnName>)
}
pub fn type_of(value: &Value) -> DbType {
match value {
Value::Indexable(IndexableValue::Int(_)) => DbType::Int,
Value::Indexable(IndexableValue::String(_)) => DbType::String,
Value::Number(_) => DbType::Number,
Value::Indexable(IndexableValue::Uuid(_)) => DbType::Uuid
}
#[derive(Debug)]
pub struct TypeMismatch {
pub column_name: ColumnName,
pub received_type: DbType,
pub expected_type: DbType,
}
pub type DbSchema<'a> = Vec<(TableName, &'a TableSchema)>;
/// 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 {
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) => {
validate_insert(&table_name, insertion_values, db_metadata)?;
validate_insert(&table_name, insertion_values, db_schema)?;
},
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) => {
// validate_update(table_name, insertion_values, db_metadata)?;
// },
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) => {
validate_create_index(table_name, column_name, db_metadata)?;
validate_create_index(table_name, column_name, db_schema)?;
},
// Operation::DropTable(table_name) => {
// validate_drop(table_name, db_metadata)?;
// validate_drop(table_name, db_schema)?;
// }
}
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> {
// db_metadata.iter().find(|(tname, _)| table_name.eq(tname))
// .ok_or(ValidationError::TableDoesNotExist(table_name.to_string()))?;
// Ok(())
// }
pub fn validate_create(table_name: &str, schema: &TableSchema, db_metadata: &Vec<(String, &TableSchema)>) -> Result<(), ValidationError> {
if db_metadata.iter().find(|(tname, _)| table_name.eq(tname)).is_some() {
return Err(ValidationError::TableExists(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());
}
pub fn validate_create(table_name: &TableName, schema: &TableSchema, db_schema: &DbSchema) -> Result<(), ValidationError> {
if let Some(_) = get_table_schema(db_schema, table_name) {
return Err(ValidationError::TableAlreadyExists(table_name.to_string()));
}
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??
Ok(())
}
pub fn validate_select(table_name: &str, column_selection: &ColumnSelection, condition: &Option<Condition>, db_metadata: &Vec<(String, &TableSchema)>) -> Result<(), ValidationError> {
let (_, schema) = db_metadata.iter().find(|(tname, _)| table_name.eq(tname))
.ok_or(ValidationError::TableDoesNotExist(table_name.to_string()))?;
pub fn validate_select(table_name: &TableName, column_selection: &ColumnSelection, condition: &Option<Condition>, db_schema: &Vec<(TableName, &TableSchema)>) -> Result<(), ValidationError> {
let schema = validate_table_exists(db_schema, table_name)?;
match column_selection {
ColumnSelection::Columns(columns) => {
columns.iter().find(|c| {
!schema.column_name_position_mapping.contains_left(*c)
}).map_or_else(||Ok(()), |c| Err(ValidationError::ColumnDoesNotExist(c.to_string())))?;
let non_existant_columns: Vec<String> =
columns.iter().filter_map(|column|
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)
}
}
_ => {}
ColumnSelection::All => Ok(())
}
validate_condition(condition, schema)?;
Ok(())
}
// 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))
// .ok_or(ValidationError::TableDoesNotExist(table_name.to_string()))?;
// let schema = validate_table_exists(db_schema, table_name)?;
// let mut column_names = HashSet::new();
// // Find duplicate columns
// for (name, _) in insertion_values {
@ -111,7 +120,7 @@ pub fn validate_select(table_name: &str, column_selection: &ColumnSelection, con
// if let Some((name, _, _)) = column_value_type.iter().find(|(_, _, t)| {
// t.is_none()
// }) {
// return Err(ValidationError::ColumnDoesNotExist((*name).clone()));
// return Err(ValidationError::ColumnsDoNotExist(vec![(*name).clone())]);
// }
// // Check types
@ -128,63 +137,56 @@ pub fn validate_select(table_name: &str, column_selection: &ColumnSelection, con
// Ok(())
// }
pub fn validate_insert(table_name: &str, insertion_values: &InsertionValues, db_metadata: &Vec<(String, &TableSchema)>) -> Result<(), ValidationError> {
let (_, schema) = db_metadata.iter().find(|(tname, _)| table_name.eq(tname))
.ok_or(ValidationError::TableDoesNotExist(table_name.to_string()))?;
let inserted_columns: HashSet<String> = HashSet::from_iter(insertion_values.iter().map(|(name, _)| name.clone()));
// TODO: primary key is not required
for (column_name, _) in &schema.column_name_position_mapping {
if !inserted_columns.contains(column_name) {
return Err(ValidationError::ValueForRequiredColumnIsMissing(column_name.clone()))
}
pub fn validate_insert(table_name: &TableName, insertion_values: &InsertionValues, db_schema: &DbSchema) -> Result<(), ValidationError> {
let 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()))
)?;
// 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()));
}
// Ensure columns exist in schema
let column_value_type: Vec<_> = insertion_values.iter().map(|(column, value)| {
(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()));
let missing_required_columns = Vec::from_iter(columns_in_schema.difference(&columns_in_query));
if missing_required_columns.len() > 0 {
return Err(ValidationError::RequiredColumnsAreMissing(missing_required_columns.iter().map(|str| str.to_string()).collect()));
}
// Check types
if let Some((_, _, _)) = column_value_type.iter().find(|(_, value, t)| {
if let Some(Some(t)) = t {
!type_of(value).eq(t)
} else {
false
for (column_name, value) in insertion_values {
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.
let value_type = value.to_type();
if value_type != expected_type {
return Err(ValidationError::TypeMismatch(TypeMismatch { column_name: column_name.to_string(), received_type: value_type, expected_type }));
}
}) {
// TODO: Add column name information
return Err(ValidationError::TypeMismatch);
}
Ok(())
}
pub fn validate_delete(table_name: &str, condition: &Option<Condition>, db_metadata: &Vec<(String, &TableSchema)>) -> Result<(), ValidationError> {
let (_, schema) = db_metadata.iter().find(|(tname, _)| table_name.eq(tname))
.ok_or(ValidationError::TableDoesNotExist(table_name.to_string()))?;
pub fn validate_delete(table_name: &TableName, condition: &Option<Condition>, db_schema: &DbSchema) -> Result<(), ValidationError> {
let schema = validate_table_exists(db_schema, table_name)?;
validate_condition(condition, schema)?;
Ok(())
}
fn validate_condition(condition: &Option<Condition>, schema: &TableSchema) -> Result<(), ValidationError> {
match condition {
Some(c) => {
match c {
Condition::Eq(left, right) => {
let position = schema.column_name_position_mapping.get_by_left(left)
.ok_or(ValidationError::ColumnDoesNotExist(left.clone()))?;
let column_type = schema.types.get(*position as usize)
.ok_or(ValidationError::BadColumnPosition(*position))?;
if !column_type.eq(&type_of(right)) {
return Err(ValidationError::TypeMismatch);
Some(condition) => {
match condition {
Condition::Eq(column_name, value) => {
let expected_type: DbType = schema.get_type_at(column_name).ok_or(ValidationError::ColumnsDoNotExist(vec![column_name.to_string()]))?;
let value_type: DbType = value.to_type();
if !expected_type.eq(&value_type) {
return Err(ValidationError::TypeMismatch(TypeMismatch { column_name: column_name.to_string(), received_type: value_type, expected_type }));
}
}
}
@ -194,13 +196,31 @@ fn validate_condition(condition: &Option<Condition>, schema: &TableSchema) -> Re
Ok(())
}
fn validate_create_index(table_name: &str, column_name: &str, db_metadata: &Vec<(String, &TableSchema)>) -> Result<(), ValidationError> {
// Ensure table exists
let (_, schema) = db_metadata.iter().find(|(tname, _)| table_name.eq(tname))
.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()));
fn validate_create_index(table_name: &TableName, column_name: &ColumnName, db_schema: &DbSchema) -> Result<(), ValidationError> {
let schema = validate_table_exists(db_schema, table_name)?;
if schema.does_column_exist(column_name) {
Ok(())
} else {
Err(ValidationError::ColumnsDoNotExist(vec![column_name.to_string()]))
}
Ok(())
}
}
// ===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)
}