概要
Rust で SQLite を使うためのラッパークレートである Rusqlite を使ってみる。
Python で SQLite バイナリを作成し、それを Rust で Rusqlite を使って読み込みクエリを実行する流れのサンプルコードを書いてみた。
Python で作成した SQLite バイナリを Rust で読み込んでクエリを実行する
ディレクトリ構成
以下のようなディレクトリ構成になってる想定でコードを書いていく。
. ├── python │ └── python3.10 │ ├── data │ │ └── users.sqlite │ ├── poetry.lock │ ├── pyproject.toml │ ├── README.md │ ├── src │ │ ├── __init__.py │ │ └── __main__.py │ └── tasks.py └── rust ├── Cargo.lock ├── Cargo.toml ├── Makefile.toml ├── README.md └── src └── main.rs
Python で SQLite のバイナリを作成する
使用するサードパーティライブラリはないので pyproject.toml は不要かもしれない。
- pyproject.toml
[tool.poetry] name = "python3-10" version = "0.1.0" description = "" authors = ["Your Name <you@example.com>"] packages = [{include = "src"}] [tool.poetry.dependencies] python = "~3.10" [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api"
以下のように、テーブル作成・値の挿入を行ったデータベースを保存する。
テスト代わりに Python 側でクエリを実行できるかを最後に確認している。
- main.py
import sqlite3 CREATE_QUERY = """ CREATE TABLE users ( id INTEGER, age INTEGER NOT NULL, name TEXT NOT NULL, PRIMARY KEY (id) ); """ INSERT_QUERY = """ INSERT INTO users VALUES (1, 20, 'Alice'), (2, 30, 'Bob'), (3, 40, 'Carol'); """ SELECT_QUERY = """ SELECT * FROM users; """ def main(): path = "data/users.sqlite" with open(path, mode="w") as f: conn = sqlite3.connect(f.name) cursor = conn.cursor() cursor.execute(CREATE_QUERY) cursor.execute(INSERT_QUERY) conn.commit() conn.close() with open(path, mode="r") as f: conn = sqlite3.connect(f.name) cursor = conn.cursor() cursor.execute(SELECT_QUERY) print(cursor.fetchall()) if __name__ == "__main__": main()
- 出力
[(1, 20, 'Alice'), (2, 30, 'Bob'), (3, 40, 'Carol')]
参考: Python: テストで SQLite3 のインメモリデータベースを使うときの問題点と解決策 - CUBE SUGAR CONTAINER
Rust で SQLite バイナリを読み込んでクエリを実行する
Cargo.toml に rusqlite
を追加する。
features には bundled
と backup
を指定する。
前者はユーザーシステムの SQLite への自動リンクを可能にし、後者は SQLite バイナリからデータベースを復元する際に必要となる。
- Cargo.toml
[package] name = "rust" version = "0.1.0" edition = "2021" [dependencies] rusqlite = { version = "0.29.0", features = ["bundled", "backup"] }
Connection::open_in_memory()
でインメモリな SQLite データベースを構築し、それに対して restore()
することで復元する。
その後はクエリを実行し、Rust で定義した構造体にクエリの実行結果をマップする。
出力内容が Python のものと一致していることが確認できる。
- main.rs
use rusqlite::{Connection, DatabaseName}; #[derive(Debug)] #[allow(dead_code)] struct User { id: i32, age: i32, name: String, } fn main() { let path = "python/python3.10/data/users.sqlite"; let path = format!("{}/../{}", env!("CARGO_MANIFEST_DIR"), path); let mut conn = Connection::open_in_memory().unwrap(); conn.restore(DatabaseName::Main, &path, Some(|_| {})).unwrap(); let mut stmt = conn.prepare("SELECT id, age, name FROM users").unwrap(); let user_iter = stmt.query_map([], |row| { Ok(User { id: row.get(0).unwrap(), age: row.get(1).unwrap(), name: row.get(2).unwrap(), }) }).unwrap(); for user in user_iter { println!("Found user {:?}", user.unwrap()); } }
- 出力
Found user User { id: 1, age: 20, name: "Alice" } Found user User { id: 2, age: 30, name: "Bob" } Found user User { id: 3, age: 40, name: "Carol" }
参考: GitHub - rusqlite/rusqlite: Ergonomic bindings to SQLite for Rust
まとめ
Rustqlite を使えば Rust でも SQLite を気軽に利用できそう。