Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 158 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <binary_name>`. 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

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

```

```
43 changes: 16 additions & 27 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ struct Args {
#[arg(long, default_value_t = 50051)]
port: u16,

/// Database URL
db_url: Option<String>,
/// Database name
db: Option<String>,
}

trait ClientEditor {
Expand Down Expand Up @@ -275,13 +275,13 @@ fn run_client<W: Write, E: ClientEditor>(
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,
Expand All @@ -292,7 +292,7 @@ fn run_client<W: Write, E: ClientEditor>(
},
};

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 {
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -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(),
Expand All @@ -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)?;

Expand Down Expand Up @@ -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(),
Expand All @@ -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)?;

Expand All @@ -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(),
Expand All @@ -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)?;

Expand All @@ -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(),
Expand All @@ -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)?;

Expand Down
10 changes: 5 additions & 5 deletions src/driver/embedded.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?)))
Expand All @@ -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()?;
Expand Down Expand Up @@ -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))")?;
Expand Down