This commit is contained in:
Yuriy Dupyn 2024-01-27 22:46:19 +01:00
parent 9771a89716
commit 11dc992476
10 changed files with 157 additions and 403 deletions

View file

@ -1,17 +1,11 @@
use crate::internals::row::ColumnPosition; use crate::internals::row::ColumnPosition;
use crate::schema::{ColumnName, TableName}; use crate::schema::{ColumnName, TableName};
use crate::operation::InsertionValues;
use crate::type_system::{DbType, Uuid, Value}; use crate::type_system::{DbType, Uuid, Value};
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
TableDoesNotExist(TableName),
ColumnDoesNotExist(TableName, ColumnName),
ColumnPositionDoesNotExist(TableName, ColumnPosition), ColumnPositionDoesNotExist(TableName, ColumnPosition),
ValueDoesNotMatchExpectedType(TableName, ColumnName, DbType, Value), ValueDoesNotMatchExpectedType(TableName, ColumnName, DbType, Value),
AttemptingToInsertAlreadyPresentId(TableName, Uuid), AttemptingToInsertAlreadyPresentId(TableName, Uuid),
MissingTypeAnnotationOfColumn(TableName, ColumnPosition),
MissingColumnInInsertValues(TableName, ColumnName, InsertionValues),
MismatchBetweenInsertValuesAndColumns(TableName, InsertionValues),
AttemptToIndexNonIndexableColumn(TableName, ColumnName), AttemptToIndexNonIndexableColumn(TableName, ColumnName),
} }

View file

@ -1,5 +1,5 @@
use crate::type_system::Value; use crate::type_system::Value;
use crate::operation::InsertionValuesForInterpreter; use crate::operation::InsertionValues;
use std::ops::{Index, IndexMut}; use std::ops::{Index, IndexMut};
use std::slice::SliceIndex; use std::slice::SliceIndex;
@ -43,7 +43,7 @@ impl Row {
Row(vec![]) Row(vec![])
} }
pub fn new_from_insertion_values(insertion_values: InsertionValuesForInterpreter) -> Self { pub fn new_from_insertion_values(insertion_values: InsertionValues) -> Self {
Row(insertion_values) Row(insertion_values)
} }

View file

@ -206,6 +206,7 @@ impl Table {
match value { match value {
IndexableValue::Uuid(id) => Ok(Some(HashSet::from([*id]))), IndexableValue::Uuid(id) => Ok(Some(HashSet::from([*id]))),
_ => { _ => {
// TODO: This validation step is not really necessary.
let column_name: ColumnName = self let column_name: ColumnName = self
.schema .schema
.column_name_from_column_position(column_position)?; .column_name_from_column_position(column_position)?;
@ -222,8 +223,8 @@ impl Table {
match self.indexes.get(&column_position) { match self.indexes.get(&column_position) {
Some(index) => { Some(index) => {
// Note that we are cloning the ids here! This can be very wasteful in some cases. // Note that we are cloning the ids here! This can be very wasteful in some cases.
// It would be possible to just return a reference, // Theoretically it would be possible to return a reference,
// but this seems fairly non-trivial. // but after attempting to do this it seems very non-trivial.
let ids = index.get(value).cloned(); let ids = index.get(value).cloned();
Ok(ids) Ok(ids)
} }

View file

@ -1,10 +1,8 @@
use crate::error::Error; use crate::internals::row::Row;
use crate::internals::row::{ColumnPosition, Row};
use crate::schema::{TableName, TableSchema}; use crate::schema::{TableName, TableSchema};
use crate::internals::table::Table; use crate::internals::table::Table;
use crate::operation::{ColumnSelection, Condition, Operation, OperationForInterpreter, ConditionForInterpreter}; use crate::operation::{Operation, Condition};
use crate::result::DbResult; use crate::result::DbResult;
use crate::type_system::{DbType, IndexableValue, Value};
use bimap::BiMap; use bimap::BiMap;
// Use `TablePosition` as index // Use `TablePosition` as index
@ -57,7 +55,6 @@ impl State {
} }
} }
/// TODO: return a reference to avoid allocations
pub fn db_schema<'a>(&'a self) -> DbSchema { pub fn db_schema<'a>(&'a self) -> DbSchema {
let mut schema: DbSchema = Vec::new(); let mut schema: DbSchema = Vec::new();
for (table_name, &table_position) in &self.table_name_position_mapping { for (table_name, &table_position) in &self.table_name_position_mapping {
@ -67,34 +64,10 @@ impl State {
schema schema
} }
// TODO: Get rid of this
fn table_from_name<'a>(&'a self, table_name: &TableName) -> DbResult<&'a Table> {
match self.table_name_position_mapping.get_by_left(table_name) {
Some(table_position) => {
let table = &self.tables[*table_position];
Ok(table)
}
None => Err(Error::TableDoesNotExist(table_name.clone())),
}
}
fn table_at<'a>(&'a self, table_position: TablePosition) -> &'a Table { fn table_at<'a>(&'a self, table_position: TablePosition) -> &'a Table {
&self.tables[table_position] &self.tables[table_position]
} }
fn table_from_name_mut<'b: 'a, 'a>(
&'b mut self,
table_name: &TableName,
) -> DbResult<&'a mut Table> {
match self.table_name_position_mapping.get_by_left(table_name) {
Some(table_position) => {
let table = &mut self.tables[*table_position];
Ok(table)
}
None => Err(Error::TableDoesNotExist(table_name.clone())),
}
}
fn table_at_mut<'a>(&'a mut self, table_position: TablePosition) -> &'a mut Table { fn table_at_mut<'a>(&'a mut self, table_position: TablePosition) -> &'a mut Table {
&mut self.tables[table_position] &mut self.tables[table_position]
} }
@ -106,9 +79,9 @@ impl State {
self.tables.push(table); self.tables.push(table);
} }
pub fn interpret_for_interpreter<'a>(&'a mut self, operation: OperationForInterpreter) -> DbResult<Response<'a>> { pub fn interpret<'a>(&'a mut self, operation: Operation) -> DbResult<Response<'a>> {
// TODO: lock stuff // TODO: lock stuff
use OperationForInterpreter::*; use Operation::*;
match operation { match operation {
Select(table_position, column_selection, maybe_condition) => { Select(table_position, column_selection, maybe_condition) => {
@ -120,7 +93,7 @@ impl State {
Box::new(x) as Box<dyn Iterator<Item=Row> + 'a + Send> Box::new(x) as Box<dyn Iterator<Item=Row> + 'a + Send>
}, },
Some(ConditionForInterpreter::Eq(eq_column, value)) => { Some(Condition::Eq(eq_column, value)) => {
let x = let x =
table.select_rows_where_eq( table.select_rows_where_eq(
column_selection, column_selection,
@ -136,7 +109,7 @@ impl State {
Insert(table_position, values) => { Insert(table_position, values) => {
let table: &mut Table = self.table_at_mut(table_position); let table: &mut Table = self.table_at_mut(table_position);
let (id, row) = table.schema().row_from_insertion_values_for_interpreter(values)?; let (id, row) = table.schema().row_from_insertion_values(values)?;
table.insert_row_at(id, row)?; table.insert_row_at(id, row)?;
Ok(Response::Inserted) Ok(Response::Inserted)
} }
@ -145,7 +118,7 @@ impl State {
let rows_affected = match maybe_condition { let rows_affected = match maybe_condition {
None => table.delete_all_rows(), None => table.delete_all_rows(),
Some(ConditionForInterpreter::Eq(eq_column, value)) => { Some(Condition::Eq(eq_column, value)) => {
table.delete_rows_where_eq(eq_column, value)? table.delete_rows_where_eq(eq_column, value)?
} }
}; };
@ -165,85 +138,15 @@ impl State {
} }
} }
} }
pub fn interpret<'a>(&'a mut self, operation: Operation) -> DbResult<Response<'a>> {
// TODO: lock stuff
use Operation::*;
match operation {
Select(table_name, column_selection, maybe_condition) => {
let table: &Table = self.table_from_name(&table_name)?;
let selected_column_positions: Vec<ColumnPosition> = table
.schema()
.column_positions_from_column_selection(&column_selection)?;
let selected_rows = match maybe_condition {
None => {
let x = table.select_all_rows(selected_column_positions);
Box::new(x) as Box<dyn Iterator<Item=Row> + 'a + Send>
},
Some(Condition::Eq(eq_column_name, value)) => {
let eq_column_position = table
.schema()
.column_position_from_column_name(&eq_column_name)?;
let x =
table.select_rows_where_eq(
selected_column_positions,
eq_column_position,
value,
)?;
Box::new(x) as Box<dyn Iterator<Item=Row> + 'a + Send>
}
};
Ok(Response::Selected(selected_rows))
}
Insert(table_name, values) => {
let table: &mut Table = self.table_from_name_mut(&table_name)?;
let (id, row) = table.schema().row_from_insertion_values(values)?;
table.insert_row_at(id, row)?;
Ok(Response::Inserted)
}
Delete(table_name, maybe_condition) => {
let table: &mut Table = self.table_from_name_mut(&table_name)?;
let rows_affected = match maybe_condition {
None => table.delete_all_rows(),
Some(Condition::Eq(eq_column_name, value)) => {
let eq_column_position = table
.schema()
.column_position_from_column_name(&eq_column_name)?;
table.delete_rows_where_eq(eq_column_position, value)?
}
};
Ok(Response::Deleted(rows_affected))
}
CreateTable(table_name, table_schema) => {
let table = Table::new(table_schema);
self.attach_table(table_name, table);
Ok(Response::TableCreated)
}
CreateIndex(table_name, column_name) => {
let table: &mut Table = self.table_from_name_mut(&table_name)?;
let column_position: ColumnPosition = table
.schema()
.column_position_from_column_name(&column_name)?;
table.attach_index(column_position)?;
Ok(Response::IndexCreated)
}
}
}
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::internals::row::ColumnPosition;
use std::collections::HashSet; use std::collections::HashSet;
use crate::type_system::{DbType, IndexableValue, Value};
use crate::operation::Operation;
fn users_schema() -> TableSchema { fn users_schema() -> TableSchema {
let id: ColumnPosition = 0; let id: ColumnPosition = 0;
@ -284,12 +187,13 @@ mod tests {
let mut state = State::new(); let mut state = State::new();
let users_schema = users_schema(); let users_schema = users_schema();
let users = users_schema.table_name().clone(); let users = users_schema.table_name().clone();
let users_position = 0;
state state
.interpret(Operation::CreateTable(users.clone(), users_schema)) .interpret(Operation::CreateTable(users, users_schema.clone()))
.unwrap(); .unwrap();
let response: Response = state let response: Response = state
.interpret(Operation::Select(users.clone(), ColumnSelection::All, None)) .interpret(Operation::Select(users_position, users_schema.all_selection(), None))
.unwrap(); .unwrap();
assert!(matches!(response, Response::Selected(_))); assert!(matches!(response, Response::Selected(_)));
let Response::Selected(rows) = response else { let Response::Selected(rows) = response else {
@ -299,18 +203,6 @@ mod tests {
assert!(rows.len() == 0); assert!(rows.len() == 0);
} }
#[test]
fn test_select_nonexistant_table() {
let mut state = State::new();
let response: DbResult<Response> = state.interpret(Operation::Select(
"table_that_doesnt_exist".to_string(),
ColumnSelection::All,
None,
));
assert!(matches!(response, Err(Error::TableDoesNotExist(_))));
}
#[test] #[test]
fn test_insert_select_basic1() { fn test_insert_select_basic1() {
use IndexableValue::*; use IndexableValue::*;
@ -318,10 +210,11 @@ mod tests {
let mut state = State::new(); let mut state = State::new();
let users_schema = users_schema(); let users_schema = users_schema();
let users = users_schema.table_name().clone(); let users = 0;
state state
.interpret(Operation::CreateTable(users.clone(), users_schema)) .interpret(Operation::CreateTable("users".to_string(), users_schema.clone()))
.unwrap(); .unwrap();
let (id, name, age) = ( let (id, name, age) = (
@ -331,17 +224,17 @@ mod tests {
); );
state state
.interpret(Operation::Insert( .interpret(Operation::Insert(
users.clone(), users,
vec![ vec![
("id".to_string(), id.clone()), id.clone(),
("name".to_string(), name.clone()), name.clone(),
("age".to_string(), age.clone()), age.clone(),
], ],
)) ))
.unwrap(); .unwrap();
let response: Response = state let response: Response = state
.interpret(Operation::Select(users.clone(), ColumnSelection::All, None)) .interpret(Operation::Select(users, users_schema.all_selection(), None))
.unwrap(); .unwrap();
assert!(matches!(response, Response::Selected(_))); assert!(matches!(response, Response::Selected(_)));
@ -360,7 +253,6 @@ mod tests {
#[test] #[test]
fn test_insert_select_basic2() { fn test_insert_select_basic2() {
use ColumnSelection::*;
use Condition::*; use Condition::*;
use IndexableValue::*; use IndexableValue::*;
use Operation::*; use Operation::*;
@ -368,10 +260,13 @@ mod tests {
let mut state = State::new(); let mut state = State::new();
let users_schema = users_schema(); let users_schema = users_schema();
let users = users_schema.table_name().clone(); let users_position: TablePosition = 0;
let id_column: ColumnPosition = 0;
let name_column: ColumnPosition = 1;
state state
.interpret(CreateTable(users.clone(), users_schema)) .interpret(CreateTable(users_schema.table_name().clone(), users_schema.clone()))
.unwrap(); .unwrap();
let (id0, name0, age0) = ( let (id0, name0, age0) = (
@ -381,11 +276,11 @@ mod tests {
); );
state state
.interpret(Insert( .interpret(Insert(
users.clone(), users_position,
vec![ vec![
("id".to_string(), id0.clone()), id0.clone(),
("name".to_string(), name0.clone()), name0.clone(),
("age".to_string(), age0.clone()), age0.clone(),
], ],
)) ))
.unwrap(); .unwrap();
@ -397,17 +292,17 @@ mod tests {
); );
state state
.interpret(Insert( .interpret(Insert(
users.clone(), users_position,
vec![ vec![
("id".to_string(), id1.clone()), id1.clone(),
("name".to_string(), name1.clone()), name1.clone(),
("age".to_string(), age1.clone()), age1.clone(),
], ],
)) ))
.unwrap(); .unwrap();
{ {
let response: Response = state.interpret(Select(users.clone(), All, None)).unwrap(); let response: Response = state.interpret(Select(users_position, users_schema.all_selection(), None)).unwrap();
assert!(matches!(response, Response::Selected(_))); assert!(matches!(response, Response::Selected(_)));
let Response::Selected(rows) = response else { let Response::Selected(rows) = response else {
@ -432,9 +327,9 @@ mod tests {
{ {
let response: Response = state let response: Response = state
.interpret(Select( .interpret(Select(
users.clone(), users_position,
All, users_schema.all_selection(),
Some(Eq("id".to_string(), id0.clone())), Some(Eq(id_column, id0.clone())),
)) ))
.unwrap(); .unwrap();
assert!(matches!(response, Response::Selected(_))); assert!(matches!(response, Response::Selected(_)));
@ -454,9 +349,9 @@ mod tests {
{ {
let response: Response = state let response: Response = state
.interpret(Select( .interpret(Select(
users.clone(), users_position,
Columns(vec!["name".to_string(), "id".to_string()]), vec![name_column, id_column],
Some(Eq("id".to_string(), id0.clone())), Some(Eq(id_column, id0.clone())),
)) ))
.unwrap(); .unwrap();
assert!(matches!(response, Response::Selected(_))); assert!(matches!(response, Response::Selected(_)));
@ -475,7 +370,6 @@ mod tests {
#[test] #[test]
fn test_delete() { fn test_delete() {
use ColumnSelection::*;
use Condition::*; use Condition::*;
use IndexableValue::*; use IndexableValue::*;
use Operation::*; use Operation::*;
@ -483,10 +377,12 @@ mod tests {
let mut state = State::new(); let mut state = State::new();
let users_schema = users_schema(); let users_schema = users_schema();
let users = users_schema.table_name().clone(); let users_position: TablePosition = 0;
let id_column: ColumnPosition = 0;
state state
.interpret(CreateTable(users.clone(), users_schema)) .interpret(CreateTable(users_schema.table_name().clone(), users_schema.clone()))
.unwrap(); .unwrap();
let (id0, name0, age0) = ( let (id0, name0, age0) = (
@ -496,11 +392,11 @@ mod tests {
); );
state state
.interpret(Insert( .interpret(Insert(
users.clone(), users_position,
vec![ vec![
("id".to_string(), id0.clone()), id0.clone(),
("name".to_string(), name0.clone()), name0.clone(),
("age".to_string(), age0.clone()), age0.clone(),
], ],
)) ))
.unwrap(); .unwrap();
@ -512,11 +408,11 @@ mod tests {
); );
state state
.interpret(Insert( .interpret(Insert(
users.clone(), users_position,
vec![ vec![
("id".to_string(), id1.clone()), id1.clone(),
("name".to_string(), name1.clone()), name1.clone(),
("age".to_string(), age1.clone()), age1.clone(),
], ],
)) ))
.unwrap(); .unwrap();
@ -524,14 +420,14 @@ mod tests {
{ {
let delete_response: Response = state let delete_response: Response = state
.interpret(Delete( .interpret(Delete(
users.clone(), users_position,
Some(Eq("id".to_string(), id0.clone())), Some(Eq(id_column, id0.clone())),
)) ))
.unwrap(); .unwrap();
assert!(matches!(delete_response, Response::Deleted(1))); assert!(matches!(delete_response, Response::Deleted(1)));
} }
let response: Response = state.interpret(Select(users.clone(), All, None)).unwrap(); let response: Response = state.interpret(Select(users_position, users_schema.all_selection(), None)).unwrap();
assert!(matches!(response, Response::Selected(_))); assert!(matches!(response, Response::Selected(_)));
let Response::Selected(rows) = response else { let Response::Selected(rows) = response else {
@ -555,14 +451,16 @@ mod tests {
let mut state = State::new(); let mut state = State::new();
let users_schema = users_schema(); let users_schema = users_schema();
let users = users_schema.table_name().clone(); let users_position: TablePosition = 0;
let name_column: ColumnPosition = 1;
state state
.interpret(CreateTable(users.clone(), users_schema)) .interpret(CreateTable(users_schema.table_name().clone(), users_schema.clone()))
.unwrap(); .unwrap();
state state
.interpret(CreateIndex(users.clone(), "name".to_string())) .interpret(CreateIndex(users_position, name_column))
.unwrap(); .unwrap();
let (id0, name0, age0) = ( let (id0, name0, age0) = (
@ -572,11 +470,11 @@ mod tests {
); );
state state
.interpret(Insert( .interpret(Insert(
users.clone(), users_position,
vec![ vec![
("id".to_string(), id0.clone()), id0.clone(),
("name".to_string(), name0.clone()), name0.clone(),
("age".to_string(), age0.clone()), age0.clone(),
], ],
)) ))
.unwrap(); .unwrap();
@ -588,11 +486,11 @@ mod tests {
); );
state state
.interpret(Insert( .interpret(Insert(
users.clone(), users_position,
vec![ vec![
("id".to_string(), id1.clone()), id1.clone(),
("name".to_string(), name1.clone()), name1.clone(),
("age".to_string(), age1.clone()), age1.clone(),
], ],
)) ))
.unwrap(); .unwrap();
@ -617,33 +515,35 @@ mod tests {
} }
pub fn example() { pub fn example() {
use ColumnSelection::*; use crate::type_system::{IndexableValue, Value, DbType};
use crate::internals::row::ColumnPosition;
use Condition::*; use Condition::*;
use IndexableValue::*; use IndexableValue::*;
use Operation::*; use Operation::*;
use Value::*; use Value::*;
let users_schema: TableSchema = { let id_column: ColumnPosition = 0;
let id: ColumnPosition = 0; let name_column: ColumnPosition = 1;
let name: ColumnPosition = 1; let age_column: ColumnPosition = 2;
let age: ColumnPosition = 2;
let users_schema: TableSchema = {
TableSchema::new( TableSchema::new(
"users".to_string(), "users".to_string(),
id, id_column,
vec!( vec!(
("id".to_string(), id), ("id".to_string(), id_column),
("name".to_string(), name), ("name".to_string(), name_column),
("age".to_string(), age), ("age".to_string(), age_column),
), ),
vec![DbType::Uuid, DbType::String, DbType::Int], vec![DbType::Uuid, DbType::String, DbType::Int],
) )
}; };
let users_position: TablePosition = 0;
let users = users_schema.table_name().clone(); let users = users_schema.table_name().clone();
let mut state = State::new(); let mut state = State::new();
state state
.interpret(Operation::CreateTable(users.clone(), users_schema)) .interpret(Operation::CreateTable(users, users_schema.clone()))
.unwrap(); .unwrap();
let (id0, name0, age0) = ( let (id0, name0, age0) = (
@ -654,11 +554,11 @@ pub fn example() {
println!("==INSERT Plato=="); println!("==INSERT Plato==");
state state
.interpret(Insert( .interpret(Insert(
users.clone(), users_position,
vec![ vec![
("id".to_string(), id0.clone()), id0.clone(),
("name".to_string(), name0.clone()), name0.clone(),
("age".to_string(), age0.clone()), age0.clone(),
], ],
)) ))
.unwrap(); .unwrap();
@ -671,11 +571,11 @@ pub fn example() {
println!("==INSERT Aristotle=="); println!("==INSERT Aristotle==");
state state
.interpret(Insert( .interpret(Insert(
users.clone(), users_position,
vec![ vec![
("id".to_string(), id1.clone()), id1.clone(),
("name".to_string(), name1.clone()), name1.clone(),
("age".to_string(), age1.clone()), age1.clone(),
], ],
)) ))
.unwrap(); .unwrap();
@ -683,7 +583,7 @@ pub fn example() {
{ {
let response: Response = state let response: Response = state
.interpret(Operation::Select(users.clone(), ColumnSelection::All, None)) .interpret(Operation::Select(users_position, users_schema.all_selection(), None))
.unwrap(); .unwrap();
println!("==SELECT ALL=="); println!("==SELECT ALL==");
println!("{:?}", response); println!("{:?}", response);
@ -692,9 +592,9 @@ pub fn example() {
{ {
let response: Response = state let response: Response = state
.interpret(Select( .interpret(Select(
users.clone(), users_position,
All, users_schema.all_selection(),
Some(Eq("id".to_string(), id0.clone())), Some(Eq(id_column, id0.clone())),
)) ))
.unwrap(); .unwrap();
println!("==SELECT Plato=="); println!("==SELECT Plato==");
@ -708,16 +608,16 @@ pub fn example() {
// "infer" them? // "infer" them?
let _delete_response: Response = state let _delete_response: Response = state
.interpret(Delete( .interpret(Delete(
users.clone(), users_position,
Some(Eq("id".to_string(), id0.clone())), Some(Eq(id_column, id0.clone())),
)) ))
.unwrap(); .unwrap();
println!("==DELETE Plato=="); println!("==DELETE Plato==");
} }
let response: Response = state let response: Response = state
.interpret(Select( .interpret(Select(
users.clone(), users_position,
Columns(vec!["name".to_string(), "id".to_string()]), vec![name_column, id_column],
None, None,
)) ))
.unwrap(); .unwrap();

View file

@ -1,58 +1,21 @@
use crate::schema::{ColumnName, TableName, TableSchema}; use crate::schema::{TableName, TableSchema};
use crate::type_system::Value; use crate::type_system::Value;
use crate::internals::row::ColumnPosition; use crate::internals::row::ColumnPosition;
use crate::interpreter::TablePosition; use crate::interpreter::TablePosition;
// ==============SQL operations================ // Validated operation. Constructed by validation crate.
// TODO: Note that every operation has a table name.
// Perhaps consider factoring the table name out
// and think of the operations as operating on a unique table.
// TODO: `TableName` should be replaced by `TablePosition`
pub enum Operation { pub enum Operation {
Select(TableName, ColumnSelection, Option<Condition>), Select(TablePosition, ColumnSelection, Option<Condition>),
Insert(TableName, InsertionValues), Insert(TablePosition, InsertionValues),
Delete(TableName, Option<Condition>), Delete(TablePosition, Option<Condition>),
// Update(...),
CreateTable(TableName, TableSchema),
CreateIndex(TableName, ColumnName),
// DropTable(TableName),
}
pub type InsertionValues = Vec<(ColumnName, Value)>;
pub enum ColumnSelection {
All,
Columns(Vec<ColumnName>),
}
pub enum Condition {
// And(Box<Condition>, Box<Condition>),
// Or(Box<Condition>, Box<Condition>),
// Not(Box<Condition>),
Eq(ColumnName, Value),
// LessOrEqual(ColumnName, DbValue),
// Less(ColumnName, DbValue),
// StringCondition(StringCondition),
}
// enum StringCondition {
// Prefix(ColumnName, String),
// Substring(ColumnName, String),
// }
pub enum OperationForInterpreter {
Select(TablePosition, ColumnSelectionForInterpreter, Option<ConditionForInterpreter>),
Insert(TablePosition, InsertionValuesForInterpreter),
Delete(TablePosition, Option<ConditionForInterpreter>),
CreateTable(TableName, TableSchema), CreateTable(TableName, TableSchema),
CreateIndex(TablePosition, ColumnPosition), CreateIndex(TablePosition, ColumnPosition),
} }
pub type InsertionValuesForInterpreter = Vec<Value>; pub type InsertionValues = Vec<Value>;
pub type ColumnSelectionForInterpreter = Vec<ColumnPosition>; pub type ColumnSelection = Vec<ColumnPosition>;
pub enum ConditionForInterpreter { pub enum Condition {
Eq(ColumnPosition, Value), Eq(ColumnPosition, Value),
} }

View file

@ -1,14 +1,13 @@
use crate::error::Error; use crate::error::Error;
use crate::internals::row::{ColumnPosition, Row}; use crate::internals::row::{ColumnPosition, Row};
use crate::operation::{ColumnSelection, InsertionValues, InsertionValuesForInterpreter, ColumnSelectionForInterpreter}; use crate::operation::{InsertionValues, ColumnSelection};
use crate::result::DbResult; use crate::result::DbResult;
use crate::type_system::{DbType, IndexableValue, Uuid, Value}; use crate::type_system::{DbType, IndexableValue, Uuid, Value};
use bimap::BiMap; use bimap::BiMap;
use std::collections::HashMap;
// Note that it is nice to split metadata from the data because // Note that it is nice to split metadata from the data because
// then you can give the metadata to the parser without giving it the data. // then you can give the metadata to the parser without giving it the data.
#[derive(Debug)] #[derive(Debug, Clone)]
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,
@ -48,12 +47,13 @@ impl TableSchema {
self.column_name_position_mapping.get_by_left(column_name).copied() self.column_name_position_mapping.get_by_left(column_name).copied()
} }
pub fn all_selection(&self) -> ColumnSelectionForInterpreter { pub fn all_selection(&self) -> ColumnSelection {
self.column_name_position_mapping.iter().map(|(_, column)| *column).collect() let mut selection: ColumnSelection = self.column_name_position_mapping.iter().map(|(_, column)| *column).collect();
selection.sort();
selection
} }
// TODO: Rename to get_column pub fn get_column(&self, column_name: &ColumnName) -> Option<(ColumnPosition, DbType)> {
pub fn get_column0(&self, column_name: &ColumnName) -> Option<(ColumnPosition, DbType)> {
let column = self.get_column_position(column_name)?; let column = self.get_column_position(column_name)?;
Some((column, self.column_type(column))) Some((column, self.column_type(column)))
} }
@ -63,48 +63,10 @@ impl TableSchema {
self.types.get(position).copied() 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) {
Some(type_) => Ok((*type_, *column_position)),
None => Err(Error::MissingTypeAnnotationOfColumn(
self.table_name.clone(),
*column_position,
)),
},
None => Err(Error::ColumnDoesNotExist(
self.table_name.clone(),
column_name.clone(),
)),
}
}
// TODO: Get rid of this after validation is merged
pub fn column_position_from_column_name(
&self,
column_name: &ColumnName,
) -> DbResult<ColumnPosition> {
self.get_column(column_name)
.map(|(_, column_position)| column_position)
}
pub fn is_primary(&self, column_position: ColumnPosition) -> bool { pub fn is_primary(&self, column_position: ColumnPosition) -> bool {
self.primary_key == column_position self.primary_key == column_position
} }
fn column_positions_from_column_names(
&self,
column_names: &[ColumnName],
) -> DbResult<Vec<ColumnPosition>> {
let mut positions: Vec<ColumnPosition> = Vec::with_capacity(column_names.len());
for column_name in column_names {
let column_position = self.column_position_from_column_name(column_name)?;
positions.push(column_position)
}
Ok(positions)
}
pub fn column_name_from_column_position( pub fn column_name_from_column_position(
&self, &self,
column_position: ColumnPosition, column_position: ColumnPosition,
@ -121,34 +83,13 @@ impl TableSchema {
} }
} }
pub fn column_positions_from_column_selection(
&self,
column_selection: &ColumnSelection,
) -> DbResult<Vec<ColumnPosition>> {
match column_selection {
ColumnSelection::All => {
let mut column_positions: Vec<ColumnPosition> = self
.column_name_position_mapping
.iter()
.map(|(_, column_position)| *column_position)
.collect();
column_positions.sort();
Ok(column_positions)
}
ColumnSelection::Columns(column_names) => {
self.column_positions_from_column_names(column_names)
}
}
}
pub fn number_of_columns(&self) -> usize { pub fn number_of_columns(&self) -> usize {
self.column_name_position_mapping.len() self.column_name_position_mapping.len()
} }
pub fn row_from_insertion_values_for_interpreter( pub fn row_from_insertion_values(
&self, &self,
insertion_values: InsertionValuesForInterpreter, insertion_values: InsertionValues,
) -> DbResult<(Uuid, Row)> { ) -> DbResult<(Uuid, Row)> {
let row: Row = Row::new_from_insertion_values(insertion_values); let row: Row = Row::new_from_insertion_values(insertion_values);
@ -160,51 +101,4 @@ impl TableSchema {
Ok((id, row)) Ok((id, row))
} }
pub fn row_from_insertion_values(
&self,
insertion_values: InsertionValues,
) -> DbResult<(Uuid, Row)> {
// TODO: There should be proper validation of the insertion_values.
// And it shouldn't really be done here.
//
// In the below we don't check for duplicate column names
//
let number_of_columns = self.number_of_columns();
if number_of_columns != insertion_values.len() {
return Err(Error::MismatchBetweenInsertValuesAndColumns(
self.table_name.clone(),
insertion_values,
));
}
let mut row: Row = Row::with_number_of_columns(number_of_columns);
let mut values: HashMap<ColumnName, Value> = HashMap::new();
for (column_name, db_value) in &insertion_values {
values.insert(column_name.clone(), db_value.clone());
}
for column_position in 0..number_of_columns {
let column_name: ColumnName = self.column_name_from_column_position(column_position)?;
match values.get(&column_name) {
Some(db_value) => row.push(db_value.clone()),
None => {
return Err(Error::MissingColumnInInsertValues(
self.table_name.clone(),
column_name,
insertion_values,
))
}
}
}
let id: Uuid = match row.get(self.primary_key) {
Some(Value::Indexable(IndexableValue::Uuid(id))) => *id,
Some(_) => unreachable!(),
None => unreachable!(),
};
Ok((id, row))
}
} }

View file

@ -1,4 +1,4 @@
use minisql::{operation::OperationForInterpreter, interpreter::DbSchema}; use minisql::{operation::Operation, interpreter::DbSchema};
use crate::syntax::RawQuerySyntax; use crate::syntax::RawQuerySyntax;
use nom::{branch::alt, multi::many0, IResult}; use nom::{branch::alt, multi::many0, IResult};
use thiserror::Error; use thiserror::Error;
@ -29,7 +29,7 @@ pub fn parse_statements<'a>(input: &'a str) -> IResult<&str, Vec<RawQuerySyntax>
many0(parse_statement)(input) many0(parse_statement)(input)
} }
pub fn parse_and_validate(query: String, db_schema: &DbSchema) -> Result<OperationForInterpreter, Error> { pub fn parse_and_validate(query: String, db_schema: &DbSchema) -> Result<Operation, Error> {
let (_, op) = parse_statement(query.as_str()) let (_, op) = parse_statement(query.as_str())
.map_err(|err| { .map_err(|err| {
Error::ParsingError(err.to_string()) Error::ParsingError(err.to_string())

View file

@ -100,8 +100,8 @@ mod tests {
RawQuerySyntax::CreateTable(name, schema) => { RawQuerySyntax::CreateTable(name, schema) => {
assert_eq!(name, "Table1"); assert_eq!(name, "Table1");
assert_eq!(schema.number_of_columns(), 2); assert_eq!(schema.number_of_columns(), 2);
assert_eq!(schema.column_position_from_column_name(&"id".to_string()).unwrap(), 0); assert_eq!(schema.get_column_position(&"id".to_string()).unwrap(), 0);
assert_eq!(schema.column_position_from_column_name(&"column1".to_string()).unwrap(), 1); assert_eq!(schema.get_column_position(&"column1".to_string()).unwrap(), 1);
} }
_ => {} _ => {}
} }

View file

@ -2,8 +2,10 @@ use std::collections::HashSet;
use std::collections::HashMap; use std::collections::HashMap;
use thiserror::Error; use thiserror::Error;
use crate::syntax::{ColumnSelection, Condition, InsertionValues, RawQuerySyntax}; use crate::syntax;
use minisql::{operation::{ColumnSelectionForInterpreter, ConditionForInterpreter, InsertionValuesForInterpreter, OperationForInterpreter}, type_system::Value, schema::{TableSchema, ColumnName, TableName}, type_system::DbType, interpreter::{TablePosition, DbSchema}}; use crate::syntax::RawQuerySyntax;
use minisql::operation;
use minisql::{operation::Operation, type_system::Value, schema::{TableSchema, ColumnName, TableName}, type_system::DbType, interpreter::{TablePosition, DbSchema}};
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum ValidationError { pub enum ValidationError {
@ -26,7 +28,7 @@ pub enum ValidationError {
} }
/// Validates and converts the raw syntax into a proper interpreter operation based on 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<OperationForInterpreter, ValidationError> { pub fn validate_operation(query: RawQuerySyntax, db_schema: &DbSchema) -> Result<Operation, ValidationError> {
match query { match query {
RawQuerySyntax::Select(table_name, column_selection, condition) => { RawQuerySyntax::Select(table_name, column_selection, condition) => {
validate_select(table_name, column_selection, condition, db_schema) validate_select(table_name, column_selection, condition, db_schema)
@ -52,7 +54,7 @@ fn validate_table_exists<'a>(db_schema: &DbSchema<'a>, table_name: &'a TableName
.map(|(_, table_position, table_schema)| (*table_position, *table_schema)) .map(|(_, table_position, table_schema)| (*table_position, *table_schema))
} }
pub fn validate_create(table_name: TableName, table_schema: TableSchema, db_schema: &DbSchema) -> Result<OperationForInterpreter, ValidationError> { pub fn validate_create(table_name: TableName, table_schema: TableSchema, db_schema: &DbSchema) -> Result<Operation, ValidationError> {
if let Some(_) = get_table_schema(db_schema, &table_name) { if let Some(_) = get_table_schema(db_schema, &table_name) {
return Err(ValidationError::TableAlreadyExists(table_name.to_string())); return Err(ValidationError::TableAlreadyExists(table_name.to_string()));
} }
@ -64,13 +66,13 @@ pub fn validate_create(table_name: TableName, table_schema: TableSchema, db_sche
)?; )?;
// TODO: Ensure it has a primary key?? // TODO: Ensure it has a primary key??
Ok(OperationForInterpreter::CreateTable(table_name, table_schema)) Ok(Operation::CreateTable(table_name, table_schema))
} }
pub fn validate_select(table_name: TableName, column_selection: ColumnSelection, condition: Option<Condition>, db_schema: &DbSchema) -> Result<OperationForInterpreter, ValidationError> { pub 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)?; let (table_position, schema) = validate_table_exists(db_schema, &table_name)?;
match column_selection { match column_selection {
ColumnSelection::Columns(columns) => { syntax::ColumnSelection::Columns(columns) => {
let non_existant_columns: Vec<ColumnName> = let non_existant_columns: Vec<ColumnName> =
columns.iter().filter_map(|column| columns.iter().filter_map(|column|
if schema.does_column_exist(&column) { if schema.does_column_exist(&column) {
@ -81,20 +83,20 @@ pub fn validate_select(table_name: TableName, column_selection: ColumnSelection,
if non_existant_columns.len() > 0 { if non_existant_columns.len() > 0 {
Err(ValidationError::ColumnsDoNotExist(non_existant_columns)) Err(ValidationError::ColumnsDoNotExist(non_existant_columns))
} else { } else {
let selection: ColumnSelectionForInterpreter = let selection: operation::ColumnSelection =
columns.iter().filter_map(|column_name| schema.get_column_position(column_name)).collect(); columns.iter().filter_map(|column_name| schema.get_column_position(column_name)).collect();
let validated_condition = validate_condition(condition, schema)?; let validated_condition = validate_condition(condition, schema)?;
Ok(OperationForInterpreter::Select(table_position, selection, validated_condition)) Ok(Operation::Select(table_position, selection, validated_condition))
} }
} }
ColumnSelection::All => { syntax::ColumnSelection::All => {
let validated_condition = validate_condition(condition, schema)?; let validated_condition = validate_condition(condition, schema)?;
Ok(OperationForInterpreter::Select(table_position, schema.all_selection(), validated_condition)) Ok(Operation::Select(table_position, schema.all_selection(), validated_condition))
} }
} }
} }
pub fn validate_insert(table_name: TableName, insertion_values: InsertionValues, db_schema: &DbSchema) -> Result<OperationForInterpreter, ValidationError> { pub 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)?; let (table_position, schema) = validate_table_exists(db_schema, &table_name)?;
// Check for duplicate columns in insertion_values. // Check for duplicate columns in insertion_values.
@ -120,7 +122,7 @@ pub fn validate_insert(table_name: TableName, insertion_values: InsertionValues,
// Check types and prepare for creation of InsertionValues for the interpreter // Check types and prepare for creation of InsertionValues for the interpreter
let mut values_map: HashMap<_, Value> = HashMap::new(); let mut values_map: HashMap<_, Value> = HashMap::new();
for (column_name, value) in insertion_values { for (column_name, value) in insertion_values {
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 (column, expected_type) = schema.get_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(); let value_type = value.to_type();
if value_type != expected_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 });
@ -129,26 +131,26 @@ pub fn validate_insert(table_name: TableName, insertion_values: InsertionValues,
} }
// These are values ordered by the column position // These are values ordered by the column position
let values: InsertionValuesForInterpreter = values_map.into_values().collect(); let values: operation::InsertionValues = values_map.into_values().collect();
Ok(OperationForInterpreter::Insert(table_position, values)) Ok(Operation::Insert(table_position, values))
} }
pub fn validate_delete(table_name: TableName, condition: Option<Condition>, db_schema: &DbSchema) -> Result<OperationForInterpreter, ValidationError> { pub 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 (table_position, schema) = validate_table_exists(db_schema, &table_name)?;
let validated_condition = validate_condition(condition, schema)?; let validated_condition = validate_condition(condition, schema)?;
Ok(OperationForInterpreter::Delete(table_position, validated_condition)) Ok(Operation::Delete(table_position, validated_condition))
} }
fn validate_condition(condition: Option<Condition>, schema: &TableSchema) -> Result<Option<ConditionForInterpreter>, ValidationError> { fn validate_condition(condition: Option<syntax::Condition>, schema: &TableSchema) -> Result<Option<operation::Condition>, ValidationError> {
match condition { match condition {
Some(condition) => { Some(condition) => {
match condition { match condition {
Condition::Eq(column_name, value) => { syntax::Condition::Eq(column_name, value) => {
let (column, expected_type) = schema.get_column0(&column_name).ok_or(ValidationError::ColumnsDoNotExist(vec![column_name.to_string()]))?; let (column, expected_type) = schema.get_column(&column_name).ok_or(ValidationError::ColumnsDoNotExist(vec![column_name.to_string()]))?;
let value_type: DbType = value.to_type(); 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))) Ok(Some(operation::Condition::Eq(column, value)))
} else { } else {
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 });
} }
@ -159,27 +161,27 @@ fn validate_condition(condition: Option<Condition>, schema: &TableSchema) -> Res
} }
} }
fn validate_create_index(table_name: TableName, column_name: ColumnName, db_schema: &DbSchema) -> Result<OperationForInterpreter, ValidationError> { fn validate_create_index(table_name: TableName, column_name: ColumnName, db_schema: &DbSchema) -> Result<Operation, ValidationError> {
// TODO: You should disallow indexing of Number columns. // TODO: You should disallow indexing of Number columns.
let (table_position, schema) = validate_table_exists(db_schema, &table_name)?; let (table_position, schema) = validate_table_exists(db_schema, &table_name)?;
schema schema
.get_column_position(&column_name) .get_column_position(&column_name)
.map_or_else( .map_or_else(
|| Err(ValidationError::ColumnsDoNotExist(vec![column_name.to_string()])), || Err(ValidationError::ColumnsDoNotExist(vec![column_name.to_string()])),
|column| Ok(OperationForInterpreter::CreateIndex(table_position, column)) |column| Ok(Operation::CreateIndex(table_position, column))
) )
} }
// ===Helpers=== // ===Helpers===
fn find_first_duplicate<A>(xs: &[A]) -> Option<&A> fn find_first_duplicate<T>(ts: &[T]) -> Option<&T>
where A: Eq + std::hash::Hash where T: Eq + std::hash::Hash
{ {
let mut already_seen_elements: HashSet<&A> = HashSet::new(); let mut already_seen_elements: HashSet<&T> = HashSet::new();
for x in xs { for t in ts {
if already_seen_elements.contains(x) { if already_seen_elements.contains(t) {
return Some(x); return Some(t);
} else { } else {
already_seen_elements.insert(&x); already_seen_elements.insert(&t);
} }
} }
None None

View file

@ -56,7 +56,7 @@ async fn handle_stream(mut stream: TcpStream) -> anyhow::Result<()> {
let db_schema = state.db_schema(); let db_schema = state.db_schema();
match parse_and_validate(data.query.as_str().to_string(), &db_schema) { match parse_and_validate(data.query.as_str().to_string(), &db_schema) {
Ok(operation) => { Ok(operation) => {
match state.interpret_for_interpreter(operation) { match state.interpret(operation) {
Ok(_) => { Ok(_) => {
send_query_response(&mut writer).await?; send_query_response(&mut writer).await?;
} }