2
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Rust】 Rust + PostgreSQL + tokio_postgresでDBアクセスする方法

Last updated at Posted at 2023-06-15

ゆるふわです

やりたいこと

Rustで簡単にサクッとローカルのPostgreSQLと連携し、こんな感じのテーブルをRust側から操作して作ります

これを作るよ
     id      |   name   
-------------+----------
  -838073477 | John Doe
 -1966422589 | John Doe
 -2115227795 | John Doe
(3 rows)

idnameには特に深い意味はありません
サンプルとして適当な値を入れているだけです

Rust側では標準入力で

標準入力 機能
a 乱数のidと名前(今回はJohn Doe固定)をrecordというdbに追加
s テーブルを全表示
d テーブルを全削除

といった機能をつけました。

動作環境

項目
OS Ubuntu22.04 On WSL2
Rust 1.70.0
psql (PostgreSQL) 15.3

流れ

  1. postgreSQLでrecordという名前の空のDBを作成
  2. Rustでプログラムを作成
  3. a, s, d等を入力してRust側でrecordを操作
  4. PostgreSQL側でテーブルを確認

といった手順になります。
それではそれぞれの項目について述べます。

1. postgreSQLで空のDB(名:record)を作成

1.インストール等の事前準備

sudo apt update
sudo apt install postgresql
sudo systemctl status postgresql

2. ターミナルを開いてpostgresユーザーに切り替え

sudo -u postgres psql

3. アカウントとrecordDBの作成

CREATE USER your_username WITH PASSWORD 'your_password';
CREATE DATABASE record;

your_username, your_passwordを自分で設定してください
DB名を変えたい場合は、recordを任意の名前に置き換えてください

4. ユーザー"your_username"に対して権限付与

GRANT ALL PRIVILEGES ON DATABASE type_record TO your_username;
GRANT ALL PRIVILEGES ON SCHEMA public TO your_username;

データベースとスキーマ、両方にアクセス権限を付与することに注意してください
(私はここでDBにしか与えておらず、後に2時間ほど詰まりました)

2. Rustでプログラムを作成

1. dependenciesにクレートを追加

Cargo.toml
[dependencies]
+ tokio-postgres = "0.7"
+ tokio = { version = "1", features = ["full"] }
+ dotenv = "0.15.0"
+ rand = "0.8.4"

2. .envファイルに追記

.env
+ DB_HOST=localhost
+ DB_PORT=5432
+ DB_USER=your_username
+ DB_PASSWORD=your_password
+ DB_NAME=record

your_username, your_password
3.アカウントとrecordDBの作成
で作成したものと同じです

.envファイルが無い方は、rustで作業しているディレクトリのルートに戻って

code .env

等で作成してください

3. Rustで実装

以下に例を示します。
適宜コメントを入れました。

main.rs
use dotenv::dotenv;
use std::env;
use std::io::{self, BufRead};
use tokio_postgres::{NoTls, Error};
use rand::Rng;

#[tokio::main]
async fn main() -> Result<(), Error> {
    // .env ファイルを読み込み
    dotenv().ok();

    // 環境変数から接続情報を取得
    let db_host = env::var("DB_HOST").expect("DB_HOST not set");
    let db_port = env::var("DB_PORT").expect("DB_PORT not set");
    let db_user = env::var("DB_USER").expect("DB_USER not set");
    let db_password = env::var("DB_PASSWORD").expect("DB_PASSWORD not set");
    let db_name = env::var("DB_NAME").expect("DB_NAME not set");

    // PostgreSQLの接続情報を設定
    let connection_string = format!(
        "host={} port={} user={} password={} dbname={}",
        db_host, db_port, db_user, db_password, db_name
    );

    let (client, connection) = tokio_postgres::connect(&connection_string, NoTls).await?;

    // 接続タスクをスポーンして実行
    tokio::spawn(async move {
        if let Err(e) = connection.await {
            eprintln!("connection error: {}", e);
        }
    });

    // テーブル作成のクエリを実行
    client
        .batch_execute(
            "
            CREATE TABLE IF NOT EXISTS users (
                id SERIAL PRIMARY KEY,
                name TEXT NOT NULL
            )
            ",
        )
        .await?;

    // 標準入力からキー入力を受け取るためのストリームを作成
    let stdin = io::stdin();
    let mut input_stream = stdin.lock().lines();

    println!("Press 'a' to add a record, 'd' to delete all records, 's' to sort and display records, or any other key to exit.");

    // キー入力を監視し、対応する操作を実行
    while let Some(Ok(input)) = input_stream.next() {
        match input.as_str() {
            "a" => {
                // レコードの追加
                let mut rng = rand::thread_rng();
                let id: i32 = rng.gen();

                client
                    .execute(
                        "INSERT INTO users (id, name) VALUES ($1, $2)",
                        &[&id, &"John Doe"],
                    )
                    .await?;
                println!("Record added.");
            }
            "d" => {
                // レコードの削除
                client.execute("DELETE FROM users", &[]).await?;
                println!("All records deleted.");
            }
            "s" => {
                // レコードのソートと表示
                let rows = client
                    .query("SELECT id, name FROM users ORDER BY id", &[])
                    .await?;
                for row in rows {
                    let id: i32 = row.get(0);
                    let name: &str = row.get(1);
                    println!("ID: {}, Name: {}", id, name);
                }
            }
            _ => {
                break;
            }
        }
    }

    Ok(())
}

3. [a, s, d]等を入力してRust側でrecordを操作

cargo run

Press 'a' to add a record, 'd' to delete all records, 's' to sort and display records, or any other key to exit.

などと表示されるので、[a]や[d], [s]をポチポチしてrecordを操作していきましょう。

実行例
Press 'a' to add a record, 'd' to delete all records, 's' to sort and display records, or any other key to exit.
a
Record added.
a
Record added.
a
Record added.
s
ID: -2115227795, Name: John Doe
ID: -1966422589, Name: John Doe
ID: -838073477, Name: John Doe

正しくテーブルを作成することができました。
地味にIDでソートされています。

4. postgreSQL側でテーブルを確認

sudo -u postgres 
psql -d type_record -c "SELECT * FROM users;"

を実行します。
以下のような結果が得られると成功です。

実行例
postgres=# \c type_record
You are now connected to database "type_record" as user "postgres".
type_record=#  SELECT * FROM users;
     id      |   name   
-------------+----------
  -838073477 | John Doe
 -1966422589 | John Doe
 -2115227795 | John Doe
(3 rows)

おわりに

Rust側からローカルのPostgreSQLをいじることができました。
HerokuやRender.com上のPostgreSQLとも連携できれば、Webアプリ開発の幅が広がりそうです。
お疲れさまでした。

2
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?