Rust の SQLx を使って PostgreSQL にアクセスする
はじめに
今回は Rust の SQLx
を使って、
マイグレーションファイルを作成し、PostgreSQL
にアクセスするところまでやっていきたいと思います。
SQLx は様々なデーターベースサービスをサポートしていますので、良いですね。
PostgreSQL
, MySQL
, MariaDB
, SQLite
▼SQLx
プロジェクトを作成
Rust を動かすディレクトリを作成します。
$ cargo new sqlx-sample
$ cd new sqlx-sample
これで一旦の準備は完了です。
このタイミングで、Cargo.toml
に必要なパッケージを追加しておきます。
SQLx
は非同期で動作するため、tokio
パッケージを入れています。
[dependencies]
dotenv = "0.15.0"
sqlx = { version = "0.7.4", features = [ "postgres", "runtime-tokio-native-tls" ]}
tokio = { version = "1.37.0", features = [ "full" ]}
SQLx CLI を使ってマイグレーションを実行する
SQLx CLI のインストール
以下のコマンドを実行し、CLIをインストールできます。
$ cargo install sqlx-cli
DB の作成
CLIがインストールできたら、プロジェクトディレクトリ直下に .env
ファイルを用意し、
以下のように記述していきます。
DATABASE_URL=postgres://postgres:postgres@localhost:5432/test_db
そして、コマンドラインで以下のように実行します。
$ sqlx database create
SQLx CLI がローカルの環境変数を読み取りデーターベースを作成してくれます。
あらかじめ、PostgreSQL がインストールされていて、起動している必要がありますので、
必要であれば以下の手順で実施ください。
PostgreSQL
ローカルに直接インストールする方法と、Docker コンテナで起動する方法があります。
🔻PostgresQL のダウンロード
🔻Docker イメージ
マイグレーションファイルの作成
以下のコマンドでマイグレーションファイルを作成します。
$ sqlx migrate add todo
切り戻し用のマイグレーションファイルも同時に作成したい場合は、-r
オプションを追加することで作成することができます。
$ sqlx migrate add -r todo
これで、migrations
フォルダの中に2024_0523090849_todo.sql
のようにタイムスタンプの入ったマイグレーションファイルが生成されていることを確認します。
マイグレーションの実行
先ほど生成した、タイムスタンプ付きのファイルに、テーブル作成用のSQLを書いていきます。
一般的な todo
のテーブルを作成していきます。
CREATE TABLE todo (
id SERIAL PRIMARY KEY,
title VARCHAR(40) NOT NULL,
description VARCHAR(255),
done BOOLEAN DEFAULT FALSE
);
書いた後、マイグレーションの実行を行います。
以下のコマンドを実行します。
$ sqlx migrate run
クエリの実行
登録処理
最後に、コードを書いていきます。
main.rs
を以下のように記述します。
#[tokio::main]
async fn main() {
dotenv::dotenv().ok();
let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
let pool = sqlx::PgPool::connect(&database_url).await.unwrap();
let todo = Todo {
id: 1,
title: "Buy milk".to_string(),
description: Some("Go to the store and buy milk".to_string()),
done: Some(false),
};
let created_todo = create_todo(&pool, todo).await.unwrap();
println!("{:?}", created_todo);
}
#[derive(Debug)]
struct Todo {
id: i32,
title: String,
description: Option<String>,
done: Option<bool>,
}
async fn create_todo(pool: &sqlx::PgPool, todo: Todo) -> Result<Todo, sqlx::Error> {
sqlx::query_as!(
Todo,
r#"
INSERT INTO todo (id, title, description, done)
VALUES ($1, $2, $3, $4)
RETURNING id, title, description, done
"#,
todo.id,
todo.title,
todo.description,
todo.done
)
.fetch_one(pool)
.await
}
処理の流れとしては、以下のような感じです。
-
.env
ファイルのDATEBASE_URL
からコネクションを作成 - TODO のサンプルデータを作成
- Todo タイトルの "Buy milk" をのレコードを1件登録する関数を実装
- 関数を実行し、出力
SQLx
の query_as!
マクロでデータを作成しつつ、fetch_one()
で取得してくれます。
これはとても便利ですね。
cargo run
を実行すると、以下のように出力されます。
Todo { id: 1, title: "Buy milk", description: Some("Go to the store and buy milk"), done: Some(false) }
取得処理
先ほどのコードの一部を修正し、レコード1件の取得を取得してみましょう。
#[tokio::main]
async fn main() {
dotenv::dotenv().ok();
let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
let pool = sqlx::PgPool::connect(&database_url).await.unwrap();
let todo = get_todo(&pool).await.unwrap();
println!("{:?}", todo);
}
#[derive(Debug)]
struct Todo {
id: i32,
title: String,
description: Option<String>,
done: Option<bool>,
}
async fn get_todo(pool: &sqlx::PgPool) -> Result<Todo, sqlx::Error> {
sqlx::query_as!(
Todo,
r#"
SELECT id, title, description, done
FROM todo
WHERE id = $1
"#,
1
)
.fetch_one(pool)
.await
}
全体的な流れはほとんど同じなので割愛しますが、cargo run
を実行すると、以下のように出力されます。
Todo { id: 1, title: "Buy milk", description: Some("Go to the store and buy milk"), done: Some(false) }
SQLx
はSQL
文をそのまま記述できる点が良いですね!
おわりに
先月、ORMライブラリの Diesel
の記事を執筆しました。
今回紹介したSQLx
は ORM ライブラリではないですが、比較しつつユースケースに応じて使い分けていきたいですね。
では。
Discussion