2022/01/26
[Rust][Psql]Dieselのよくやる使い方まとめ
最近では、WebアプリのバックエンドにRustを使うことが多く、
そのRDBのDriverとして、dieselを採用しています。
(使っているDBはpostgreqlです。)
今回は、私がよく使うdieselの使い方をまとめてみました。
どうやって実装したっけ?とよく昔のプロジェクトのソースコードを参照することが多かったので、自分向けのメモとしても残しておきます。
Scheme編
jsonフィールドを扱う
CREATE TABLE items (
id serial primary key,
content json,
);
Cargo.toml
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
# serde_jsonのfeatureが必要です。
diesel = { version = "1.4.8", features = ["postgres", "chrono", "r2d2", "serde_json", "numeric"] }
# sumなどを行うときはbigdecimalが便利です。
[dependencies.bigdecimal]
version = ">= 0.0.10, < 0.2.0"
table! {
items (id) {
id -> Int4,
content -> Nullable<Json>,
}
}
use crate::scheme::items;
use serde_json;
#[derive(Queryable, Debug)]
pub struct Item {
pub id: i32,
pub content: Option<serde_json::Value>,
}
#[derive(Insertable, Debug)]
#[table_name = "items"]
pub struct NewItem {
pub content: Option<serde_json::Value>,
}
connection編
connectionの作成
use diesel::pg::PgConnection;
use diesel::prelude::*;
pub fn establish_connection<S: Into<String>>(database_url: S) -> PgConnection {
let database_url = database_url.into();
PgConnection::establish(&database_url).expect(&format!("Error connecting to {}", database_url))
}
connection poolの作成
use diesel::r2d2::{ConnectionManager, Pool};
use diesel::PgConnection;
pub type PgPool = Pool<ConnectionManager<PgConnection>>;
pub fn new_pool<S: Into<String>>(database_url: S, max_size: u32) -> PgPool {
let manager = ConnectionManager::<PgConnection>::new(database_url);
Pool::builder()
.max_size(max_size)
.build(manager)
.expect("Failed to create pool")
}
SQL編
get by id
// your_schemesというテーブルの主キー検索
your_schemes::table.find(id).first::<YourScheme>(conn);
get by unique field value
// your_schemes.unique_field_name = value の limit 1検索
your_schemes::table
.filter(your_schemes::unique_field_name.eq(value))
.first::<YourScheme>(conn);
select for update by id
your_schemes::table.find(id).for_update().first::<YourScheme>(conn);
select with multiple and conditions
// where your_schemes.some_field1 = value1 and your_schemes.some_field2 = value2
your_schemes::table
.filter(your_schemes::some_field1.eq(value1))
.filter(your_schemes::some_field2.eq(value2))
.load::<YourScheme>(conn);
select with complex and/or conditions
// select * from your_schemes
// where
// some_field1 = value1
// and (
// some_field2 < value2
// or
// (some_field3 = value3 and some_field4 <= value4)
// )
// order by id desc
your_schemes::table
.filter(your_schemes::some_field1.eq(value1))
.filter(
your_schemes::some_field2.lt(value2)
.or(your_schemes::some_field3.eq(value3).and(your_schemes::some_field4.le(value4))),
)
.order(your_schemes::id.desc())
.load::<YourScheme>(conn)
subquery
/// select * from your_schemes
/// where
/// id in (select your_scheme_id from your_schemes2 where some_field1 = value1)
/// order by id asc
let target_ids = your_schemes2::table
.filter(your_schemes2::some_field1.eq(value1))
.select(your_schemes2::your_scheme_id);
your_schemes::table
.filter(your_schemes::id.eq_any(target_ids))
.order(your_schemes::id.asc())
.load::<YourScheme>(conn);
select sum
use diesel::dsl::sum;
// select sum(your_field) from your_schemes
your_schemes::table
.select(sum(your_schemes::your_field))
.first::<Option<bigdecimal::BigDecimal>>(conn)
select max
use diesel::dsl::max;
// select max(your_field) from your_schemes
your_schemes::table
.select(max(your_schemes::your_field))
.first(conn)
select count
// select count(*) from your_schemes where your_schemes.some_field = value
your_schemes::table
.filter(your_schemes::some_field.eq(value))
.count()
.get_result(conn)
select limit offset
your_schemes::table
.filter(your_schemes::some_field.eq(value))
.order(your_schemes::id.desc())
.limit(limit)
.offset(offset)
.load::<YourScheme>(conn)
pager with buiding query
いろんな条件を動的に組み合わせて、ページングするクエリです。
BoxedQuery
をうまく使っています。
// まずはqueryを動的に作る関数を作成します。
// こうすることでクエリを作成するロジックを1つに集約することができます。
fn build_query_for_search<'a>(
some_id: Option<i32>,
search_text: Option<&'a str>,
) -> your_schemes::BoxedQuery<'a, diesel::pg::Pg> {
let mut query = your_schemes::table.into_boxed();
if let Some(some_id) = some_id {
query = query.filter(your_schemes::some_id.eq(some_id))
}
let like = format!("%{}%", search_text.unwrap_or(""));
if search_text.is_some() {
query = query.filter(
your_schemes::some_field
.ilike(like.clone())
.or(your_schemes::some_field2.like(like)),
)
}
query
}
// 上記の関数を使って、検索の合計件数とページングした結果を返す関数を作ります。
pub fn search(
conn: &PgConnection,
some_id: Option<i32>,
search_text: Option<&'a str>,
limit: i64,
offset: i64,
) -> Result<(i64, Vec<YourScheme>), Error> {
let total = build_query_for_search(some_id, search_text)
.count()
.get_result(conn)?;
let results = build_query_for_search(some_id, search_text)
.order(your_schemes::id.asc())
.limit(limit)
.offset(offset)
.load::<YourScheme>(conn)?;
OK((total, results))
}
join
/// select
/// your_schemes1.*, your_schemes2.*
/// from
/// your_schemes1
/// inner join your_schemes2 on
/// your_schemes2.some_foreign_key = your_schemes1.related_key
/// where
/// your_schemes1.some_field1 in (values)
/// order by your_schemes1.id asc, your_schemes2.id asc
your_schemes1::table
.inner_join(your_schemes2::table)
.filter(your_schemes1::some_field1.eq_any(values))
.order((
your_schemes1::id.asc(),
your_schemes2::id.asc(),
))
.load::<(YourScheme1, YourScheme2)>(conn);
raw sql
use diesel::sql_types::*;
// まずは、フィールド名からクエリーできる構造体を定義しておきます。
// sql_typeは取得するカラムのデータ型に合わせておきます。(ここが不整合でもコンパイルが通ってしまい、ランタイム時にエラーになるので注意が必要です。)
#[derive(QueryableByName, Debug)]
pub struct YourScheme {
#[sql_type = "Integer"]
pub id: i32,
#[sql_type = "Varchar"]
pub your_field1: String,
#[sql_type = "Integer"]
pub your_field2: i32,
}
// diesel::sql_query を使ってクエリーを作ります。
// Raw SQLを書いて、プレースホルダーをうまく使ってクエリーできるようにします。
let your_field1 = "test";
let your_field2 = 10;
let limit = 100;
let query = r###"SELECT
id,
your_field1,
your_field2
FROM
your_schemes
WHERE
your_field1 = $1
AND your_field2 = $2
LIMIT $3
"###;
diesel::sql_query(query)
.bind::<Varchar, _>(your_field1)
.bind::<Integer, _>(your_field2)
.bind::<BigInt, _>(limit)
.load::<YourScheme>(conn)
update single field by id
diesel::update(your_schemes::dsl::your_schemes.find(id))
.set(your_schemes::target_field.eq(value))
.execute(conn);
update multiple field by id
// タプル形式で指定
diesel::update(your_schemes::dsl::your_schemes.find(id))
.set((
your_schemes::target_field1.eq(value1),
your_schemes::target_field2.eq(value2),
))
.execute(conn);
delete by id
// your_schemesというテーブルの主キーで一致したものを削除
diesel::delete(your_schemes::table.find(id)).execute(conn);
insert
// new_entity is Insertable struct value
diesel::insert_into(your_schemes::table).values(&new_entity).get_result(conn)
以上です。
今後頻繁に使うものがあれば随時追加していこうと思います。
関連する記事
Concordiumノードをローカルで動かしてみた
Concordiumの調査のために、ローカルでソースコードをビルドしてノードを動かしてみました
[Rust]axumとdragonflyを使ったWebsocket Chatのサンプル実装
redis互換のdragonflyをPUBSUBとして利用して、Websocket Chatアプリのサンプル実装を行いました。
[Rust]TiDBを使ったサンプルアプリケーションの実装
RustからTiDBを使ったアプリケーションの実装を行いました。
[Rust]Google Cloud Storageを利用する
GCSやNFSのファイルを扱えるpackageをRustで実装しました。