Open8
Rust の sqlx 使い方メモ
SELECT した結果が NOT NULL であることを伝える
SELECT
U.id AS user_id,
U.name AS user_name,
R.code AS role_code
FROM
users AS U
LEFT OUTER JOIN
roles AS R
ON U.role_id = R.role_id
のような検索で、user_id
と user_name
が NULL でないとしても、このまま sqlx で実行するとうまくいかない
struct User {
user_id: i32,
user_name: String,
role_code: Option<String>,
}
~~~~
sqlx::query_file_as!(User, "select_users.sql").fetch_many(&pool).await;
SQL 文で NOT NULL であることをヒントとして感嘆符マークで与えなければならない
SELECT
U.id AS "user_id!",
U.name AS "user_name!",
R.code AS role_code
FROM
users AS U
LEFT OUTER JOIN
roles AS R
ON U.role_id = R.role_id
JSON 型のカラムを受け取る
Postgres で用意されている JSON 型を sqlx で行データとして受け取りたい
{ "a": 42, "b": "hello" }
のような値を JSON 型として持つ列を取得するために、列 col
の型として、sqlx::types::Json<T>
を指定する
#[derive(Debug, Serialize, Deserialize)]
struct ColData {
a: i32,
b: String,
}
#[derive(Debug, Serialize, Deserialize)]
struct RowData {
col: sqlx::types::Json<ColData>,
}
SQL を実行する時に、列 col
の型を明示する。NULL でないことを示すために !
も付ける
let row = sqlx::query_as!(
RowData,
r#"
WITH vars (col) AS (SELECT '{ "a": 100, "b": "hello" }'::json)
SELECT col AS "col!: sqlx::types::Json<ColData>" FROM vars
"#
)
.fetch_one(conn.as_mut())
.await
カラムが NULL許容の場合は、RowData
のフィールドの定義を Option
にする
#[derive(Debug, Serialize, Deserialize)]
struct RowData {
col: Option<sqlx::types::Json<ColData>>,
}
SQL で表明する列の型に Option
は不要。!
だけ除く
let row = sqlx::query_as!(
RowData,
r#"
WITH vars (col) AS (SELECT '{ "a": 100, "b": "hello" }'::json)
SELECT col AS "col: sqlx::types::Json<ColData>" FROM vars
"#
)
.fetch_one(conn.as_mut())
.await
カラムにデータベースの意味での NULL
が格納されている場合と、JSON の null
が非NULL値として格納されている場合は区別されるので注意する
let row = sqlx::query_as!(
RowData,
r#"
WITH vars (col) AS (SELECT NULL::json)
SELECT col AS "col: sqlx::types::Json<ColData>" FROM vars
"#
)
.fetch_one(conn.as_mut())
.await
は正常に処理されて RowData { col: None }
を得るが
let row = sqlx::query_as!(
RowData,
r#"
WITH vars (col) AS (SELECT 'null'::json)
SELECT col AS "col: sqlx::types::Json<ColData>" FROM vars
"#
)
.fetch_one(conn.as_mut())
.await
はパニックする
1 対 n の結合を取得する
JSON で取得することで、例えばブログの 1投稿に対して、複数のコメントを Vec<Comment>
のような形式で取得できる
テーブルの例
ER図
SQL
詳細
WITH
posts_comment (
post_id
, comment_array
) AS (
SELECT
posts.id
, CASE
WHEN MAX(comments.id) IS NULL THEN ARRAY[]::json[]
ELSE ARRAY_AGG(
JSON_BUILD_OBJECT(
'comment_id', comments.id,
'comment_user', comments.username,
'comment', comments.comment
)
)
END
FROM
posts
LEFT OUTER JOIN
comments
ON
posts.id = comments.post_id
GROUP BY
posts.id
)
SELECT
posts.id
, posts.title
, posts.username
, posts.content
, posts_comment.comment_array
FROM
posts
LEFT OUTER JOIN
posts_comment
ON
posts.id = posts_comment.post_id
- 1つの投稿に対して、複数のコメントを
ARRAY_AGG
関数で集約する - 配列の要素となるJSONを
JSON_BUILD_OBJECT
でコメント1行をJSONに変換する - 投稿に対するコメントが存在しない場合に対して空の配列を作成するために、
comments.id
が NULL かどうか CASE 文で判定する
sqlx のためにカラムのエイリアスを調整する
DIFF
, posts.title
, posts.username
, posts.content
- , posts_comment.comment_array
+ , posts_comment.comment_array AS "comments!: Vec<sqlx::types::Json<CommentModel>>"
FROM
posts
LEFT OUTER JOIN posts_comment ON posts.id = posts_comment.post_id
受け取るデータ型を用意する
型定義
#[derive(Debug, Serialize, Deserialize)]
pub struct CommentModel {
pub comment_id: i32,
pub comment_user: String,
pub comment: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PostModel {
pub id: i32,
pub title: String,
pub username: String,
pub content: String,
pub comments: Vec<sqlx::types::Json<CommentModel>>,
}
- SQL の SELECT 節で型を指定した comments に合わせて、
PostModel
型のフィールドの型を指定する
sqlx::query_as! に渡して得られるデータ例
データ例
[
PostModel {
id: 1,
title: "Getting started Rust",
username: "john",
content: "Make it so, devastation! Pathways fly with voyage!",
comments: [
Json(
CommentModel {
comment_id: 1,
comment_user: "yamada",
comment: "Prarere saepe ducunt ad clemens fiscina.",
},
),
Json(
CommentModel {
comment_id: 2,
comment_user: "sato",
comment: "Nobilis aonidess ducunt ad equiso.",
},
),
Json(
CommentModel {
comment_id: 3,
comment_user: "hako",
comment: "Vae, flavum nutrix!",
},
),
],
},
PostModel {
id: 2,
title: "Deep Dive Rust",
username: "ken",
content: "Kahlesses walk from galaxies like brave spacecrafts.",
comments: [
Json(
CommentModel {
comment_id: 4,
comment_user: "minu",
comment: "Est gratis amicitia, cesaris.",
},
),
],
},
PostModel {
id: 3,
title: "Web app for Rust",
username: "jammy",
content: "Evasive parasites, to the infinity room.",
comments: [],
},
]
New Type パターンを使うには
例えば、todos
テーブルの主キー id BIGSERIAL
のカラムを単純に i64
ではなく、TodoId
型としたい。
todos テーブル
CREATE TABLE todos (
id BIGSERIAL NOT NULL PRIMARY KEY,
title VARCHAR(255) NOT NULL,
description TEXT NULL,
completed BOOLEAN NOT NULL DEFAULT(false)
)
対応する struct
pub struct TodoId(i64);
pub struct Todo {
pub id: TodoId,
pub title: String,
pub description: Option<String>,
pub completed: bool
}
TodoId
をクエリで使うには
- パラメータとして渡せる
- クエリの戻り値として受け取れる
必要がある