fix ups + rewrite parser
This commit is contained in:
parent
206ccb66ad
commit
41f6a1b4c3
8
.vscode/settings.json
vendored
8
.vscode/settings.json
vendored
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"rust-analyzer.linkedProjects": [
|
||||
"./squirrel-server/Cargo.toml",
|
||||
"./squirrel-client/Cargo.toml",
|
||||
"./squirrel-server/Cargo.toml",
|
||||
"./squirrel-server/Cargo.toml"
|
||||
]
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
use std::net::{TcpStream};
|
||||
use std::io::{Read, Write};
|
||||
use std::str::from_utf8;
|
||||
use std::io;
|
||||
use std::io::{Read, Write};
|
||||
use std::net::TcpStream;
|
||||
|
||||
fn main() {
|
||||
match TcpStream::connect("localhost:5433") {
|
||||
|
@ -23,9 +22,12 @@ fn main() {
|
|||
let response_size: usize = usize::from_le_bytes(response_size_buffer);
|
||||
let mut response_buffer = vec![0 as u8; response_size];
|
||||
stream.read_exact(&mut response_buffer).unwrap();
|
||||
println!("{}", String::from_utf8(response_buffer).expect("a utf-8 string"));
|
||||
println!(
|
||||
"{}",
|
||||
String::from_utf8(response_buffer).expect("a utf-8 string")
|
||||
);
|
||||
}
|
||||
},
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Failed to connect: {}", e);
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"rustc_fingerprint":15117991565403657335,"outputs":{"10376369925670944939":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.dylib\nlib___.dylib\nlib___.a\nlib___.dylib\n/Users/nickorlow/.rustup/toolchains/stable-x86_64-apple-darwin\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_feature=\"sse3\"\ntarget_feature=\"ssse3\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"macos\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"apple\"\nunix\n","stderr":""},"4614504638168534921":{"success":true,"status":"","code":0,"stdout":"rustc 1.66.1 (90743e729 2023-01-10)\nbinary: rustc\ncommit-hash: 90743e7298aca107ddaa0c202a4d3604e29bfeb6\ncommit-date: 2023-01-10\nhost: x86_64-apple-darwin\nrelease: 1.66.1\nLLVM version: 15.0.2\n","stderr":""},"15697416045686424142":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.dylib\nlib___.dylib\nlib___.a\nlib___.dylib\n","stderr":""}},"successes":{}}
|
||||
{"rustc_fingerprint":17949831956763062573,"outputs":{"15729799797837862367":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/home/nickorlow/.rustup/toolchains/stable-x86_64-unknown-linux-gnu\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"gnu\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"linux\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"unknown\"\nunix\n","stderr":""},"4614504638168534921":{"success":true,"status":"","code":0,"stdout":"rustc 1.69.0 (84c898d65 2023-04-16)\nbinary: rustc\ncommit-hash: 84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc\ncommit-date: 2023-04-16\nhost: x86_64-unknown-linux-gnu\nrelease: 1.69.0\nLLVM version: 15.0.7\n","stderr":""}},"successes":{}}
|
Binary file not shown.
|
@ -1 +1 @@
|
|||
/Users/nickorlow/programming/personal/SQUIRREL/squirrel-client/target/debug/squirrel-client: /Users/nickorlow/programming/personal/SQUIRREL/squirrel-client/src/main.rs
|
||||
/home/nickorlow/programming/personal/squirrel/squirrel-client/target/debug/squirrel-client: /home/nickorlow/programming/personal/squirrel/squirrel-client/src/main.rs
|
||||
|
|
80
squirrel-server/Cargo.lock
generated
80
squirrel-server/Cargo.lock
generated
|
@ -5,3 +5,83 @@ version = 3
|
|||
[[package]]
|
||||
name = "SQUIRREL"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"fancy-regex",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.72"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854"
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
|
||||
dependencies = [
|
||||
"bit-vec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-vec"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
|
||||
|
||||
[[package]]
|
||||
name = "fancy-regex"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2"
|
||||
dependencies = [
|
||||
"bit-set",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7b6d6190b7594385f61bd3911cd1be99dfddcfc365a4160cc2ab5bff4aed294"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
|
||||
|
|
|
@ -6,3 +6,6 @@ edition = "2021"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.72"
|
||||
fancy-regex = "0.11.0"
|
||||
regex = "1.9.1"
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
id integer 0
|
||||
name varchar 24
|
|
@ -1,42 +1,125 @@
|
|||
use std::thread;
|
||||
use std::net::{TcpListener, TcpStream, Shutdown};
|
||||
use std::io::{Read, Write};
|
||||
use core::str::Split;
|
||||
use std::error::Error;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fs;
|
||||
use std::io::{BufRead, BufReader, Read, Write};
|
||||
use std::net::{Shutdown, TcpListener, TcpStream};
|
||||
use std::thread;
|
||||
|
||||
mod parser;
|
||||
pub use parser::command::Command;
|
||||
|
||||
mod table;
|
||||
use parser::command::CreateCommand;
|
||||
use parser::command::{CreateCommand, InsertCommand, SelectCommand};
|
||||
pub use table::datatypes::Datatype;
|
||||
pub use table::table::TableDefinition;
|
||||
pub use table::table::{ColumnDefinition, TableDefinition};
|
||||
|
||||
use crate::parser::command::InsertItem;
|
||||
|
||||
const BUFFER_SIZE: usize = 500;
|
||||
|
||||
|
||||
/*
|
||||
CREATE TABLE [IF NOT EXISTS] table_name (
|
||||
column1 datatype(length) column_contraint,
|
||||
column2 datatype(length) column_contraint,
|
||||
column3 datatype(length) column_contraint,
|
||||
table_constraints
|
||||
);
|
||||
*/
|
||||
fn handle_create(command: CreateCommand) -> Result<TableDefinition, String> {
|
||||
println!("Creating table with name: {}", command.table_definition.name);
|
||||
let mut file = fs::File::create(format!("./data/tabledefs/{}", command.table_definition.name)).unwrap();
|
||||
fn handle_create(command: CreateCommand) -> ::anyhow::Result<TableDefinition> {
|
||||
let mut file = fs::File::create(format!(
|
||||
"./data/tabledefs/{}",
|
||||
command.table_definition.name
|
||||
))?;
|
||||
|
||||
for column in &command.table_definition.column_defs {
|
||||
println!("creating col: {} {} {}", column.name, column.data_type.as_str(), column.length);
|
||||
let line = format!("{} {} {} \n", column.name, column.data_type.as_str(), column.length);
|
||||
let line = format!(
|
||||
"{} {} {} \n",
|
||||
column.name,
|
||||
column.data_type.as_str(),
|
||||
column.length
|
||||
);
|
||||
file.write_all(line.as_bytes()).unwrap();
|
||||
}
|
||||
|
||||
return Ok(command.table_definition);
|
||||
}
|
||||
|
||||
fn read_tabledef(table_name: String) -> ::anyhow::Result<TableDefinition> {
|
||||
let file = fs::File::open(format!("./data/tabledefs/{}", table_name))?;
|
||||
|
||||
let mut column_defs = vec![];
|
||||
|
||||
for line in BufReader::new(file).lines() {
|
||||
let line_str = line?;
|
||||
let parts: Vec<&str> = line_str.split(" ").collect();
|
||||
let col_def = ColumnDefinition {
|
||||
name: parts[0].to_string(),
|
||||
data_type: Datatype::from_str(parts[1]).unwrap(),
|
||||
length: parts[2].parse::<u16>()?.into(),
|
||||
};
|
||||
column_defs.push(col_def);
|
||||
}
|
||||
|
||||
return Ok(TableDefinition {
|
||||
name: table_name,
|
||||
column_defs,
|
||||
});
|
||||
}
|
||||
|
||||
fn handle_insert(command: InsertCommand) -> ::anyhow::Result<()> {
|
||||
let mut file = fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.append(true)
|
||||
.open(format!("./data/blobs/{}", command.table_name))
|
||||
.unwrap();
|
||||
|
||||
let tabledef = read_tabledef(command.table_name).unwrap();
|
||||
|
||||
for col_def in &tabledef.column_defs {
|
||||
if let Some(insert_item) = command.items.get(&col_def.name) {
|
||||
let bytes = col_def
|
||||
.data_type
|
||||
.to_bytes(insert_item.column_value.clone())?;
|
||||
file.write_all(&bytes)?;
|
||||
if bytes.len() < col_def.length {
|
||||
let length = col_def.length - bytes.len();
|
||||
let empty_bytes = vec![0; length];
|
||||
file.write_all(&empty_bytes)?;
|
||||
}
|
||||
} else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"ERROR: INSERT statement is missing data for column '{}'",
|
||||
col_def.name
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
fn handle_select(command: SelectCommand) -> ::anyhow::Result<String> {
|
||||
let mut file = fs::File::open(format!("./data/blobs/{}", command.table_name))?;
|
||||
let tabledef = read_tabledef(command.table_name).unwrap();
|
||||
let mut response = String::new();
|
||||
|
||||
response += "| ";
|
||||
for col_def in &tabledef.column_defs {
|
||||
response += format!("{} | ", col_def.name).as_str();
|
||||
}
|
||||
response += "\n";
|
||||
response += "-----------\n";
|
||||
let mut buf: Vec<u8> = vec![0; tabledef.get_byte_size()];
|
||||
while file.read_exact(buf.as_mut_slice()).is_ok() {
|
||||
response += "| ";
|
||||
let mut idx = 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();
|
||||
idx += len;
|
||||
}
|
||||
response += "\n";
|
||||
}
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
|
||||
fn run_command(query: String) -> String {
|
||||
let response: String;
|
||||
if query.chars().nth(0).unwrap() == '\\' {
|
||||
|
@ -44,7 +127,7 @@ fn run_command(query: String) -> String {
|
|||
return String::from("Slash commands are not yet supported in SQUIRREL");
|
||||
}
|
||||
|
||||
let command_result: Result<Command, String> = Command::from_string(query);
|
||||
let command_result: ::anyhow::Result<Command> = Command::from_string(query);
|
||||
|
||||
if command_result.is_ok() {
|
||||
let command: Command = command_result.unwrap();
|
||||
|
@ -57,10 +140,21 @@ fn run_command(query: String) -> String {
|
|||
String::from("Table created.")
|
||||
}
|
||||
}
|
||||
_ => { String::from("Invalid command") }
|
||||
Command::Insert(insert_command) => {
|
||||
let result = handle_insert(insert_command);
|
||||
if result.is_err() {
|
||||
String::from(result.err().unwrap().to_string())
|
||||
} else {
|
||||
String::from("Data inserted.")
|
||||
}
|
||||
}
|
||||
Command::Select(select_command) => {
|
||||
return handle_select(select_command).unwrap();
|
||||
}
|
||||
_ => String::from("Invalid command"),
|
||||
}
|
||||
} else {
|
||||
response = command_result.err().unwrap();
|
||||
response = command_result.err().unwrap().to_string();
|
||||
}
|
||||
|
||||
return response;
|
||||
|
@ -71,33 +165,19 @@ fn handle_client(mut stream: TcpStream) {
|
|||
|
||||
while match stream.read(&mut data) {
|
||||
Ok(size) => {
|
||||
let mut query_string = String::from_utf8(data.to_vec()).expect("A UTF-8 string");
|
||||
println!("Received: {}", query_string);
|
||||
|
||||
let mut i = 0;
|
||||
for c in query_string.chars() {
|
||||
if c == ';' {
|
||||
query_string = query_string.get(0..i).unwrap().to_string();
|
||||
i = 0;
|
||||
break;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
|
||||
let response: String;
|
||||
if i == 0 {
|
||||
response = run_command(query_string);
|
||||
} else {
|
||||
response = String::from("No semicolon.");
|
||||
}
|
||||
let query_string = String::from_utf8(data.to_vec()).expect("A UTF-8 string");
|
||||
let response: String = run_command(query_string);
|
||||
|
||||
let mut response_data_size = response.len().to_le_bytes();
|
||||
stream.write(&mut response_data_size).unwrap(); // send length of message
|
||||
stream.write(response.as_bytes()).unwrap(); // send message
|
||||
true
|
||||
},
|
||||
}
|
||||
Err(_) => {
|
||||
println!("An error occurred, terminating connection with {}", stream.peer_addr().unwrap());
|
||||
println!(
|
||||
"An error occurred, terminating connection with {}",
|
||||
stream.peer_addr().unwrap()
|
||||
);
|
||||
stream.shutdown(Shutdown::Both).unwrap();
|
||||
false
|
||||
}
|
||||
|
@ -105,10 +185,10 @@ fn handle_client(mut stream: TcpStream) {
|
|||
}
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
fs::remove_dir_all("./data")?;
|
||||
fs::create_dir("./data")?;
|
||||
fs::create_dir("./data/tabledefs")?;
|
||||
fs::create_dir("./data/blobs")?;
|
||||
//fs::remove_dir_all("./data")?;
|
||||
let _ensure_data_exists = fs::create_dir("./data");
|
||||
let _ensure_tabledefs_exists = fs::create_dir("./data/tabledefs");
|
||||
let _ensure_blob_exists = fs::create_dir("./data/blobs");
|
||||
let listener = TcpListener::bind("0.0.0.0:5433")?;
|
||||
|
||||
for stream in listener.incoming() {
|
||||
|
@ -120,3 +200,80 @@ fn main() -> std::io::Result<()> {
|
|||
|
||||
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(())
|
||||
}
|
||||
|
|
|
@ -1,65 +1,349 @@
|
|||
use crate::{TableDefinition, Datatype};
|
||||
use crate::table::table::Column;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use crate::table::table::ColumnDefinition;
|
||||
use crate::{Datatype, TableDefinition};
|
||||
use anyhow::anyhow;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub enum Command {
|
||||
Select,
|
||||
Select(SelectCommand),
|
||||
Create(CreateCommand),
|
||||
Insert,
|
||||
Delete
|
||||
Insert(InsertCommand),
|
||||
Delete,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct CreateCommand {
|
||||
pub table_definition: TableDefinition,
|
||||
}
|
||||
|
||||
impl Command {
|
||||
pub fn from_string(command_str: String) -> Result<Command, String> {
|
||||
let mut parts = command_str.split(' ');
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct InsertCommand {
|
||||
pub table_name: String,
|
||||
pub items: HashMap<String, InsertItem>,
|
||||
}
|
||||
|
||||
match parts.nth(0).unwrap() {
|
||||
"CREATE" => {
|
||||
let object = String::from(parts.nth(0).unwrap());
|
||||
if object.eq_ignore_ascii_case("TABLE") {
|
||||
let mut column_definitions: Vec<Column> = vec![];
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct SelectCommand {
|
||||
pub table_name: String,
|
||||
// TODO Later: pub column_names: Vec<String>,
|
||||
}
|
||||
|
||||
let column_def_begin_idx = command_str.chars().position(|c| c == '(').unwrap() + 1;
|
||||
let column_def_end_idx = command_str.chars().position(|c| c == ')').unwrap();
|
||||
let coldef_str = command_str.get(column_def_begin_idx..column_def_end_idx).unwrap().to_string();
|
||||
let col_strs = coldef_str.split(',');
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct InsertItem {
|
||||
pub column_name: String,
|
||||
pub column_value: String,
|
||||
}
|
||||
|
||||
for col_str in col_strs {
|
||||
println!("{}", col_str);
|
||||
let mut parts = col_str.split_ascii_whitespace();
|
||||
let mut col: Column = Column {
|
||||
length: 0,
|
||||
name: parts.nth(0).unwrap().to_string(),
|
||||
data_type: Datatype::from_str(parts.nth(0).unwrap()).unwrap()
|
||||
};
|
||||
let len = parts.nth(0);
|
||||
if len.is_some() {
|
||||
if col.data_type.has_len() {
|
||||
col.length = len.unwrap().parse().unwrap();
|
||||
} else {
|
||||
return Err(format!("ERROR: Datatype '{}' does not accept a length parameter", col.data_type.as_str()));
|
||||
}
|
||||
} else if col.data_type.has_len() {
|
||||
return Err(format!("ERROR: Datatype '{}' requires a length parameter", col.data_type.as_str()));
|
||||
}
|
||||
enum CreateParserState {
|
||||
FindObject,
|
||||
FindTableName,
|
||||
FindColumnName,
|
||||
FindColumnDefinitions,
|
||||
FindColumnDatatype,
|
||||
FindColumnDefinitionEnd,
|
||||
FindColumnLength,
|
||||
FindSemicolon,
|
||||
}
|
||||
|
||||
column_definitions.push(col);
|
||||
}
|
||||
enum SelectParserState {
|
||||
FindWildcard, // Temporary, col selection coming soon
|
||||
FindFrom,
|
||||
FindTableName,
|
||||
FindSemicolon,
|
||||
}
|
||||
|
||||
return Ok(Command::Create(CreateCommand {
|
||||
table_definition: TableDefinition {
|
||||
name: String::from(parts.nth(0).unwrap()),
|
||||
column_defs: column_definitions
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
return Err(format!("ERROR: syntax error at or near '{}'", object));
|
||||
}
|
||||
},
|
||||
_ => { Err(String::from("Unable to parse command")) }
|
||||
enum InsertParserState {
|
||||
FindIntoKeyword,
|
||||
FindTableName,
|
||||
FindColumnListBegin,
|
||||
FindColumnName,
|
||||
FindColumnNameEnd,
|
||||
FindValuesKeyword,
|
||||
FindValuesListBegin,
|
||||
FindValue,
|
||||
FindValueEnd,
|
||||
FindSemicolon,
|
||||
}
|
||||
|
||||
pub fn tokenizer(text: String) -> Vec<String> {
|
||||
let parts = HashSet::from([' ', ',', ';', '(', ')']);
|
||||
let mut tokens: Vec<String> = vec![];
|
||||
let mut cur_str = String::new();
|
||||
let mut in_quotes = false;
|
||||
|
||||
for cur_char in text.chars() {
|
||||
if cur_char == '\"' {
|
||||
in_quotes = !in_quotes;
|
||||
}
|
||||
|
||||
if !in_quotes && parts.contains(&cur_char) {
|
||||
if cur_str.len() != 0 {
|
||||
tokens.push(cur_str);
|
||||
cur_str = String::new();
|
||||
}
|
||||
if cur_char != ' ' {
|
||||
tokens.push(cur_char.to_string());
|
||||
}
|
||||
} else {
|
||||
cur_str.push(cur_char);
|
||||
}
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
impl Command {
|
||||
fn parse_insert_command(tokens: &mut Vec<String>) -> ::anyhow::Result<Command> {
|
||||
let mut state: InsertParserState = InsertParserState::FindIntoKeyword;
|
||||
|
||||
let mut table_name = String::new();
|
||||
let mut column_name = String::new();
|
||||
let mut column_val = String::new();
|
||||
|
||||
let mut column_list: Vec<String> = vec![];
|
||||
let mut value_list: Vec<String> = vec![];
|
||||
|
||||
while let Some(token) = &tokens.pop() {
|
||||
match state {
|
||||
InsertParserState::FindIntoKeyword => {
|
||||
if !token.eq_ignore_ascii_case("INTO") {
|
||||
return Err(anyhow!("Expected to find INTO at or near '{}'", token));
|
||||
} else {
|
||||
state = InsertParserState::FindTableName;
|
||||
}
|
||||
}
|
||||
InsertParserState::FindTableName => {
|
||||
table_name = token.to_string();
|
||||
state = InsertParserState::FindColumnListBegin;
|
||||
}
|
||||
InsertParserState::FindColumnListBegin => {
|
||||
if token != "(" {
|
||||
return Err(anyhow!(
|
||||
"Unexpected token at or near '{}'. Expected start of column list",
|
||||
token
|
||||
));
|
||||
}
|
||||
state = InsertParserState::FindColumnName;
|
||||
}
|
||||
InsertParserState::FindColumnName => {
|
||||
column_name = token.to_string();
|
||||
state = InsertParserState::FindColumnNameEnd;
|
||||
}
|
||||
InsertParserState::FindColumnNameEnd => {
|
||||
if token == "," {
|
||||
state = InsertParserState::FindColumnName;
|
||||
} else if token == ")" {
|
||||
state = InsertParserState::FindValuesKeyword;
|
||||
} else {
|
||||
return Err(anyhow!(
|
||||
"Unexpected token at or near '{}'. Expected comma or rparen.",
|
||||
token
|
||||
));
|
||||
}
|
||||
column_list.push(column_name.clone());
|
||||
}
|
||||
InsertParserState::FindValuesKeyword => {
|
||||
if token != "VALUES" {
|
||||
return Err(anyhow!(
|
||||
"Unexpected token at or near '{}'. Expected 'VALUES'.",
|
||||
token
|
||||
));
|
||||
}
|
||||
state = InsertParserState::FindValuesListBegin;
|
||||
}
|
||||
InsertParserState::FindValuesListBegin => {
|
||||
if token != "(" {
|
||||
return Err(anyhow!(
|
||||
"Unexpected token at or near '{}'. Expected start of values list",
|
||||
token
|
||||
));
|
||||
}
|
||||
state = InsertParserState::FindValue;
|
||||
}
|
||||
InsertParserState::FindValue => {
|
||||
column_val = token.to_string();
|
||||
state = InsertParserState::FindValueEnd;
|
||||
}
|
||||
InsertParserState::FindValueEnd => {
|
||||
if token == "," {
|
||||
state = InsertParserState::FindValue;
|
||||
} else if token == ")" {
|
||||
state = InsertParserState::FindSemicolon;
|
||||
} else {
|
||||
return Err(anyhow!(
|
||||
"Unexpected token at or near '{}'. Expected comma or rparen.",
|
||||
token
|
||||
));
|
||||
}
|
||||
|
||||
value_list.push(column_val.clone());
|
||||
}
|
||||
InsertParserState::FindSemicolon => {
|
||||
if token != ";" {
|
||||
return Err(anyhow!("Expected semicolon at or near '{}'", token));
|
||||
} else {
|
||||
let mut insert_item_list: HashMap<String, InsertItem> = HashMap::new();
|
||||
for item in column_list.iter().zip(&mut value_list.iter_mut()) {
|
||||
let (col_name, value) = item;
|
||||
|
||||
insert_item_list.insert(
|
||||
col_name.clone().trim().to_string(),
|
||||
InsertItem {
|
||||
column_name: col_name.trim().to_string(),
|
||||
column_value: value.trim().to_string(),
|
||||
},
|
||||
);
|
||||
}
|
||||
return Ok(Command::Insert(InsertCommand {
|
||||
table_name,
|
||||
items: insert_item_list,
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Err(anyhow!("Unexpected end of input"));
|
||||
}
|
||||
|
||||
fn parse_select_command(tokens: &mut Vec<String>) -> ::anyhow::Result<Command> {
|
||||
let mut state: SelectParserState = SelectParserState::FindWildcard;
|
||||
|
||||
// intermediate tmp vars
|
||||
let mut table_name = String::new();
|
||||
|
||||
while let Some(token) = &tokens.pop() {
|
||||
match state {
|
||||
SelectParserState::FindWildcard => {
|
||||
if token != "*" {
|
||||
return Err(anyhow!("Expected to find selection at or near '{}' (SQUIRREL does not support column seletion)", token));
|
||||
} else {
|
||||
state = SelectParserState::FindFrom;
|
||||
}
|
||||
}
|
||||
SelectParserState::FindFrom => {
|
||||
if !token.eq_ignore_ascii_case("FROM") {
|
||||
return Err(anyhow!("Expected to find FROM at or near '{}'", token));
|
||||
} else {
|
||||
state = SelectParserState::FindTableName;
|
||||
}
|
||||
}
|
||||
SelectParserState::FindTableName => {
|
||||
table_name = token.to_string();
|
||||
state = SelectParserState::FindSemicolon;
|
||||
}
|
||||
SelectParserState::FindSemicolon => {
|
||||
if token != ";" {
|
||||
return Err(anyhow!("Expected semicolon at or near '{}'", token));
|
||||
} else {
|
||||
return Ok(Command::Select(SelectCommand { table_name }));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Err(anyhow!("Unexpected end of input"));
|
||||
}
|
||||
|
||||
fn parse_create_command(tokens: &mut Vec<String>) -> ::anyhow::Result<Command> {
|
||||
let mut state: CreateParserState = CreateParserState::FindObject;
|
||||
let mut col_defs: Vec<ColumnDefinition> = vec![];
|
||||
|
||||
// intermediate tmp vars
|
||||
let mut table_name = String::new();
|
||||
let mut data_type: Option<Datatype> = None;
|
||||
let mut length = 0;
|
||||
let mut col_name = String::new();
|
||||
|
||||
while let Some(token) = &tokens.pop() {
|
||||
match state {
|
||||
CreateParserState::FindObject => match token.to_uppercase().as_str() {
|
||||
"TABLE" => {
|
||||
state = CreateParserState::FindTableName;
|
||||
}
|
||||
_ => return Err(anyhow!("Can't create object of type '{}'", token.as_str())),
|
||||
},
|
||||
CreateParserState::FindTableName => {
|
||||
state = CreateParserState::FindColumnDefinitions;
|
||||
table_name = token.to_string();
|
||||
}
|
||||
CreateParserState::FindColumnDefinitions => {
|
||||
if token != "(" {
|
||||
return Err(anyhow!("Could not find column list"));
|
||||
} else {
|
||||
state = CreateParserState::FindColumnName;
|
||||
}
|
||||
}
|
||||
CreateParserState::FindColumnName => {
|
||||
col_name = token.to_string();
|
||||
state = CreateParserState::FindColumnDatatype;
|
||||
}
|
||||
CreateParserState::FindColumnDatatype => {
|
||||
let dtype = Datatype::from_str(&token).unwrap();
|
||||
if dtype.has_len() {
|
||||
state = CreateParserState::FindColumnLength;
|
||||
} else {
|
||||
state = CreateParserState::FindColumnDefinitionEnd;
|
||||
}
|
||||
data_type = Some(dtype);
|
||||
}
|
||||
CreateParserState::FindColumnLength => {
|
||||
length = token.parse()?;
|
||||
state = CreateParserState::FindColumnDefinitionEnd;
|
||||
}
|
||||
CreateParserState::FindColumnDefinitionEnd => {
|
||||
let column_def = ColumnDefinition {
|
||||
data_type: data_type.unwrap(),
|
||||
length,
|
||||
name: col_name,
|
||||
};
|
||||
|
||||
length = 0;
|
||||
col_name = String::new();
|
||||
data_type = None;
|
||||
|
||||
col_defs.push(column_def);
|
||||
|
||||
match token.as_str() {
|
||||
"," => {
|
||||
state = CreateParserState::FindColumnName;
|
||||
}
|
||||
")" => {
|
||||
state = CreateParserState::FindSemicolon;
|
||||
}
|
||||
_ => return Err(anyhow!("Expected end")),
|
||||
}
|
||||
}
|
||||
CreateParserState::FindSemicolon => {
|
||||
if token != ";" {
|
||||
return Err(anyhow!("Expected semicolon at or near '{}'", token));
|
||||
} else {
|
||||
return Ok(Command::Create(CreateCommand {
|
||||
table_definition: TableDefinition {
|
||||
name: table_name,
|
||||
column_defs: col_defs,
|
||||
},
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Err(anyhow!("Unexpected end of input"));
|
||||
}
|
||||
|
||||
pub fn from_string(command_str: String) -> ::anyhow::Result<Command> {
|
||||
let mut tokens: Vec<String> = tokenizer(command_str);
|
||||
tokens.reverse();
|
||||
if let Some(token) = tokens.pop() {
|
||||
return match token.to_uppercase().as_str() {
|
||||
"CREATE" => Self::parse_create_command(&mut tokens),
|
||||
"INSERT" => Self::parse_insert_command(&mut tokens),
|
||||
"SELECT" => Self::parse_select_command(&mut tokens),
|
||||
_ => Err(anyhow!("Unknown command '{}'", token)),
|
||||
};
|
||||
}
|
||||
return Err(anyhow!("Unexpected end of statement"));
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub enum Datatype {
|
||||
Integer,
|
||||
CharacterVarying,
|
||||
|
@ -7,25 +8,60 @@ impl Datatype {
|
|||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Datatype::CharacterVarying => "varchar",
|
||||
Datatype::Integer => "integer"
|
||||
Datatype::Integer => "integer",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_len(&self) -> bool {
|
||||
match self {
|
||||
Datatype::CharacterVarying => true,
|
||||
Datatype::Integer => false
|
||||
Datatype::Integer => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self, data_val: String) -> ::anyhow::Result<Vec<u8>> {
|
||||
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);
|
||||
return Ok(str_bytes);
|
||||
}
|
||||
Datatype::Integer => {
|
||||
let val = data_val.parse::<u8>()?;
|
||||
return Ok(vec![val]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_bytes(&self, data_val: &[u8]) -> ::anyhow::Result<String> {
|
||||
match self {
|
||||
Datatype::CharacterVarying => {
|
||||
let str_val = String::from_utf8(data_val.to_vec())?;
|
||||
return Ok(str_val);
|
||||
}
|
||||
Datatype::Integer => {
|
||||
let val = data_val.first().unwrap();
|
||||
return Ok(format!("{}", val));
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn from_str(string: &str) -> Result<Datatype, String> {
|
||||
match string {
|
||||
"varchar" => return Ok(Datatype::CharacterVarying),
|
||||
"character varying" => return Ok(Datatype::CharacterVarying),
|
||||
"integer" => return Ok(Datatype::Integer),
|
||||
"int" => return Ok(Datatype::Integer),
|
||||
"int4" => return Ok(Datatype::Integer),
|
||||
_ => {return Err(String::from("Undefined data type"))}
|
||||
"int8" => return Ok(Datatype::Integer),
|
||||
_ => return Err(String::from("Undefined data type")),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +1,31 @@
|
|||
use crate::Datatype;
|
||||
|
||||
pub struct Column {
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct ColumnDefinition {
|
||||
pub name: String,
|
||||
pub data_type: Datatype,
|
||||
pub length: u16 // used for char(n), varchar(n)
|
||||
pub length: usize, // used for char(n), varchar(n)
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct TableDefinition {
|
||||
pub name: String,
|
||||
pub column_defs: Vec<Column>,
|
||||
pub column_defs: Vec<ColumnDefinition>,
|
||||
}
|
||||
|
||||
impl TableDefinition {
|
||||
pub fn get_byte_size(&self) -> usize {
|
||||
let mut sum: usize = 0;
|
||||
for col_def in self.column_defs.iter() {
|
||||
// TODO HACK FIXME
|
||||
// We should keep track of length
|
||||
// even for built-in datatypes.
|
||||
sum += if col_def.length > 0 {
|
||||
col_def.length
|
||||
} else {
|
||||
1
|
||||
};
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue