add files
This commit is contained in:
commit
206ccb66ad
140 changed files with 367 additions and 0 deletions
BIN
squirrel-server/.DS_Store
vendored
Normal file
BIN
squirrel-server/.DS_Store
vendored
Normal file
Binary file not shown.
1
squirrel-server/.gitignore
vendored
Normal file
1
squirrel-server/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target
|
7
squirrel-server/Cargo.lock
generated
Normal file
7
squirrel-server/Cargo.lock
generated
Normal file
|
@ -0,0 +1,7 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "SQUIRREL"
|
||||
version = "0.1.0"
|
8
squirrel-server/Cargo.toml
Normal file
8
squirrel-server/Cargo.toml
Normal file
|
@ -0,0 +1,8 @@
|
|||
[package]
|
||||
name = "SQUIRREL"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
2
squirrel-server/data/tabledefs/users
Normal file
2
squirrel-server/data/tabledefs/users
Normal file
|
@ -0,0 +1,2 @@
|
|||
id integer 0
|
||||
name varchar 24
|
122
squirrel-server/src/main.rs
Normal file
122
squirrel-server/src/main.rs
Normal file
|
@ -0,0 +1,122 @@
|
|||
use std::thread;
|
||||
use std::net::{TcpListener, TcpStream, Shutdown};
|
||||
use std::io::{Read, Write};
|
||||
use core::str::Split;
|
||||
use std::error::Error;
|
||||
use std::fs;
|
||||
|
||||
mod parser;
|
||||
pub use parser::command::Command;
|
||||
|
||||
mod table;
|
||||
use parser::command::CreateCommand;
|
||||
pub use table::datatypes::Datatype;
|
||||
pub use table::table::TableDefinition;
|
||||
|
||||
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();
|
||||
|
||||
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);
|
||||
file.write_all(line.as_bytes()).unwrap();
|
||||
}
|
||||
|
||||
return Ok(command.table_definition);
|
||||
}
|
||||
|
||||
fn run_command(query: String) -> String {
|
||||
let response: String;
|
||||
if query.chars().nth(0).unwrap() == '\\' {
|
||||
// handle PSQL's slash commands e.g.: \dt \d
|
||||
return String::from("Slash commands are not yet supported in SQUIRREL");
|
||||
}
|
||||
|
||||
let command_result: Result<Command, String> = Command::from_string(query);
|
||||
|
||||
if command_result.is_ok() {
|
||||
let command: Command = command_result.unwrap();
|
||||
response = match command {
|
||||
Command::Create(create_command) => {
|
||||
let result_result = handle_create(create_command);
|
||||
if result_result.is_err() {
|
||||
String::from("Error creating table.")
|
||||
} else {
|
||||
String::from("Table created.")
|
||||
}
|
||||
}
|
||||
_ => { String::from("Invalid command") }
|
||||
}
|
||||
} else {
|
||||
response = command_result.err().unwrap();
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
fn handle_client(mut stream: TcpStream) {
|
||||
let mut data = [0 as u8; BUFFER_SIZE];
|
||||
|
||||
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 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());
|
||||
stream.shutdown(Shutdown::Both).unwrap();
|
||||
false
|
||||
}
|
||||
} {}
|
||||
}
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
fs::remove_dir_all("./data")?;
|
||||
fs::create_dir("./data")?;
|
||||
fs::create_dir("./data/tabledefs")?;
|
||||
fs::create_dir("./data/blobs")?;
|
||||
let listener = TcpListener::bind("0.0.0.0:5433")?;
|
||||
|
||||
for stream in listener.incoming() {
|
||||
thread::spawn(|| {
|
||||
handle_client(stream.expect("A valid stream"));
|
||||
()
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
65
squirrel-server/src/parser/command.rs
Normal file
65
squirrel-server/src/parser/command.rs
Normal file
|
@ -0,0 +1,65 @@
|
|||
use crate::{TableDefinition, Datatype};
|
||||
use crate::table::table::Column;
|
||||
|
||||
pub enum Command {
|
||||
Select,
|
||||
Create(CreateCommand),
|
||||
Insert,
|
||||
Delete
|
||||
}
|
||||
|
||||
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(' ');
|
||||
|
||||
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![];
|
||||
|
||||
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(',');
|
||||
|
||||
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()));
|
||||
}
|
||||
|
||||
column_definitions.push(col);
|
||||
}
|
||||
|
||||
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")) }
|
||||
}
|
||||
}
|
||||
}
|
1
squirrel-server/src/parser/mod.rs
Normal file
1
squirrel-server/src/parser/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub mod command;
|
31
squirrel-server/src/table/datatypes.rs
Normal file
31
squirrel-server/src/table/datatypes.rs
Normal file
|
@ -0,0 +1,31 @@
|
|||
pub enum Datatype {
|
||||
Integer,
|
||||
CharacterVarying,
|
||||
}
|
||||
|
||||
impl Datatype {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Datatype::CharacterVarying => "varchar",
|
||||
Datatype::Integer => "integer"
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_len(&self) -> bool {
|
||||
match self {
|
||||
Datatype::CharacterVarying => true,
|
||||
Datatype::Integer => false
|
||||
}
|
||||
}
|
||||
|
||||
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"))}
|
||||
}
|
||||
}
|
||||
}
|
2
squirrel-server/src/table/mod.rs
Normal file
2
squirrel-server/src/table/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
pub mod table;
|
||||
pub mod datatypes;
|
12
squirrel-server/src/table/table.rs
Normal file
12
squirrel-server/src/table/table.rs
Normal file
|
@ -0,0 +1,12 @@
|
|||
use crate::Datatype;
|
||||
|
||||
pub struct Column {
|
||||
pub name: String,
|
||||
pub data_type: Datatype,
|
||||
pub length: u16 // used for char(n), varchar(n)
|
||||
}
|
||||
|
||||
pub struct TableDefinition {
|
||||
pub name: String,
|
||||
pub column_defs: Vec<Column>,
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue