value exprs

This commit is contained in:
Nicholas Orlowsky 2023-12-24 00:21:04 -05:00
parent 824a5cdb43
commit d298cb129a
25 changed files with 451 additions and 210 deletions

View file

@ -1,8 +1,12 @@
# Changelog # Changelog
## 12/24/23
- Created common logic for parsing ValueExpressions
## 12/23/23 ## 12/23/23
- Added LogicExpressions for creating and evaluating expressions - Added LogicExpressions for creating and evaluating expressions
- Added LogicExpressions to SELECT and DELETE commands (i.e SELECT FROM WHERE and DELETE FROM WHERE) - Added LogicExpressions to SELECT and DELETE commands (i.e SELECT FROM WHERE and DELETE FROM WHERE)
- Updated SELECT formatting to pad table columns - Updated SELECT formatting to pad table columns
- General system stability improvements to enhance the user's experience. - General system stability improvements to enhance the user's experience.
- Moved many functions over to squirrel_core library

View file

@ -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 [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 [ ] Move parsing to client

View file

@ -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"

View file

@ -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]

View file

@ -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"

23
squirrel_client/Cargo.lock generated Normal file
View file

@ -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",
]

View file

@ -0,0 +1,7 @@
[package]
name = "squirrel_client"
version = "0.1.0"
edition = "2021"
[dependencies]
squirrel_core = { path = "../squirrel_core" }

16
squirrel_core/Cargo.lock generated Normal file
View file

@ -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",
]

11
squirrel_core/Cargo.toml Normal file
View file

@ -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"

181
squirrel_core/src/main.rs Normal file
View file

@ -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(())
}

View file

@ -1,8 +1,8 @@
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::mem; use std::mem;
use crate::table::table_definition::ColumnDefinition; use crate::table::table_definition::{TableDefinition, ColumnDefinition};
use crate::{Datatype, TableDefinition}; use crate::table::datatypes::{Datatype};
use anyhow::anyhow; use anyhow::anyhow;
#[derive(Debug, Eq, PartialEq)] #[derive(Debug, Eq, PartialEq)]
@ -55,26 +55,38 @@ enum LogicalOperator {
} }
#[derive(Debug, Eq, PartialEq, Clone)] #[derive(Debug, Eq, PartialEq, Clone)]
pub enum LogicValue { pub enum DataValue {
StringValue(String), StringValue(String),
U8Value(u8), U8Value(u8),
BoolValue(bool), BoolValue(bool),
ColumnName(String),
} }
#[derive(Debug, Eq, PartialEq)] #[derive(Debug, Eq, PartialEq)]
pub enum LogicSide { pub enum LogicSide {
// pub expression: LogicExpression, // pub expression: LogicExpression,
Value(LogicValue) Value(DataValue)
} }
#[derive(Debug, Eq, PartialEq, Clone)] #[derive(Debug, Eq, PartialEq, Clone)]
pub struct LogicExpression { pub struct LogicExpression {
pub left_hand: LogicValue, pub left_hand: ValueExpression,
pub right_hand: LogicValue, pub right_hand: ValueExpression,
pub operator: LogicalOperator pub operator: LogicalOperator
} }
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct FunctionCall {
pub function_name: String,
pub parameters: Vec<String>,
}
#[derive(Debug, Eq, PartialEq, Clone)]
pub enum ValueExpression {
FunctionCall(FunctionCall),
DataValue(DataValue),
ColumnName(String),
}
enum CreateParserState { enum CreateParserState {
Object, Object,
TableName, TableName,
@ -114,6 +126,14 @@ enum InsertParserState {
Semicolon, Semicolon,
} }
enum ValueExpressionParserState {
NumericOrQuoteOrColumnOrFunction,
StringValue,
EndQuote,
FunctionOpenParenOrEnd,
FunctionCloseParen,
}
#[derive(Debug)] #[derive(Debug)]
enum LogicExpressionParserState { enum LogicExpressionParserState {
NumberOrQuoteOrColname, NumberOrQuoteOrColname,
@ -124,14 +144,26 @@ enum LogicExpressionParserState {
pub fn tokenizer(text: String) -> Vec<String> { pub fn tokenizer(text: String) -> Vec<String> {
let parts = HashSet::from([' ', ',', ';', '(', ')', '\'']); let parts = HashSet::from([' ', ',', ';', '(', ')', '\'', '\"']);
let mut tokens: Vec<String> = vec![]; let mut tokens: Vec<String> = vec![];
let mut cur_str = String::new(); let mut cur_str = String::new();
let mut in_quotes = false; let mut in_quotes = false;
let mut cur_quote = '\0';
for cur_char in text.chars() { for cur_char in text.chars() {
if cur_char == '\"' { if !in_quotes && (cur_char == '\"' || cur_char == '\''){
in_quotes = !in_quotes; 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) { if !in_quotes && parts.contains(&cur_char) {
@ -146,21 +178,24 @@ pub fn tokenizer(text: String) -> Vec<String> {
cur_str.push(cur_char); cur_str.push(cur_char);
} }
} }
tokens.push(cur_str);
if cur_str.len() > 0 {
tokens.push(cur_str);
}
tokens tokens
} }
impl LogicValue { impl DataValue {
pub fn from_string(string: String) -> ::anyhow::Result<LogicValue> { pub fn from_string(string: String) -> ::anyhow::Result<DataValue> {
let test = string.parse::<u8>(); let test = string.parse::<u8>();
match test { match test {
Ok(u8_val) => { Ok(u8_val) => {
return Ok(LogicValue::U8Value(u8_val)); return Ok(DataValue::U8Value(u8_val));
}, },
Err(_) => { Err(_) => {
let res = string.trim_matches(char::from(0)); 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 { impl LogicExpression {
pub fn is_valid(&self) -> bool { 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 { pub fn is_evaluatable(&self) -> bool {
return mem::discriminant(&self.left_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(&LogicValue::ColumnName(String::from(""))); mem::discriminant(&self.right_hand) == mem::discriminant(&ValueExpression::DataValue(DataValue::U8Value(0)));
} }
pub fn fill_values(&mut self, hmap: HashMap<String, LogicValue>) -> ::anyhow::Result<()> { pub fn fill_values(&mut self, hmap: HashMap<String, ValueExpression>) -> ::anyhow::Result<()> {
for (name, value) in hmap { 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(); 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(); self.right_hand = value.clone();
} }
} }
@ -197,18 +241,16 @@ impl LogicExpression {
} }
println!("{:?}", self); println!("{:?}", self);
match self.left_hand { match self.left_hand {
LogicValue::StringValue(_) => { ValueExpression::DataValue(DataValue::StringValue(_)) => {
return self.evaluate_string(); return self.evaluate_string();
} }
LogicValue::BoolValue(_) => { ValueExpression::DataValue(DataValue::BoolValue(_)) => {
return self.evaluate_bool(); return self.evaluate_bool();
} }
LogicValue::U8Value(_) => { ValueExpression::DataValue(DataValue::U8Value(_)) => {
return self.evaluate_u8(); return self.evaluate_u8();
} }
LogicValue::ColumnName(_) => { _ => {return Err(anyhow!("Cannot compare non-finalized value expressions"))}
return Err(anyhow!("Cannot compare names of 2 columns, only values"));
}
} }
} }
@ -229,16 +271,16 @@ impl LogicExpression {
return Ok(self.left_hand == self.right_hand); return Ok(self.left_hand == self.right_hand);
} }
LogicalOperator::And => { LogicalOperator::And => {
if let LogicValue::BoolValue(left) = self.left_hand { if let ValueExpression::DataValue(DataValue::BoolValue(left)) = self.left_hand {
if let LogicValue::BoolValue(right) = self.right_hand { if let ValueExpression::DataValue(DataValue::BoolValue(right)) = self.right_hand {
return Ok(left && right); return Ok(left && right);
} }
} }
return Err(anyhow!("Mismatched datatypes")); return Err(anyhow!("Mismatched datatypes"));
} }
LogicalOperator::Or => { LogicalOperator::Or => {
if let LogicValue::BoolValue(left) = self.left_hand { if let ValueExpression::DataValue(DataValue::BoolValue(left)) = self.left_hand {
if let LogicValue::BoolValue(right) = self.right_hand { if let ValueExpression::DataValue(DataValue::BoolValue(right)) = self.right_hand {
return Ok(left || right); return Ok(left || right);
} }
} }
@ -256,32 +298,32 @@ impl LogicExpression {
return Ok(self.left_hand == self.right_hand); return Ok(self.left_hand == self.right_hand);
} }
LogicalOperator::GreaterThan => { LogicalOperator::GreaterThan => {
if let LogicValue::U8Value(left) = self.left_hand { if let ValueExpression::DataValue(DataValue::U8Value(left)) = self.left_hand {
if let LogicValue::U8Value(right) = self.right_hand { if let ValueExpression::DataValue(DataValue::U8Value(right)) = self.right_hand {
return Ok(left > right); return Ok(left > right);
} }
} }
return Err(anyhow!("Mismatched datatypes")); return Err(anyhow!("Mismatched datatypes"));
} }
LogicalOperator::LessThan => { LogicalOperator::LessThan => {
if let LogicValue::U8Value(left) = self.left_hand { if let ValueExpression::DataValue(DataValue::U8Value(left)) = self.left_hand {
if let LogicValue::U8Value(right) = self.right_hand { if let ValueExpression::DataValue(DataValue::U8Value(right)) = self.right_hand {
return Ok(left < right); return Ok(left < right);
} }
} }
return Err(anyhow!("Mismatched datatypes")); return Err(anyhow!("Mismatched datatypes"));
} }
LogicalOperator::GreaterThanEqualTo => { LogicalOperator::GreaterThanEqualTo => {
if let LogicValue::U8Value(left) = self.left_hand { if let ValueExpression::DataValue(DataValue::U8Value(left)) = self.left_hand {
if let LogicValue::U8Value(right) = self.right_hand { if let ValueExpression::DataValue(DataValue::U8Value(right)) = self.right_hand {
return Ok(left >= right); return Ok(left >= right);
} }
} }
return Err(anyhow!("Mismatched datatypes")); return Err(anyhow!("Mismatched datatypes"));
} }
LogicalOperator::LessThanEqualTo => { LogicalOperator::LessThanEqualTo => {
if let LogicValue::U8Value(left) = self.left_hand { if let ValueExpression::DataValue(DataValue::U8Value(left)) = self.left_hand {
if let LogicValue::U8Value(right) = self.right_hand { if let ValueExpression::DataValue(DataValue::U8Value(right)) = self.right_hand {
return Ok(left <= right); return Ok(left <= right);
} }
} }
@ -295,6 +337,70 @@ impl LogicExpression {
} }
impl Command { impl Command {
fn parse_value_expression(tokens: &mut Vec<String>) -> ::anyhow::Result<ValueExpression> {
let mut state: ValueExpressionParserState = ValueExpressionParserState::NumericOrQuoteOrColumnOrFunction;
let mut quote_type = "'";
let mut value_expr: Option<ValueExpression> = 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::<u8>();
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<String>) -> ::anyhow::Result<Command> { fn parse_insert_command(tokens: &mut Vec<String>) -> ::anyhow::Result<Command> {
let mut state: InsertParserState = InsertParserState::IntoKeyword; let mut state: InsertParserState = InsertParserState::IntoKeyword;
@ -363,7 +469,23 @@ impl Command {
state = InsertParserState::Value; state = InsertParserState::Value;
} }
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; state = InsertParserState::ValueEnd;
} }
InsertParserState::ValueEnd => { InsertParserState::ValueEnd => {
@ -410,8 +532,8 @@ impl Command {
fn parse_logic_expression(tokens: &mut Vec<String>) -> ::anyhow::Result<LogicExpression> { fn parse_logic_expression(tokens: &mut Vec<String>) -> ::anyhow::Result<LogicExpression> {
let mut state: LogicExpressionParserState = LogicExpressionParserState::NumberOrQuoteOrColname; let mut state: LogicExpressionParserState = LogicExpressionParserState::NumberOrQuoteOrColname;
let mut left_hand: Option<LogicValue> = None; let mut left_hand: Option<ValueExpression> = None;
let mut right_hand: Option<LogicValue> = None; let mut right_hand: Option<ValueExpression> = None;
let mut operator: Option<LogicalOperator> = None; let mut operator: Option<LogicalOperator> = None;
while let Some(token) = &tokens.pop() { while let Some(token) = &tokens.pop() {
@ -424,19 +546,19 @@ impl Command {
match test { match test {
Ok(u8_val) => { Ok(u8_val) => {
if left_hand.is_none() { if left_hand.is_none() {
left_hand = Some(LogicValue::U8Value(u8_val)); left_hand = Some(ValueExpression::DataValue(DataValue::U8Value(u8_val)));
state = LogicExpressionParserState::Operator; state = LogicExpressionParserState::Operator;
} else { } 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()}); return Ok(LogicExpression {left_hand: left_hand.unwrap(), right_hand: right_hand.unwrap(), operator: operator.unwrap()});
} }
}, },
Err(_) => { Err(_) => {
if left_hand.is_none() { if left_hand.is_none() {
left_hand = Some(LogicValue::ColumnName(token.to_string())); left_hand = Some(ValueExpression::ColumnName(token.to_string()));
state = LogicExpressionParserState::Operator; state = LogicExpressionParserState::Operator;
} else { } 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()}); return Ok(LogicExpression {left_hand: left_hand.unwrap(), right_hand: right_hand.unwrap(), operator: operator.unwrap()});
} }
}, },
@ -445,11 +567,11 @@ impl Command {
} }
LogicExpressionParserState::StringValue => { LogicExpressionParserState::StringValue => {
let mut value: Option<LogicValue> = None; let mut value: Option<ValueExpression> = None;
if token == "'" { if token == "'" {
value = Some(LogicValue::StringValue("".to_string())); value = Some(ValueExpression::DataValue(DataValue::StringValue("".to_string())));
} else { } else {
value = Some(LogicValue::StringValue(token.to_string())); value = Some(ValueExpression::DataValue(DataValue::StringValue(token.to_string())));
} }
if left_hand.is_none() { if left_hand.is_none() {
left_hand = value; left_hand = value;
@ -701,4 +823,12 @@ impl Command {
tokens.reverse(); tokens.reverse();
return Ok(Self::parse_logic_expression(&mut tokens)?); return Ok(Self::parse_logic_expression(&mut tokens)?);
} }
pub fn value_expression_from_string(command_str: String) -> ::anyhow::Result<ValueExpression> {
let mut tokens: Vec<String> = tokenizer(command_str.clone());
println!("{}", command_str);
println!("{:?}", tokens);
tokens.reverse();
return Ok(Self::parse_value_expression(&mut tokens)?);
}
} }

View file

@ -24,17 +24,7 @@ impl Datatype {
pub fn to_bytes(&self, data_val: String) -> ::anyhow::Result<Vec<u8>> { pub fn to_bytes(&self, data_val: String) -> ::anyhow::Result<Vec<u8>> {
match self { match self {
Datatype::CharacterVarying => { 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(); 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) Ok(str_bytes)
} }
Datatype::Integer => { Datatype::Integer => {

View file

@ -1,4 +1,4 @@
use crate::Datatype; use crate::table::datatypes::Datatype;
#[derive(Debug, Eq, PartialEq)] #[derive(Debug, Eq, PartialEq)]
pub struct ColumnDefinition { pub struct ColumnDefinition {

2
squirrel_server/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/target
/data

View file

@ -9,8 +9,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854"
[[package]] [[package]]
name = "squirrel-server" name = "squirrel_core"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
] ]
[[package]]
name = "squirrel_server"
version = "0.1.0"
dependencies = [
"anyhow",
"squirrel_core",
]

View file

@ -0,0 +1,8 @@
[package]
name = "squirrel_server"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "1.0.72"
squirrel_core = { path = "../squirrel_core" }

View file

@ -6,13 +6,10 @@ use std::thread;
use std::collections::HashMap; use std::collections::HashMap;
use std::cmp; use std::cmp;
mod parser; pub use squirrel_core::parser::command::Command;
pub use parser::command::Command; use squirrel_core::parser::command::{CreateCommand, InsertCommand, SelectCommand, DeleteCommand, LogicExpression, InsertItem, DataValue, ValueExpression};
pub use squirrel_core::table::datatypes::Datatype;
mod table; pub use squirrel_core::table::table_definition::{ColumnDefinition, TableDefinition};
use parser::command::{CreateCommand, InsertCommand, SelectCommand, DeleteCommand, LogicExpression, InsertItem, LogicValue};
pub use table::datatypes::Datatype;
pub use table::table_definition::{ColumnDefinition, TableDefinition};
const BUFFER_SIZE: usize = 500; const BUFFER_SIZE: usize = 500;
@ -101,7 +98,7 @@ fn handle_delete(command: DeleteCommand) -> ::anyhow::Result<String> {
.open(format!("./data/blobs/{}_new", command.table_name))?; .open(format!("./data/blobs/{}_new", command.table_name))?;
while file.read_exact(buf.as_mut_slice()).is_ok() { while file.read_exact(buf.as_mut_slice()).is_ok() {
let mut row_data: HashMap<String, LogicValue> = HashMap::new(); let mut row_data: HashMap<String, ValueExpression> = HashMap::new();
let mut idx: usize = 0; let mut idx: usize = 0;
if let Some(ref le) = command.logic_expression { if let Some(ref le) = command.logic_expression {
let mut logic_expr = le.clone(); let mut logic_expr = le.clone();
@ -113,7 +110,7 @@ fn handle_delete(command: DeleteCommand) -> ::anyhow::Result<String> {
}; };
let str_val = col_def.data_type.from_bytes(&buf[idx..(idx + len)])?; let str_val = col_def.data_type.from_bytes(&buf[idx..(idx + len)])?;
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; idx = 0;
logic_expr.fill_values(row_data); logic_expr.fill_values(row_data);
@ -160,7 +157,7 @@ fn handle_select(command: SelectCommand) -> ::anyhow::Result<String> {
while file.read_exact(buf.as_mut_slice()).is_ok() { while file.read_exact(buf.as_mut_slice()).is_ok() {
let mut idx: usize = 0; let mut idx: usize = 0;
let mut row_data: HashMap<String, LogicValue> = HashMap::new(); let mut row_data: HashMap<String, ValueExpression> = HashMap::new();
if let Some(ref le) = command.logic_expression { if let Some(ref le) = command.logic_expression {
let mut logic_expr = le.clone(); let mut logic_expr = le.clone();
for col_def in &tabledef.column_defs { for col_def in &tabledef.column_defs {
@ -171,7 +168,7 @@ fn handle_select(command: SelectCommand) -> ::anyhow::Result<String> {
}; };
let str_val = col_def.data_type.from_bytes(&buf[idx..(idx + len)])?; let str_val = col_def.data_type.from_bytes(&buf[idx..(idx + len)])?;
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; idx = 0;
logic_expr.fill_values(row_data); 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(()) Ok(())
} }