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 01/13] 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!() } } From fbd7bf1f72b84da64bce3d434a733eee31b9e598 Mon Sep 17 00:00:00 2001 From: Yuriy Dupyn <2153100+omedusyo@users.noreply.github.com> Date: Thu, 1 Feb 2024 14:45:29 +0100 Subject: [PATCH 02/13] Fix errors in parsing crate --- parser/src/parsing/literal.rs | 28 +++++++++++------------ parser/src/validation.rs | 42 +++++++++++++++++------------------ 2 files changed, 34 insertions(+), 36 deletions(-) diff --git a/parser/src/parsing/literal.rs b/parser/src/parsing/literal.rs index c37f963..8ca72e8 100644 --- a/parser/src/parsing/literal.rs +++ b/parser/src/parsing/literal.rs @@ -1,4 +1,4 @@ -use minisql::type_system::{IndexableValue, Value}; +use minisql::type_system::Value; use nom::{ branch::alt, character::complete::{char, digit1, none_of, u64}, @@ -34,13 +34,13 @@ pub fn parse_number(input: &str) -> IResult<&str, Value> { let value = format!("{}{}", sign.unwrap_or('+'), digits) .parse::() .map_err(|_| nom::Err::Failure(make_error(input, nom::error::ErrorKind::Fail)))?; - Ok((input, Value::Indexable(IndexableValue::Int(value)))) + Ok((input, Value::Int(value))) } } } pub fn parse_int(input: &str) -> IResult<&str, Value> { - u64(input).map(|(input, v)| (input, Value::Indexable(IndexableValue::Int(v)))) + u64(input).map(|(input, v)| (input, Value::Int(v))) } fn escape_tab(input: &str) -> IResult<&str, char> { @@ -85,19 +85,19 @@ pub fn parse_string(input: &str) -> IResult<&str, Value> { // Combine the characters into a string let value: String = content.into_iter().collect(); - Ok((input, Value::Indexable(IndexableValue::String(value)))) + Ok((input, Value::String(value))) } pub fn parse_uuid(input: &str) -> IResult<&str, Value> { let (input, value) = pair(char('u'), u64)(input) - .map(|(input, (_, v))| (input, Value::Indexable(IndexableValue::Uuid(v))))?; + .map(|(input, (_, v))| (input, Value::Uuid(v)))?; Ok((input, value)) } #[cfg(test)] mod tests { use crate::parsing::literal::{parse_db_value, parse_string, parse_uuid}; - use minisql::type_system::{IndexableValue, Value}; + use minisql::type_system::Value; #[test] fn test_string_parser() { @@ -105,21 +105,21 @@ mod tests { parse_string(r#""simple""#), Ok(( "", - Value::Indexable(IndexableValue::String(String::from("simple"))) + Value::String(String::from("simple")) )) ); assert_eq!( parse_string(r#""\"\t\r\n\\""#), Ok(( "", - Value::Indexable(IndexableValue::String(String::from("\"\t\r\n\\"))) + Value::String(String::from("\"\t\r\n\\")) )) ); assert_eq!( parse_string(r#""name is \"John\".""#), Ok(( "", - Value::Indexable(IndexableValue::String(String::from("name is \"John\"."))) + Value::String(String::from("name is \"John\".")) )) ); } @@ -128,7 +128,7 @@ mod tests { fn test_parse_db_value() { let (input, value) = parse_db_value("5").expect("should parse"); assert_eq!(input, ""); - assert_eq!(value, Value::Indexable(IndexableValue::Int(5))); + assert_eq!(value, Value::Int(5)); let (input, value) = parse_db_value("5.5").expect("should parse"); assert_eq!(input, ""); @@ -140,9 +140,9 @@ mod tests { assert_eq!(input, ""); assert_eq!( value, - Value::Indexable(IndexableValue::String( + Value::String( "abcdefghkjklmnopqrstuvwxyz!@#$%^&*()_+ ".to_string() - )) + ) ); } @@ -183,7 +183,7 @@ mod tests { fn test_parse_int() { assert_eq!( parse_db_value("5134616"), - Ok(("", Value::Indexable(IndexableValue::Int(5134616)))) + Ok(("", Value::Int(5134616))) ); } @@ -191,7 +191,7 @@ mod tests { fn test_parse_uuid() { assert_eq!( parse_uuid("u131515"), - Ok(("", Value::Indexable(IndexableValue::Uuid(131515)))) + Ok(("", Value::Uuid(131515))) ) } } diff --git a/parser/src/validation.rs b/parser/src/validation.rs index bd948a7..5bbf8e6 100644 --- a/parser/src/validation.rs +++ b/parser/src/validation.rs @@ -105,7 +105,7 @@ fn validate_table_schema(raw_table_schema: RawTableSchema) -> Result TableSchema { TableSchema::new( @@ -567,7 +565,7 @@ mod tests { let syntax: RawQuerySyntax = Select( "users".to_string(), ColumnSelection::All, - Some(Eq("age".to_string(), Indexable(Int(25)))), + Some(Eq("age".to_string(), Value::Int(25))), ); let result = validate_operation(syntax, &db_schema); assert!(matches!(result, Ok(Operation::Select(_, _, _)))); @@ -579,7 +577,7 @@ mod tests { assert!(table_position == users_position); assert!(column_selection == vec![id, name, age]); - assert!(condition == Some(operation::Condition::Eq(age, Indexable(Int(25))))); + assert!(condition == Some(operation::Condition::Eq(age, Value::Int(25)))); } #[test] @@ -634,7 +632,7 @@ mod tests { let syntax: RawQuerySyntax = Select( "users".to_string(), ColumnSelection::All, - Some(Eq("does_not_exist".to_string(), Indexable(Int(25)))), + Some(Eq("does_not_exist".to_string(), Value::Int(25))), ); let result = validate_operation(syntax, &db_schema); assert!(matches!(result, Err(ValidationError::ColumnsDoNotExist(_)))); @@ -648,7 +646,7 @@ mod tests { let syntax: RawQuerySyntax = Select( "users".to_string(), ColumnSelection::All, - Some(Eq("age".to_string(), Indexable(String("25".to_string())))), + Some(Eq("age".to_string(), Value::String("25".to_string()))), ); let result = validate_operation(syntax, &db_schema); assert!(matches!(result, Err(ValidationError::TypeMismatch { .. }))); @@ -665,9 +663,9 @@ mod tests { let syntax: RawQuerySyntax = Insert( "users".to_string(), vec![ - ("name".to_string(), Indexable(String("Alice".to_string()))), - ("id".to_string(), Indexable(Uuid(0))), - ("age".to_string(), Indexable(Int(25))), + ("name".to_string(), Value::String("Alice".to_string())), + ("id".to_string(), Value::Uuid(0)), + ("age".to_string(), Value::Int(25)), ], ); let result = validate_operation(syntax, &db_schema); @@ -685,9 +683,9 @@ mod tests { assert!( values == vec![ - Indexable(Uuid(0)), - Indexable(String("Alice".to_string())), - Indexable(Int(25)) + Value::Uuid(0), + Value::String("Alice".to_string()), + Value::Int(25) ] ); } @@ -700,10 +698,10 @@ mod tests { let syntax: RawQuerySyntax = Insert( "users".to_string(), vec![ - ("name".to_string(), Indexable(String("Alice".to_string()))), - ("id".to_string(), Indexable(Uuid(0))), - ("age".to_string(), Indexable(Int(25))), - ("does_not_exist".to_string(), Indexable(Int(25))), + ("name".to_string(), Value::String("Alice".to_string())), + ("id".to_string(), Value::Uuid(0)), + ("age".to_string(), Value::Int(25)), + ("does_not_exist".to_string(), Value::Int(25)), ], ); let result = validate_operation(syntax, &db_schema); @@ -718,9 +716,9 @@ mod tests { let syntax: RawQuerySyntax = Insert( "users".to_string(), vec![ - ("name".to_string(), Indexable(String("Alice".to_string()))), - ("id".to_string(), Indexable(Uuid(0))), - ("age".to_string(), Number(25.0)), + ("name".to_string(), Value::String("Alice".to_string())), + ("id".to_string(), Value::Uuid(0)), + ("age".to_string(), Value::Number(25.0)), ], ); let result = validate_operation(syntax, &db_schema); @@ -756,7 +754,7 @@ mod tests { let syntax: RawQuerySyntax = Delete( "users".to_string(), - Some(Eq("age".to_string(), Indexable(Int(25)))), + Some(Eq("age".to_string(), Value::Int(25))), ); let result = validate_operation(syntax, &db_schema); assert!(matches!( @@ -772,7 +770,7 @@ mod tests { assert!(table_position == users_position); assert!(column == age); - assert!(value == Indexable(Int(25))); + assert!(value == Value::Int(25)); } // ====CreateIndex==== From 22a2730b960f6e015f1e11c232e2a67a883bc82c Mon Sep 17 00:00:00 2001 From: Yuriy Dupyn <2153100+omedusyo@users.noreply.github.com> Date: Thu, 1 Feb 2024 14:57:45 +0100 Subject: [PATCH 03/13] Make test compile --- minisql/src/interpreter.rs | 236 ++++++++++++++++------------------- minisql/src/type_system.rs | 21 ++-- parser/src/parsing/common.rs | 4 +- parser/src/parsing/insert.rs | 10 +- 4 files changed, 128 insertions(+), 143 deletions(-) diff --git a/minisql/src/interpreter.rs b/minisql/src/interpreter.rs index 699265d..2e672d1 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::{TableName, TablePosition, TableSchema}; +use crate::schema::{Column, TableName, TablePosition, TableSchema}; use bimap::BiMap; use serde::{Deserialize, Serialize}; @@ -212,9 +212,6 @@ mod tests { #[test] fn test_insert_select_basic1() { - use IndexableValue::*; - use Value::*; - let mut state = State::new(); let users_schema = users_schema(); let users = 0; @@ -224,9 +221,9 @@ mod tests { .unwrap(); let (id, name, age) = ( - Indexable(Uuid(0)), - Indexable(String("Plato".to_string())), - Indexable(Int(64)), + Value::Uuid(0), + Value::String("Plato".to_string()), + Value::Int(64), ); state .interpret(Operation::Insert( @@ -256,9 +253,7 @@ mod tests { #[test] fn test_insert_select_basic2() { use Condition::*; - use IndexableValue::*; use Operation::*; - use Value::*; let mut state = State::new(); let users_schema = users_schema(); @@ -270,9 +265,9 @@ mod tests { state.interpret(CreateTable(users_schema.clone())).unwrap(); let (id0, name0, age0) = ( - Indexable(Uuid(0)), - Indexable(String("Plato".to_string())), - Indexable(Int(64)), + Value::Uuid(0), + Value::String("Plato".to_string()), + Value::Int(64), ); state .interpret(Insert( @@ -282,9 +277,9 @@ mod tests { .unwrap(); let (id1, name1, age1) = ( - Indexable(Uuid(1)), - Indexable(String("Aristotle".to_string())), - Indexable(Int(20)), + Value::Uuid(1), + Value::String("Aristotle".to_string()), + Value::Int(20), ); state .interpret(Insert( @@ -366,9 +361,7 @@ mod tests { #[test] fn test_delete() { use Condition::*; - use IndexableValue::*; use Operation::*; - use Value::*; let mut state = State::new(); let users_schema = users_schema(); @@ -379,9 +372,9 @@ mod tests { state.interpret(CreateTable(users_schema.clone())).unwrap(); let (id0, name0, age0) = ( - Indexable(Uuid(0)), - Indexable(String("Plato".to_string())), - Indexable(Int(64)), + Value::Uuid(0), + Value::String("Plato".to_string()), + Value::Int(64), ); state .interpret(Insert( @@ -391,9 +384,9 @@ mod tests { .unwrap(); let (id1, name1, age1) = ( - Indexable(Uuid(1)), - Indexable(String("Aristotle".to_string())), - Indexable(Int(20)), + Value::Uuid(1), + Value::String("Aristotle".to_string()), + Value::Int(20), ); state .interpret(Insert( @@ -429,9 +422,7 @@ mod tests { #[test] fn test_index() { - use IndexableValue::*; use Operation::*; - use Value::*; let mut state = State::new(); let users_schema = users_schema(); @@ -446,9 +437,9 @@ mod tests { .unwrap(); let (id0, name0, age0) = ( - Indexable(Uuid(0)), - Indexable(String("Plato".to_string())), - Indexable(Int(64)), + Value::Uuid(0), + Value::String("Plato".to_string()), + Value::Int(64), ); state .interpret(Insert( @@ -458,9 +449,9 @@ mod tests { .unwrap(); let (id1, name1, age1) = ( - Indexable(Uuid(1)), - Indexable(String("Aristotle".to_string())), - Indexable(Int(20)), + Value::Uuid(1), + Value::String("Aristotle".to_string()), + Value::Int(20), ); state .interpret(Insert( @@ -482,7 +473,7 @@ mod tests { let aristotle_id = 1; let plato_ids = index - .get(&String("Plato".to_string())) + .get(&IndexableValue::String("Plato".to_string())) .cloned() .unwrap_or(HashSet::new()); assert!(plato_ids.contains(&plato_id)); @@ -491,103 +482,98 @@ mod tests { } } -// TODO -// pub fn example() { -// use crate::type_system::{DbType, IndexableValue, Value}; -// use Condition::*; -// use IndexableValue::*; -// use Operation::*; -// use Value::*; +pub fn example() { + use crate::type_system::{DbType, Value}; + use Condition::*; + use Operation::*; -// 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) = ( + Value::Uuid(0), + Value::String("Plato".to_string()), + Value::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) = ( + Value::Uuid(1), + Value::String("Aristotle".to_string()), + Value::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!(); -// } -// } + { + { + 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/type_system.rs b/minisql/src/type_system.rs index 1327be2..0d3d4b6 100644 --- a/minisql/src/type_system.rs +++ b/minisql/src/type_system.rs @@ -239,9 +239,8 @@ impl TryFrom for Value { #[cfg(test)] mod tests { - use super::{IndexableValue, Value}; + use super::Value; use crate::error::TypeConversionError::UnknownType; - use crate::type_system::Value::{Indexable, Number}; #[test] fn test_encode_number() { @@ -261,7 +260,7 @@ mod tests { #[test] fn test_encode_string() { - let value = Value::Indexable(IndexableValue::String("hello".to_string())); + let value = Value::String("hello".to_string()); let vtype = value.to_type(); let oid = vtype.type_oid(); let size = vtype.type_size(); @@ -277,7 +276,7 @@ mod tests { #[test] fn test_encode_string_utf8() { - let value = Value::Indexable(IndexableValue::String("#速度与激情9 早上好中国 现在我有冰激淋 我很喜欢冰激淋 但是《速度与激情9》比冰激淋 🍧🍦🍨".to_string())); + let value = Value::String("#速度与激情9 早上好中国 现在我有冰激淋 我很喜欢冰激淋 但是《速度与激情9》比冰激淋 🍧🍦🍨".to_string()); let vtype = value.to_type(); let oid = vtype.type_oid(); let size = vtype.type_size(); @@ -293,7 +292,7 @@ mod tests { #[test] fn test_encode_int() { - let value = Value::Indexable(IndexableValue::Int(123)); + let value = Value::Int(123); let vtype = value.to_type(); let oid = vtype.type_oid(); let size = vtype.type_size(); @@ -309,7 +308,7 @@ mod tests { #[test] fn test_encode_uuid() { - let value = Value::Indexable(IndexableValue::Uuid(123)); + let value = Value::Uuid(123); let vtype = value.to_type(); let oid = vtype.type_oid(); let size = vtype.type_size(); @@ -325,7 +324,7 @@ mod tests { #[test] fn test_mismatched_size() { - let value = Value::Indexable(IndexableValue::Uuid(123)); + let value = Value::Uuid(123); let vtype = value.to_type(); let oid = vtype.type_oid(); let size = 8; @@ -342,13 +341,13 @@ mod tests { #[test] fn test_value_stringification() { let pairs = vec![ - (Number(1.0), "Number(1)"), + (Value::Number(1.0), "Number(1)"), ( - Indexable(IndexableValue::String("hello".to_string())), + Value::String("hello".to_string()), "Indexable(String(hello))", ), - (Indexable(IndexableValue::Int(123)), "Indexable(Int(123))"), - (Indexable(IndexableValue::Uuid(123)), "Indexable(Uuid(123))"), + (Value::Int(123), "Indexable(Int(123))"), + (Value::Uuid(123), "Indexable(Uuid(123))"), ]; for (value, string) in pairs { diff --git a/parser/src/parsing/common.rs b/parser/src/parsing/common.rs index 508289e..fac2a0a 100644 --- a/parser/src/parsing/common.rs +++ b/parser/src/parsing/common.rs @@ -82,11 +82,11 @@ mod tests { #[test] fn test_parse_equality() { - use minisql::type_system::{IndexableValue, Value}; + use minisql::type_system::Value; match parse_equality("id = 1") { Ok(("", Condition::Eq(column_name, value))) => { assert!(column_name.eq("id")); - assert_eq!(value, Value::Indexable(IndexableValue::Int(1))) + assert_eq!(value, Value::Int(1)) } _ => { panic!("should parse"); diff --git a/parser/src/parsing/insert.rs b/parser/src/parsing/insert.rs index 0b62574..2dfa061 100644 --- a/parser/src/parsing/insert.rs +++ b/parser/src/parsing/insert.rs @@ -57,7 +57,7 @@ pub fn parse_values(input: &str) -> IResult<&str, Vec> { #[cfg(test)] mod tests { - use minisql::type_system::{IndexableValue, Value}; + use minisql::type_system::Value; use super::parse_insert; use crate::syntax::RawQuerySyntax; @@ -72,10 +72,10 @@ mod tests { assert_eq!( insertion_values, vec![ - ("id".to_string(), Value::Indexable(IndexableValue::Int(1))), + ("id".to_string(), Value::Int(1)), ( "data".to_string(), - Value::Indexable(IndexableValue::String("Text".to_string())) + Value::String("Text".to_string()) ) ] ); @@ -97,10 +97,10 @@ mod tests { assert_eq!( insertion_values, vec![ - ("id".to_string(), Value::Indexable(IndexableValue::Int(1))), + ("id".to_string(), Value::Int(1)), ( "data".to_string(), - Value::Indexable(IndexableValue::String("Text".to_string())) + Value::String("Text".to_string()) ) ] ); From 6b93452d3eb7a77d7976efc3491f3980f619b81c Mon Sep 17 00:00:00 2001 From: Yuriy Dupyn <2153100+omedusyo@users.noreply.github.com> Date: Thu, 1 Feb 2024 15:22:03 +0100 Subject: [PATCH 04/13] Make IndexableValue into a total order --- minisql/src/type_system.rs | 49 +++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/minisql/src/type_system.rs b/minisql/src/type_system.rs index 0d3d4b6..8e7b0a1 100644 --- a/minisql/src/type_system.rs +++ b/minisql/src/type_system.rs @@ -3,7 +3,7 @@ use std::cmp::Ordering; use serde::{Deserialize, Serialize}; // ==============Types================ -#[derive(Debug, Clone, Ord, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum DbType { String, Int, @@ -12,13 +12,6 @@ pub enum DbType { 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; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -36,7 +29,7 @@ pub enum Value { } -#[derive(Debug, Ord, Eq, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Eq, Clone, PartialEq, Serialize, Deserialize)] #[serde(try_from = "String", into = "String")] pub enum IndexableValue { String(String), @@ -48,7 +41,42 @@ pub enum IndexableValue { impl PartialOrd for IndexableValue { fn partial_cmp(&self, other: &Self) -> Option { - todo!() + match (self, other) { + (IndexableValue::String(s0), IndexableValue::String(s1)) => s0.partial_cmp(s1), + (IndexableValue::Int(n0), IndexableValue::Int(n1)) => n0.partial_cmp(n1), + (IndexableValue::Uuid(id0), IndexableValue::Uuid(id1)) => id0.partial_cmp(id1), + (IndexableValue::None(_), IndexableValue::None(_)) => Some(Ordering::Equal), + (IndexableValue::None(_), IndexableValue::Some(_)) => Some(Ordering::Less), + (IndexableValue::Some(_), IndexableValue::None(_)) => Some(Ordering::Greater), + (IndexableValue::Some(v0), IndexableValue::Some(v1)) => v0.partial_cmp(v1), + _ => None + } + } +} + +impl Ord for IndexableValue { + fn cmp(&self, other: &Self) -> Ordering { + match (self, other) { + (IndexableValue::String(s0), IndexableValue::String(s1)) => s0.cmp(s1), + (IndexableValue::Int(n0), IndexableValue::Int(n1)) => n0.cmp(n1), + (IndexableValue::Uuid(id0), IndexableValue::Uuid(id1)) => id0.cmp(id1), + (IndexableValue::None(_), IndexableValue::None(_)) => Ordering::Equal, + (IndexableValue::None(_), IndexableValue::Some(_)) => Ordering::Less, + (IndexableValue::Some(_), IndexableValue::None(_)) => Ordering::Greater, + (IndexableValue::Some(v0), IndexableValue::Some(v1)) => v0.cmp(v1), + _ => + // SAFETY: + // We are using indexable values as keys in key-value maps. + // When validation is done, it can't happen that we will be comparing two values + // of different types. + // Ofcourse another option is to artificialy order e.g. + // None < Some(...) < String < Int < Uuid + // where ... is again None < Some(...) < String < Int < Uuid + // where ... + // infinitely deep total order. But this is pointless for our usecase. + unreachable!() + } + } } @@ -156,7 +184,6 @@ fn indexable_value_to_string(value: IndexableValue) -> String { 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(), } From d4a941a7383442535547618f28dd3fdad0129447 Mon Sep 17 00:00:00 2001 From: Yuriy Dupyn <2153100+omedusyo@users.noreply.github.com> Date: Thu, 1 Feb 2024 15:50:19 +0100 Subject: [PATCH 05/13] Write new parser/formatters for Values/IndexableValues --- minisql/src/type_system.rs | 196 ++++++++++++++++++++++++++----------- 1 file changed, 140 insertions(+), 56 deletions(-) diff --git a/minisql/src/type_system.rs b/minisql/src/type_system.rs index 8e7b0a1..3113cd6 100644 --- a/minisql/src/type_system.rs +++ b/minisql/src/type_system.rs @@ -179,16 +179,134 @@ impl Value { } } +// ===formatting=== +fn db_type_to_string(type_: DbType) -> String { + match type_ { + DbType::String => format!("String()"), + DbType::Int => format!("Int()"), + DbType::Number => format!("Number()"), + DbType::Uuid => format!("Uuid()"), + DbType::Option(type_) => format!("Option({})", db_type_to_string(*type_)), + } +} + 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) => todo!(), - IndexableValue::None(type_) => "None()".to_string(), + IndexableValue::Some(val) => format!("Some({})", indexable_value_to_string(*val)), + IndexableValue::None(_type_) => "None()".to_string(), } } +fn value_to_string(value: Value) -> String { + match value { + Value::Number(x) => format!("Number({x})"), + Value::String(s) => format!("String({s})"), + Value::Int(i) => format!("Int({i})"), + Value::Uuid(u) => format!("Uuid({u})"), + Value::Some(val) => format!("Some({})", value_to_string(*val)), + Value::None(_type_) => "None()".to_string(), + } +} + +// ===parsing=== +fn parse_db_type(str: &str) -> Result { + if !str.ends_with(')') { + return Err(format!("Invalid DbType: {}", str)); + } + + if str.starts_with("String(") { + Ok(DbType::String) + } else if str.starts_with("Int(") { + Ok(DbType::Int) + } else if str.starts_with("Number(") { + Ok(DbType::Number) + } else if str.starts_with("Uuid(") { + Ok(DbType::Uuid) + } else if str.starts_with("Option(") { + let s = &str[7..str.len() - 1]; + Ok(DbType::Option(Box::new(parse_db_type(s)?))) + } else { + Err(format!("Invalid DbType: {}", str)) + } +} + +fn parse_value(str: &str) -> Result { + if !str.ends_with(')') { + return Err(format!("Invalid IndexableValue: {}", str)); + } + + if str.starts_with("Number(") { + let s = str[7..str.len() - 1].to_string(); + let n = s + .parse::() + .map_err(|e| format!("Invalid Number: {}", e))?; + Ok(Value::Number(n)) + } else if str.starts_with("String(") { + let s = str[7..str.len() - 1].to_string(); + Ok(Value::String(s)) + } else if str.starts_with("Int(") { + let s = str[4..str.len() - 1].to_string(); + let i = s + .parse::() + .map_err(|e| format!("Invalid Int: {}", e))?; + Ok(Value::Int(i)) + } else if str.starts_with("Uuid(") { + let s = str[5..str.len() - 1].to_string(); + let u = s + .parse::() + .map_err(|e| format!("Invalid UUID: {}", e))?; + Ok(Value::Uuid(u)) + } else if str.starts_with("Some(") { + let s = str[5..str.len() - 1].to_string(); + let val: Value = parse_value(&s)?; + Ok(Value::Some(Box::new(val))) + } else if str.starts_with("None(") { + let s = str[5..str.len() - 1].to_string(); + let type_: DbType = TryFrom::try_from(s)?; + Ok(Value::None(type_)) + } else { + Err(format!("Invalid IndexableValue: {}", str)) + } +} + +fn parse_indexable_value(str: &str) -> Result { + if !str.ends_with(')') { + return Err(format!("Invalid IndexableValue: {}", str)); + } + + if str.starts_with("String(") { + let s = str[7..str.len() - 1].to_string(); + Ok(IndexableValue::String(s)) + } else if str.starts_with("Int(") { + let s = str[4..str.len() - 1].to_string(); + let i = s + .parse::() + .map_err(|e| format!("Invalid Int: {}", e))?; + Ok(IndexableValue::Int(i)) + } else if str.starts_with("Uuid(") { + let s = str[5..str.len() - 1].to_string(); + let u = s + .parse::() + .map_err(|e| format!("Invalid UUID: {}", e))?; + Ok(IndexableValue::Uuid(u)) + } else if str.starts_with("Some(") { + let s = str[5..str.len() - 1].to_string(); + let val: IndexableValue = parse_indexable_value(&s)?; + Ok(IndexableValue::Some(Box::new(val))) + } else if str.starts_with("None(") { + let s = str[5..str.len() - 1].to_string(); + let type_: DbType = parse_db_type(&s)?; + Ok(IndexableValue::None(type_)) + } else { + Err(format!("Invalid IndexableValue: {}", str)) + } +} + + + // Own string serialization so enums can be used as keys in maps impl From for String { fn from(value: IndexableValue) -> Self { @@ -196,71 +314,37 @@ impl From for String { } } -impl TryFrom for IndexableValue { - type Error = String; - fn try_from(value: String) -> Result { - if !value.ends_with(')') { - return Err(format!("Invalid IndexableValue: {}", value)); - } - - if value.starts_with("String(") { - let s = value[7..value.len() - 1].to_string(); - 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))?; - 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))?; - 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)) - } +impl From for String { + fn from(type_: DbType) -> Self { + db_type_to_string(type_) } } +impl TryFrom for DbType { + type Error = String; + fn try_from(str: String) -> Result { + parse_db_type(&str) + } +} + +impl TryFrom for IndexableValue { + type Error = String; + fn try_from(str: String) -> Result { + parse_indexable_value(&str) + } +} + + 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)), - // } - todo!() + value_to_string(value) } } impl TryFrom for Value { type Error = String; fn try_from(value: String) -> Result { - if !value.ends_with(')') { - 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))?; - // 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)) - // } - - todo!() + parse_value(&value) } } From ae4061980ee41b2d1057dc59821855a8cd93d3c0 Mon Sep 17 00:00:00 2001 From: Yuriy Dupyn <2153100+omedusyo@users.noreply.github.com> Date: Thu, 1 Feb 2024 15:51:31 +0100 Subject: [PATCH 06/13] white space --- minisql/src/type_system.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/minisql/src/type_system.rs b/minisql/src/type_system.rs index 3113cd6..92cabb4 100644 --- a/minisql/src/type_system.rs +++ b/minisql/src/type_system.rs @@ -334,7 +334,6 @@ impl TryFrom for IndexableValue { } } - impl From for String { fn from(value: Value) -> Self { value_to_string(value) From af5490e2dcf6c3fe1c2e1cdf1f31cd4de2e652aa Mon Sep 17 00:00:00 2001 From: Yuriy Dupyn <2153100+omedusyo@users.noreply.github.com> Date: Thu, 1 Feb 2024 17:14:50 +0100 Subject: [PATCH 07/13] Cleanup --- minisql/src/type_system.rs | 272 ++++++++++++++++++++----------------- 1 file changed, 144 insertions(+), 128 deletions(-) diff --git a/minisql/src/type_system.rs b/minisql/src/type_system.rs index 92cabb4..08a2352 100644 --- a/minisql/src/type_system.rs +++ b/minisql/src/type_system.rs @@ -28,7 +28,6 @@ pub enum Value { None(DbType), } - #[derive(Debug, Eq, Clone, PartialEq, Serialize, Deserialize)] #[serde(try_from = "String", into = "String")] pub enum IndexableValue { @@ -91,6 +90,37 @@ impl DbType { } } + fn to_json_key_string(self) -> String { + match self { + DbType::String => format!("String()"), + DbType::Int => format!("Int()"), + DbType::Number => format!("Number()"), + DbType::Uuid => format!("Uuid()"), + DbType::Option(type_) => format!("Option({})", type_.to_json_key_string()), + } + } + + fn parse_json_key_string(str: &str) -> Result { + if !str.ends_with(')') { + return Err(format!("Invalid DbType: {}", str)); + } + + if str.starts_with("String(") { + Ok(DbType::String) + } else if str.starts_with("Int(") { + Ok(DbType::Int) + } else if str.starts_with("Number(") { + Ok(DbType::Number) + } else if str.starts_with("Uuid(") { + Ok(DbType::Uuid) + } else if str.starts_with("Option(") { + let s = &str[7..str.len() - 1]; + Ok(DbType::Option(Box::new(Self::parse_json_key_string(s)?))) + } else { + Err(format!("Invalid DbType: {}", str)) + } + } + pub fn type_oid(&self) -> i32 { // match self { // Self::String => 25, @@ -135,6 +165,58 @@ impl Value { } } + fn to_json_key_string(self) -> String { + match self { + Self::Number(x) => format!("Number({x})"), + Self::String(s) => format!("String({s})"), + Self::Int(i) => format!("Int({i})"), + Self::Uuid(u) => format!("Uuid({u})"), + Self::Some(val) => format!("Some({})", val.to_json_key_string()), + Self::None(_type_) => "None()".to_string(), + } + } + + // We don't really need this, since only indexable values are keys in maps. It is here for consistency. + fn parse_json_key_string(str: &str) -> Result { + if !str.ends_with(')') { + return Err(format!("Invalid IndexableValue: {}", str)); + } + + if str.starts_with("Number(") { + let s = str[7..str.len() - 1].to_string(); + let n = s + .parse::() + .map_err(|e| format!("Invalid Number: {}", e))?; + Ok(Self::Number(n)) + } else if str.starts_with("String(") { + let s = str[7..str.len() - 1].to_string(); + Ok(Self::String(s)) + } else if str.starts_with("Int(") { + let s = str[4..str.len() - 1].to_string(); + let i = s + .parse::() + .map_err(|e| format!("Invalid Int: {}", e))?; + Ok(Self::Int(i)) + } else if str.starts_with("Uuid(") { + let s = str[5..str.len() - 1].to_string(); + let u = s + .parse::() + .map_err(|e| format!("Invalid UUID: {}", e))?; + Ok(Self::Uuid(u)) + } else if str.starts_with("Some(") { + let s = str[5..str.len() - 1].to_string(); + let val: Value = Self::parse_json_key_string(&s)?; + Ok(Self::Some(Box::new(val))) + } else if str.starts_with("None(") { + let s = str[5..str.len() - 1].to_string(); + let type_: DbType = TryFrom::try_from(s)?; + Ok(Value::None(type_)) + } else { + Err(format!("Invalid IndexableValue: {}", str)) + } + } + + pub fn as_text_bytes(&self) -> Vec { // match self { // Self::Number(n) => format!("{n}").into_bytes(), @@ -179,171 +261,105 @@ impl Value { } } -// ===formatting=== -fn db_type_to_string(type_: DbType) -> String { - match type_ { - DbType::String => format!("String()"), - DbType::Int => format!("Int()"), - DbType::Number => format!("Number()"), - DbType::Uuid => format!("Uuid()"), - DbType::Option(type_) => format!("Option({})", db_type_to_string(*type_)), +impl IndexableValue { + fn to_value(self) -> Value { + match self { + IndexableValue::String(str) => Value::String(str), + IndexableValue::Int(n) => Value::Int(n), + IndexableValue::Uuid(id) => Value::Uuid(id), + IndexableValue::Some(indexable_value) => Value::Some(Box::new(indexable_value.to_value())), + IndexableValue::None(type_) => Value::None(type_), + } + } + + fn to_json_key_string(self) -> String { + match self { + IndexableValue::String(s) => format!("String({s})"), + IndexableValue::Int(i) => format!("Int({i})"), + IndexableValue::Uuid(u) => format!("Uuid({u})"), + IndexableValue::Some(val) => format!("Some({})", val.to_json_key_string()), + IndexableValue::None(_type_) => "None()".to_string(), + } + } + + fn parse_json_key_string(str: &str) -> Result { + if !str.ends_with(')') { + return Err(format!("Invalid IndexableValue: {}", str)); + } + + if str.starts_with("String(") { + let s = str[7..str.len() - 1].to_string(); + Ok(IndexableValue::String(s)) + } else if str.starts_with("Int(") { + let s = str[4..str.len() - 1].to_string(); + let i = s + .parse::() + .map_err(|e| format!("Invalid Int: {}", e))?; + Ok(IndexableValue::Int(i)) + } else if str.starts_with("Uuid(") { + let s = str[5..str.len() - 1].to_string(); + let u = s + .parse::() + .map_err(|e| format!("Invalid UUID: {}", e))?; + Ok(IndexableValue::Uuid(u)) + } else if str.starts_with("Some(") { + let s = str[5..str.len() - 1].to_string(); + let val: IndexableValue = Self::parse_json_key_string(&s)?; + Ok(IndexableValue::Some(Box::new(val))) + } else if str.starts_with("None(") { + let s = str[5..str.len() - 1].to_string(); + let type_: DbType = DbType::parse_json_key_string(&s)?; + Ok(IndexableValue::None(type_)) + } else { + Err(format!("Invalid IndexableValue: {}", str)) + } } } -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::None(_type_) => "None()".to_string(), +impl From for Value { + fn from(indexable_value: IndexableValue) -> Self { + indexable_value.to_value() } } -fn value_to_string(value: Value) -> String { - match value { - Value::Number(x) => format!("Number({x})"), - Value::String(s) => format!("String({s})"), - Value::Int(i) => format!("Int({i})"), - Value::Uuid(u) => format!("Uuid({u})"), - Value::Some(val) => format!("Some({})", value_to_string(*val)), - Value::None(_type_) => "None()".to_string(), - } -} - -// ===parsing=== -fn parse_db_type(str: &str) -> Result { - if !str.ends_with(')') { - return Err(format!("Invalid DbType: {}", str)); - } - - if str.starts_with("String(") { - Ok(DbType::String) - } else if str.starts_with("Int(") { - Ok(DbType::Int) - } else if str.starts_with("Number(") { - Ok(DbType::Number) - } else if str.starts_with("Uuid(") { - Ok(DbType::Uuid) - } else if str.starts_with("Option(") { - let s = &str[7..str.len() - 1]; - Ok(DbType::Option(Box::new(parse_db_type(s)?))) - } else { - Err(format!("Invalid DbType: {}", str)) - } -} - -fn parse_value(str: &str) -> Result { - if !str.ends_with(')') { - return Err(format!("Invalid IndexableValue: {}", str)); - } - - if str.starts_with("Number(") { - let s = str[7..str.len() - 1].to_string(); - let n = s - .parse::() - .map_err(|e| format!("Invalid Number: {}", e))?; - Ok(Value::Number(n)) - } else if str.starts_with("String(") { - let s = str[7..str.len() - 1].to_string(); - Ok(Value::String(s)) - } else if str.starts_with("Int(") { - let s = str[4..str.len() - 1].to_string(); - let i = s - .parse::() - .map_err(|e| format!("Invalid Int: {}", e))?; - Ok(Value::Int(i)) - } else if str.starts_with("Uuid(") { - let s = str[5..str.len() - 1].to_string(); - let u = s - .parse::() - .map_err(|e| format!("Invalid UUID: {}", e))?; - Ok(Value::Uuid(u)) - } else if str.starts_with("Some(") { - let s = str[5..str.len() - 1].to_string(); - let val: Value = parse_value(&s)?; - Ok(Value::Some(Box::new(val))) - } else if str.starts_with("None(") { - let s = str[5..str.len() - 1].to_string(); - let type_: DbType = TryFrom::try_from(s)?; - Ok(Value::None(type_)) - } else { - Err(format!("Invalid IndexableValue: {}", str)) - } -} - -fn parse_indexable_value(str: &str) -> Result { - if !str.ends_with(')') { - return Err(format!("Invalid IndexableValue: {}", str)); - } - - if str.starts_with("String(") { - let s = str[7..str.len() - 1].to_string(); - Ok(IndexableValue::String(s)) - } else if str.starts_with("Int(") { - let s = str[4..str.len() - 1].to_string(); - let i = s - .parse::() - .map_err(|e| format!("Invalid Int: {}", e))?; - Ok(IndexableValue::Int(i)) - } else if str.starts_with("Uuid(") { - let s = str[5..str.len() - 1].to_string(); - let u = s - .parse::() - .map_err(|e| format!("Invalid UUID: {}", e))?; - Ok(IndexableValue::Uuid(u)) - } else if str.starts_with("Some(") { - let s = str[5..str.len() - 1].to_string(); - let val: IndexableValue = parse_indexable_value(&s)?; - Ok(IndexableValue::Some(Box::new(val))) - } else if str.starts_with("None(") { - let s = str[5..str.len() - 1].to_string(); - let type_: DbType = parse_db_type(&s)?; - Ok(IndexableValue::None(type_)) - } else { - Err(format!("Invalid IndexableValue: {}", str)) - } -} - - // Own string serialization so enums can be used as keys in maps impl From for String { fn from(value: IndexableValue) -> Self { - indexable_value_to_string(value) + value.to_json_key_string() } } impl From for String { fn from(type_: DbType) -> Self { - db_type_to_string(type_) + type_.to_json_key_string() } } impl TryFrom for DbType { type Error = String; fn try_from(str: String) -> Result { - parse_db_type(&str) + Self::parse_json_key_string(&str) } } impl TryFrom for IndexableValue { type Error = String; fn try_from(str: String) -> Result { - parse_indexable_value(&str) + Self::parse_json_key_string(&str) } } impl From for String { fn from(value: Value) -> Self { - value_to_string(value) + value.to_json_key_string() } } impl TryFrom for Value { type Error = String; fn try_from(value: String) -> Result { - parse_value(&value) + Value::parse_json_key_string(&value) } } From 896d40ab64e4ab8239aadc1240b35ffe8c66edaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jind=C5=99ich=20Moravec?= Date: Thu, 1 Feb 2024 21:22:32 +0100 Subject: [PATCH 08/13] feat: encoding of nested option types --- minisql/src/type_system.rs | 197 +++++++++++++++++++++++++------------ 1 file changed, 132 insertions(+), 65 deletions(-) diff --git a/minisql/src/type_system.rs b/minisql/src/type_system.rs index 08a2352..35588cd 100644 --- a/minisql/src/type_system.rs +++ b/minisql/src/type_system.rs @@ -1,6 +1,7 @@ use crate::error::TypeConversionError; -use std::cmp::Ordering; use serde::{Deserialize, Serialize}; +use std::cmp::Ordering; +use std::fmt::format; // ==============Types================ #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -9,7 +10,7 @@ pub enum DbType { Int, Number, Uuid, - Option(Box) + Option(Box), } // ==============Values================ @@ -48,7 +49,7 @@ impl PartialOrd for IndexableValue { (IndexableValue::None(_), IndexableValue::Some(_)) => Some(Ordering::Less), (IndexableValue::Some(_), IndexableValue::None(_)) => Some(Ordering::Greater), (IndexableValue::Some(v0), IndexableValue::Some(v1)) => v0.partial_cmp(v1), - _ => None + _ => None, } } } @@ -64,29 +65,38 @@ impl Ord for IndexableValue { (IndexableValue::Some(_), IndexableValue::None(_)) => Ordering::Greater, (IndexableValue::Some(v0), IndexableValue::Some(v1)) => v0.cmp(v1), _ => - // SAFETY: - // We are using indexable values as keys in key-value maps. - // When validation is done, it can't happen that we will be comparing two values - // of different types. - // Ofcourse another option is to artificialy order e.g. - // None < Some(...) < String < Int < Uuid - // where ... is again None < Some(...) < String < Int < Uuid - // where ... - // infinitely deep total order. But this is pointless for our usecase. - unreachable!() + // SAFETY: + // We are using indexable values as keys in key-value maps. + // When validation is done, it can't happen that we will be comparing two values + // of different types. + // Ofcourse another option is to artificialy order e.g. + // None < Some(...) < String < Int < Uuid + // where ... is again None < Some(...) < String < Int < Uuid + // where ... + // infinitely deep total order. But this is pointless for our usecase. + { + unreachable!() + } } - } } impl DbType { + fn new_n_option(n: usize, inside: DbType) -> DbType { + if n == 0 { + inside + } else { + DbType::Option(Box::new(DbType::new_n_option(n - 1, inside))) + } + } + pub fn is_indexable(&self) -> bool { match self { Self::String => true, Self::Int => true, Self::Number => false, Self::Uuid => true, - Self::Option(type_) => type_.is_indexable() + Self::Option(type_) => type_.is_indexable(), } } @@ -121,24 +131,39 @@ impl DbType { } } + fn from_oid_and_size(oid: i32, size: i16) -> Option { + match (oid, size) { + (25, -2) => Some(DbType::String), + (23, 8) => Some(DbType::Int), + (701, 8) => Some(DbType::Number), + (2950, 16) => Some(DbType::Uuid), + _ => None, + } + } + pub fn type_oid(&self) -> i32 { - // match self { - // Self::String => 25, - // Self::Int => 23, - // Self::Number => 701, - // Self::Uuid => 2950, - // } - todo!() + match self { + Self::String => 25, + Self::Int => 23, + Self::Number => 701, + Self::Uuid => 2950, + Self::Option(t) => { + let oid = t.type_oid(); + let type_part = (oid & 0x0000FFFF) as u16; + let nest_part = ((oid & 0xFFFF0000) >> 16) as u16 + 1; + (type_part | (nest_part << 16)) as i32 + } + } } pub fn type_size(&self) -> i16 { - // match self { - // Self::String => -2, // null terminated string - // Self::Int => 8, - // Self::Number => 8, - // Self::Uuid => 16, - // } - todo!() + match self { + Self::String => -2, // null terminated string + Self::Int => 8, + Self::Number => 8, + Self::Uuid => 16, + Self::Option(t) => t.type_size(), + } } } @@ -216,17 +241,20 @@ impl Value { } } - 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!() + match self { + Self::Number(n) => format!("{n}").into_bytes(), + Self::String(s) => format!("{s}\0").into_bytes(), + Self::Int(i) => format!("{i}").into_bytes(), + Self::Uuid(u) => format!("u{u}").into_bytes(), + Self::None(_) => b"None".into(), + Self::Some(val) => { + let mut bytes = Vec::from(b"Some("); + bytes.append(&mut val.as_text_bytes()); + bytes.push(b')'); + bytes + } + } } pub fn from_text_bytes( @@ -234,30 +262,68 @@ impl Value { 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 }), - // } - todo!() + let text = std::str::from_utf8(bytes)?; + let type_part = (type_oid & 0x0000FFFF) as u16; + let nest_part = ((type_oid as u32 & 0xFFFF0000) >> 16) as u16; + + Self::internal_from_text_bytes(text, type_part, nest_part, type_size) + } + + fn internal_from_text_bytes( + text: &str, + type_part: u16, + nest_part: u16, + type_size: i16, + ) -> Result { + if text == "None" { + let db_type = + DbType::from_oid_and_size(type_part as i32, type_size).ok_or_else(|| { + TypeConversionError::UnknownType { + oid: type_part as i32, + size: type_size, + } + })?; + return if nest_part == 0 { + Ok(Value::None(db_type)) + } else { + Ok(Value::None(DbType::new_n_option( + nest_part as usize, + db_type, + ))) + }; + } + + if text.starts_with("Some(") { + let val = Self::from_text_bytes( + &text[5..text.len() - 1].as_bytes(), + type_part as i32, + type_size, + )?; + return Ok(Value::Some(Box::new(val))); + } + + match (type_part, type_size) { + (701, 8) => { + let n = text.parse::()?; + Ok(Value::Number(n)) + } + (25, -2) => { + let s = &text[..text.len() - 1]; // remove null terminator + Ok(Value::String(s.to_string())) + } + (23, 8) => { + let n = text.parse::()?; + Ok(Value::Int(n)) + } + (2950, 16) => { + let n = text.parse::()?; + Ok(Value::Uuid(n)) + } + (oid, size) => Err(TypeConversionError::UnknownType { + oid: oid as i32, + size, + }), + } } } @@ -267,7 +333,9 @@ impl IndexableValue { IndexableValue::String(str) => Value::String(str), IndexableValue::Int(n) => Value::Int(n), IndexableValue::Uuid(id) => Value::Uuid(id), - IndexableValue::Some(indexable_value) => Value::Some(Box::new(indexable_value.to_value())), + IndexableValue::Some(indexable_value) => { + Value::Some(Box::new(indexable_value.to_value())) + } IndexableValue::None(type_) => Value::None(type_), } } @@ -322,7 +390,6 @@ impl From for Value { } } - // Own string serialization so enums can be used as keys in maps impl From for String { fn from(value: IndexableValue) -> Self { From 14d432e8178ec832b25a766313fda0bd691458e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jind=C5=99ich=20Moravec?= Date: Thu, 1 Feb 2024 21:40:05 +0100 Subject: [PATCH 09/13] tests: add cases for nested types --- minisql/src/type_system.rs | 94 ++++++++++++++++++++++++++++++++++---- 1 file changed, 84 insertions(+), 10 deletions(-) diff --git a/minisql/src/type_system.rs b/minisql/src/type_system.rs index 35588cd..8dea3d0 100644 --- a/minisql/src/type_system.rs +++ b/minisql/src/type_system.rs @@ -1,7 +1,6 @@ use crate::error::TypeConversionError; use serde::{Deserialize, Serialize}; use std::cmp::Ordering; -use std::fmt::format; // ==============Types================ #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -150,8 +149,8 @@ impl DbType { Self::Option(t) => { let oid = t.type_oid(); let type_part = (oid & 0x0000FFFF) as u16; - let nest_part = ((oid & 0xFFFF0000) >> 16) as u16 + 1; - (type_part | (nest_part << 16)) as i32 + let nest_part = ((oid as u32 & 0xFFFF0000) >> 16) as u16 + 1; + (type_part as u32 | ((nest_part as u32) << 16)) as i32 } } } @@ -294,11 +293,8 @@ impl Value { } if text.starts_with("Some(") { - let val = Self::from_text_bytes( - &text[5..text.len() - 1].as_bytes(), - type_part as i32, - type_size, - )?; + let inner = &text[5..text.len() - 1]; + let val = Self::internal_from_text_bytes(inner, type_part, nest_part - 1, type_size)?; return Ok(Value::Some(Box::new(val))); } @@ -316,7 +312,7 @@ impl Value { Ok(Value::Int(n)) } (2950, 16) => { - let n = text.parse::()?; + let n = text[1..].parse::()?; Ok(Value::Uuid(n)) } (oid, size) => Err(TypeConversionError::UnknownType { @@ -432,7 +428,7 @@ impl TryFrom for Value { #[cfg(test)] mod tests { - use super::Value; + use super::{DbType, Value}; use crate::error::TypeConversionError::UnknownType; #[test] @@ -551,4 +547,82 @@ mod tests { assert_eq!(deserialized, Ok(value)); } } + + #[test] + fn test_encode_nested_value() { + let value = Value::Some(Box::new(Value::Some(Box::new(Value::Int(123))))); + let vtype = value.to_type(); + let oid = vtype.type_oid(); + let size = vtype.type_size(); + + let bytes = value.as_text_bytes(); + let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap(); + + assert_eq!(value, from_bytes); + + let expected_type_oid = 23; + let expected_nest_oid = 2; + let expected_oid = expected_type_oid | (expected_nest_oid << 16); + + assert_eq!(oid, expected_oid); + assert_eq!(size, 8); + } + + #[test] + fn test_encode_nested_none() { + let value = Value::Some(Box::new(Value::Some(Box::new(Value::None( + DbType::Option(Box::new(DbType::Int)), + ))))); + let vtype = value.to_type(); + let oid = vtype.type_oid(); + let size = vtype.type_size(); + + let bytes = value.as_text_bytes(); + let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap(); + + assert_eq!(value, from_bytes); + + let expected_type_oid = 23; + let expected_nest_oid = 3; + let expected_oid = expected_type_oid | (expected_nest_oid << 16); + + assert_eq!(oid, expected_oid); + assert_eq!(size, 8); + } + + #[test] + fn test_encode_surface_none() { + let value = Value::Some(Box::new(Value::None(DbType::Number))); + let vtype = value.to_type(); + let oid = vtype.type_oid(); + let size = vtype.type_size(); + + let bytes = value.as_text_bytes(); + let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap(); + + assert_eq!(value, from_bytes); + + let expected_type_oid = 701; + let expected_nest_oid = 1; + let expected_oid = expected_type_oid | (expected_nest_oid << 16); + + assert_eq!(oid, expected_oid); + assert_eq!(size, 8); + } + + #[test] + fn test_encode_none() { + let value = Value::None(DbType::Number); + let vtype = value.to_type(); + let oid = vtype.type_oid(); + let size = vtype.type_size(); + + let bytes = value.as_text_bytes(); + + let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap(); + + assert_eq!(value, from_bytes); + assert_eq!(oid, 701); + assert_eq!(size, 8); + } } From 12488e23bf0e5169fe5c323804b0a6d89796a927 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jind=C5=99ich=20Moravec?= Date: Thu, 1 Feb 2024 21:46:26 +0100 Subject: [PATCH 10/13] tests: update test case for value stringification --- minisql/src/type_system.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/minisql/src/type_system.rs b/minisql/src/type_system.rs index 8dea3d0..0ed03f4 100644 --- a/minisql/src/type_system.rs +++ b/minisql/src/type_system.rs @@ -531,12 +531,9 @@ mod tests { fn test_value_stringification() { let pairs = vec![ (Value::Number(1.0), "Number(1)"), - ( - Value::String("hello".to_string()), - "Indexable(String(hello))", - ), - (Value::Int(123), "Indexable(Int(123))"), - (Value::Uuid(123), "Indexable(Uuid(123))"), + (Value::String("hello".to_string()), "String(hello)"), + (Value::Int(123), "Int(123)"), + (Value::Uuid(123), "Uuid(123)"), ]; for (value, string) in pairs { From bdb6a955dfb652926bad342606c215ad8a5e4c8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jind=C5=99ich=20Moravec?= Date: Thu, 1 Feb 2024 21:49:23 +0100 Subject: [PATCH 11/13] tests: add asserts for the expected byte representation --- minisql/src/type_system.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/minisql/src/type_system.rs b/minisql/src/type_system.rs index 0ed03f4..a48102a 100644 --- a/minisql/src/type_system.rs +++ b/minisql/src/type_system.rs @@ -442,6 +442,7 @@ mod tests { let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap(); assert_eq!(value, from_bytes); + assert_eq!(bytes, b"123.456"); assert_eq!(oid, 701); assert_eq!(size, 8); @@ -458,6 +459,7 @@ mod tests { let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap(); assert_eq!(value, from_bytes); + assert_eq!(bytes, b"hello\0"); assert_eq!(oid, 25); assert_eq!(size, -2); @@ -490,6 +492,7 @@ mod tests { let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap(); assert_eq!(value, from_bytes); + assert_eq!(bytes, b"123"); assert_eq!(oid, 23); assert_eq!(size, 8); @@ -506,6 +509,7 @@ mod tests { let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap(); assert_eq!(value, from_bytes); + assert_eq!(bytes, b"u123"); assert_eq!(oid, 2950); assert_eq!(size, 16); @@ -556,6 +560,7 @@ mod tests { let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap(); assert_eq!(value, from_bytes); + assert_eq!(bytes, b"Some(Some(123))"); let expected_type_oid = 23; let expected_nest_oid = 2; @@ -578,6 +583,7 @@ mod tests { let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap(); assert_eq!(value, from_bytes); + assert_eq!(bytes, b"Some(Some(None))"); let expected_type_oid = 23; let expected_nest_oid = 3; @@ -598,6 +604,7 @@ mod tests { let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap(); assert_eq!(value, from_bytes); + assert_eq!(bytes, b"Some(None)"); let expected_type_oid = 701; let expected_nest_oid = 1; @@ -619,6 +626,8 @@ mod tests { let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap(); assert_eq!(value, from_bytes); + assert_eq!(bytes, b"None"); + assert_eq!(oid, 701); assert_eq!(size, 8); } From 6d1af26fa8a2778d90c18f85fd89dc55e7117c60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jind=C5=99ich=20Moravec?= Date: Thu, 1 Feb 2024 22:27:58 +0100 Subject: [PATCH 12/13] refactor: add standalone PgOid type --- Cargo.lock | 6 ++- minisql/Cargo.toml | 1 + minisql/src/error.rs | 3 +- minisql/src/type_system.rs | 81 +++++++++++++--------------- proto/Cargo.toml | 3 ++ proto/src/message/backend.rs | 13 ++--- proto/src/message/primitive/mod.rs | 1 + proto/src/message/primitive/pgoid.rs | 79 +++++++++++++++++++++++++++ server/Cargo.toml | 2 - server/src/proto_wrapper.rs | 13 +---- 10 files changed, 136 insertions(+), 66 deletions(-) create mode 100644 proto/src/message/primitive/pgoid.rs diff --git a/Cargo.lock b/Cargo.lock index 5ea9fdc..14ae99d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -284,6 +284,7 @@ name = "minisql" version = "0.1.0" dependencies = [ "bimap", + "proto", "serde", "thiserror", ] @@ -429,6 +430,9 @@ version = "0.1.0" dependencies = [ "async-trait", "bincode", + "rand", + "rand_pcg", + "rand_seeder", "thiserror", "tokio", ] @@ -568,8 +572,6 @@ dependencies = [ "parser", "proto", "rand", - "rand_pcg", - "rand_seeder", "serde_json", "tokio", ] diff --git a/minisql/Cargo.toml b/minisql/Cargo.toml index 3bfcbfa..f44d711 100644 --- a/minisql/Cargo.toml +++ b/minisql/Cargo.toml @@ -10,3 +10,4 @@ rust-version = "1.74" bimap = { version = "0.6.3", features = ["serde"] } serde = { version = "1.0.196", features = ["derive"] } thiserror = "1.0.50" +proto = { path = "../proto" } diff --git a/minisql/src/error.rs b/minisql/src/error.rs index adc2933..d74af4e 100644 --- a/minisql/src/error.rs +++ b/minisql/src/error.rs @@ -1,5 +1,6 @@ use crate::schema::{ColumnName, TableName}; use crate::type_system::Uuid; +use proto::message::primitive::pgoid::PgOid; use std::num::{ParseFloatError, ParseIntError}; use std::str::Utf8Error; use thiserror::Error; @@ -23,5 +24,5 @@ pub enum TypeConversionError { #[error("failed to parse int from text")] IntDecodeFailed(#[from] ParseIntError), #[error("unknown type with oid {oid} and size {size}")] - UnknownType { oid: i32, size: i16 }, + UnknownType { oid: PgOid, size: i16 }, } diff --git a/minisql/src/type_system.rs b/minisql/src/type_system.rs index a48102a..e30edea 100644 --- a/minisql/src/type_system.rs +++ b/minisql/src/type_system.rs @@ -1,4 +1,5 @@ use crate::error::TypeConversionError; +use proto::message::primitive::pgoid::PgOid; use serde::{Deserialize, Serialize}; use std::cmp::Ordering; @@ -130,8 +131,9 @@ impl DbType { } } - fn from_oid_and_size(oid: i32, size: i16) -> Option { - match (oid, size) { + fn from_oid_and_size(oid: PgOid, size: i16) -> Option { + let (type_part, _) = oid.as_nested(); + match (type_part, size) { (25, -2) => Some(DbType::String), (23, 8) => Some(DbType::Int), (701, 8) => Some(DbType::Number), @@ -140,17 +142,15 @@ impl DbType { } } - pub fn type_oid(&self) -> i32 { + pub fn type_oid(&self) -> PgOid { match self { - Self::String => 25, - Self::Int => 23, - Self::Number => 701, - Self::Uuid => 2950, + Self::String => PgOid::Simple(25), + Self::Int => PgOid::Simple(23), + Self::Number => PgOid::Simple(701), + Self::Uuid => PgOid::Simple(2950), Self::Option(t) => { - let oid = t.type_oid(); - let type_part = (oid & 0x0000FFFF) as u16; - let nest_part = ((oid as u32 & 0xFFFF0000) >> 16) as u16 + 1; - (type_part as u32 | ((nest_part as u32) << 16)) as i32 + let (inner_type, inner_nest) = t.type_oid().as_nested(); + PgOid::Nested(inner_type, inner_nest + 1) } } } @@ -258,12 +258,11 @@ impl Value { pub fn from_text_bytes( bytes: &[u8], - type_oid: i32, + type_oid: PgOid, type_size: i16, ) -> Result { let text = std::str::from_utf8(bytes)?; - let type_part = (type_oid & 0x0000FFFF) as u16; - let nest_part = ((type_oid as u32 & 0xFFFF0000) >> 16) as u16; + let (type_part, nest_part) = type_oid.as_nested(); Self::internal_from_text_bytes(text, type_part, nest_part, type_size) } @@ -275,13 +274,13 @@ impl Value { type_size: i16, ) -> Result { if text == "None" { - let db_type = - DbType::from_oid_and_size(type_part as i32, type_size).ok_or_else(|| { - TypeConversionError::UnknownType { - oid: type_part as i32, - size: type_size, - } - })?; + let oid = PgOid::Nested(type_part, nest_part); + let db_type = DbType::from_oid_and_size(oid, type_size).ok_or_else(|| { + TypeConversionError::UnknownType { + oid, + size: type_size, + } + })?; return if nest_part == 0 { Ok(Value::None(db_type)) } else { @@ -316,7 +315,7 @@ impl Value { Ok(Value::Uuid(n)) } (oid, size) => Err(TypeConversionError::UnknownType { - oid: oid as i32, + oid: PgOid::Nested(oid, nest_part), size, }), } @@ -430,6 +429,7 @@ impl TryFrom for Value { mod tests { use super::{DbType, Value}; use crate::error::TypeConversionError::UnknownType; + use proto::message::primitive::pgoid::PgOid; #[test] fn test_encode_number() { @@ -444,7 +444,7 @@ mod tests { assert_eq!(value, from_bytes); assert_eq!(bytes, b"123.456"); - assert_eq!(oid, 701); + assert_eq!(oid.as_simple(), 701); assert_eq!(size, 8); } @@ -461,7 +461,7 @@ mod tests { assert_eq!(value, from_bytes); assert_eq!(bytes, b"hello\0"); - assert_eq!(oid, 25); + assert_eq!(oid.as_simple(), 25); assert_eq!(size, -2); } @@ -477,7 +477,7 @@ mod tests { assert_eq!(value, from_bytes); - assert_eq!(oid, 25); + assert_eq!(oid.as_simple(), 25); assert_eq!(size, -2); } @@ -494,7 +494,7 @@ mod tests { assert_eq!(value, from_bytes); assert_eq!(bytes, b"123"); - assert_eq!(oid, 23); + assert_eq!(oid.as_simple(), 23); assert_eq!(size, 8); } @@ -511,7 +511,7 @@ mod tests { assert_eq!(value, from_bytes); assert_eq!(bytes, b"u123"); - assert_eq!(oid, 2950); + assert_eq!(oid.as_simple(), 2950); assert_eq!(size, 16); } @@ -525,9 +525,14 @@ mod tests { let bytes = value.as_text_bytes(); let from_bytes = Value::from_text_bytes(&bytes, oid, size); + println!("{from_bytes:?}"); + assert!(matches!( from_bytes, - Err(UnknownType { oid: 2950, size: 8 }) + Err(UnknownType { + oid: PgOid::Nested(2950, 0), + size: 8 + }) )) } @@ -562,11 +567,7 @@ mod tests { assert_eq!(value, from_bytes); assert_eq!(bytes, b"Some(Some(123))"); - let expected_type_oid = 23; - let expected_nest_oid = 2; - let expected_oid = expected_type_oid | (expected_nest_oid << 16); - - assert_eq!(oid, expected_oid); + assert_eq!(oid.as_simple(), PgOid::Nested(23, 2).as_simple()); assert_eq!(size, 8); } @@ -585,11 +586,7 @@ mod tests { assert_eq!(value, from_bytes); assert_eq!(bytes, b"Some(Some(None))"); - let expected_type_oid = 23; - let expected_nest_oid = 3; - let expected_oid = expected_type_oid | (expected_nest_oid << 16); - - assert_eq!(oid, expected_oid); + assert_eq!(oid.as_simple(), PgOid::Nested(23, 3).as_simple()); assert_eq!(size, 8); } @@ -606,11 +603,7 @@ mod tests { assert_eq!(value, from_bytes); assert_eq!(bytes, b"Some(None)"); - let expected_type_oid = 701; - let expected_nest_oid = 1; - let expected_oid = expected_type_oid | (expected_nest_oid << 16); - - assert_eq!(oid, expected_oid); + assert_eq!(oid.as_simple(), PgOid::Nested(701, 1).as_simple()); assert_eq!(size, 8); } @@ -628,7 +621,7 @@ mod tests { assert_eq!(value, from_bytes); assert_eq!(bytes, b"None"); - assert_eq!(oid, 701); + assert_eq!(oid.as_simple(), 701); assert_eq!(size, 8); } } diff --git a/proto/Cargo.toml b/proto/Cargo.toml index c8c9134..40f5991 100644 --- a/proto/Cargo.toml +++ b/proto/Cargo.toml @@ -8,3 +8,6 @@ bincode = "2.0.0-rc.3" tokio = { version = "1.34.0", features = ["io-util", "macros", "test-util"] } async-trait = "0.1.74" thiserror = "1.0.50" +rand_seeder = "0.2.3" +rand_pcg = "0.3.1" +rand = "0.8.5" diff --git a/proto/src/message/backend.rs b/proto/src/message/backend.rs index 869fe14..cfa1078 100644 --- a/proto/src/message/backend.rs +++ b/proto/src/message/backend.rs @@ -1,6 +1,7 @@ use crate::message::errors::{ProtoDeserializeError, ProtoSerializeError}; use crate::message::primitive::data::MessageData; use crate::message::primitive::pglist::PgList; +use crate::message::primitive::pgoid::PgOid; use crate::message::primitive::pgstring::PgString; use crate::message::proto_message::ProtoMessage; use bincode::{Decode, Encode}; @@ -186,9 +187,9 @@ impl From for BackendMessage { #[derive(Debug, Clone, Encode, Decode)] pub struct ColumnDescription { pub name: PgString, - pub table_oid: i32, + pub table_oid: PgOid, pub column_index: i16, - pub type_oid: i32, + pub type_oid: PgOid, pub type_size: i16, pub type_modifier: i32, pub format_code: i16, @@ -330,9 +331,9 @@ mod tests { let backend = BackendMessage::RowDescription(RowDescriptionData { columns: PgList::from(vec![ColumnDescription { name: PgString::from("Some name"), - table_oid: 123, + table_oid: PgOid::Simple(123), column_index: 456, - type_oid: 789, + type_oid: PgOid::Simple(789), type_size: 101, type_modifier: 112, format_code: 113, @@ -347,9 +348,9 @@ mod tests { let columns: Vec = columns.into(); let column = &columns[0]; column.name.as_str() == "Some name" - && column.table_oid == 123 + && column.table_oid.as_simple() == 123 && column.column_index == 456 - && column.type_oid == 789 + && column.type_oid.as_simple() == 789 && column.type_size == 101 && column.type_modifier == 112 && column.format_code == 113 diff --git a/proto/src/message/primitive/mod.rs b/proto/src/message/primitive/mod.rs index 4e84a1b..11b0f1a 100644 --- a/proto/src/message/primitive/mod.rs +++ b/proto/src/message/primitive/mod.rs @@ -1,3 +1,4 @@ pub(crate) mod data; pub mod pglist; +pub mod pgoid; pub mod pgstring; diff --git a/proto/src/message/primitive/pgoid.rs b/proto/src/message/primitive/pgoid.rs new file mode 100644 index 0000000..36806da --- /dev/null +++ b/proto/src/message/primitive/pgoid.rs @@ -0,0 +1,79 @@ +use bincode::de::Decoder; +use bincode::enc::Encoder; +use bincode::error::{DecodeError, EncodeError}; +use bincode::{BorrowDecode, Decode, Encode}; +use rand::Rng; +use rand_pcg::Pcg64; +use rand_seeder::Seeder; +use std::fmt::Display; + +#[derive(Debug, Clone, Copy, BorrowDecode)] +pub enum PgOid { + Simple(i32), + Nested(u16, u16), +} + +impl PgOid { + /// Generates a pseudo-random OID from a name. + pub fn from_unique_name(name: &str) -> PgOid { + let mut rng: Pcg64 = Seeder::from(name).make_rng(); + let oid = rng.gen::(); + PgOid::Simple(oid) + } + + pub fn as_nested(&self) -> (u16, u16) { + match self { + PgOid::Nested(a, b) => (*a, *b), + PgOid::Simple(oid) => { + let type_part = (oid & 0x0000FFFF) as u16; + let nest_part = (((*oid as u32) & 0xFFFF0000) >> 16) as u16; + (type_part, nest_part) + } + } + } + + pub fn as_simple(&self) -> i32 { + match self { + PgOid::Simple(oid) => *oid, + PgOid::Nested(type_part, nest_part) => { + let oid = (*type_part as u32) | ((*nest_part as u32) << 16); + oid as i32 + } + } + } +} + +impl Display for PgOid { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let oid = self.as_simple(); + write!(f, "{}", oid) + } +} + +impl Encode for PgOid { + fn encode(&self, encoder: &mut E) -> Result<(), EncodeError> { + let oid = self.as_simple(); + oid.encode(encoder) + } +} + +impl Decode for PgOid { + fn decode(decoder: &mut D) -> Result { + let oid = i32::decode(decoder)?; + Ok(PgOid::Simple(oid)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_pgoid() { + let oid = PgOid::Simple(0x12345678); + let (type_part, nest_part) = oid.as_nested(); + assert_eq!(type_part, 0x5678); + assert_eq!(nest_part, 0x1234); + assert_eq!(oid.as_simple(), 0x12345678); + } +} diff --git a/server/Cargo.toml b/server/Cargo.toml index 2b76e8e..67c87d7 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -11,8 +11,6 @@ anyhow = "1.0.76" clap = { version = "4.4.18", features = ["derive"] } async-trait = "0.1.74" rand = "0.8.5" -rand_seeder = "0.2.3" -rand_pcg = "0.3.1" serde_json = "1.0.112" minisql = { path = "../minisql" } proto = { path = "../proto" } diff --git a/server/src/proto_wrapper.rs b/server/src/proto_wrapper.rs index 960522e..1ddf59c 100644 --- a/server/src/proto_wrapper.rs +++ b/server/src/proto_wrapper.rs @@ -7,10 +7,8 @@ use proto::message::backend::{ ReadyForQueryData, RowDescriptionData, }; use proto::message::primitive::pglist::PgList; +use proto::message::primitive::pgoid::PgOid; use proto::writer::backend::BackendProtoWriter; -use rand::Rng; -use rand_pcg::Pcg64; -use rand_seeder::Seeder; use std::fmt; pub enum CompleteStatus { @@ -123,7 +121,7 @@ fn column_to_description( column: Column, ) -> anyhow::Result { let table_name = schema.table_name(); - let table_oid = table_name_to_oid(table_name); + let table_oid = PgOid::from_unique_name(table_name); let column_type = schema.column_type(column); let name = schema.column_name_from_column(column); @@ -142,10 +140,3 @@ fn column_to_description( format_code: 0, // text format }) } - -/// Hashes the table name into single i32 used as a substitute for the table OID -/// in the PostgreSQL protocol. -fn table_name_to_oid(table_name: &str) -> i32 { - let mut rng: Pcg64 = Seeder::from(table_name).make_rng(); - rng.gen::() -} From 700acf5661c44ca7796e9434958873e1b4fa0da2 Mon Sep 17 00:00:00 2001 From: Yuriy Dupyn <2153100+omedusyo@users.noreply.github.com> Date: Thu, 1 Feb 2024 22:39:08 +0100 Subject: [PATCH 13/13] Remove unnecessary import --- parser/src/validation.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parser/src/validation.rs b/parser/src/validation.rs index 5bbf8e6..9959abf 100644 --- a/parser/src/validation.rs +++ b/parser/src/validation.rs @@ -352,7 +352,7 @@ mod tests { use minisql::operation; use minisql::operation::Operation; use minisql::schema::TableSchema; - use minisql::type_system::{IndexableValue, Value}; + use minisql::type_system::Value; use Condition::*; use RawQuerySyntax::*;