- 前説:非同期+sqlx+query_asでRDB読み取りタプルへ
- 構造体は、特製「仕切り付き弁当容器」の仕様
- main.rsの #[derive(sqlx::FromRow, Debug)]とstruct
- 構造体名の直前に置く「_」は”型推論の空欄"
- 「構造体」使って、MariaDB読み取った main.rs コード
- 補足説明:Cargo.toml、日付型、十進数型、そしてquery_as!

前説:非同期+sqlx+query_asでRDB読み取りタプルへ
爺、Rust触りだしたのは2月中旬からです。Rust+RDBは、同期読み取りから始め、tokioで非同期読み取り、変数の一種「タプル」に収め、ターミナルに値表示(println!)できました。これが前回までの状況です。
RustによるRDB読み取りでは、他言語のSQL経験が役にたちました。一方、型の扱いには翻弄されました。
- 整数の処理はコレまでの経験(Python)でほぼできる
- Rustでは、varchr、charなど文字型は、「String」として扱う
- Rustでは、日付型、十進数型(Decimal)は、外部クレートが必要でsqlxへ追記必須
Rust的なトコロで混乱したです...。
「構造体+query_as! がRDB関係の鉄板でしょ!」と見通しておられた方からすると、ずいぶん「亀」な進行です。今回、「構造体」が登場します。
- 構造体とコードの関係図を記事先頭近くに掲載したのでご参照ください。
- 引き続き、前回までのコード(main.rs、Cargo.toml)を使い、少し修正します。
- 今回は、外部クレート追加はありません。含める「型」により外部クレートが必要になります(例:NaiveDate → chrono、Decimal → rust_decimal など)。
- 参考コードは、今回も「一行読み取り」です。チョットした爺の都合で「十進数型」を今回は使いません。次回お勉強予定ですので悪しからず。
構造体は、特製「仕切り付き弁当容器」の仕様
RDBから読み取るデータを、型も読み取れる「構造体」に変更したのが今回のポイントです(これまでは「タプル」)。
一行読取りの場合、
- structで明示する「構造体」は、特製「仕切り付き弁当容器」仕様(盛り付け済みの弁当ではない)
- SQLx(=sqlx)と構造体の関係確立は、derive 内の(sqlx::FromRow)が行う
- FromRowが構造体への変換ルール(盛り付け手順書)
- deriveは、その変換ルールを自動生成するマクロ
- SQLxは、deriveに従い、RDBから取得したデータを各SQL型に合わせRustの型へ自動変換(食材調理)
- 自動変換したデータは、SQL文直前にある「<_, 構造体名>」の仕様に従い、変数(今回はrow)に保管(盛り付け作業)
- 利用(今回はprintln!)は、「前項変数名.構造体項目名」を使い適宜書くと表示できた(駅弁店頭に並ぶ)、となるはず。やっと、今回のゴール到着
...手順長いですね。
最後の部分、例えば「row.date」 なのはメモしとかないと、爺すぐ忘れそう。
「構造体はSQL専用ではありません。derive内へ適応する記述を、CSV、JSON、メモリ上のデータなどごとに設定すれば、sql以外でも使えます」との旨をCopilotに教えてもらいました。
main.rsの #[derive(sqlx::FromRow, Debug)]とstruct
前回使用した十進数型、お勉強の都合で今回は使いません。
use (省略)
#[derive(sqlx::FromRow, Debug)]
struct StockData {
num: i32,
cd: String,
date: NaiveDate,
}
#[tokio::main]
async fn main() -> (略)
- #[ derive(sqlx::FromRow, Debug)]:SQL列名と構造体フィールド名を対応させる出発点
- sqlxがSQLの型をRustの型に変換する中心
- struct StockData {...}:構造体の名前と内容
単にstruct書いただけで「構造体」は働かず、爺はココで見事にコケたです。「derive」と内容記載で、SQLとstructの関係づけが明示され、sqlxがSQL行を構造体へ自動マッピングする準備ができるようです。用意したワンプレートに料理配置の「指示書」(= FromRow のお作法)が無かった...と。
構造体名の直前に置く「_」は”型推論の空欄"
「<_, 構造体名>」と書いとけば、Rustがうまく処理してくれる...。
Copilotによると、
<_, 構造体名> の「_」は型を書く場所で、Rustに型推論を任せ、具体的に書くのを省いている「_」
だそうです。
「構造体」使って、MariaDB読み取った main.rs コード
MariaDB に接続し、
- query_as で 1 行だけ構造体に読み込む
- match で Some/None を分岐して表示
というこれまでの記事の流れと同じです。
コードは次の通りです。
use sqlx::mysql::MySqlPoolOptions;
use chrono::{NaiveDate};
// use rust_decimal::Decimal;
#[derive(sqlx::FromRow, Debug)]
struct StockData {
num: i32,
cd: String,
date: NaiveDate,
// dec: Decimal, //今回未使用
}
#[tokio::main]
async fn main() ->std::result::Result<(), Box<dyn std::error::Error>>{
connect_with_jpx()
.await?;
Ok(())
}
async fn connect_with_jpx() ->std::result::Result<(), Box<dyn std::error::Error>>{
println!("RDB 接続します");
let url ="mysql://user_name:password@192.168.x.x:3306/db_name";
let pool = MySqlPoolOptions::new()
.max_connections(5)
.connect(url)
.await?;
let row =sqlx::query_as::<_, StockData> (
"SELECT num, cd, date FROM table_name LIMIT 1"
)
.fetch_optional(&pool)
.await?;
match row {
Some(row) => println!("num={}, cd={}, date={}", row.num, row.cd, row.date),
None => println!("データがありません"),
}
Ok(())
}
補足説明:Cargo.toml、日付型、十進数型、そしてquery_as!
- 今回は Cargo.toml 追加などありません
- 日付型は、時刻を含まない単に「日付」です。
- 十進数型(NULL含む)を扱うにはOption設定が必要です。次のお勉強にまわしました。
- 鉄板のはず!「struct+query_as!」まであと少しです。
改めて「構造体はSQL専用ではありません」を追記するようCopilotに勧められました。