sqlx-code-review について
sqlx-code-reviewは、sqlxを使うRustプロジェクトのコードレビュー向けスキルです。クエリ、プール、トランザクション、型対応、マイグレーションを確認し、重大度の判定前に根拠を求めます。
主な機能
- 01Cargo.toml と sqlx 設定の確認
- 02クエリ形式とバインドパラメーターのレビュー
- 03プールとトランザクション処理の確認
- 04Rust と SQL の型対応の検証
- 05重大度判定前の根拠確認
活用シーン
- 01Rust プロジェクトの sqlx クエリのレビュー
- 02プール設定とロールバック経路の確認
- 03マイグレーションの可逆性と安全性の評価
- 04ファイルと行番号を添えた指摘の記録
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ existential-birds/ beagle --skill sqlx-code-reviewインストール後、Claude Code で/sqlx-code-reviewを入力して読み込まれたか確認します。
同梱ファイルreferences/migrations.mdreferences/queries.md
詳細
sqlx コードレビュー
レビューワークフロー
- Cargo.toml を確認する — sqlx の機能(
runtime-tokio、tls-rustls/tls-native-tls、postgres/mysql/sqlite、uuid、chrono、json、migrate)と Rust エディションを確認します(2024 エディションでは RPIT のライフタイムキャプチャが変更され、async-traitが不要になります)。 - クエリパターンを確認する — コンパイル時チェック(
query!、query_as!)と実行時チェック(query、query_as)を確認します。 - プール設定を確認する — 接続数の上限、タイムアウト、アイドル設定を確認します。
- マイグレーションを確認する — ファイル名、可逆性、データマイグレーションの安全性を確認します。
- 型マッピングを確認する — Rust の型が SQL のカラム型と一致していることを確認します。
ゲート(重大度を付ける前に根拠を確認)
順番に完了してください。該当するゲートを通過するまでは、Critical/Major を割り当てないでください。
- スコープ — レビュー対象のクレート(
Cargo.tomlのパス)と、開いた.rsファイル(またはディレクトリ)を特定します。合格条件: 確認した具体的なパスを少なくとも1つ挙げていること。 - sqlx/コンパイルに関する指摘 —
query!/query_as!、オフラインモード、sqlx.toml、DATABASE_URL、または Cargo の機能に関する問題を指摘する前に、該当するCargo.tomlと、該当する場合はsqlx.tomlまたは文書化された環境設定を開きます。合格条件: 指摘に行番号を示すか、それらのファイルが存在しない、またはスコープ外であることを明記していること。 - 指摘の参照箇所 — 報告する各問題には、出力形式に従って
[FILE:LINE]を含めます。合格条件: 行番号の参照なしに Critical または Major としていないこと。 - プロトコル — ゲート1〜3の後、最終的な重大度ラベルを付ける前に、review-verification-protocol スキルを読み込み、完了します。合格条件: 継続して報告する各指摘について、プロトコルの手順を満たしていること。
出力形式
指摘は次の形式で報告します。
[FILE:LINE] ISSUE_TITLE
Severity: Critical | Major | Minor | Informational
Description of the issue and why it matters.
クイックリファレンス
| 問題の種類 | 参照先 |
|---|---|
| クエリマクロ、バインドパラメーター、結果マッピング | references/queries.md |
| マイグレーション、プール設定、トランザクションパターン | references/migrations.md |
レビューチェックリスト
クエリパターン
- 可能な場合はコンパイル時チェック付きクエリ(
query!、query_as!)を使用している - オフラインでのコンパイル時チェック用に、
sqlx.tomlまたはDATABASE_URLを設定している - クエリに文字列補間を使用していない(SQL インジェクションのリスク)。バインドパラメーター(
$1、$2)を使用している - 公開 API では、
query_as!が無名レコードではなく名前付き構造体にマッピングされている -
.fetch_one()、.fetch_optional()、.fetch_all()を適切に選択している - 大きな結果セットには
.fetch()(ストリーミング)を使用している
接続プール
-
PgPoolをArcまたはフレームワークの状態を通じて共有している(リクエストごとに作成していない) - デプロイ環境に合わせてプールサイズを設定している(本番環境でデフォルトのままにしていない)
- 接続取得のタイムアウトを設定している
- アイドル接続のクリーンアップを設定している
- 2024 エディション:静的プールのシングルトンには
once_cell::sync::Lazyやlazy_static!ではなく、std::sync::LazyLockを使用してプールを初期化している
トランザクション
- 複数ステートメントの操作には
pool.begin()を使用している - 暗黙のロールバック(ドロップ時)に頼らず、トランザクションを明示的にコミットしている
- トランザクション内でエラーが発生した場合、伝播する前にロールバックしている
- 必要に応じて、ネストしたトランザクションにセーブポイント(
tx.begin())を使用している
型マッピング
-
sqlx::Typeの derive がデータベースのカラム型と一致している - Rust、serde、SQL 間で列挙型の表現に一貫性がある
- 構造化データに文字列を使わず、
Uuid、DateTime<Utc>、Decimal型を使用している - NULL 許容カラムには
Option<T>を使用している - JSONB カラムには
serde_json::Valueを使用している - エディション2024で予約語となる
genという名前の列挙型バリアントや構造体フィールドがない(#[sqlx(rename = "gen")]を付けたr#genを使うか、別の名前を選択する)
Edition 2024 との互換性
-
-> impl Streamまたは-> impl Futureを返す関数では、RPIT のライフタイムキャプチャの変更に対応しています(スコープ内のすべてのライフタイムがデフォルトでキャプチャされます。厳密に制御するには+ use<'a>を使用してください) - カスタムの
FromRowまたはTypeトレイト実装では、該当する場合にトレイト内のネイティブなasync fnを使用しています(#[async_trait]は不要です。Rust 1.75 で安定化されています) - 一部のコードパスでのみ使用されるコンパイル時クエリフィールドには、
#[allow(unused)]ではなく#[expect(unused)]を優先しています(自動的に解消される lint 抑制。1.81 で安定化されています) - 静的プールの初期化には
std::sync::LazyLockを使用しています(once_cellやlazy_static!ではなく)
マイグレーション
- マイグレーションファイルが命名規則(
YYYYMMDDHHMMSS_description.sql)に従っています - 破壊的なマイグレーション(DROP、ALTER DROP COLUMN)は元に戻せるか、データのバックアップ計画があります
- データに依存するスキーマ変更とデータ変更を同じマイグレーションに含めていません
- アプリケーションの起動時に
sqlx::migrate!()を呼び出しています
重大度の基準
重大
- SQL クエリでの文字列補間(SQL インジェクション)
- 複数ステートメントの書き込みにトランザクションがない(エラー時に一部の書き込みが残る)
- リクエストごとにコネクションプールを作成している(コネクション枯渇)
- バインドパラメーターのエスケープがない
高
- コンパイル時に正しさを検証できる箇所でランタイムクエリ(
query())を使用している(query!()を使用できる) - エラー経路でトランザクションがロールバックされない
- Rust とデータベースで列挙型が一致していない
- 大きくなる可能性のあるテーブルに対して
.fetch_all()に上限がない r#genのエスケープなしでgenという名前のフィールドまたはバリアントを使用している(Edition 2024 でコンパイルに失敗)
軽微
- 本番環境でプールのデフォルト設定を調整せずに使用している
.fetch_optional()がなく、「見つからない」場合のエラー処理に.fetch_one()を使用している- 必要な列が限られているのに
SELECT *の範囲が広すぎる - クエリ対象の列にインデックスがない(クエリパターンが明らかに遅い場合のみ指摘)
- Edition 2024:
std::sync::LazyLockが使える箇所でonce_cell::sync::Lazyまたはlazy_static!を使用している - クエリフィールドに
#[expect(unused)]ではなく#[allow(unused)]を使用している(自動的に解消される lint 抑制を優先)
情報
- 型安全な結果マッピングに
query_as!を使用する提案 - Rust による検証と併せて、データベースレベルの制約を追加する提案
- マイグレーションの構成改善
有効なパターン(指摘しないでください)
- 動的クエリでのランタイム
query()— 動的 SQL ではコンパイル時チェックは機能しません sqlx::FromRowderive — 再利用可能な行型にはquery_as!の有効な代替手段です- 列挙型の保存に
TEXT列を使用する —sqlx::Typederive と併用すれば有効で、カスタム SQL 型より簡潔です .execute()で行数を無視する — 冪等な操作(upsert、delete)では許容されます- 他言語とデータベースを共有する — 例:マイグレーションは Elixir が管理し、Rust が読み取ります。有効なアーキテクチャです。
#[sqlx(rename = "gen")]を伴うr#gen— データベース型のgen列に対する、Edition 2024 での正しい回避策です- クエリヘルパーの戻り値型に
+ use<'a>を指定する — RPIT のライフタイムキャプチャを厳密に制御します(Edition 2024) - 静的プールの初期化に
std::sync::LazyLockを使用する —once_cell/lazy_staticに代わるものです(Rust 1.80 で安定化されています) - カスタムの
FromRow/Typeトレイト実装でネイティブなasync fnを使用する —async-traitクレートは不要になりました(Rust 1.75 で安定化されています)
指摘事項を提出する前に
**ゲート(重大度判定前の証拠)**を完了してから、問題を報告する前に review-verification-protocol スキルを読み込み、その指示に従ってください。
コメント
Markdown 対応