AIエージェント向け読み取り専用DBクエリコマンド `rails query` の追加
Railsに bin/rails query コマンドが追加され、AIエージェントや自動化ツールが本番データベースに対して安全かつ構造化された形でクエリを実行できるようになりました。読み取り専用の強制、JSON出力、ページネーションを標準で備えています。
背景
AIエージェントや自動化ツールが本番データベースへの問い合わせを必要とする場面が増える中、既存の rails runner には実用上の課題がありました。rails runner "puts Account.count" は平文テキストを出力するため、エージェントが確実にパースできる一貫したフォーマットがなく、また Account.delete_all のような書き込みクエリの実行も防げませんでした。さらに、大量のレコードをすべて取得してしまうことを避けるために、呼び出し側が自前で LIMIT/OFFSET ロジックを組み込む必要もありました。
Kamal を使えば kamal app exec で本番へのコマンド実行は容易ですが、構造化された出力を返し、かつ読み取り専用を保証するコマンドが不足していました。rails query はこの空白を埋めるために設計されています。
技術的な変更
rails query コマンドは railties/lib/rails/commands/query/query_command.rb に Rails::Command::QueryCommand クラスとして実装されています。ActiveRecord 式と生 SQL の両方をサポートし、結果は常に JSON 形式で返します。
perform メソッドはサブコマンドのディスパッチを担い、引数に応じて schema・models・explain・通常クエリの4つの実行パスに分岐します。全体の実行は ActiveSupport::Notifications.instrument("query.rails") でラップされており、監査ログのフックポイントとして機能します。
def perform(expression = nil, *args)
boot_application!
Rails.application.load_runner
ActiveSupport::Notifications.instrument("query.rails", expression: expression) do
case expression
when "schema"
run_schema(args.first)
when "models"
run_models
when "explain"
run_explain(args.first)
else
run_query(expression)
end
end
rescue StandardError, SyntaxError, NotImplementedError => e
output_error(e.message)
exit 1
end
出力形式は columns・rows・meta の3フィールドで構成された JSON です。meta には row_count・query_time_ms・page・per_page・has_more・sql が含まれます。スカラー値(Account.count など)も {"columns":["result"], "rows":[[42]]} の形に正規化されるため、呼び出し側は結果の型を意識せず同一フォーマットで扱えます。エラーが発生した場合も {"error": "...", "meta": {...}} の JSON として出力されます。
ページネーションは --page と --per オプションで制御します。per は最大 10,000 件に上限が設けられており、[ [ options[:per], 1 ].max, 10_000 ].min で安全に境界を処理しています。
利用できる主なオプションとサブコマンドは以下の通りです:
-
--sql: ActiveRecord 式ではなく生 SQL として実行 -
--page/--per: ページネーション制御 -
--database/--db: マルチデータベース環境での接続先指定 -
schema [TABLE]: テーブル一覧、またはカラム・インデックス・enum・アソシエーションの詳細表示 -
models: ActiveRecord モデル一覧とアソシエーション情報 -
explain EXPRESSION: クエリプランの表示
設計判断
読み取り専用の強制はリーディングレプリカロールへの接続をデフォルトとし、シングルデータベース構成では while_preventing_writes にフォールバックする2段構えの設計です。書き込みを試みた場合は "Write query attempted while in readonly mode" というエラーが JSON で返ります。
ActiveSupport::Notifications による計装は、Console1984 のような監査ツールとのインテグレーションを想定した設計です。"query.rails" イベントにサブスクライブするだけで、コマンド自体にパッチを当てることなくアプリケーション側での追跡が可能になります。ペイロードには評価された式が含まれます。
--sql フラグによる信頼モデルの分離も注目すべき点です。デフォルトでは ActiveRecord 式を Ruby として評価するため、rails runner や rails console と同等の信頼モデルを前提としています。--sql フラグを指定した場合は SQL のみの実行に限定でき、例えば本番オペレーション向けの CI スクリプトで SQL のみを許可するといった運用上の制限を設けることができます。
まとめ
rails query は、AIエージェントや自動化ツールが本番データベースに安全にアクセスするための専用インターフェースをRailsフレームワーク自体に組み込んだ変更です。JSON出力の統一、読み取り専用の強制、計装フックの提供という3つの要素が組み合わさることで、rails runner では実現できなかった「エージェントが安全に問い合わせできる」運用パターンを標準ツールとして確立しています。