From a26ea16f553ad6fe6917269f80e85497d350dd5c Mon Sep 17 00:00:00 2001 From: Nicholas Orlowsky Date: Sun, 10 Dec 2023 13:34:29 -0600 Subject: [PATCH] filtered column names and delete --- README.md | 4 +- squirrel-client/.gitignore | 1 + squirrel-server/.gitignore | 1 + squirrel-server/src/main.rs | 82 ++++++++++++++++++++++++--- squirrel-server/src/parser/command.rs | 75 +++++++++++++++++++----- 5 files changed, 139 insertions(+), 24 deletions(-) create mode 100644 squirrel-client/.gitignore diff --git a/README.md b/README.md index 7568791..01d14d7 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,9 @@ This is a SQL database written in Rust. It will be based off of (and hopefully b [X] SELECT * query -[ ] DELETE command +[x] SELECT (filtered columns) query -[ ] SELECT (filtered columns) query +[x] DELETE command [ ] WHERE clause for SELECT and DELETE diff --git a/squirrel-client/.gitignore b/squirrel-client/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/squirrel-client/.gitignore @@ -0,0 +1 @@ +/target diff --git a/squirrel-server/.gitignore b/squirrel-server/.gitignore index ea8c4bf..a727c0a 100644 --- a/squirrel-server/.gitignore +++ b/squirrel-server/.gitignore @@ -1 +1,2 @@ /target +/data diff --git a/squirrel-server/src/main.rs b/squirrel-server/src/main.rs index f9b3d82..6f2c9fa 100644 --- a/squirrel-server/src/main.rs +++ b/squirrel-server/src/main.rs @@ -3,12 +3,13 @@ use std::fs; use std::io::{BufRead, BufReader, Read, Write}; use std::net::{Shutdown, TcpListener, TcpStream}; use std::thread; +use std::collections::HashMap; mod parser; pub use parser::command::Command; mod table; -use parser::command::{CreateCommand, InsertCommand, SelectCommand}; +use parser::command::{CreateCommand, InsertCommand, SelectCommand, DeleteCommand}; pub use table::datatypes::Datatype; pub use table::table_definition::{ColumnDefinition, TableDefinition}; @@ -86,31 +87,80 @@ fn handle_insert(command: InsertCommand) -> ::anyhow::Result<()> { Ok(()) } +fn handle_delete(command: DeleteCommand) -> ::anyhow::Result { + let mut file = fs::File::open(format!("./data/blobs/{}", command.table_name))?; + let tabledef = read_tabledef(command.table_name.clone())?; + let mut buf: Vec = vec![0; tabledef.get_byte_size()]; + let mut row_count: usize = 0; + + while file.read_exact(buf.as_mut_slice()).is_ok() { + row_count += 1; + } + + let _ = fs::remove_file(format!("./data/blobs/{}", command.table_name)); + + let _ = fs::OpenOptions::new() + .create(true) + .write(true) + .append(true) + .open(format!("./data/blobs/{}", command.table_name))?; + + return Ok(format!("{} Rows Deleted", row_count)); +} + fn handle_select(command: SelectCommand) -> ::anyhow::Result { let mut file = fs::File::open(format!("./data/blobs/{}", command.table_name))?; let tabledef = read_tabledef(command.table_name)?; let mut response = String::new(); + let mut column_names: Vec = vec![]; + + for col_name in &command.column_names { + if col_name == "*" { + for col_defs in &tabledef.column_defs { + column_names.push(col_defs.name.clone()); + } + } else { + column_names.push(col_name.clone()); + } + } response += "| "; - for col_def in &tabledef.column_defs { - response += format!("{} | ", col_def.name).as_str(); + for col_name in &column_names { + response += format!("{} | ", col_name).as_str(); } response += "\n"; response += "-----------\n"; let mut buf: Vec = vec![0; tabledef.get_byte_size()]; + + let mut table: HashMap> = HashMap::new(); + let mut num_rows: usize = 0; + + for col_name in &column_names { + table.insert(col_name.clone(), vec![]); + } + while file.read_exact(buf.as_mut_slice()).is_ok() { - response += "| "; - let mut idx = 0; + let mut idx: usize = 0; for col_def in &tabledef.column_defs { let len = if col_def.length > 0 { col_def.length } else { 1 }; - let str_val = col_def.data_type.from_bytes(&buf[idx..(idx + len)])?; - response += format!("{} | ", str_val).as_str(); + if column_names.iter().any(|col_name| &col_def.name == col_name) { + let str_val = col_def.data_type.from_bytes(&buf[idx..(idx + len)])?; + table.get_mut(&col_def.name).unwrap().push(str_val); + } idx += len; } + num_rows += 1; + } + + for i in 0..num_rows { + response += "| "; + for col_name in &column_names { + response += format!("{} | ", table.get(col_name).unwrap()[i]).as_str(); + } response += "\n"; } @@ -125,6 +175,8 @@ fn run_command(query: String) -> ::anyhow::Result { let command: Command = Command::from_string(query)?; + println!("Parsed Command: {:?}", command); + match command { Command::Create(create_command) => { let result = handle_create(create_command); @@ -150,7 +202,14 @@ fn run_command(query: String) -> ::anyhow::Result { Ok(result.err().unwrap().to_string()) } } - _ => Err(anyhow!("Invalid command")), + Command::Delete(delete_command) => { + let result = handle_delete(delete_command); + if result.is_ok() { + Ok(result?) + } else { + Ok(result.err().unwrap().to_string()) + } + } } } @@ -160,8 +219,13 @@ fn handle_client(mut stream: TcpStream) -> ::anyhow::Result<()> { while match stream.read(&mut data) { Ok(_size) => { let query_string = String::from_utf8(data.to_vec())?; - let response: String = run_command(query_string)?; + let response_res: ::anyhow::Result = run_command(query_string); + let response = match response_res { + Ok(result) => result, + Err(err_msg) => String::from(format!("Error: {}", err_msg.to_string())) + }; + let response_data_size = response.len().to_le_bytes(); stream.write_all(&response_data_size)?; // send length of message stream.write_all(response.as_bytes())?; // send message diff --git a/squirrel-server/src/parser/command.rs b/squirrel-server/src/parser/command.rs index 9b24830..fe64142 100644 --- a/squirrel-server/src/parser/command.rs +++ b/squirrel-server/src/parser/command.rs @@ -9,7 +9,7 @@ pub enum Command { Select(SelectCommand), Create(CreateCommand), Insert(InsertCommand), - Delete, + Delete(DeleteCommand), } #[derive(Debug, Eq, PartialEq)] @@ -17,6 +17,11 @@ pub struct CreateCommand { pub table_definition: TableDefinition, } +#[derive(Debug, Eq, PartialEq)] +pub struct DeleteCommand { + pub table_name: String, +} + #[derive(Debug, Eq, PartialEq)] pub struct InsertCommand { pub table_name: String, @@ -26,7 +31,7 @@ pub struct InsertCommand { #[derive(Debug, Eq, PartialEq)] pub struct SelectCommand { pub table_name: String, - // TODO Later: pub column_names: Vec, + pub column_names: Vec, } #[derive(Debug, Eq, PartialEq)] @@ -47,7 +52,13 @@ enum CreateParserState { } enum SelectParserState { - Wildcard, // Temporary, col selection coming soon + ColumnName, + ColumnNameCommaOrFrom, + TableName, + Semicolon, +} + +enum DeleteParserState { FromKeyword, TableName, Semicolon, @@ -208,25 +219,29 @@ impl Command { } fn parse_select_command(tokens: &mut Vec) -> ::anyhow::Result { - let mut state: SelectParserState = SelectParserState::Wildcard; + let mut state: SelectParserState = SelectParserState::ColumnName; // intermediate tmp vars let mut table_name = String::new(); + let mut column_names: Vec = vec![]; while let Some(token) = &tokens.pop() { match state { - SelectParserState::Wildcard => { - if token != "*" { - return Err(anyhow!("Expected to find selection at or near '{}' (SQUIRREL does not support column seletion)", token)); + SelectParserState::ColumnName => { + if token.eq_ignore_ascii_case("FROM") { + return Err(anyhow!("Did not expect FROM keyword at or near '{}'", token)); } else { - state = SelectParserState::FromKeyword; + column_names.push(token.clone()); + state = SelectParserState::ColumnNameCommaOrFrom; } } - SelectParserState::FromKeyword => { - if !token.eq_ignore_ascii_case("FROM") { - return Err(anyhow!("Expected to find FROM at or near '{}'", token)); - } else { + SelectParserState::ColumnNameCommaOrFrom => { + if token == "," { + state = SelectParserState::ColumnName; + } else if token.eq_ignore_ascii_case("FROM") { state = SelectParserState::TableName; + } else { + return Err(anyhow!("Expected comma or FROM keyword at or near '{}'", token)); } } SelectParserState::TableName => { @@ -237,7 +252,40 @@ impl Command { if token != ";" { return Err(anyhow!("Expected semicolon at or near '{}'", token)); } else { - return Ok(Command::Select(SelectCommand { table_name })); + return Ok(Command::Select(SelectCommand { table_name, column_names })); + } + } + } + } + + Err(anyhow!("Unexpected end of input")) + } + + + fn parse_delete_command(tokens: &mut Vec) -> ::anyhow::Result { + let mut state: DeleteParserState = DeleteParserState::FromKeyword; + + // intermediate tmp vars + let mut table_name = String::new(); + + while let Some(token) = &tokens.pop() { + match state { + DeleteParserState::FromKeyword => { + if !token.eq_ignore_ascii_case("FROM") { + return Err(anyhow!("Expected FROM keyword at or near '{}'", token)); + } else { + state = DeleteParserState::TableName; + } + } + DeleteParserState::TableName => { + table_name = token.to_string(); + state = DeleteParserState::Semicolon; + } + DeleteParserState::Semicolon => { + if token != ";" { + return Err(anyhow!("Expected semicolon at or near '{}'", token)); + } else { + return Ok(Command::Delete(DeleteCommand { table_name })); } } } @@ -345,6 +393,7 @@ impl Command { "CREATE" => Self::parse_create_command(&mut tokens), "INSERT" => Self::parse_insert_command(&mut tokens), "SELECT" => Self::parse_select_command(&mut tokens), + "DELETE" => Self::parse_delete_command(&mut tokens), _ => Err(anyhow!("Unknown command '{}'", token)), }; }