Rust をやりたい!DuckDB も触りたい!
ということで Rust から DuckDB を操作し、データの作成・挿入・取得をやってみました!
1. DuckDB とは?
DuckDB は、列指向のデータベースであり、組み込みデータベースながら高速なクエリ処理を実現します。
特に、データ分析用途 に適しており、Python や Rust などの言語から簡単に利用できます。
DuckDB の特徴
- 軽量でシンプルなインターフェース
- メモリ内データベースを簡単に作成可能
- SQL による直感的なデータ操作
- Rust からの操作も簡単!
2. Rust から DuckDB を使う準備
Rust で DuckDB を利用するには、まず duckdb クレートを Cargo に追加します。
[dependencies]
duckdb = { version = "1.1.1", features = ["bundled"]}
duckdb クレートは現在時点での最新バージョン v1.1.1 を使用します。
features = ["bundled"]
オプションは、DuckDB クレート内にバンドルされた
DuckDB ライブラリを使用するように設定するオプションです。
これにより、依存ライブラリのインストールが不要となるため
システムに DuckDB がインストールされていなくてもクレートをそのまま利用できる
ようになります。
3. Rust で DuckDB を操作する
ここでは、Rust を使ってメモリ上にデータベースを作成し
データを挿入・取得するサンプルコードを紹介します。
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. コードの解説
① メモリ上のデータベースを作成
let conn = Connection::open_in_memory()?;
DuckDB は、ファイルを作成しなくてもメモリ上にデータベースを作成できます。
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');",
[],
)?;
execute() を使用して、SQL の CREATE TABLE や INSERT を実行できます。
③ データの取得
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) にマッピング
④ 結果の表示
for row in rows {
let (id, name) = row?;
println!("id: {}, name: {}", id, name);
}
取得したデータをループで回して、コンソールに出力します。
5. 実行結果
上記のコードを実行すると、次のような結果が表示されます。
id: 1, name: Alice
id: 2, name: Bob
id: 3, name: Charlie
どうでしょう?
結構簡単に操作できてしまいましたね!
簡単だったので、少しコードを修正してみたいと思います。
6. Prepared Statementを活用した改善版
上記のコードでは execute() を直接使ってレコードを挿入していましたが
Prepared Statement を利用すると
SQL文を事前に準備し、複数回の実行を効率化 できます。
以下の修正を加えました:
- 名前のリストを配列で定義
- Prepared Statement を事前に用意し、ループで再利用
- ToSql トレイトを活用し、動的に値をバインド
修正後のコード
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. 実行結果(改善版)
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 を活用してみましょう! 🚀