fix ups + rewrite parser

This commit is contained in:
Nicholas Orlowsky 2023-08-08 01:38:02 -05:00 committed by Nicholas Orlowsky
parent 206ccb66ad
commit 41f6a1b4c3
13 changed files with 708 additions and 137 deletions

View file

@ -1,8 +0,0 @@
{
"rust-analyzer.linkedProjects": [
"./squirrel-server/Cargo.toml",
"./squirrel-client/Cargo.toml",
"./squirrel-server/Cargo.toml",
"./squirrel-server/Cargo.toml"
]
}

View file

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

View file

@ -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":{}}

View file

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

View file

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

View file

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

View file

@ -1,2 +0,0 @@
id integer 0
name varchar 24

View file

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

View file

@ -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"));
}
}

View file

@ -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")),
}
}
}

View file

@ -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;
}
}