diff --git a/README.md b/README.md index 75a2ffe..f5768c3 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,160 @@ This repository is a Rust implementation of SimpleDB from the book "[Database De ## Overview -The main goal of this repoistory is my own learning. The code from Chapter 3 to Chapter 15 of the book was originally implemented in Java, and I have re-implemented it in Rust. I will add the code for the exercises of each chapter when I feel like it. +The main goal of this repoistory is my own learning. The code from Chapter 3 to Chapter 15 of the book was originally implemented in Java, and I have re-implemented it in Rust. + +## Usage + +Three binaries are available: + +- `client`: A SQL client for interacting with the database. + - It receives three command line arguments: + - `db`: The name of the database to connect to. + - `host`: The host where the database server is running. If it is not specified, the client will connect to local DB with the embedded driver. Otherwise, it will connect to the remote DB server with the specified host and port. + - `port`: The port on which the database server is listening (default: `50051`). If the `host` is not specified, this argument is ignored. +- `server`: The database server that handles client requests. +- `create_student_db`: Creates a sample database for the student records. + +You can run these binaries using `cargo run --bin `. For example, to run the client, use: + +```bash +cargo run --bin client studentdb +``` + +### Supported SQL Commands + +The client supports the following SQL commands: + +- `SELECT`: Retrieve data from one or more tables. It supports some additional clauses and keywords: + + - `WHERE`: Filter records based on conditions. + - `ORDER BY`: Sort the results by one or more columns. + - `GROUP BY`: Group records based on one or more columns. + - `AS`: Rename columns in the result set. + +- `INSERT`: Add new records to a table. +- `DELETE`: Remove records from a table. +- `MODIFY`: Update existing records in a table. +- `SHOW TABLES`: List all tables in the database. +- `CREATE TABLE`: Create a new table in the database. +- `CREATE INDEX`: Create an index on a table. + +For more details, please see the grammar in `src/parser/grammar.lalrpop`. + +### Example (embedded) + +```bash +➜ simpledb-rs git:(main) ✗ cargo run --quiet --bin create_student_db # Create STUDENT DB for DEMO +Table STUDENT created. +STUDENT records inserted. +Table DEPT created. +DEPT records inserted. +Table COURSE created. +COURSE records inserted. +Table SECTION created. +SECTION records inserted. +Table ENROLL created. +ENROLL records inserted. +➜ simpledb-rs git:(main) ✗ cargo run --quiet --bin client studentdb # Connect to the local studentdb with embedded driver +SQL (studentdb)> show tables + name | schema +------------------------------------------------------------------------------------ +tblcat | slotsize I32, tblname VARCHAR(50) +fldcat | type I32, length I32, offset I32, tblname VARCHAR(50), fldname VARCHAR(50) +idxcat | index_name VARCHAR(255), table_name VARCHAR(255), field_name VARCHAR(255) +STUDENT | SId I32, MajorId I32, GradYear I32, SName VARCHAR(10) +DEPT | DId I32, DName VARCHAR(8) +COURSE | CId I32, DeptId I32, Title VARCHAR(20) +SECTION | SectId I32, CourseId I32, YearOffered I32, Prof VARCHAR(8) +ENROLL | EId I32, StudentId I32, SectionId I32, Grade VARCHAR(2) + +SQL (studentdb)> select * from STUDENT + SId | MajorId | GradYear | SName +------------------------------------------------------- + 1 | 10 | 2021 | joe + 2 | 20 | 2020 | amy + 3 | 10 | 2022 | max + 4 | 20 | 2022 | sue + 5 | 30 | 2020 | bob + 6 | 20 | 2020 | kim + 7 | 30 | 2021 | art + 8 | 20 | 2019 | pat + 9 | 10 | 2021 | lee + +SQL (studentdb)> select SName, DName, Grade, YearOffered from STUDENT, DEPT, ENROLL, SECTION where SId = StudentId and SectId = SectionId and DId = MajorId and YearOffered = 2018 + YearOffered | SName | DName | Grade +-------------------------------------------- + 2018 | joe | compsci | A + 2018 | sue | math | A + 2018 | kim | math | A + +SQL (studentdb)> select * from ENROLL + EId | StudentId | SectionId | Grade +-------------------------------------------------- + 14 | 1 | 13 | A + 24 | 1 | 43 | C + 34 | 2 | 43 | B+ + 44 | 4 | 33 | B + 54 | 4 | 53 | A + 64 | 6 | 53 | A + +SQL (studentdb)> MODIFY ENROLL SET Grade = 'A+' WHERE StudentId = 6 +1 records processed + +SQL (studentdb)> select SName, DName, Grade, YearOffered from STUDENT, DEPT, ENROLL, SECTION where SId = StudentId and SectId = SectionId and DId = MajorId and YearOffered = 2018 + YearOffered | SName | DName | Grade +-------------------------------------------- + 2018 | joe | compsci | A + 2018 | sue | math | A + 2018 | kim | math | A+ + +SQL (studentdb)> DELETE FROM STUDENT WHERE SName = 'joe' +1 records processed + +SQL (studentdb)> select * from STUDENT + SId | MajorId | GradYear | SName +------------------------------------------------------- + 2 | 20 | 2020 | amy + 3 | 10 | 2022 | max + 4 | 20 | 2022 | sue + 5 | 30 | 2020 | bob + 6 | 20 | 2020 | kim + 7 | 30 | 2021 | art + 8 | 20 | 2019 | pat + 9 | 10 | 2021 | lee + +SQL (studentdb)> exit + +``` + +### Example (remote) + +In terminal A, start the server: + +```bash +➜ simpledb-rs git:(main) ✗ cargo run --quiet --bin server # Start the server whose port is 50051 +``` + +In terminal B, connect to the server: + +```bash +➜ simpledb-rs git:(main) ✗ cargo run --quiet --bin client -- --host 127.0.0.1 studentdb # Connect to the local server with network driver +SQL ()> select * from COURSE + CId | DeptId | Title +-------------------------------------------------- + 12 | 10 | db systems + 22 | 10 | compilers + 32 | 20 | calculus + 42 | 20 | algebra + 52 | 30 | acting + 62 | 30 | elocution + +SQL ()> exit +``` + +### Interactive Client + +The `client` binary now uses `rustyline` for input. This allows convenient line editing and command history. Previous statements are stored in a `.simpledb_history` file in the current directory and loaded automatically the next time the client runs. ## Completed excercises @@ -31,11 +184,11 @@ The following issues correspond to finished exercises from the book: - [Exercise 13.16](https://github.com/flowlight0/simpledb-rs/issues/112): Implement more aggregation functions - [Exercise 13.7](https://github.com/flowlight0/simpledb-rs/issues/127): Support "GROUP BY" clause -## Interactive Client - -The `client` binary now uses `rustyline` for input. This allows convenient line editing and command history. Previous statements are stored in a `.simpledb_history` file in the current directory and loaded automatically the next time the client runs. - ## Useful links - https://github.com/mnogu/simpledb-rs: Better implementation of SimpleDB in Rust - https://zenn.dev/hmarui66/scraps/850df4edc50c58: Well organized Japanese summary of each chapter of the book + +``` + +``` diff --git a/src/client.rs b/src/client.rs index 2a61def..80dde45 100644 --- a/src/client.rs +++ b/src/client.rs @@ -29,8 +29,8 @@ struct Args { #[arg(long, default_value_t = 50051)] port: u16, - /// Database URL - db_url: Option, + /// Database name + db: Option, } trait ClientEditor { @@ -275,13 +275,13 @@ fn run_client( driver: Driver, editor: &mut E, writer: &mut W, - db_url: Option<&str>, + db: Option<&str>, ) -> Result<(), anyhow::Error> { let history_path = PathBuf::from(".simpledb_history"); let _ = editor.load_history(&history_path); - let db_url = match db_url { - Some(url) => url.to_string(), + let db_name = match db { + Some(db_name) => db_name.to_string(), None => loop { match editor.readline("Connect> ") { Ok(line) => break line, @@ -292,7 +292,7 @@ fn run_client( }, }; - let (db_name, mut connection) = driver.connect(db_url.trim_end())?; + let (db_name, mut connection) = driver.connect(db_name.trim_end())?; let mut statement = connection.create_statement()?; loop { @@ -348,7 +348,7 @@ fn main() -> Result<(), anyhow::Error> { Driver::Embedded(EmbeddedDriver::new()) }; - run_client(driver, &mut editor, &mut stdout(), args.db_url.as_deref()) + run_client(driver, &mut editor, &mut stdout(), args.db.as_deref()) } #[cfg(test)] @@ -407,10 +407,8 @@ mod tests { #[serial_test::serial] fn test_run_client_select() -> Result<(), anyhow::Error> { let work_dir = tempfile::tempdir()?; - let db_url = format!( - "jdbc:simpledb:{}", - work_dir.path().join("db").to_string_lossy() - ); + let db_name = work_dir.path().join("db").to_string_lossy().to_string(); + let commands = vec![ "create table T(A I32)".to_string(), "insert into T(A) values (1)".to_string(), @@ -426,7 +424,7 @@ mod tests { Driver::Embedded(EmbeddedDriver::new()), &mut editor, &mut output, - Some(&db_url), + Some(&db_name), )?; std::env::set_current_dir(current)?; @@ -455,10 +453,7 @@ mod tests { #[serial_test::serial] fn test_run_client_select_two_columns() -> Result<(), anyhow::Error> { let work_dir = tempfile::tempdir()?; - let db_url = format!( - "jdbc:simpledb:{}", - work_dir.path().join("db").to_string_lossy() - ); + let db_name = work_dir.path().join("db").to_string_lossy().to_string(); let commands = vec![ "create table T(A I32, B I32, C VARCHAR(5))".to_string(), "insert into T(A, B, C) values (1, 2, 'foo')".to_string(), @@ -473,7 +468,7 @@ mod tests { Driver::Embedded(EmbeddedDriver::new()), &mut editor, &mut output, - Some(&db_url), + Some(&db_name), )?; std::env::set_current_dir(current)?; @@ -499,10 +494,7 @@ mod tests { #[serial_test::serial] fn test_run_client_ignore_empty_input() -> Result<(), anyhow::Error> { let work_dir = tempfile::tempdir()?; - let db_url = format!( - "jdbc:simpledb:{}", - work_dir.path().join("db").to_string_lossy() - ); + let db_name = work_dir.path().join("db").to_string_lossy().to_string(); let commands = vec![ "".to_string(), "create table T(A I32)".to_string(), @@ -518,7 +510,7 @@ mod tests { Driver::Embedded(EmbeddedDriver::new()), &mut editor, &mut output, - Some(&db_url), + Some(&db_name), )?; std::env::set_current_dir(current)?; @@ -544,10 +536,7 @@ mod tests { #[serial_test::serial] fn test_run_client_show_tables() -> Result<(), anyhow::Error> { let work_dir = tempfile::tempdir()?; - let db_url = format!( - "jdbc:simpledb:{}", - work_dir.path().join("db").to_string_lossy() - ); + let db_name = work_dir.path().join("db").to_string_lossy().to_string(); let commands = vec![ "create table T1(A I32, B VARCHAR(10))".to_string(), "create table T2(C I32)".to_string(), @@ -562,7 +551,7 @@ mod tests { Driver::Embedded(EmbeddedDriver::new()), &mut editor, &mut output, - Some(&db_url), + Some(&db_name), )?; std::env::set_current_dir(current)?; diff --git a/src/driver/embedded.rs b/src/driver/embedded.rs index 1e168a9..6cf94ad 100644 --- a/src/driver/embedded.rs +++ b/src/driver/embedded.rs @@ -265,7 +265,7 @@ impl EmbeddedDriver { impl DriverControl for EmbeddedDriver { fn connect(&self, db_url: &str) -> Result<(String, Connection), anyhow::Error> { - let db_name = db_url.replace("jdbc:simpledb:", "").trim().to_string(); + let db_name = db_url.trim().to_string(); let db_directory = PathBuf::from(&db_name); let db = SimpleDB::new(db_directory, DEFAULT_BLOCK_SIZE, DEFAULT_NUM_BUFFERS)?; Ok((db_name, Connection::Embedded(EmbeddedConnection::new(db)?))) @@ -279,10 +279,10 @@ mod tests { #[test] fn test_embedded_driver_basic_flow() -> Result<(), anyhow::Error> { let temp_dir = tempfile::tempdir().unwrap().into_path().join("directory"); - let db_url = format!("jdbc:simpledb:{}", temp_dir.to_string_lossy()); + let db_name = temp_dir.to_string_lossy().to_string(); let driver = EmbeddedDriver::new(); - let (db_name, mut connection) = driver.connect(&db_url)?; + let (db_name, mut connection) = driver.connect(&db_name)?; assert_eq!(db_name, temp_dir.to_string_lossy()); let mut statement = connection.create_statement()?; @@ -322,10 +322,10 @@ mod tests { #[test] fn test_embedded_driver_null_handling() -> Result<(), anyhow::Error> { let temp_dir = tempfile::tempdir().unwrap().into_path().join("dir_null"); - let db_url = format!("jdbc:simpledb:{}", temp_dir.to_string_lossy()); + let db_name = temp_dir.to_string_lossy(); let driver = EmbeddedDriver::new(); - let (_db_name, connection) = driver.connect(&db_url)?; + let (_db_name, connection) = driver.connect(&db_name)?; let mut statement = connection.create_statement()?; statement.execute_update("create table test (A I32, B VARCHAR(20))")?;