From 339686f5c58019d67863f8575fae738a0f95c4fd Mon Sep 17 00:00:00 2001 From: Yuriy Dupyn <2153100+omedusyo@users.noreply.github.com> Date: Thu, 1 Feb 2024 14:35:25 +0100 Subject: [PATCH] Introduce optional type/value --- minisql/src/internals/table.rs | 30 ++--- minisql/src/interpreter.rs | 187 +++++++++++++-------------- minisql/src/schema.rs | 15 ++- minisql/src/type_system.rs | 227 ++++++++++++++++++++------------- 4 files changed, 255 insertions(+), 204 deletions(-) diff --git a/minisql/src/internals/table.rs b/minisql/src/internals/table.rs index 0f9bb2c..d3a59bf 100644 --- a/minisql/src/internals/table.rs +++ b/minisql/src/internals/table.rs @@ -85,18 +85,18 @@ impl Table { value: Value, ) -> DbResult + '_> { let restrict_columns_of_row = move |row: Row| row.restrict_columns(&selected_columns); - match value { - Value::Indexable(value) => match self.fetch_ids_from_index(column, &value)? { + match value.to_indexable() { + Some(indexable_value) => match self.fetch_ids_from_index(column, &indexable_value)? { Some(ids) => Ok(self .get_rows_by_ids(ids) .into_iter() .map(restrict_columns_of_row)), None => Ok(self - .get_rows_by_value(column, &Value::Indexable(value)) + .get_rows_by_value(column, &value) .into_iter() .map(restrict_columns_of_row)), }, - _ => Ok(self + None => Ok(self .get_rows_by_value(column, &value) .into_iter() .map(restrict_columns_of_row)), @@ -113,8 +113,8 @@ impl Table { } for (column, column_index) in &mut self.indexes { - if let Value::Indexable(val) = &row[*column] { - column_index.add(val.clone(), id) + if let Some(indexable_value) = &row[*column].to_indexable() { + column_index.add(indexable_value.clone(), id) } } @@ -127,8 +127,8 @@ impl Table { match self.rows.remove(&id) { Some(row) => { for (column, column_index) in &mut self.indexes { - if let Value::Indexable(value) = &row[*column] { - let _ = column_index.remove(value, id); + if let Some(indexable_value) = &row[*column].to_indexable() { + let _ = column_index.remove(indexable_value, id); }; } 1 @@ -168,12 +168,12 @@ impl Table { } pub fn delete_rows_where_eq(&mut self, column: Column, value: Value) -> DbResult { - match value { - Value::Indexable(value) => match self.fetch_ids_from_index(column, &value)? { + match value.to_indexable() { + Some(indexable_value) => match self.fetch_ids_from_index(column, &indexable_value)? { Some(ids) => Ok(self.delete_rows_by_ids(ids)), - None => Ok(self.delete_rows_by_value(column, &Value::Indexable(value))), + None => Ok(self.delete_rows_by_value(column, &value)), }, - _ => Ok(self.delete_rows_by_value(column, &value)), + None => Ok(self.delete_rows_by_value(column, &value)), } } @@ -226,9 +226,9 @@ fn update_index_from_table( column: Column, ) -> DbResult<()> { for (id, row) in &table.rows { - let value = match &row[column] { - Value::Indexable(value) => value.clone(), - _ => { + let value = match &row[column].to_indexable() { + Some(indexable_value) => indexable_value.clone(), + None => { let column_name: ColumnName = table.schema.column_name_from_column(column); return Err(RuntimeError::AttemptToIndexNonIndexableColumn( table.table_name().to_string(), diff --git a/minisql/src/interpreter.rs b/minisql/src/interpreter.rs index 773eac1..699265d 100644 --- a/minisql/src/interpreter.rs +++ b/minisql/src/interpreter.rs @@ -2,7 +2,7 @@ use crate::internals::table::Table; use crate::operation::{ColumnSelection, Condition, Operation}; use crate::restricted_row::RestrictedRow; use crate::result::DbResult; -use crate::schema::{Column, TableName, TablePosition, TableSchema}; +use crate::schema::{TableName, TablePosition, TableSchema}; use bimap::BiMap; use serde::{Deserialize, Serialize}; @@ -491,102 +491,103 @@ mod tests { } } -pub fn example() { - use crate::type_system::{DbType, IndexableValue, Value}; - use Condition::*; - use IndexableValue::*; - use Operation::*; - use Value::*; +// TODO +// pub fn example() { +// use crate::type_system::{DbType, IndexableValue, Value}; +// use Condition::*; +// use IndexableValue::*; +// use Operation::*; +// use Value::*; - let id_column: Column = 0; - let name_column: Column = 1; - // let age_column: ColumnPosition = 2; +// let id_column: Column = 0; +// let name_column: Column = 1; +// // let age_column: ColumnPosition = 2; - let users_schema: TableSchema = { - TableSchema::new( - "users".to_string(), - "id".to_string(), - vec![ - "id".to_string(), // 0 - "name".to_string(), // 1 - "age".to_string(), // 2 - ], - vec![DbType::Uuid, DbType::String, DbType::Int], - ) - }; - let users_position: TablePosition = 0; +// let users_schema: TableSchema = { +// TableSchema::new( +// "users".to_string(), +// "id".to_string(), +// vec![ +// "id".to_string(), // 0 +// "name".to_string(), // 1 +// "age".to_string(), // 2 +// ], +// vec![DbType::Uuid, DbType::String, DbType::Int], +// ) +// }; +// let users_position: TablePosition = 0; - let mut state = State::new(); - state - .interpret(Operation::CreateTable(users_schema.clone())) - .unwrap(); +// let mut state = State::new(); +// state +// .interpret(Operation::CreateTable(users_schema.clone())) +// .unwrap(); - let (id0, name0, age0) = ( - Indexable(Uuid(0)), - Indexable(String("Plato".to_string())), - Indexable(Int(64)), - ); - println!("==INSERT Plato=="); - state - .interpret(Insert( - users_position, - vec![id0.clone(), name0.clone(), age0.clone()], - )) - .unwrap(); +// let (id0, name0, age0) = ( +// Indexable(Uuid(0)), +// Indexable(String("Plato".to_string())), +// Indexable(Int(64)), +// ); +// println!("==INSERT Plato=="); +// state +// .interpret(Insert( +// users_position, +// vec![id0.clone(), name0.clone(), age0.clone()], +// )) +// .unwrap(); - let (id1, name1, age1) = ( - Indexable(Uuid(1)), - Indexable(String("Aristotle".to_string())), - Indexable(Int(20)), - ); - println!("==INSERT Aristotle=="); - state - .interpret(Insert( - users_position, - vec![id1.clone(), name1.clone(), age1.clone()], - )) - .unwrap(); - println!(); +// let (id1, name1, age1) = ( +// Indexable(Uuid(1)), +// Indexable(String("Aristotle".to_string())), +// Indexable(Int(20)), +// ); +// println!("==INSERT Aristotle=="); +// state +// .interpret(Insert( +// users_position, +// vec![id1.clone(), name1.clone(), age1.clone()], +// )) +// .unwrap(); +// println!(); - { - let response: Response = state - .interpret(Operation::Select( - users_position, - users_schema.all_selection(), - None, - )) - .unwrap(); - println!("==SELECT ALL=="); - println!("{:?}", response); - println!(); - } - { - let response: Response = state - .interpret(Select( - users_position, - users_schema.all_selection(), - Some(Eq(id_column, id0.clone())), - )) - .unwrap(); - println!("==SELECT Plato=="); - println!("{:?}", response); - println!(); - } +// { +// let response: Response = state +// .interpret(Operation::Select( +// users_position, +// users_schema.all_selection(), +// None, +// )) +// .unwrap(); +// println!("==SELECT ALL=="); +// println!("{:?}", response); +// println!(); +// } +// { +// let response: Response = state +// .interpret(Select( +// users_position, +// users_schema.all_selection(), +// Some(Eq(id_column, id0.clone())), +// )) +// .unwrap(); +// println!("==SELECT Plato=="); +// println!("{:?}", response); +// println!(); +// } - { - { - // TODO: Why do I have to write these braces explicitely? Why doesn't Rust compiler - // "infer" them? - let _delete_response: Response = state - .interpret(Delete(users_position, Some(Eq(id_column, id0.clone())))) - .unwrap(); - println!("==DELETE Plato=="); - } - let response: Response = state - .interpret(Select(users_position, vec![name_column, id_column], None)) - .unwrap(); - println!("==SELECT All=="); - println!("{:?}", response); - println!(); - } -} +// { +// { +// // TODO: Why do I have to write these braces explicitely? Why doesn't Rust compiler +// // "infer" them? +// let _delete_response: Response = state +// .interpret(Delete(users_position, Some(Eq(id_column, id0.clone())))) +// .unwrap(); +// println!("==DELETE Plato=="); +// } +// let response: Response = state +// .interpret(Select(users_position, vec![name_column, id_column], None)) +// .unwrap(); +// println!("==SELECT All=="); +// println!("{:?}", response); +// println!(); +// } +// } diff --git a/minisql/src/schema.rs b/minisql/src/schema.rs index ed5ddce..6833a3d 100644 --- a/minisql/src/schema.rs +++ b/minisql/src/schema.rs @@ -1,7 +1,7 @@ use crate::internals::row::Row; use crate::operation::{ColumnSelection, InsertionValues}; use crate::result::DbResult; -use crate::type_system::{DbType, IndexableValue, Uuid, Value}; +use crate::type_system::{DbType, IndexableValue, Uuid}; use bimap::BiMap; use serde::{Deserialize, Serialize}; @@ -51,7 +51,7 @@ impl TableSchema { } pub fn column_type(&self, column: Column) -> DbType { - self.types[column] + self.types[column].clone() } pub fn get_columns(&self) -> Vec<&ColumnName> { @@ -88,7 +88,7 @@ impl TableSchema { pub fn get_type_at(&self, column_name: &ColumnName) -> Option { let position = self.get_column(column_name)?; - self.types.get(position).copied() + self.types.get(position).cloned() } pub fn is_primary(&self, column: Column) -> bool { @@ -115,8 +115,13 @@ impl TableSchema { let row: Row = Row::new_from_insertion_values(insertion_values); let id: Uuid = match row.get(self.primary_key) { - Some(Value::Indexable(IndexableValue::Uuid(id))) => *id, - Some(_) => unreachable!(), // SAFETY: Should be guaranteed by validation (type-safety) + Some(value) => { + match value.to_indexable() { + Some(IndexableValue::Uuid(id)) => id, + Some(_) => unreachable!(), // SAFETY: Should be guaranteed by validation (type-safety) + None => unreachable!(), // SAFETY: Should be guaranteed by validation (type-safety) + } + } None => unreachable!(), // SAFETY: Should be guaranteed by validation (missing columns) }; diff --git a/minisql/src/type_system.rs b/minisql/src/type_system.rs index 013a48d..1327be2 100644 --- a/minisql/src/type_system.rs +++ b/minisql/src/type_system.rs @@ -1,36 +1,55 @@ use crate::error::TypeConversionError; +use std::cmp::Ordering; use serde::{Deserialize, Serialize}; // ==============Types================ -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Ord, PartialEq, Eq, Serialize, Deserialize)] pub enum DbType { String, Int, Number, Uuid, + Option(Box) +} + +impl PartialOrd for DbType { + // TODO: Explain why we need this (because of IndexableValue::None contains a type) + fn partial_cmp(&self, other: &Self) -> Option { + todo!() + } } // ==============Values================ pub type Uuid = u64; - -// TODO: What about nulls? I would rather not have that in SQL, it sucks. -// I would rather have non-nullable values by default, -// and something like an explicit Option type for nulls. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(try_from = "String", into = "String")] pub enum Value { - Number(f64), // TODO: Can't put floats as keys in maps, since they don't implement Eq. What to - // do? - Indexable(IndexableValue), + Number(f64), + String(String), + Int(u64), + Uuid(Uuid), + Some(Box), + // Note that without polymorphic types, + // we can't type None on its own. Hence we require additional type information in the value + // itself. + None(DbType), } -#[derive(Debug, Ord, Eq, Clone, PartialOrd, PartialEq, Serialize, Deserialize)] + +#[derive(Debug, Ord, Eq, Clone, PartialEq, Serialize, Deserialize)] #[serde(try_from = "String", into = "String")] pub enum IndexableValue { String(String), Int(u64), Uuid(Uuid), - // TODO: what about null? + Some(Box), + None(DbType), // See Value::None +} + +impl PartialOrd for IndexableValue { + fn partial_cmp(&self, other: &Self) -> Option { + todo!() + } } impl DbType { @@ -40,25 +59,28 @@ impl DbType { Self::Int => true, Self::Number => false, Self::Uuid => true, + Self::Option(type_) => type_.is_indexable() } } pub fn type_oid(&self) -> i32 { - match self { - Self::String => 25, - Self::Int => 23, - Self::Number => 701, - Self::Uuid => 2950, - } + // match self { + // Self::String => 25, + // Self::Int => 23, + // Self::Number => 701, + // Self::Uuid => 2950, + // } + todo!() } pub fn type_size(&self) -> i16 { - match self { - Self::String => -2, // null terminated string - Self::Int => 8, - Self::Number => 8, - Self::Uuid => 16, - } + // match self { + // Self::String => -2, // null terminated string + // Self::Int => 8, + // Self::Number => 8, + // Self::Uuid => 16, + // } + todo!() } } @@ -66,63 +88,84 @@ impl Value { pub fn to_type(&self) -> DbType { match self { Self::Number(_) => DbType::Number, - Self::Indexable(val) => match val { - IndexableValue::String(_) => DbType::String, - IndexableValue::Int(_) => DbType::Int, - IndexableValue::Uuid(_) => DbType::Uuid, - }, + Self::String(_) => DbType::String, + Self::Int(_) => DbType::Int, + Self::Uuid(_) => DbType::Uuid, + Self::Some(val) => DbType::Option(Box::new(val.to_type())), + Self::None(type_) => type_.clone(), } } - pub fn as_text_bytes(&self) -> Vec { + + pub fn to_indexable(&self) -> Option { match self { - Self::Number(n) => format!("{n}").into_bytes(), - Self::Indexable(i) => match i { - IndexableValue::String(s) => format!("{s}\0").into_bytes(), - IndexableValue::Int(i) => format!("{i}").into_bytes(), - IndexableValue::Uuid(u) => format!("{u}").into_bytes(), - }, + Self::Number(_) => None, + Self::String(s) => Some(IndexableValue::String(s.to_string())), + Self::Int(n) => Some(IndexableValue::Int(*n)), + Self::Uuid(id) => Some(IndexableValue::Uuid(*id)), + Self::Some(val) => val.to_indexable(), + Self::None(type_) => Some(IndexableValue::None(type_.clone())), } } + pub fn as_text_bytes(&self) -> Vec { + // match self { + // Self::Number(n) => format!("{n}").into_bytes(), + // Self::Indexable(i) => match i { + // IndexableValue::String(s) => format!("{s}\0").into_bytes(), + // IndexableValue::Int(i) => format!("{i}").into_bytes(), + // IndexableValue::Uuid(u) => format!("{u}").into_bytes(), + // }, + // } + todo!() + } + pub fn from_text_bytes( bytes: &[u8], type_oid: i32, type_size: i16, ) -> Result { - match (type_oid, type_size) { - (701, 8) => { - let s = std::str::from_utf8(bytes)?; - let n = s.parse::()?; - Ok(Value::Number(n)) - } - (25, -2) => { - let s = std::str::from_utf8(bytes)?; - let s = &s[..s.len() - 1]; // remove null terminator - Ok(Value::Indexable(IndexableValue::String(s.to_string()))) - } - (23, 8) => { - let s = std::str::from_utf8(bytes)?; - let n = s.parse::()?; - Ok(Value::Indexable(IndexableValue::Int(n))) - } - (2950, 16) => { - let s = std::str::from_utf8(bytes)?; - let n = s.parse::()?; - Ok(Value::Indexable(IndexableValue::Uuid(n))) - } - (oid, size) => Err(TypeConversionError::UnknownType { oid, size }), - } + // match (type_oid, type_size) { + // (701, 8) => { + // let s = std::str::from_utf8(bytes)?; + // let n = s.parse::()?; + // Ok(Value::Number(n)) + // } + // (25, -2) => { + // let s = std::str::from_utf8(bytes)?; + // let s = &s[..s.len() - 1]; // remove null terminator + // Ok(Value::Indexable(IndexableValue::String(s.to_string()))) + // } + // (23, 8) => { + // let s = std::str::from_utf8(bytes)?; + // let n = s.parse::()?; + // Ok(Value::Indexable(IndexableValue::Int(n))) + // } + // (2950, 16) => { + // let s = std::str::from_utf8(bytes)?; + // let n = s.parse::()?; + // Ok(Value::Indexable(IndexableValue::Uuid(n))) + // } + // (oid, size) => Err(TypeConversionError::UnknownType { oid, size }), + // } + todo!() + } +} + +fn indexable_value_to_string(value: IndexableValue) -> String { + match value { + IndexableValue::String(s) => format!("String({s})"), + IndexableValue::Int(i) => format!("Int({i})"), + IndexableValue::Uuid(u) => format!("Uuid({u})"), + IndexableValue::Some(val) => format!("Some({})", indexable_value_to_string(*val)), + IndexableValue::Some(val) => todo!(), + IndexableValue::None(type_) => "None()".to_string(), } } // Own string serialization so enums can be used as keys in maps impl From for String { fn from(value: IndexableValue) -> Self { - match value { - IndexableValue::String(s) => format!("String({s})"), - IndexableValue::Int(i) => format!("Int({i})"), - IndexableValue::Uuid(u) => format!("Uuid({u})"), - } + indexable_value_to_string(value) } } @@ -135,35 +178,37 @@ impl TryFrom for IndexableValue { if value.starts_with("String(") { let s = value[7..value.len() - 1].to_string(); - return Ok(Self::String(s)); - } - - if value.starts_with("Int(") { + Ok(Self::String(s)) + } else if value.starts_with("Int(") { let s = value[4..value.len() - 1].to_string(); let i = s .parse::() .map_err(|e| format!("Invalid Int: {}", e))?; - return Ok(Self::Int(i)); - } - - if value.starts_with("Uuid(") { + Ok(Self::Int(i)) + } else if value.starts_with("Uuid(") { let s = value[5..value.len() - 1].to_string(); let u = s .parse::() .map_err(|e| format!("Invalid UUID: {}", e))?; - return Ok(Self::Uuid(u)); + Ok(Self::Uuid(u)) + } else if value.starts_with("Some(") { + // TODO: This needs some recursion + todo!() + } else if value.starts_with("None(") { + todo!() + } else { + Err(format!("Invalid IndexableValue: {}", value)) } - - Err(format!("Invalid IndexableValue: {}", value)) } } impl From for String { fn from(value: Value) -> Self { - match value { - Value::Number(n) => format!("Number({n})"), - Value::Indexable(i) => format!("Indexable({})", String::from(i)), - } + // match value { + // Value::Number(n) => format!("Number({n})"), + // Value::Indexable(i) => format!("Indexable({})", String::from(i)), + // } + todo!() } } @@ -174,21 +219,21 @@ impl TryFrom for Value { return Err(format!("Invalid Value: {}", value)); } - if value.starts_with("Number(") { - let s = value[7..value.len() - 1].to_string(); - let n = s - .parse::() - .map_err(|e| format!("Invalid Number: {}", e))?; - return Ok(Self::Number(n)); - } + // if value.starts_with("Number(") { + // let s = value[7..value.len() - 1].to_string(); + // let n = s + // .parse::() + // .map_err(|e| format!("Invalid Number: {}", e))?; + // Ok(Self::Number(n)) + // } else if value.starts_with("Indexable(") { + // let s = value[10..value.len() - 1].to_string(); + // let i = IndexableValue::try_from(s)?; + // Ok(Self::Indexable(i)) + // } else { + // Err(format!("Invalid Value: {}", value)) + // } - if value.starts_with("Indexable(") { - let s = value[10..value.len() - 1].to_string(); - let i = IndexableValue::try_from(s)?; - return Ok(Self::Indexable(i)); - } - - Err(format!("Invalid Value: {}", value)) + todo!() } }