概要
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 を気軽に利用できそう。