diff --git a/Cargo.toml b/Cargo.toml index 1af75dc..f56cbcb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,14 +10,5 @@ authors = ["Paolo Rechia"] [dependencies] log = "0.4.20" env_logger = { version = "0.10.1", features = ["color"] } -steeldb-parser = { path = "steeldb-parser", optional = true } -hyper = { version = "1", features = ["full"], optional = true} -tokio = { version = "1", features = ["full"], optional = true} -http-body-util = {version = "0.1", optional = true} -hyper-util = { version = "0.1", features = ["full"], optional = true} - -[features] -default = ["database"] -database = ["dep:steeldb-parser"] -client = ["dep:hyper", "dep:tokio", "dep:http-body-util", "dep:hyper-util"] -server = ["dep:hyper", "dep:tokio", "dep:http-body-util", "dep:hyper-util", "dep:steeldb-parser"] \ No newline at end of file +steeldb-parser = { path = "steeldb-parser" } +steeldb-core = { path = "steeldb-core" } \ No newline at end of file diff --git a/src/database.rs b/src/database.rs index a5e1da2..e50e101 100644 --- a/src/database.rs +++ b/src/database.rs @@ -2,41 +2,20 @@ //! It's the root module of the SteelDB package, being the entrypoint into the Database code. //! Everything that should be accessed is re-exported in the 'lib.rs' file. -pub mod datatypes; - mod logger; -pub mod table; - -#[cfg(feature = "database")] -mod table_impl; +mod in_memory_table; -#[cfg(feature = "database")] mod command; -#[cfg(feature = "database")] pub mod config; -#[cfg(feature = "database")] -pub mod console_printer; - -#[cfg(feature = "database")] mod file_io; -#[cfg(feature = "database")] mod parser; -#[cfg(feature = "database")] -pub mod repl; - -#[cfg(feature = "database")] pub mod steeldb; -#[cfg(feature = "database")] mod tests; -#[cfg(feature = "database")] mod virtual_machine; - -#[cfg(feature = "server")] -pub mod server; diff --git a/src/database/command.rs b/src/database/command.rs index 2747685..a612ab0 100644 --- a/src/database/command.rs +++ b/src/database/command.rs @@ -1,5 +1,5 @@ //! Defines commands that the VirtualMachine may execute. -use crate::database::table::Table; +use crate::database::in_memory_table::InMemoryTable; /// All known commands are defined in this enum. pub enum Command { @@ -12,7 +12,7 @@ pub enum Command { /// Defines possible results from a command execution. pub enum CommandResult { /// Command returned a table. - RetrievedDataSuccess(Table), + RetrievedDataSuccess(InMemoryTable), /// Command failed by an unexpected reason. Error(String), /// Command succeded but has no output. diff --git a/src/database/file_io.rs b/src/database/file_io.rs index 1bad663..90fe19d 100644 --- a/src/database/file_io.rs +++ b/src/database/file_io.rs @@ -1,38 +1,12 @@ //! This module defines structs / methods to save/read data to/from disk. -use crate::database::datatypes::DataType; use std::collections::HashMap; use std::fs::File; use std::io::{Read, Write}; +use steeldb_core::DataType; /// Defines the string 'TABLE COLUMNAR FORMAT HEADER\n' that goes to the top of the columnar file. const COLUMNAR_HEADER: [u8; 29] = *b"TABLE COLUMNAR FORMAT HEADER\n"; -// Enums -/// Defines the supported file formats by the Database -#[derive(Debug)] -pub enum FileFormat { - /// The only supported file for now is the SimpleColumnar, which is a naive ASCII format. - /// Here is an example of this format: - /// ```txt - /// TABLE COLUMNAR FORMAT HEADER - /// Field name: final_grade; Type: f32; Number of elements: 3 - /// 4.0 - /// 3.2 - /// 5 - /// Field name: name; Type: String; Number of elements: 3 - /// John Man - /// Lenon - /// Mary - /// Field name: annual_salary; Type: i32; Number of elements: 3 - /// 60000 - /// 200000 - /// 3012000 - /// - /// ``` - /// Notice that the newline at the end is not optional. - SimpleColumnar, -} - // Traits /// The public interface of a table Writer. Used for dynamic dispatching in runtime. pub trait Writer { diff --git a/src/database/table_impl.rs b/src/database/in_memory_table.rs similarity index 67% rename from src/database/table_impl.rs rename to src/database/in_memory_table.rs index 0311e09..53cf582 100644 --- a/src/database/table_impl.rs +++ b/src/database/in_memory_table.rs @@ -1,12 +1,31 @@ //! In memory data representations. use crate::database::config::DATA_DIR; -use crate::database::file_io::{ColumnarReader, ColumnarWriter, FileFormat, Reader, Writer}; -use crate::database::table::{SaveMode, Table, TableErrors}; +use crate::database::file_io::{ColumnarReader, ColumnarWriter, Reader, Writer}; use log::info; +use std::collections::HashMap; use std::fs::OpenOptions; use std::path::Path; +use steeldb_core::{DataType, FileFormat, SaveMode, Table, TableErrors}; -impl Table { +/// This defines a way to keep the data in-memory by the SteelDB. +/// It also represents the Table that the user receives back when querying the database. +/// This is currently in a columnar format. +/// Most of the exposed functionality here is a low level API meant to be used during the +/// database development. It is not meant to be used directly by database users. +#[derive(Debug)] +pub struct InMemoryTable { + /// The table name, this is used as an identifier for retrieving the correct table. + pub name: String, + /// The table fields or schema. + pub fields: HashMap, + /// The actual data stored in columnar format. + pub columns: HashMap>, + /// Used when retrieving data, allowing for projection push-down on query. + /// That is, the Database do not read columns that were not specified in the query. + pub select_columns: Vec, +} + +impl InMemoryTable { /// Creates the data dir if it does not yet exist. pub fn init_data_dir() { if !Path::new(DATA_DIR).exists() { @@ -20,9 +39,20 @@ impl Table { FileFormat::SimpleColumnar => format!("{}/{}.columnar", DATA_DIR, name), } } + pub fn new() -> InMemoryTable { + InMemoryTable { + name: String::new(), + fields: HashMap::::new(), + columns: HashMap::>::new(), + select_columns: Vec::::new(), + } + } +} + +impl Table for InMemoryTable { /// Saves the table to disk. - pub fn save(&self, mode: SaveMode, format: FileFormat) -> Result<(), TableErrors> { - let s = Table::get_table_path(&self.name, &format); + fn save(&self, mode: SaveMode, format: FileFormat) -> Result<(), TableErrors> { + let s = InMemoryTable::get_table_path(&self.name, &format); let path = Path::new(&s); info!( "Saving table in format {:?} ({:?}) to path: {:?}", @@ -71,12 +101,13 @@ impl Table { return Ok(()); } /// Loads the table from disk. - pub fn load( + fn load( + &self, table_name: String, select_columns: Vec, format: FileFormat, - ) -> Result { - let s = Table::get_table_path(&table_name, &format); + ) -> Result, TableErrors> { + let s = InMemoryTable::get_table_path(&table_name, &format); let path = Path::new(&s); info!("Loading table in format {:?} from path: {:?}", format, path); @@ -106,12 +137,12 @@ impl Table { return Err(TableErrors::ColumnNotFound(select_col.clone())); } } - let table = Table { + let table = InMemoryTable { name: table_name, fields, columns, select_columns, }; - return Ok(table); + return Ok(Box::new(table)); } } diff --git a/src/database/server.rs b/src/database/server.rs deleted file mode 100644 index 8b13789..0000000 --- a/src/database/server.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/database/steeldb.rs b/src/database/steeldb.rs index 405f39d..27d2dd6 100644 --- a/src/database/steeldb.rs +++ b/src/database/steeldb.rs @@ -3,9 +3,9 @@ use crate::database::command::CommandResult; use crate::database::logger::logger_init; use crate::database::parser::{parse, ParseError}; -use crate::database::table::Table; use crate::database::virtual_machine::VirtualMachine; use log::{error, info}; +use steeldb_core::ExecutionResult; /// The main struct exposed by the crate. /// See the crate root documentation on how to use it. @@ -15,19 +15,6 @@ pub struct SteelDB { virtual_machine: VirtualMachine, } -/// The return type given by the [SteelDB::execute] function. -pub enum ExecutionResult { - /// A result where a table was successfully computed/retrieved, and is available for inspection. - TableResult(Table), - /// A result where a command was successfully executed, but with no output. - VoidOK, - /// Parse error. The given input string was not valid for the parser. - ParseError(String), - /// Command error. Something went wrong when executing the command. - /// Examples include `ColumnNotFound`, `TableNotFound` etc. - CommandError(String), -} - impl SteelDB { /// SteelDB constructor, should be called to initialize logger and virtual machine. pub fn new() -> SteelDB { @@ -49,7 +36,7 @@ impl SteelDB { match command_result { CommandResult::RetrievedDataSuccess(table) => { info!("Retrieved data successfully"); - return ExecutionResult::TableResult(table); + return ExecutionResult::TableResult(Box::new(table)); } CommandResult::VoidSuccess => { info!("Command successful"); diff --git a/src/database/table.rs b/src/database/table.rs deleted file mode 100644 index 7cd5de7..0000000 --- a/src/database/table.rs +++ /dev/null @@ -1,48 +0,0 @@ -//! Public interface of table. -use crate::database::datatypes::DataType; -use std::collections::HashMap; - -/// This defines a way to keep the data in-memory by the SteelDB. -/// It also represents the Table that the user receives back when querying the database. -/// This is currently in a columnar format. -/// Most of the exposed functionality here is a low level API meant to be used during the -/// database development. It is not meant to be used directly by database users. -#[derive(Debug)] -pub struct Table { - /// The table name, this is used as an identifier for retrieving the correct table. - pub name: String, - /// The table fields or schema. - pub fields: HashMap, - /// The actual data stored in columnar format. - pub columns: HashMap>, - /// Used when retrieving data, allowing for projection push-down on query. - /// That is, the Database do not read columns that were not specified in the query. - pub select_columns: Vec, -} - -/// The defined errors that might occur when loading or saving a table. -/// This is forwarded back by the VirtualMachine. -#[derive(Debug)] -pub enum TableErrors { - /// The table with the given name was not found. - TableNotFound, - /// Attempted to save a table with a name that already exists. - TableAlreadyExists, - /// The select column was not found in the table. - ColumnNotFound(String), - /// Unspecified write error when saving the table. - WriteError(String), - /// Unspecified read error when loading the table. - ReadError(String), - /// Generic unspecified error. - Error(String), -} - -/// Defines how the table should be saved. -/// This is a low level API and not meant to used directly -/// by database users. -#[derive(Debug)] -pub enum SaveMode { - Overwrite, - Append, -} diff --git a/src/database/tests.rs b/src/database/tests.rs index 8fd20f6..f6a1515 100644 --- a/src/database/tests.rs +++ b/src/database/tests.rs @@ -2,16 +2,16 @@ #[cfg(test)] mod tests { use crate::database::config::DATA_DIR; - use crate::database::datatypes::DataType; - use crate::database::file_io::FileFormat; - use crate::database::table::{SaveMode, Table, TableErrors}; + use crate::database::in_memory_table::InMemoryTable; use std::collections::HashMap; use std::path::Path; + use steeldb_core::DataType; + use steeldb_core::{FileFormat, SaveMode, Table, TableErrors}; pub fn load_test_table( table_name: String, select_columns: Vec, - ) -> Result { + ) -> Result { let mut fields = HashMap::::new(); fields.insert("name".to_string(), DataType::String("name".to_string())); fields.insert("annual_salary".to_string(), DataType::Integer32(0)); @@ -39,7 +39,7 @@ mod tests { columns.insert("annual_salary".to_string(), annual_salary_column); columns.insert("final_grade".to_string(), final_grade_column); - let mut test_table = Table { + let mut test_table = InMemoryTable { name: table_name, fields: fields, columns: columns, @@ -64,7 +64,7 @@ mod tests { } fn write_test_table(table_name: &str) { - Table::init_data_dir(); + InMemoryTable::init_data_dir(); let mut filename = table_name.to_string(); filename.push_str(".columnar"); @@ -104,7 +104,7 @@ mod tests { "annual_salary".to_string(), "final_grade".to_string(), ]; - let load_result = Table::load( + let load_result = InMemoryTable::new().load( table_name.to_string(), select_columns, FileFormat::SimpleColumnar, @@ -162,7 +162,7 @@ mod tests { let table_name = "test_column_not_found"; write_test_table(&table_name); let select_columns = vec!["durp".to_string()]; - let load_result = Table::load( + let load_result = InMemoryTable::new().load( table_name.to_string(), select_columns, FileFormat::SimpleColumnar, diff --git a/src/database/virtual_machine.rs b/src/database/virtual_machine.rs index 1d415f2..d8f835f 100644 --- a/src/database/virtual_machine.rs +++ b/src/database/virtual_machine.rs @@ -1,8 +1,9 @@ //! VirtualMachine that takes parsed interpreted as commands and executes them. //! This effectively maps the Parser output into an actual code. use crate::database::command::{Command, CommandResult}; -use crate::database::file_io::FileFormat; -use crate::database::table::Table; +use crate::database::in_memory_table::InMemoryTable; +use steeldb_core::FileFormat; +use steeldb_core::Table; /// For now, an empty struct, but could be extended. pub struct VirtualMachine {} @@ -24,7 +25,8 @@ impl VirtualMachine { // this assumes the parser built a list of commands in the right order of execution for command in commands { if let Command::SelectFrom(columns, table_name) = command { - let table_result = Table::load(table_name, columns, FileFormat::SimpleColumnar); + let table_result = + InMemoryTable::new().load(table_name, columns, FileFormat::SimpleColumnar); // if we found an error, we want to immediately abort the nested execution if table_result.is_err() { diff --git a/src/lib.rs b/src/lib.rs index 8d57080..f70aa6d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -57,10 +57,8 @@ mod database; pub use database::config; -pub use database::datatypes::DataType; -pub use database::repl::Repl; -pub use database::steeldb::{ExecutionResult, SteelDB}; -pub use database::table::Table; +pub use database::steeldb::SteelDB; +pub use steeldb_core::{DataType, Table}; /// Crate version defined in `Cargo.toml` file, retrieved at runtime. /// This is displayed in the REPL. diff --git a/steeldb-client/Cargo.toml b/steeldb-client/Cargo.toml new file mode 100644 index 0000000..722bea5 --- /dev/null +++ b/steeldb-client/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "steeldb-client" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +hyper = { version = "1", features = ["full"], optional = true} +tokio = { version = "1", features = ["full"], optional = true} +http-body-util = {version = "0.1", optional = true} +hyper-util = { version = "0.1", features = ["full"], optional = true} diff --git a/steeldb-client/src/main.rs b/steeldb-client/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/steeldb-client/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/steeldb-core/Cargo.toml b/steeldb-core/Cargo.toml new file mode 100644 index 0000000..651360f --- /dev/null +++ b/steeldb-core/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "steeldb-core" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/database/console_printer.rs b/steeldb-core/src/console_printer.rs similarity index 98% rename from src/database/console_printer.rs rename to steeldb-core/src/console_printer.rs index a2e5f69..8755103 100644 --- a/src/database/console_printer.rs +++ b/steeldb-core/src/console_printer.rs @@ -1,5 +1,5 @@ -use crate::database::datatypes::DataType; -use crate::database::table::Table; +use steeldb_core::DataType; +use steeldb_core::Table; use crate::VERSION; use std::collections::HashMap; use std::io; diff --git a/src/database/datatypes.rs b/steeldb-core/src/datatypes.rs similarity index 100% rename from src/database/datatypes.rs rename to steeldb-core/src/datatypes.rs diff --git a/steeldb-core/src/lib.rs b/steeldb-core/src/lib.rs new file mode 100644 index 0000000..296e32c --- /dev/null +++ b/steeldb-core/src/lib.rs @@ -0,0 +1,5 @@ +pub mod datatypes; +pub mod table; + +pub use crate::table::{Table, TableErrors, SaveMode, ExecutionResult, FileFormat}; +pub use crate::datatypes::DataType; diff --git a/src/database/repl.rs b/steeldb-core/src/repl.rs similarity index 100% rename from src/database/repl.rs rename to steeldb-core/src/repl.rs diff --git a/steeldb-core/src/table.rs b/steeldb-core/src/table.rs new file mode 100644 index 0000000..41fe6a2 --- /dev/null +++ b/steeldb-core/src/table.rs @@ -0,0 +1,78 @@ +//! Public interface of table. + + +// Enums +/// Defines the supported file formats by the Database +#[derive(Debug)] +pub enum FileFormat { + /// The only supported file for now is the SimpleColumnar, which is a naive ASCII format. + /// Here is an example of this format: + /// ```txt + /// TABLE COLUMNAR FORMAT HEADER + /// Field name: final_grade; Type: f32; Number of elements: 3 + /// 4.0 + /// 3.2 + /// 5 + /// Field name: name; Type: String; Number of elements: 3 + /// John Man + /// Lenon + /// Mary + /// Field name: annual_salary; Type: i32; Number of elements: 3 + /// 60000 + /// 200000 + /// 3012000 + /// + /// ``` + /// Notice that the newline at the end is not optional. + SimpleColumnar, +} + +pub trait Table { + fn save(&self, mode: SaveMode, format: FileFormat) -> Result<(), TableErrors>; + fn load( + &self, + table_name: String, + select_columns: Vec, + format: FileFormat, + ) -> Result, TableErrors>; +} + +/// The return type given by the [SteelDB::execute] function. +pub enum ExecutionResult { + /// A result where a table was successfully computed/retrieved, and is available for inspection. + TableResult(Box), + /// A result where a command was successfully executed, but with no output. + VoidOK, + /// Parse error. The given input string was not valid for the parser. + ParseError(String), + /// Command error. Something went wrong when executing the command. + /// Examples include `ColumnNotFound`, `TableNotFound` etc. + CommandError(String), +} + +/// The defined errors that might occur when loading or saving a table. +/// This is forwarded back by the VirtualMachine. +#[derive(Debug)] +pub enum TableErrors { + /// The table with the given name was not found. + TableNotFound, + /// Attempted to save a table with a name that already exists. + TableAlreadyExists, + /// The select column was not found in the table. + ColumnNotFound(String), + /// Unspecified write error when saving the table. + WriteError(String), + /// Unspecified read error when loading the table. + ReadError(String), + /// Generic unspecified error. + Error(String), +} + +/// Defines how the table should be saved. +/// This is a low level API and not meant to used directly +/// by database users. +#[derive(Debug)] +pub enum SaveMode { + Overwrite, + Append, +} diff --git a/steeldb-server/Cargo.toml b/steeldb-server/Cargo.toml new file mode 100644 index 0000000..dc62529 --- /dev/null +++ b/steeldb-server/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "steeldb-server" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +hyper = { version = "1", features = ["full"], optional = true} +tokio = { version = "1", features = ["full"], optional = true} +http-body-util = {version = "0.1", optional = true} +hyper-util = { version = "0.1", features = ["full"], optional = true} +steeldb-parser = { path = "steeldb-parser", optional = true } diff --git a/steeldb-server/src/main.rs b/steeldb-server/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/steeldb-server/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +}