Add parsing (incl. validation)
Ensure identifiers start with alphabetical character Rename parse_variable_name -> parse_column_name Add DB value parsers and condition parser placeholder Fix number parser, basic condition parser Move select parser to select module Add create statement parser Move condition parser to common; add delete statement parser Add drop statement parser Add insert parser Add update parser, combine operation parsers into one Add initial validation, fix compiler warnings Validation WIP Allow more spaces in create statement, update TableSchema struct Add create index parser and validator Add todo in parse_identifier Rework the new structure, many other changes
This commit is contained in:
parent
143dc0e5ce
commit
61c0a34253
20 changed files with 1138 additions and 39 deletions
206
parser/src/validation.rs
Normal file
206
parser/src/validation.rs
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use minisql::{operation::{ColumnSelection, Condition, InsertionValues, Operation}, schema::TableSchema, type_system::{DbType, IndexableValue, Value}};
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ValidationError {
|
||||
TableDoesNotExist(String),
|
||||
TableExists(String),
|
||||
ColumnDoesNotExist(String),
|
||||
BadColumnPosition(usize),
|
||||
DuplicateColumn(String),
|
||||
TypeMismatch,
|
||||
ValueForRequiredColumnIsMissing(String)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
/// Validates the operation based on db_metadata
|
||||
pub fn validate_operation(operation: &Operation, db_metadata: &Vec<(String, &TableSchema)>) -> Result<(), ValidationError> {
|
||||
match operation {
|
||||
Operation::Select(table_name, column_selection, condition) => {
|
||||
validate_select(table_name, column_selection, condition, db_metadata)?;
|
||||
},
|
||||
Operation::Insert(table_name, insertion_values) => {
|
||||
validate_insert(&table_name, insertion_values, db_metadata)?;
|
||||
},
|
||||
Operation::Delete(table_name, condition) => {
|
||||
validate_delete(table_name, condition, db_metadata)?;
|
||||
},
|
||||
// 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)?;
|
||||
},
|
||||
Operation::CreateIndex(table_name, column_name) => {
|
||||
validate_create_index(table_name, column_name, db_metadata)?;
|
||||
},
|
||||
// Operation::DropTable(table_name) => {
|
||||
// validate_drop(table_name, db_metadata)?;
|
||||
// }
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
|
||||
// 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()))?;
|
||||
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())))?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
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 mut column_names = HashSet::new();
|
||||
// // Find duplicate columns
|
||||
// for (name, _) in insertion_values {
|
||||
// if column_names.contains(name) {
|
||||
// return Err(ValidationError::DuplicateColumn(name.clone()));
|
||||
// } else {
|
||||
// column_names.insert(name.clone());
|
||||
// }
|
||||
// }
|
||||
// // 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)| {
|
||||
// t.is_none()
|
||||
// }) {
|
||||
// return Err(ValidationError::ColumnDoesNotExist((*name).clone()));
|
||||
// }
|
||||
|
||||
// // Check types
|
||||
// if let Some((_, _, _)) = column_value_type.iter().find(|(_, value, t)| {
|
||||
// if let Some(Some(column_type)) = t {
|
||||
// !type_of(value).eq(column_type)
|
||||
// } else {
|
||||
// false
|
||||
// }
|
||||
// }) {
|
||||
// // TODO: Add column name information
|
||||
// return Err(ValidationError::TypeMismatch);
|
||||
// }
|
||||
// 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()))
|
||||
}
|
||||
}
|
||||
// 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()));
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}) {
|
||||
// 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()))?;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
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()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue