diff --git a/CHANGELOG.md b/CHANGELOG.md index 0079cf5..b8bc019 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,12 @@ # Changelog +## 12/24/23 +- Created common logic for parsing ValueExpressions + ## 12/23/23 - Added LogicExpressions for creating and evaluating expressions - Added LogicExpressions to SELECT and DELETE commands (i.e SELECT FROM WHERE and DELETE FROM WHERE) - Updated SELECT formatting to pad table columns - General system stability improvements to enhance the user's experience. +- Moved many functions over to squirrel_core library diff --git a/README.md b/README.md index b328c5e..cdaf2f9 100644 --- a/README.md +++ b/README.md @@ -20,9 +20,9 @@ This is a SQL database written in Rust. It will be based off of (and hopefully b [x] WHERE clause for SELECT and DELETE -[ ] Create squirrel-core library for shared code between client & server +[x] Create squirrel-core library for shared code between client & server -[ ] Update parser to use common logic to identify 'objects' (i.e function calls, column references, and variables) +[x] Update parser to use common logic to identify 'objects' (i.e function calls, column references, and variables) [ ] Move parsing to client diff --git a/squirrel-client/Cargo.lock b/squirrel-client/Cargo.lock deleted file mode 100644 index 2c5d6c0..0000000 --- a/squirrel-client/Cargo.lock +++ /dev/null @@ -1,7 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "squirrel-client" -version = "0.1.0" diff --git a/squirrel-client/Cargo.toml b/squirrel-client/Cargo.toml deleted file mode 100644 index 4ec4894..0000000 --- a/squirrel-client/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "squirrel-client" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] diff --git a/squirrel-server/Cargo.toml b/squirrel-server/Cargo.toml deleted file mode 100644 index 88048ee..0000000 --- a/squirrel-server/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "squirrel-server" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -anyhow = "1.0.72" diff --git a/squirrel-client/.DS_Store b/squirrel_client/.DS_Store similarity index 100% rename from squirrel-client/.DS_Store rename to squirrel_client/.DS_Store diff --git a/squirrel-client/.gitignore b/squirrel_client/.gitignore similarity index 100% rename from squirrel-client/.gitignore rename to squirrel_client/.gitignore diff --git a/squirrel_client/Cargo.lock b/squirrel_client/Cargo.lock new file mode 100644 index 0000000..f8ce60b --- /dev/null +++ b/squirrel_client/Cargo.lock @@ -0,0 +1,23 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anyhow" +version = "1.0.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59d2a3357dde987206219e78ecfbbb6e8dad06cbb65292758d3270e6254f7355" + +[[package]] +name = "squirrel_client" +version = "0.1.0" +dependencies = [ + "squirrel_core", +] + +[[package]] +name = "squirrel_core" +version = "0.1.0" +dependencies = [ + "anyhow", +] diff --git a/squirrel_client/Cargo.toml b/squirrel_client/Cargo.toml new file mode 100644 index 0000000..44a8bac --- /dev/null +++ b/squirrel_client/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "squirrel_client" +version = "0.1.0" +edition = "2021" + +[dependencies] +squirrel_core = { path = "../squirrel_core" } diff --git a/squirrel-client/src/main.rs b/squirrel_client/src/main.rs similarity index 100% rename from squirrel-client/src/main.rs rename to squirrel_client/src/main.rs diff --git a/squirrel-client/test_queries.sql b/squirrel_client/test_queries.sql similarity index 100% rename from squirrel-client/test_queries.sql rename to squirrel_client/test_queries.sql diff --git a/squirrel-server/.gitignore b/squirrel_core/.gitignore similarity index 100% rename from squirrel-server/.gitignore rename to squirrel_core/.gitignore diff --git a/squirrel_core/Cargo.lock b/squirrel_core/Cargo.lock new file mode 100644 index 0000000..938a21b --- /dev/null +++ b/squirrel_core/Cargo.lock @@ -0,0 +1,16 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anyhow" +version = "1.0.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59d2a3357dde987206219e78ecfbbb6e8dad06cbb65292758d3270e6254f7355" + +[[package]] +name = "squirrel_core" +version = "0.1.0" +dependencies = [ + "anyhow", +] diff --git a/squirrel_core/Cargo.toml b/squirrel_core/Cargo.toml new file mode 100644 index 0000000..7f79fa7 --- /dev/null +++ b/squirrel_core/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "squirrel_core" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1.0.72" + +[lib] +name = "squirrel_core" +path = "src/main.rs" diff --git a/squirrel_core/src/main.rs b/squirrel_core/src/main.rs new file mode 100644 index 0000000..2f75422 --- /dev/null +++ b/squirrel_core/src/main.rs @@ -0,0 +1,181 @@ +pub mod parser; +pub mod table; + +pub use crate::parser::command::Command; +use crate::parser::command::{CreateCommand, InsertCommand, SelectCommand, DeleteCommand, LogicExpression, InsertItem, DataValue, FunctionCall, ValueExpression}; +pub use crate::table::datatypes::Datatype; +pub use crate::table::table_definition::{ColumnDefinition, TableDefinition}; + +use anyhow::anyhow; +use std::fs; +use std::io::{BufRead, BufReader, Read, Write}; +use std::net::{Shutdown, TcpListener, TcpStream}; +use std::thread; +use std::collections::HashMap; +use std::cmp; + +fn main() { +} + +#[test] +fn value_expression() -> anyhow::Result<()> { + let tests = HashMap::from([ + ( + "TEST()", + Ok(ValueExpression::FunctionCall(FunctionCall { function_name: String::from("TEST"), parameters: vec![]})) + ), + ( + "TEST_TWO()", + Ok(ValueExpression::FunctionCall(FunctionCall { function_name: String::from("TEST_TWO"), parameters: vec![]})) + ), + ( + "\"Name\'", + Err(anyhow!("Error")) + ), + ( + "id", + Ok(ValueExpression::ColumnName(String::from("id"))) + ), + ( + "55", + Ok(ValueExpression::DataValue(DataValue::U8Value(55))) + ), + ( + "\"Name\"", + Ok(ValueExpression::DataValue(DataValue::StringValue(String::from("Name")))) + ), + + ]); + + for (string, expected) in tests { + match expected { + Ok(expected_res) => { + assert_eq!( + parser::command::Command::value_expression_from_string(String::from(string)).unwrap(), + expected_res); + }, + Err(_) => { + assert_eq!( + parser::command::Command::value_expression_from_string(String::from(string)).is_ok(), + false + ); + + } + } + } + + + Ok(()) +} + +#[test] +fn logical_expression() -> anyhow::Result<()> { + assert_eq!(Command::le_from_string(String::from("1 < 5")).unwrap().evaluate().unwrap(), true); + assert_eq!(Command::le_from_string(String::from("1 > 5")).unwrap().evaluate().unwrap(), false); + assert_eq!(Command::le_from_string(String::from("1 <= 5")).unwrap().evaluate().unwrap(), true); + assert_eq!(Command::le_from_string(String::from("1 >= 5")).unwrap().evaluate().unwrap(), false); + assert_eq!(Command::le_from_string(String::from("5 >= 5")).unwrap().evaluate().unwrap(), true); + assert_eq!(Command::le_from_string(String::from("5 <= 5")).unwrap().evaluate().unwrap(), true); + assert_eq!(Command::le_from_string(String::from("5 = 5")).unwrap().evaluate().unwrap(), true); + assert_eq!(Command::le_from_string(String::from("5 AND 5")).unwrap().evaluate().is_ok(), false); + assert_eq!(Command::le_from_string(String::from("5 OR 5")).unwrap().evaluate().is_ok(), false); + + assert_eq!(Command::le_from_string(String::from("'Test' = 'Test'")).unwrap().evaluate().unwrap(), true); + assert_eq!(Command::le_from_string(String::from("'Test' = 'Text'")).unwrap().evaluate().unwrap(), false); + assert_eq!(Command::le_from_string(String::from("'Test' <= 'Test'")).unwrap().evaluate().is_ok(), false); + assert_eq!(Command::le_from_string(String::from("'Test' >= 'Test'")).unwrap().evaluate().is_ok(), false); + assert_eq!(Command::le_from_string(String::from("'Test' < 'Test'")).unwrap().evaluate().is_ok(), false); + assert_eq!(Command::le_from_string(String::from("'Test' > 'Test'")).unwrap().evaluate().is_ok(), false); + assert_eq!(Command::le_from_string(String::from("'Test' AND 'Test'")).unwrap().evaluate().is_ok(), false); + assert_eq!(Command::le_from_string(String::from("'Test' OR 'Test'")).unwrap().evaluate().is_ok(), false); + + assert_eq!(Command::le_from_string(String::from("'Test' < 5")).unwrap().evaluate().is_ok(), false); + assert_eq!(Command::le_from_string(String::from("'Test' > 5")).unwrap().evaluate().is_ok(), false); + assert_eq!(Command::le_from_string(String::from("'Test' <= 5")).unwrap().evaluate().is_ok(), false); + assert_eq!(Command::le_from_string(String::from("'Test' >= 5")).unwrap().evaluate().is_ok(), false); + assert_eq!(Command::le_from_string(String::from("'Test' >= 5")).unwrap().evaluate().is_ok(), false); + assert_eq!(Command::le_from_string(String::from("'Test' <= 5")).unwrap().evaluate().is_ok(), false); + assert_eq!(Command::le_from_string(String::from("'Test' = 5")).unwrap().evaluate().is_ok(), false); + assert_eq!(Command::le_from_string(String::from("'Test' AND 5")).unwrap().evaluate().is_ok(), false); + assert_eq!(Command::le_from_string(String::from("'Test' OR 5")).unwrap().evaluate().is_ok(), false); + + Ok(()) +} + +#[test] +fn insert_statement() -> anyhow::Result<()> { + let empty_statement = ""; + let regular_statement = "INSERT INTO users (id, name) VALUES (1, \"Test\");"; + let extra_ws_statement = + "INSERT INTO users (id, name) VALUES (1, \"Test\") ;"; + let min_ws_statement = "INSERT INTO users(id, name) VALUES(1, \"Test\");"; + let str_comma_statement = "INSERT INTO users(id, name) VALUES(1, \"Firstname, Lastname\");"; + + let expected_output = Command::Insert(InsertCommand { + table_name: "users".to_string(), + items: HashMap::from([ + ( + "id".to_string(), + InsertItem { + column_name: "id".to_string(), + column_value: "1".to_string(), + }, + ), + ( + "name".to_string(), + InsertItem { + column_name: "name".to_string(), + column_value: "Test".to_string(), + }, + ), + ]), + }); + + let expected_output_comma = Command::Insert(InsertCommand { + table_name: "users".to_string(), + items: HashMap::from([ + ( + "id".to_string(), + InsertItem { + column_name: "id".to_string(), + column_value: "1".to_string(), + }, + ), + ( + "name".to_string(), + InsertItem { + column_name: "name".to_string(), + column_value: "Firstname, Lastname".to_string(), + }, + ), + ]), + }); + + assert_eq!( + Command::from_string(String::from(empty_statement)).is_ok(), + false + ); + + assert_eq!( + Command::from_string(String::from(regular_statement))?, + expected_output + ); + + assert_eq!( + Command::from_string(String::from(extra_ws_statement))?, + expected_output + ); + + assert_eq!( + Command::from_string(String::from(min_ws_statement))?, + expected_output + ); + + assert_eq!( + Command::from_string(String::from(str_comma_statement))?, + expected_output_comma + ); + + + Ok(()) +} diff --git a/squirrel-server/src/parser/command.rs b/squirrel_core/src/parser/command.rs similarity index 73% rename from squirrel-server/src/parser/command.rs rename to squirrel_core/src/parser/command.rs index bff00d5..f068804 100644 --- a/squirrel-server/src/parser/command.rs +++ b/squirrel_core/src/parser/command.rs @@ -1,8 +1,8 @@ use std::collections::{HashMap, HashSet}; use std::mem; -use crate::table::table_definition::ColumnDefinition; -use crate::{Datatype, TableDefinition}; +use crate::table::table_definition::{TableDefinition, ColumnDefinition}; +use crate::table::datatypes::{Datatype}; use anyhow::anyhow; #[derive(Debug, Eq, PartialEq)] @@ -55,26 +55,38 @@ enum LogicalOperator { } #[derive(Debug, Eq, PartialEq, Clone)] -pub enum LogicValue { +pub enum DataValue { StringValue(String), U8Value(u8), BoolValue(bool), - ColumnName(String), } #[derive(Debug, Eq, PartialEq)] pub enum LogicSide { // pub expression: LogicExpression, - Value(LogicValue) + Value(DataValue) } #[derive(Debug, Eq, PartialEq, Clone)] pub struct LogicExpression { - pub left_hand: LogicValue, - pub right_hand: LogicValue, + pub left_hand: ValueExpression, + pub right_hand: ValueExpression, pub operator: LogicalOperator } +#[derive(Debug, Eq, PartialEq, Clone)] +pub struct FunctionCall { + pub function_name: String, + pub parameters: Vec, +} + +#[derive(Debug, Eq, PartialEq, Clone)] +pub enum ValueExpression { + FunctionCall(FunctionCall), + DataValue(DataValue), + ColumnName(String), +} + enum CreateParserState { Object, TableName, @@ -114,6 +126,14 @@ enum InsertParserState { Semicolon, } +enum ValueExpressionParserState { + NumericOrQuoteOrColumnOrFunction, + StringValue, + EndQuote, + FunctionOpenParenOrEnd, + FunctionCloseParen, +} + #[derive(Debug)] enum LogicExpressionParserState { NumberOrQuoteOrColname, @@ -124,14 +144,26 @@ enum LogicExpressionParserState { pub fn tokenizer(text: String) -> Vec { - let parts = HashSet::from([' ', ',', ';', '(', ')', '\'']); + let parts = HashSet::from([' ', ',', ';', '(', ')', '\'', '\"']); let mut tokens: Vec = vec![]; let mut cur_str = String::new(); let mut in_quotes = false; + let mut cur_quote = '\0'; for cur_char in text.chars() { - if cur_char == '\"' { - in_quotes = !in_quotes; + if !in_quotes && (cur_char == '\"' || cur_char == '\''){ + tokens.push(cur_char.to_string()); + in_quotes = true; + cur_quote = cur_char; + continue; + } + + if in_quotes && cur_char == cur_quote { + tokens.push(cur_str); + cur_str = String::new(); + tokens.push(cur_char.to_string()); + in_quotes = false; + continue; } if !in_quotes && parts.contains(&cur_char) { @@ -146,21 +178,24 @@ pub fn tokenizer(text: String) -> Vec { cur_str.push(cur_char); } } - tokens.push(cur_str); + + if cur_str.len() > 0 { + tokens.push(cur_str); + } tokens } -impl LogicValue { - pub fn from_string(string: String) -> ::anyhow::Result { +impl DataValue { + pub fn from_string(string: String) -> ::anyhow::Result { let test = string.parse::(); match test { Ok(u8_val) => { - return Ok(LogicValue::U8Value(u8_val)); + return Ok(DataValue::U8Value(u8_val)); }, Err(_) => { let res = string.trim_matches(char::from(0)); - return Ok(LogicValue::StringValue(res.to_string())); + return Ok(DataValue::StringValue(res.to_string())); }, } } @@ -168,20 +203,29 @@ impl LogicValue { impl LogicExpression { pub fn is_valid(&self) -> bool { - return mem::discriminant(&self.left_hand) == mem::discriminant(&self.right_hand); + println!("{:?}", self.left_hand); + if !self.is_evaluatable() { + return false; + } + if let ValueExpression::DataValue(left) = self.left_hand.clone() { + if let ValueExpression::DataValue(right) = self.right_hand.clone() { + return mem::discriminant(&left) == mem::discriminant(&right); + } + } + return false; } pub fn is_evaluatable(&self) -> bool { - return mem::discriminant(&self.left_hand) != mem::discriminant(&LogicValue::ColumnName(String::from(""))) && - mem::discriminant(&self.right_hand) != mem::discriminant(&LogicValue::ColumnName(String::from(""))); + return mem::discriminant(&self.left_hand) == mem::discriminant(&ValueExpression::DataValue(DataValue::U8Value(0))) && + mem::discriminant(&self.right_hand) == mem::discriminant(&ValueExpression::DataValue(DataValue::U8Value(0))); } - pub fn fill_values(&mut self, hmap: HashMap) -> ::anyhow::Result<()> { + pub fn fill_values(&mut self, hmap: HashMap) -> ::anyhow::Result<()> { for (name, value) in hmap { - if self.left_hand == LogicValue::ColumnName(name.clone()) { + if self.left_hand == ValueExpression::ColumnName(name.clone()) { self.left_hand = value.clone(); } - if self.right_hand == LogicValue::ColumnName(name.clone()) { + if self.right_hand == ValueExpression::ColumnName(name.clone()) { self.right_hand = value.clone(); } } @@ -197,18 +241,16 @@ impl LogicExpression { } println!("{:?}", self); match self.left_hand { - LogicValue::StringValue(_) => { + ValueExpression::DataValue(DataValue::StringValue(_)) => { return self.evaluate_string(); } - LogicValue::BoolValue(_) => { + ValueExpression::DataValue(DataValue::BoolValue(_)) => { return self.evaluate_bool(); } - LogicValue::U8Value(_) => { + ValueExpression::DataValue(DataValue::U8Value(_)) => { return self.evaluate_u8(); } - LogicValue::ColumnName(_) => { - return Err(anyhow!("Cannot compare names of 2 columns, only values")); - } + _ => {return Err(anyhow!("Cannot compare non-finalized value expressions"))} } } @@ -229,16 +271,16 @@ impl LogicExpression { return Ok(self.left_hand == self.right_hand); } LogicalOperator::And => { - if let LogicValue::BoolValue(left) = self.left_hand { - if let LogicValue::BoolValue(right) = self.right_hand { + if let ValueExpression::DataValue(DataValue::BoolValue(left)) = self.left_hand { + if let ValueExpression::DataValue(DataValue::BoolValue(right)) = self.right_hand { return Ok(left && right); } } return Err(anyhow!("Mismatched datatypes")); } LogicalOperator::Or => { - if let LogicValue::BoolValue(left) = self.left_hand { - if let LogicValue::BoolValue(right) = self.right_hand { + if let ValueExpression::DataValue(DataValue::BoolValue(left)) = self.left_hand { + if let ValueExpression::DataValue(DataValue::BoolValue(right)) = self.right_hand { return Ok(left || right); } } @@ -256,32 +298,32 @@ impl LogicExpression { return Ok(self.left_hand == self.right_hand); } LogicalOperator::GreaterThan => { - if let LogicValue::U8Value(left) = self.left_hand { - if let LogicValue::U8Value(right) = self.right_hand { + if let ValueExpression::DataValue(DataValue::U8Value(left)) = self.left_hand { + if let ValueExpression::DataValue(DataValue::U8Value(right)) = self.right_hand { return Ok(left > right); } } return Err(anyhow!("Mismatched datatypes")); } LogicalOperator::LessThan => { - if let LogicValue::U8Value(left) = self.left_hand { - if let LogicValue::U8Value(right) = self.right_hand { + if let ValueExpression::DataValue(DataValue::U8Value(left)) = self.left_hand { + if let ValueExpression::DataValue(DataValue::U8Value(right)) = self.right_hand { return Ok(left < right); } } return Err(anyhow!("Mismatched datatypes")); } LogicalOperator::GreaterThanEqualTo => { - if let LogicValue::U8Value(left) = self.left_hand { - if let LogicValue::U8Value(right) = self.right_hand { + if let ValueExpression::DataValue(DataValue::U8Value(left)) = self.left_hand { + if let ValueExpression::DataValue(DataValue::U8Value(right)) = self.right_hand { return Ok(left >= right); } } return Err(anyhow!("Mismatched datatypes")); } LogicalOperator::LessThanEqualTo => { - if let LogicValue::U8Value(left) = self.left_hand { - if let LogicValue::U8Value(right) = self.right_hand { + if let ValueExpression::DataValue(DataValue::U8Value(left)) = self.left_hand { + if let ValueExpression::DataValue(DataValue::U8Value(right)) = self.right_hand { return Ok(left <= right); } } @@ -295,6 +337,70 @@ impl LogicExpression { } impl Command { + fn parse_value_expression(tokens: &mut Vec) -> ::anyhow::Result { + let mut state: ValueExpressionParserState = ValueExpressionParserState::NumericOrQuoteOrColumnOrFunction; + let mut quote_type = "'"; + let mut value_expr: Option = None; + let mut ref_name = String::from(""); + + while let Some(token) = &tokens.pop() { + match state { + ValueExpressionParserState::NumericOrQuoteOrColumnOrFunction => { + // String test + if token == "'" || token == "\"" { + quote_type = if token == "'" { "'" } else { "\"" }; + state = ValueExpressionParserState::StringValue; + continue; + } + + // Numeric Test + let test = token.parse::(); + match test { + Ok(u8_val) => { + return Ok(ValueExpression::DataValue(DataValue::U8Value(u8_val))); + }, + Err(_) => { }, + } + + // Function name / Column name test + ref_name = token.clone(); + state = ValueExpressionParserState::FunctionOpenParenOrEnd; + } + ValueExpressionParserState::StringValue => { + value_expr = Some(ValueExpression::DataValue(DataValue::StringValue(token.to_string()))); + state = ValueExpressionParserState::EndQuote; + } + ValueExpressionParserState::EndQuote => { + if token == quote_type { + return Ok(value_expr.unwrap()); + } else { + return Err(anyhow!("Mismatched quotes at or near {}", token)); + } + } + ValueExpressionParserState::FunctionOpenParenOrEnd => { + if token == "(" { + state = ValueExpressionParserState::FunctionCloseParen; + } else { + tokens.push(token.to_string()); + return Ok(ValueExpression::ColumnName(ref_name.to_string())); + } + } + ValueExpressionParserState::FunctionCloseParen => { + if token == ")" { + return Ok(ValueExpression::FunctionCall(FunctionCall { function_name: ref_name, parameters: vec![] })); + } else { + return Err(anyhow!("Expected funciton closing parenthesis at or near {}", token)); + } + } + } + } + + if ref_name.len() != 0 { + return Ok(ValueExpression::ColumnName(ref_name.to_string())); + } + return Err(anyhow!("Unexpected end of statement")); + } + fn parse_insert_command(tokens: &mut Vec) -> ::anyhow::Result { let mut state: InsertParserState = InsertParserState::IntoKeyword; @@ -363,7 +469,23 @@ impl Command { state = InsertParserState::Value; } InsertParserState::Value => { - column_val = token.to_string(); + tokens.push(token.to_string()); + let expr = Self::parse_value_expression(tokens)?; + if let ValueExpression::DataValue(value) = expr { + match value { + DataValue::StringValue(val) => { + column_val = val.to_string() + }, + DataValue::U8Value(val) => { + column_val = val.to_string(); + } + DataValue::BoolValue(val) => { + column_val = if val { String::from("TRUE") } else { String::from("FALSE") }; + } + } + } else { + return Err(anyhow!("Expected data at or near {}", token)); + } state = InsertParserState::ValueEnd; } InsertParserState::ValueEnd => { @@ -410,8 +532,8 @@ impl Command { fn parse_logic_expression(tokens: &mut Vec) -> ::anyhow::Result { let mut state: LogicExpressionParserState = LogicExpressionParserState::NumberOrQuoteOrColname; - let mut left_hand: Option = None; - let mut right_hand: Option = None; + let mut left_hand: Option = None; + let mut right_hand: Option = None; let mut operator: Option = None; while let Some(token) = &tokens.pop() { @@ -424,19 +546,19 @@ impl Command { match test { Ok(u8_val) => { if left_hand.is_none() { - left_hand = Some(LogicValue::U8Value(u8_val)); + left_hand = Some(ValueExpression::DataValue(DataValue::U8Value(u8_val))); state = LogicExpressionParserState::Operator; } else { - right_hand = Some(LogicValue::U8Value(u8_val)); + right_hand = Some(ValueExpression::DataValue(DataValue::U8Value(u8_val))); return Ok(LogicExpression {left_hand: left_hand.unwrap(), right_hand: right_hand.unwrap(), operator: operator.unwrap()}); } }, Err(_) => { if left_hand.is_none() { - left_hand = Some(LogicValue::ColumnName(token.to_string())); + left_hand = Some(ValueExpression::ColumnName(token.to_string())); state = LogicExpressionParserState::Operator; } else { - right_hand = Some(LogicValue::ColumnName(token.to_string())); + right_hand = Some(ValueExpression::ColumnName(token.to_string())); return Ok(LogicExpression {left_hand: left_hand.unwrap(), right_hand: right_hand.unwrap(), operator: operator.unwrap()}); } }, @@ -445,11 +567,11 @@ impl Command { } LogicExpressionParserState::StringValue => { - let mut value: Option = None; + let mut value: Option = None; if token == "'" { - value = Some(LogicValue::StringValue("".to_string())); + value = Some(ValueExpression::DataValue(DataValue::StringValue("".to_string()))); } else { - value = Some(LogicValue::StringValue(token.to_string())); + value = Some(ValueExpression::DataValue(DataValue::StringValue(token.to_string()))); } if left_hand.is_none() { left_hand = value; @@ -701,4 +823,12 @@ impl Command { tokens.reverse(); return Ok(Self::parse_logic_expression(&mut tokens)?); } + + pub fn value_expression_from_string(command_str: String) -> ::anyhow::Result { + let mut tokens: Vec = tokenizer(command_str.clone()); + println!("{}", command_str); + println!("{:?}", tokens); + tokens.reverse(); + return Ok(Self::parse_value_expression(&mut tokens)?); + } } diff --git a/squirrel-server/src/parser/mod.rs b/squirrel_core/src/parser/mod.rs similarity index 100% rename from squirrel-server/src/parser/mod.rs rename to squirrel_core/src/parser/mod.rs diff --git a/squirrel-server/src/table/datatypes.rs b/squirrel_core/src/table/datatypes.rs similarity index 80% rename from squirrel-server/src/table/datatypes.rs rename to squirrel_core/src/table/datatypes.rs index 3fbb049..a6677af 100644 --- a/squirrel-server/src/table/datatypes.rs +++ b/squirrel_core/src/table/datatypes.rs @@ -24,17 +24,7 @@ impl Datatype { pub fn to_bytes(&self, data_val: String) -> ::anyhow::Result> { match self { Datatype::CharacterVarying => { - // Ensure string is formatted properly - if !data_val.starts_with('\"') || !data_val.ends_with('\"') { - return Err(::anyhow::anyhow!( - "ERROR: Unable to parse value for type CharacterVarying" - )); - } let mut str_bytes = data_val.as_bytes().to_vec(); - - // Remove dquotes - str_bytes.remove(0); - str_bytes.remove(str_bytes.len() - 1); Ok(str_bytes) } Datatype::Integer => { diff --git a/squirrel-server/src/table/mod.rs b/squirrel_core/src/table/mod.rs similarity index 100% rename from squirrel-server/src/table/mod.rs rename to squirrel_core/src/table/mod.rs diff --git a/squirrel-server/src/table/table_definition.rs b/squirrel_core/src/table/table_definition.rs similarity index 94% rename from squirrel-server/src/table/table_definition.rs rename to squirrel_core/src/table/table_definition.rs index 463aa96..7b20668 100644 --- a/squirrel-server/src/table/table_definition.rs +++ b/squirrel_core/src/table/table_definition.rs @@ -1,4 +1,4 @@ -use crate::Datatype; +use crate::table::datatypes::Datatype; #[derive(Debug, Eq, PartialEq)] pub struct ColumnDefinition { diff --git a/squirrel-server/.DS_Store b/squirrel_server/.DS_Store similarity index 100% rename from squirrel-server/.DS_Store rename to squirrel_server/.DS_Store diff --git a/squirrel_server/.gitignore b/squirrel_server/.gitignore new file mode 100644 index 0000000..a727c0a --- /dev/null +++ b/squirrel_server/.gitignore @@ -0,0 +1,2 @@ +/target +/data diff --git a/squirrel-server/Cargo.lock b/squirrel_server/Cargo.lock similarity index 73% rename from squirrel-server/Cargo.lock rename to squirrel_server/Cargo.lock index 68ad5ce..517470c 100644 --- a/squirrel-server/Cargo.lock +++ b/squirrel_server/Cargo.lock @@ -9,8 +9,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" [[package]] -name = "squirrel-server" +name = "squirrel_core" version = "0.1.0" dependencies = [ "anyhow", ] + +[[package]] +name = "squirrel_server" +version = "0.1.0" +dependencies = [ + "anyhow", + "squirrel_core", +] diff --git a/squirrel_server/Cargo.toml b/squirrel_server/Cargo.toml new file mode 100644 index 0000000..acf0f12 --- /dev/null +++ b/squirrel_server/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "squirrel_server" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1.0.72" +squirrel_core = { path = "../squirrel_core" } diff --git a/squirrel-server/src/main.rs b/squirrel_server/src/main.rs similarity index 63% rename from squirrel-server/src/main.rs rename to squirrel_server/src/main.rs index 08a7a3c..fd2371f 100644 --- a/squirrel-server/src/main.rs +++ b/squirrel_server/src/main.rs @@ -6,13 +6,10 @@ use std::thread; use std::collections::HashMap; use std::cmp; -mod parser; -pub use parser::command::Command; - -mod table; -use parser::command::{CreateCommand, InsertCommand, SelectCommand, DeleteCommand, LogicExpression, InsertItem, LogicValue}; -pub use table::datatypes::Datatype; -pub use table::table_definition::{ColumnDefinition, TableDefinition}; +pub use squirrel_core::parser::command::Command; +use squirrel_core::parser::command::{CreateCommand, InsertCommand, SelectCommand, DeleteCommand, LogicExpression, InsertItem, DataValue, ValueExpression}; +pub use squirrel_core::table::datatypes::Datatype; +pub use squirrel_core::table::table_definition::{ColumnDefinition, TableDefinition}; const BUFFER_SIZE: usize = 500; @@ -101,7 +98,7 @@ fn handle_delete(command: DeleteCommand) -> ::anyhow::Result { .open(format!("./data/blobs/{}_new", command.table_name))?; while file.read_exact(buf.as_mut_slice()).is_ok() { - let mut row_data: HashMap = HashMap::new(); + let mut row_data: HashMap = HashMap::new(); let mut idx: usize = 0; if let Some(ref le) = command.logic_expression { let mut logic_expr = le.clone(); @@ -113,7 +110,7 @@ fn handle_delete(command: DeleteCommand) -> ::anyhow::Result { }; let str_val = col_def.data_type.from_bytes(&buf[idx..(idx + len)])?; idx += len; - row_data.insert(col_def.name.clone(), LogicValue::from_string(str_val)?); + row_data.insert(col_def.name.clone(), ValueExpression::DataValue(DataValue::from_string(str_val)?)); } idx = 0; logic_expr.fill_values(row_data); @@ -160,7 +157,7 @@ fn handle_select(command: SelectCommand) -> ::anyhow::Result { while file.read_exact(buf.as_mut_slice()).is_ok() { let mut idx: usize = 0; - let mut row_data: HashMap = HashMap::new(); + let mut row_data: HashMap = HashMap::new(); if let Some(ref le) = command.logic_expression { let mut logic_expr = le.clone(); for col_def in &tabledef.column_defs { @@ -171,7 +168,7 @@ fn handle_select(command: SelectCommand) -> ::anyhow::Result { }; let str_val = col_def.data_type.from_bytes(&buf[idx..(idx + len)])?; idx += len; - row_data.insert(col_def.name.clone(), LogicValue::from_string(str_val)?); + row_data.insert(col_def.name.clone(), ValueExpression::DataValue(DataValue::from_string(str_val)?)); } idx = 0; logic_expr.fill_values(row_data); @@ -315,117 +312,5 @@ fn main() -> std::io::Result<()> { }); } - Ok(()) -} - -#[test] -fn logical_expression() -> anyhow::Result<()> { - assert_eq!(Command::le_from_string(String::from("1 < 5")).unwrap().evaluate().unwrap(), true); - assert_eq!(Command::le_from_string(String::from("1 > 5")).unwrap().evaluate().unwrap(), false); - assert_eq!(Command::le_from_string(String::from("1 <= 5")).unwrap().evaluate().unwrap(), true); - assert_eq!(Command::le_from_string(String::from("1 >= 5")).unwrap().evaluate().unwrap(), false); - assert_eq!(Command::le_from_string(String::from("5 >= 5")).unwrap().evaluate().unwrap(), true); - assert_eq!(Command::le_from_string(String::from("5 <= 5")).unwrap().evaluate().unwrap(), true); - assert_eq!(Command::le_from_string(String::from("5 = 5")).unwrap().evaluate().unwrap(), true); - assert_eq!(Command::le_from_string(String::from("5 AND 5")).unwrap().evaluate().is_ok(), false); - assert_eq!(Command::le_from_string(String::from("5 OR 5")).unwrap().evaluate().is_ok(), false); - - assert_eq!(Command::le_from_string(String::from("'Test' = 'Test'")).unwrap().evaluate().unwrap(), true); - assert_eq!(Command::le_from_string(String::from("'Test' = 'Text'")).unwrap().evaluate().unwrap(), false); - assert_eq!(Command::le_from_string(String::from("'Test' <= 'Test'")).unwrap().evaluate().is_ok(), false); - assert_eq!(Command::le_from_string(String::from("'Test' >= 'Test'")).unwrap().evaluate().is_ok(), false); - assert_eq!(Command::le_from_string(String::from("'Test' < 'Test'")).unwrap().evaluate().is_ok(), false); - assert_eq!(Command::le_from_string(String::from("'Test' > 'Test'")).unwrap().evaluate().is_ok(), false); - assert_eq!(Command::le_from_string(String::from("'Test' AND 'Test'")).unwrap().evaluate().is_ok(), false); - assert_eq!(Command::le_from_string(String::from("'Test' OR 'Test'")).unwrap().evaluate().is_ok(), false); - - assert_eq!(Command::le_from_string(String::from("'Test' < 5")).unwrap().evaluate().is_ok(), false); - assert_eq!(Command::le_from_string(String::from("'Test' > 5")).unwrap().evaluate().is_ok(), false); - assert_eq!(Command::le_from_string(String::from("'Test' <= 5")).unwrap().evaluate().is_ok(), false); - assert_eq!(Command::le_from_string(String::from("'Test' >= 5")).unwrap().evaluate().is_ok(), false); - assert_eq!(Command::le_from_string(String::from("'Test' >= 5")).unwrap().evaluate().is_ok(), false); - assert_eq!(Command::le_from_string(String::from("'Test' <= 5")).unwrap().evaluate().is_ok(), false); - assert_eq!(Command::le_from_string(String::from("'Test' = 5")).unwrap().evaluate().is_ok(), false); - assert_eq!(Command::le_from_string(String::from("'Test' AND 5")).unwrap().evaluate().is_ok(), false); - assert_eq!(Command::le_from_string(String::from("'Test' OR 5")).unwrap().evaluate().is_ok(), false); - - Ok(()) -} - -#[test] -fn insert_statement() -> anyhow::Result<()> { - let empty_statement = ""; - let regular_statement = "INSERT INTO users (id, name) VALUES (1, \"Test\");"; - let extra_ws_statement = - "INSERT INTO users (id, name) VALUES (1, \"Test\") ;"; - let min_ws_statement = "INSERT INTO users(id, name) VALUES(1, \"Test\");"; - let str_comma_statement = "INSERT INTO users(id, name) VALUES(1, \"Firstname, Lastname\");"; - - let expected_output = Command::Insert(InsertCommand { - table_name: "users".to_string(), - items: HashMap::from([ - ( - "id".to_string(), - InsertItem { - column_name: "id".to_string(), - column_value: "1".to_string(), - }, - ), - ( - "name".to_string(), - InsertItem { - column_name: "name".to_string(), - column_value: "\"Test\"".to_string(), - }, - ), - ]), - }); - - let expected_output_comma = Command::Insert(InsertCommand { - table_name: "users".to_string(), - items: HashMap::from([ - ( - "id".to_string(), - InsertItem { - column_name: "id".to_string(), - column_value: "1".to_string(), - }, - ), - ( - "name".to_string(), - InsertItem { - column_name: "name".to_string(), - column_value: "\"Firstname, Lastname\"".to_string(), - }, - ), - ]), - }); - - assert_eq!( - Command::from_string(String::from(empty_statement)).is_ok(), - false - ); - - assert_eq!( - Command::from_string(String::from(regular_statement))?, - expected_output - ); - - assert_eq!( - Command::from_string(String::from(extra_ws_statement))?, - expected_output - ); - - assert_eq!( - Command::from_string(String::from(min_ws_statement))?, - expected_output - ); - - assert_eq!( - Command::from_string(String::from(str_comma_statement))?, - expected_output_comma - ); - - Ok(()) }