Rust で DuckDB を操作する方法 – メモリ上のデータベースで簡単な SQL 実行

Rust をやりたい！DuckDB も触りたい！
ということで **Rust** から DuckDB を操作し、データの作成・挿入・取得をやってみました！

## **1. DuckDB とは？**
**DuckDB** は、列指向のデータベースであり、組み込みデータベースながら高速なクエリ処理を実現します。
特に、**データ分析用途** に適しており、Python や Rust などの言語から簡単に利用できます。

**DuckDB の特徴**
- 軽量でシンプルなインターフェース
- メモリ内データベースを簡単に作成可能
- SQL による直感的なデータ操作
- **Rust からの操作も簡単！**

## **2. Rust から DuckDB を使う準備**
Rust で DuckDB を利用するには、まず duckdb クレートを Cargo に追加します。

```toml:Cargo.toml
[dependencies]
duckdb = { version = "1.1.1", features = ["bundled"]}
```

duckdb クレートは現在時点での最新バージョン v1.1.1 を使用します。

`features = ["bundled"]` オプションは、DuckDB クレート内にバンドルされた  
DuckDB ライブラリを使用するように設定するオプションです。
これにより、依存ライブラリのインストールが不要となるため
システムに DuckDB がインストールされていなくてもクレートをそのまま利用できる  
ようになります。

## **3. Rust で DuckDB を操作する**
ここでは、Rust を使って**メモリ上にデータベースを作成し  
データを挿入・取得する**サンプルコードを紹介します。


```rust:main.rs
use duckdb::{Connection, Result};

fn main() -> Result<()> {
    // メモリ上に DuckDB データベースを作成
    let conn = Connection::open_in_memory()?;

    // データベースを作成してデータを挿入
    conn.execute("CREATE TABLE users (id INTEGER, name VARCHAR);", [])?;
    conn.execute(
        "INSERT INTO users VALUES (1, 'Alice'), (2, 'Bob'), (3, 'Charlie');",
        [],
    )?;

    // データを取得
    let mut stmt = conn.prepare("SELECT * FROM users;")?;
    let rows = stmt.query_map([], |row| {
        let id: i32 = row.get(0)?;
        let name: String = row.get(1)?;
        Ok((id, name))
    })?;

    // 結果を表示
    for row in rows {
        let (id, name) = row?;
        println!("id: {}, name: {}", id, name);
    }
    Ok(())
}
```


## **4. コードの解説**
**① メモリ上のデータベースを作成**
```rust
let conn = Connection::open_in_memory()?;
```
DuckDB は、ファイルを作成しなくても**メモリ上にデータベースを作成**できます。  
Connection::open_in_memory() を使うことで、手軽に一時的なデータベースを扱えます。

**② テーブルの作成とデータの挿入**
```rust
conn.execute("CREATE TABLE users (id INTEGER, name VARCHAR);", [])?;
conn.execute(
    "INSERT INTO users VALUES (1, 'Alice'), (2, 'Bob'), (3, 'Charlie');",
    [],
)?;
```
execute() を使用して、SQL の CREATE TABLE や INSERT を実行できます。

**③ データの取得**
```rust
let mut stmt = conn.prepare("SELECT * FROM users;")?;
let rows = stmt.query_map([], |row| {
    let id: i32 = row.get(0)?;
    let name: String = row.get(1)?;
    Ok((id, name))
})?;
```
prepare() を使ってSQL文を準備して
query_map() を用いて、取得したデータをタプル (id, name) にマッピング

**④ 結果の表示**
```rust
for row in rows {
    let (id, name) = row?;
    println!("id: {}, name: {}", id, name);
}
```

取得したデータをループで回して、コンソールに出力します。

## **5. 実行結果**

上記のコードを実行すると、次のような結果が表示されます。
```bash
id: 1, name: Alice
id: 2, name: Bob
id: 3, name: Charlie
```
どうでしょう？
結構簡単に操作できてしまいましたね！
簡単だったので、少しコードを修正してみたいと思います。

## **6. Prepared Statementを活用した改善版**
上記のコードでは execute() を直接使ってレコードを挿入していましたが
**Prepared Statement** を利用すると
**SQL文を事前に準備し、複数回の実行を効率化** できます。

以下の修正を加えました：
- **名前のリストを配列で定義**
- **Prepared Statement を事前に用意し、ループで再利用**
- **ToSql トレイトを活用し、動的に値をバインド**


**修正後のコード**
```rust:main.rs
use duckdb::{Connection, Result, ToSql};

fn main() -> Result<()> {
    let conn = Connection::open_in_memory()?;
    conn.execute("CREATE TABLE users (id INTEGER, name VARCHAR);", [])?;

		// 名前のリストを配列で定義
    let names = ["Liam", "Noel", "Paul", "Tony"];
    
    // Prepared Statement を事前に用意
    let mut stmt = conn.prepare("INSERT INTO users VALUES (?, ?);")?;
    
    // Prepared Statement をループ処理で再利用
    for (i, name) in names.iter().enumerate() {
        let id = (i as i32) + 1;
        // execute() の引数 params にパラメータのスライスを渡す
        // duckdb::ToSql にキャストし型推論を行わせる
        stmt.execute([&id as &dyn ToSql, &name as &dyn ToSql])?;
    }

    let mut stmt = conn.prepare("SELECT * FROM users;")?;
    let rows = stmt.query_map([], |row| {
        let id: i32 = row.get(0)?;
        let name: String = row.get(1)?;
        Ok((id, name))
    })?;

    for row in rows {
        let (id, name) = row?;
        println!("id: {}, name: {}", id, name);
    }
    Ok(())
}
```

## **7. 実行結果（改善版）**

```bash
id: 1, name: Liam
id: 2, name: Noel
id: 3, name: Paul
id: 4, name: Tony
```

## **8. まとめ**

本記事では、Rust から DuckDB を使い**メモリ上でデータベースを作成・操作**する方法を
紹介しました。

DuckDB はシンプルで使いやすく、データ分析用途にも最適です。

**次に試したいこと**

- **ファイルベースの DuckDB** (Connection::open("mydb.db"))
- **より複雑なクエリ** を試す（集計・結合など）
- **WASM 版 DuckDB** を活用して、**ブラウザ上で SQL を実行**

**Rust を使ってデータ処理に DuckDB を活用してみましょう！** 🚀