👻

Rust の SQLx を使って PostgreSQL にアクセスする

2024/05/25に公開

はじめに

今回は Rust の SQLx を使って、
マイグレーションファイルを作成し、PostgreSQLにアクセスするところまでやっていきたいと思います。

SQLx は様々なデーターベースサービスをサポートしていますので、良いですね。
PostgreSQL, MySQL, MariaDB, SQLite

▼SQLx
https://github.com/launchbadge/sqlx

プロジェクトを作成

Rust を動かすディレクトリを作成します。

$ cargo new sqlx-sample
$ cd new sqlx-sample

これで一旦の準備は完了です。

このタイミングで、Cargo.tomlに必要なパッケージを追加しておきます。
SQLxは非同期で動作するため、tokioパッケージを入れています。

Cargo.toml
[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ファイルを用意し、
以下のように記述していきます。

.env
DATABASE_URL=postgres://postgres:postgres@localhost:5432/test_db

そして、コマンドラインで以下のように実行します。

$ sqlx database create

SQLx CLI がローカルの環境変数を読み取りデーターベースを作成してくれます。
あらかじめ、PostgreSQL がインストールされていて、起動している必要がありますので、
必要であれば以下の手順で実施ください。

PostgreSQL

ローカルに直接インストールする方法と、Docker コンテナで起動する方法があります。
🔻PostgresQL のダウンロード
https://www.postgresql.jp/download

🔻Docker イメージ
https://hub.docker.com/_/postgres

マイグレーションファイルの作成

以下のコマンドでマイグレーションファイルを作成します。

$ sqlx migrate add todo

切り戻し用のマイグレーションファイルも同時に作成したい場合は、-rオプションを追加することで作成することができます。

$ sqlx migrate add -r todo

これで、migrationsフォルダの中に2024_0523090849_todo.sqlのようにタイムスタンプの入ったマイグレーションファイルが生成されていることを確認します。

マイグレーションの実行

先ほど生成した、タイムスタンプ付きのファイルに、テーブル作成用のSQLを書いていきます。
一般的な todoのテーブルを作成していきます。

2024_0523090849_todo.sql
CREATE TABLE todo (
    id SERIAL PRIMARY KEY,
    title VARCHAR(40) NOT NULL,
    description VARCHAR(255),
    done BOOLEAN DEFAULT FALSE
);

書いた後、マイグレーションの実行を行います。
以下のコマンドを実行します。

$ sqlx migrate run

クエリの実行

登録処理

最後に、コードを書いていきます。
main.rsを以下のように記述します。

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
}

処理の流れとしては、以下のような感じです。

  1. .envファイルの DATEBASE_URL からコネクションを作成
  2. TODO のサンプルデータを作成
  3. Todo タイトルの "Buy milk" をのレコードを1件登録する関数を実装
  4. 関数を実行し、出力

SQLxquery_as!マクロでデータを作成しつつ、fetch_one()で取得してくれます。
これはとても便利ですね。

cargo runを実行すると、以下のように出力されます。

Todo { id: 1, title: "Buy milk", description: Some("Go to the store and buy milk"), done: Some(false) }

取得処理

先ほどのコードの一部を修正し、レコード1件の取得を取得してみましょう。

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 = 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) }

SQLxSQL文をそのまま記述できる点が良いですね!

おわりに

先月、ORMライブラリの Dieselの記事を執筆しました。
https://zenn.dev/collabostyle/articles/6b03e11b1b78cd

今回紹介したSQLxは ORM ライブラリではないですが、比較しつつユースケースに応じて使い分けていきたいですね。

では。

コラボスタイル Developers

Discussion