RustでDBを自作しよう！！

# 概要

以下の一般的なRDBMSのアーキテクチャの3～6の部分を実装をする。

|機能|説明|実装有無|
|---|---|---|
|構文解析器|SQLを解析|×|
|クエリプランナ|実行計画作成|×|
|クエリエクスキュータ|実行計画に従いアクセスメソッドを呼び出し|○|
|アクセスメソッド|データ構造をたどりクエリエクスキュータの要求通り結果を返す（B+Treeを利用）|○|
|バッファプールマネージャ|データをメモリにキャッシュ|○|
|ディスクマネージャ|データをディスクに書込＆データをディスクから読込|○|

構文解析器や実行計画作成ないのにどうやって実行するの？
→ SQLではなく、クエリエクスキュータを実行する

# 元ネタ

元ネタは以下
WEB+DB PRESS Vol.122の特集「Rustで実装！作って学ぶRDBMSのしくみ」

上記の雑誌を参考に記載しています
なるべく一般的な内容＆公開されているプログラムコードから判断できることのみを記載しています
詳細な説明を知りたい場合は、雑誌を買いましょう！！めちゃくちゃ良い巻の認識！！

# 環境構築

DBの作成にあたり環境構築を行う

## Visual Studio C++ Build toolsのダウンロード＆インストール

Rustを使いはじめるには、Visual Studio C++ Build toolsが必要なので、以下のURLからダウンロード
https://visualstudio.microsoft.com/visual-cpp-build-tools/

「C++によるデスクトップ開発」にチェックを入れ、インストール
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103334/579db356-3c63-30b1-655c-729321a52e84.png)

## Rustをダウンロード＆インストール

以下のURLからRUSTUP-INIT.EXE(64-BIT)をダウンロード
https://www.rust-lang.org/ja/learn/get-started

rustup-init.exeを実行
※デフォルトの設定でインストール

コマンドプロンプトで以下を実行してバージョンが確認できれば成功

```
C:\>rustc --version
rustc 1.58.1 (db9d1b20b 2022-01-20)
```

## visual studio codeをダウンロード＆インストール（任意）

以下からイントーラーをダウンロードして、画面に従ってインストール（任意）
https://code.visualstudio.com/download
※デフォルトの設定でインストール

コード補完の拡張機能（rust-lang.rust）をインストール（任意）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103334/37d4e81a-e625-b607-dabf-f48f296e067c.png)

デバックの拡張機能（vadimcn.vscode-lldb）をインストール（任意）

# 実装（というか配置）

githubの
https://github.com/KOBA789/relly
をクローンorダウンロードしてきて配置

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103334/5d4dc9d5-3898-1e16-3ef5-782d61a7f247.png)

main.rsがなく、lib.rsなので、ライブラリとして作成されていますね

visual studio codeを起動し、C:\dev\relly-wdbフォルダをOPENする

## ディスクマネージャはどのソース？

・以下がディスクマネージャの実装
https://github.com/KOBA789/relly/blob/wdb/src/disk.rs

・ポイント
ファイルは、ヒープファイル構造
ファイルの読み書きしている
※ヒープファイル（ファイルをページと呼ばれる固定の長さごとに区切る）

```rust

//ページ単位は4K
pub const PAGE_SIZE: usize = 4096;

//構造
pub struct DiskManager {
    heap_file: File,
    next_page_id: u64,
}

impl DiskManager {
    pub fn new(heap_file: File) -> io::Result<Self> {
        let heap_file_size = heap_file.metadata()?.len();
        let next_page_id = heap_file_size / PAGE_SIZE as u64;
        Ok(Self {
            heap_file,
            next_page_id,
        })
    }

    //省略

    // 読んだり
    pub fn read_page_data(&mut self, page_id: PageId, data: &mut [u8]) -> io::Result<()> {
        //省略
    }

    // 書いたり
    pub fn write_page_data(&mut self, page_id: PageId, data: &[u8]) -> io::Result<()> {
        //省略
    }

    //省略

```

## バッファプールマネージャはどのソース？

・以下がバッファプールマネージャの実装
https://github.com/KOBA789/relly/blob/wdb/src/buffer.rs

・ポイント1
ディスクの内容をメモリ上にキャッシュすることで、ディスクの遅さを回避
キャッシュになかったら、ディスク読み込む

```rust

//構造
pub struct BufferPoolManager {
    disk: DiskManager,
    pool: BufferPool,
    page_table: HashMap<PageId, BufferId>,
}

impl BufferPoolManager {
    pub fn new(disk: DiskManager, pool: BufferPool) -> Self {
        let page_table = HashMap::new();
        Self {
            disk,
            pool,
            page_table,
        }
    }

    pub fn fetch_page(&mut self, page_id: PageId) -> Result<Rc<Buffer>, Error> {
        //キャッシュにある場合
        if let Some(&buffer_id) = self.page_table.get(&page_id) {
            let frame = &mut self.pool[buffer_id];
            frame.usage_count += 1;
            return Ok(Rc::clone(&frame.buffer));
        }
        //キャッシュにない場合
        let buffer_id = self.pool.evict().ok_or(Error::NoFreeBuffer)?;
        let frame = &mut self.pool[buffer_id];
        let evict_page_id = frame.buffer.page_id;
        {
            let buffer = Rc::get_mut(&mut frame.buffer).unwrap();
            if buffer.is_dirty.get() {
                self.disk
                    .write_page_data(evict_page_id, buffer.page.get_mut())?;
            }
            buffer.page_id = page_id;
            buffer.is_dirty.set(false);
            self.disk.read_page_data(page_id, buffer.page.get_mut())?;
            frame.usage_count = 1;
        }
        let page = Rc::clone(&frame.buffer);
        self.page_table.remove(&evict_page_id);
        self.page_table.insert(page_id, buffer_id);
        Ok(page)
    }

    //省略
```

・ポイント2
どのバッファを捨てるかのアルゴリズムはClock-sweepを利用
※Clock-sweepはポスグレでも採用されている
※くるくる回して、いらないキャッシュを探索


```rust
    fn evict(&mut self) -> Option<BufferId> {
        let pool_size = self.size();
        let mut consecutive_pinned = 0;
        // くるくる回しまーす
        let victim_id = loop {
            let next_victim_id = self.next_victim_id;
            let frame = &mut self[next_victim_id];
            // バッファの利用回数0の場合
            if frame.usage_count == 0 {      
                // いらないキャッシュのバッファIDを返却
                break self.next_victim_id;
            }
            if Rc::get_mut(&mut frame.buffer).is_some() {
                frame.usage_count -= 1;
                consecutive_pinned = 0;
            } else {
                consecutive_pinned += 1;
                //捨てられるバッファがない場合
                if consecutive_pinned >= pool_size {
                    return None;
                }
            }
            self.next_victim_id = self.increment_id(self.next_victim_id);
        };
        Some(victim_id)
    }
```

## アクセスメソッドはどのソース？

・以下がアクセスメソッドの実装
https://github.com/KOBA789/relly/blob/wdb/src/btree.rs

・ポイント
B+Treeをアルゴリズムとして利用
B+Treeの実装は難しいので、以下で概念を理解するのが良さそう
https://qiita.com/kiyodori/items/f66a545a47dc59dd8839

・サンプル

以下にB+Treeの動作確認の為、サンプルあり
C:\dev\relly-wdb\examples

B+Treeにデータ挿入するコード

```
C:\dev\relly-wdb>cargo run --example btree-create
```

B+Treeのデータを検索

```
C:\dev\relly-wdb>cargo run --example btree-query
   Compiling relly v0.1.0 (C:\dev\relly-wdb)
    Finished dev [unoptimized + debuginfo] target(s) in 1.12s
     Running `target\debug\examples\btree-query.exe`
[48, 79, 6f, 67, 6f] = [4b, 6f, 62, 65]
```

他にも色々なサンプルコードあり

## クエリエクスキュータはどのソース？

・以下がクエリエクスキュータの実装
https://github.com/KOBA789/relly/blob/wdb/src/query.rs

・ポイント
クエリエクスキュータは、さまざまな種類（今回は、シーケンススキャン、フィルター、インデックススキャン）がある
このクエリエクスキュータの組み合わせ方を定義するのが実行計画

クエリエクスキュータの例（フィルター）
くるくる回して、条件に合致するか判別

```rust
impl<'a> Executor for ExecFilter<'a> {
    fn next(&mut self, bufmgr: &mut BufferPoolManager) -> Result<Option<Tuple>> {
        // データくるくる
        loop {
            match self.inner_iter.next(bufmgr)? {
                Some(tuple) => {
                    // 条件に一致した場合
                    if (self.cond)(&tuple) {
                        return Ok(Some(tuple));
                    }
                }
                None => return Ok(None),
            }
        }
    }
}
```

実行計画の例（フィルター）

```rust
impl<'a> PlanNode for Filter<'a> {
    fn start(&self, bufmgr: &mut BufferPoolManager) -> Result<BoxExecutor> {
        let inner_iter = self.inner_plan.start(bufmgr)?;
        Ok(Box::new(ExecFilter {
            inner_iter,
            cond: self.cond,
        }))
    }
}
```

## クエリエクスキュータを実行してみる

・以下が実装例
https://github.com/KOBA789/relly/blob/wdb/examples/simple-table-plan.rs

・ポイント
SeqScanをFilterで入れ子にして、select * 省略 where id >= 'w'  and id < 'z' and first_name < 'Dave'
を表現している

```rust
fn main() -> Result<()> {
    let disk = DiskManager::open("simple.rly")?;
    let pool = BufferPool::new(10);
    let mut bufmgr = BufferPoolManager::new(disk, pool);
    //　フィルター
    let plan = Filter {
        // 検索条件
        cond: &|record| record[1].as_slice() < b"Dave",
        // シーケンススキャン
        inner_plan: &SeqScan {
            table_meta_page_id: PageId(0),
            // 検索条件
            search_mode: TupleSearchMode::Key(&[b"w"]),
            while_cond: &|pkey| pkey[0].as_slice() < b"z",
        },
    };
    let mut exec = plan.start(&mut bufmgr)?;

    while let Some(record) = exec.next(&mut bufmgr)? {
        println!("{:?}", tuple::Pretty(&record));
    }
    Ok(())
}
```

事前作業（テーブル作成＆データ挿入）

```
C:\dev\relly-wdb>cargo run --example simple-table-create
```

実行

```
C:\dev\relly-wdb>cargo run --example simple-table-plan
   Compiling relly v0.1.0 (C:\dev\relly-wdb)
    Finished dev [unoptimized + debuginfo] target(s) in 1.19s
     Running `target\debug\examples\simple-table-plan.exe`
Tuple("x" [78], "Bob" [42, 6f, 62], "Johnson" [4a, 6f, 68, 6e, 73, 6f, 6e])
Tuple("y" [79], "Charlie" [43, 68, 61, 72, 6c, 69, 65], "Williams" [57, 69, 6c, 6c, 69, 61, 6d, 73])
```