diff --git a/minisql/src/operation.rs b/minisql/src/operation.rs index 7c45fe3..332aff0 100644 --- a/minisql/src/operation.rs +++ b/minisql/src/operation.rs @@ -18,25 +18,13 @@ pub enum Operation { // DropTable(TableName), } -pub enum OperationForInterpreter { - Select(TablePosition, ColumnSelectionForInterpreter, Option), - Insert(TablePosition, InsertionValuesForInterpreter), - Delete(TablePosition, Option), - CreateTable(TableName, TableSchema), - CreateIndex(TablePosition, ColumnPosition), -} - pub type InsertionValues = Vec<(ColumnName, Value)>; -pub type InsertionValuesForInterpreter = Vec; - pub enum ColumnSelection { All, Columns(Vec), } -pub type ColumnSelectionForInterpreter = Vec; - pub enum Condition { // And(Box, Box), // Or(Box, Box), @@ -48,11 +36,23 @@ pub enum Condition { // StringCondition(StringCondition), } -pub enum ConditionForInterpreter { - Eq(ColumnPosition, Value), -} - // enum StringCondition { // Prefix(ColumnName, String), // Substring(ColumnName, String), // } + +pub enum OperationForInterpreter { + Select(TablePosition, ColumnSelectionForInterpreter, Option), + Insert(TablePosition, InsertionValuesForInterpreter), + Delete(TablePosition, Option), + CreateTable(TableName, TableSchema), + CreateIndex(TablePosition, ColumnPosition), +} + +pub type InsertionValuesForInterpreter = Vec; + +pub type ColumnSelectionForInterpreter = Vec; + +pub enum ConditionForInterpreter { + Eq(ColumnPosition, Value), +} diff --git a/minisql/src/schema.rs b/minisql/src/schema.rs index 90022d2..0d44b76 100644 --- a/minisql/src/schema.rs +++ b/minisql/src/schema.rs @@ -48,6 +48,16 @@ impl TableSchema { self.column_name_position_mapping.get_by_left(column_name).copied() } + pub fn all_selection(&self) -> ColumnSelectionForInterpreter { + self.column_name_position_mapping.iter().map(|(_, column)| *column).collect() + } + + // TODO: Rename to get_column + pub fn get_column0(&self, column_name: &ColumnName) -> Option<(ColumnPosition, DbType)> { + let column = self.get_column_position(column_name)?; + Some((column, self.column_type(column))) + } + pub fn get_type_at(&self, column_name: &ColumnName) -> Option { let position = self.get_column_position(column_name)?; self.types.get(position).copied() diff --git a/parser/src/core.rs b/parser/src/core.rs index 248c5c6..258c928 100644 --- a/parser/src/core.rs +++ b/parser/src/core.rs @@ -1,4 +1,5 @@ use minisql::{operation::Operation, schema::TableSchema}; +use crate::syntax::RawQuerySyntax; use nom::{branch::alt, multi::many0, IResult}; use thiserror::Error; @@ -34,7 +35,9 @@ pub fn parse_and_validate(query: String, db_metadata: &Vec<(String, &TableSchema Error::ParsingError(err.to_string()) })?; - validate_operation(&op, db_metadata)?; + // TODO + // validate_operation(&op, db_metadata)?; + todo!(); Ok(op) } diff --git a/parser/src/lib.rs b/parser/src/lib.rs index 94b121b..18eab0c 100644 --- a/parser/src/lib.rs +++ b/parser/src/lib.rs @@ -2,6 +2,7 @@ mod parsing; mod validation; mod core; +mod syntax; pub use core::parse_and_validate; pub use core::Error; diff --git a/parser/src/syntax.rs b/parser/src/syntax.rs new file mode 100644 index 0000000..039a06d --- /dev/null +++ b/parser/src/syntax.rs @@ -0,0 +1,36 @@ +use minisql::{type_system::Value, schema::{TableSchema, ColumnName, TableName}}; + +// TODO: Move this out into separate file and rename to something like Syntax, SyntaxTree, +// OperationSyntax, RawOperationSyntax +pub enum RawQuerySyntax { + Select(TableName, ColumnSelection, Option), + Insert(TableName, InsertionValues), + Delete(TableName, Option), + // Update(...), + CreateTable(TableName, TableSchema), + CreateIndex(TableName, ColumnName), + // DropTable(TableName), +} + +pub type InsertionValues = Vec<(ColumnName, Value)>; + +pub enum ColumnSelection { + All, + Columns(Vec), +} + +pub enum Condition { + // And(Box, Box), + // Or(Box, Box), + // Not(Box), + Eq(ColumnName, Value), + // LessOrEqual(ColumnName, DbValue), + // Less(ColumnName, DbValue), + + // StringCondition(StringCondition), +} + +// enum StringCondition { +// Prefix(ColumnName, String), +// Substring(ColumnName, String), +// } diff --git a/parser/src/validation.rs b/parser/src/validation.rs index 2c20c24..017491b 100644 --- a/parser/src/validation.rs +++ b/parser/src/validation.rs @@ -1,9 +1,9 @@ - use std::collections::HashSet; +use std::collections::HashMap; use thiserror::Error; -use minisql::{operation::{ColumnSelection, Condition, InsertionValues, Operation}, schema::{TableSchema, ColumnName, TableName}, type_system::DbType}; - +use crate::syntax::{ColumnSelection, Condition, InsertionValues, RawQuerySyntax}; +use minisql::{operation::{ColumnSelectionForInterpreter, ConditionForInterpreter, InsertionValuesForInterpreter, OperationForInterpreter}, type_system::Value, schema::{TableSchema, ColumnName, TableName}, type_system::DbType, interpreter::TablePosition}; #[derive(Debug, Error)] pub enum ValidationError { @@ -25,69 +25,55 @@ pub enum ValidationError { RequiredColumnsAreMissing(Vec) } -pub type DbSchema<'a> = Vec<(TableName, &'a TableSchema)>; +pub type DbSchema<'a> = Vec<(TableName, TablePosition, &'a TableSchema)>; -/// Validates the operation based on db_metadata -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_schema)?; +/// Validates and converts the raw syntax into a proper interpreter operation based on db schema. +pub fn validate_operation(query: RawQuerySyntax, db_schema: &DbSchema) -> Result { + match query { + RawQuerySyntax::Select(table_name, column_selection, condition) => { + validate_select(table_name, column_selection, condition, db_schema) }, - Operation::Insert(table_name, insertion_values) => { - validate_insert(&table_name, insertion_values, db_schema)?; + RawQuerySyntax::Insert(table_name, insertion_values) => { + validate_insert(table_name, insertion_values, db_schema) }, - Operation::Delete(table_name, condition) => { - validate_delete(table_name, condition, db_schema)?; + RawQuerySyntax::Delete(table_name, condition) => { + 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_schema)?; + RawQuerySyntax::CreateTable(table_name, schema) => { + validate_create(table_name, schema, db_schema) }, - Operation::CreateIndex(table_name, column_name) => { - validate_create_index(table_name, column_name, db_schema)?; + RawQuerySyntax::CreateIndex(table_name, column_name) => { + validate_create_index(table_name, column_name, db_schema) }, - // Operation::DropTable(table_name) => { - // 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)) +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_schema)| table_schema).copied() + .map(|(_, table_position, table_schema)| (*table_position, *table_schema)) } - -// 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: &TableName, schema: &TableSchema, db_schema: &DbSchema) -> Result<(), ValidationError> { - if let Some(_) = get_table_schema(db_schema, table_name) { +pub fn validate_create(table_name: TableName, table_schema: TableSchema, db_schema: &DbSchema) -> Result { + if let Some(_) = get_table_schema(db_schema, &table_name) { return Err(ValidationError::TableAlreadyExists(table_name.to_string())); } - find_first_duplicate(&schema.get_columns()) + find_first_duplicate(&table_schema.get_columns()) .map_or_else( || Ok(()), |duplicate_column| Err(ValidationError::DuplicateColumn(duplicate_column.to_string())) )?; // TODO: Ensure it has a primary key?? - Ok(()) + Ok(OperationForInterpreter::CreateTable(table_name, table_schema)) } -pub fn validate_select(table_name: &TableName, column_selection: &ColumnSelection, condition: &Option, db_schema: &Vec<(TableName, &TableSchema)>) -> Result<(), ValidationError> { - let schema = validate_table_exists(db_schema, table_name)?; +pub fn validate_select(table_name: TableName, column_selection: ColumnSelection, condition: Option, db_schema: &DbSchema) -> Result { + let (table_position, schema) = validate_table_exists(db_schema, &table_name)?; match column_selection { ColumnSelection::Columns(columns) => { - let non_existant_columns: Vec = + let non_existant_columns: Vec = columns.iter().filter_map(|column| if schema.does_column_exist(&column) { Some(column.clone()) @@ -97,52 +83,21 @@ pub fn validate_select(table_name: &TableName, column_selection: &ColumnSelectio if non_existant_columns.len() > 0 { Err(ValidationError::ColumnsDoNotExist(non_existant_columns)) } else { - validate_condition(condition, schema) + let selection: ColumnSelectionForInterpreter = + columns.iter().filter_map(|column_name| schema.get_column_position(column_name)).collect(); + let validated_condition = validate_condition(condition, schema)?; + Ok(OperationForInterpreter::Select(table_position, selection, validated_condition)) } } - ColumnSelection::All => Ok(()) + ColumnSelection::All => { + let validated_condition = validate_condition(condition, schema)?; + Ok(OperationForInterpreter::Select(table_position, schema.all_selection(), validated_condition)) + } } } -// pub fn validate_update(table_name: &str, insertion_values: &InsertionValues, db_metadata: &Vec<(String, TableSchema)>) -> Result<(), ValidationError> { -// let schema = validate_table_exists(db_schema, table_name)?; -// 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::ColumnsDoNotExist(vec![(*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: &TableName, insertion_values: &InsertionValues, db_schema: &DbSchema) -> Result<(), ValidationError> { - let schema = validate_table_exists(db_schema, table_name)?; +pub fn validate_insert(table_name: TableName, insertion_values: InsertionValues, db_schema: &DbSchema) -> Result { + 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(); @@ -157,56 +112,64 @@ pub fn validate_insert(table_name: &TableName, insertion_values: &InsertionValue 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())); + 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.len() > 0 { return Err(ValidationError::RequiredColumnsAreMissing(missing_required_columns.iter().map(|str| str.to_string()).collect())); } - // Check types + // Check types and prepare for creation of InsertionValues for the interpreter + let mut values_map: HashMap<_, Value> = HashMap::new(); 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 (column, expected_type) = schema.get_column0(&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 }); } + values_map.insert(column, value); } - Ok(()) + // These are values ordered by the column position + let values: InsertionValuesForInterpreter = values_map.into_values().collect(); + + Ok(OperationForInterpreter::Insert(table_position, values)) } -pub fn validate_delete(table_name: &TableName, condition: &Option, db_schema: &DbSchema) -> Result<(), ValidationError> { - let schema = validate_table_exists(db_schema, table_name)?; - validate_condition(condition, schema)?; - Ok(()) +pub fn validate_delete(table_name: TableName, condition: Option, db_schema: &DbSchema) -> Result { + let (table_position, schema) = validate_table_exists(db_schema, &table_name)?; + let validated_condition = validate_condition(condition, schema)?; + Ok(OperationForInterpreter::Delete(table_position, validated_condition)) } -fn validate_condition(condition: &Option, schema: &TableSchema) -> Result<(), ValidationError> { +fn validate_condition(condition: Option, schema: &TableSchema) -> Result, ValidationError> { match condition { 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 (column, expected_type) = schema.get_column0(&column_name).ok_or(ValidationError::ColumnsDoNotExist(vec![column_name.to_string()]))?; let value_type: DbType = value.to_type(); - if !expected_type.eq(&value_type) { + if expected_type.eq(&value_type) { + Ok(Some(ConditionForInterpreter::Eq(column, value))) + } else { return Err(ValidationError::TypeMismatch { column_name: column_name.to_string(), received_type: value_type, expected_type }); } } } } - None => {} + None => Ok(None) } - Ok(()) } -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()])) - } +fn validate_create_index(table_name: TableName, column_name: ColumnName, db_schema: &DbSchema) -> Result { + // TODO: You should disallow indexing of Number columns. + let (table_position, schema) = validate_table_exists(db_schema, &table_name)?; + schema + .get_column_position(&column_name) + .map_or_else( + || Err(ValidationError::ColumnsDoNotExist(vec![column_name.to_string()])), + |column| Ok(OperationForInterpreter::CreateIndex(table_position, column)) + ) } // ===Helpers=== @@ -225,6 +188,6 @@ where A: Eq + std::hash::Hash } 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))?; + let (_, _, table_schema) = db_schema.iter().find(|(tname, _, _)| table_name.eq(tname))?; Some(table_schema) }